diff --git a/apps/cloud/drizzle/0016_burly_champions.sql b/apps/cloud/drizzle/0016_burly_champions.sql new file mode 100644 index 0000000000..9f2172cf41 --- /dev/null +++ b/apps/cloud/drizzle/0016_burly_champions.sql @@ -0,0 +1 @@ +ALTER TABLE "tool" ADD COLUMN "result_encoding" text; \ No newline at end of file diff --git a/apps/cloud/drizzle/meta/0016_snapshot.json b/apps/cloud/drizzle/meta/0016_snapshot.json new file mode 100644 index 0000000000..73a6925388 --- /dev/null +++ b/apps/cloud/drizzle/meta/0016_snapshot.json @@ -0,0 +1,1492 @@ +{ + "id": "1d8421c4-c9d3-4fcd-8498-f6b49ee19ace", + "prevId": "d666b31a-c3d1-4bd7-9bd6-85f2abc4fb55", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memberships": { + "name": "memberships", + "schema": "", + "columns": { + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memberships_account_id_accounts_id_fk": { + "name": "memberships_account_id_accounts_id_fk", + "tableFrom": "memberships", + "tableTo": "accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memberships_organization_id_organizations_id_fk": { + "name": "memberships_organization_id_organizations_id_fk", + "tableFrom": "memberships", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "memberships_account_id_organization_id_pk": { + "name": "memberships_account_id_organization_id_pk", + "columns": ["account_id", "organization_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.artifact": { + "name": "artifact", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bindings": { + "name": "bindings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "preview": { + "name": "preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "artifact_uidx": { + "name": "artifact_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.blob": { + "name": "blob", + "schema": "", + "columns": { + "namespace": { + "name": "namespace", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blob_id_uidx": { + "name": "blob_id_uidx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection": { + "name": "connection", + "schema": "", + "columns": { + "integration": { + "name": "integration", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_ids": { + "name": "item_ids", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health": { + "name": "last_health", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "tools_synced_at": { + "name": "tools_synced_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "oauth_client": { + "name": "oauth_client", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_owner": { + "name": "oauth_client_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_item_id": { + "name": "refresh_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "oauth_scope": { + "name": "oauth_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_token_url": { + "name": "oauth_token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_state": { + "name": "provider_state", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "connection_uidx": { + "name": "connection_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.definition": { + "name": "definition", + "schema": "", + "columns": { + "integration": { + "name": "integration", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection": { + "name": "connection", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "definition_uidx": { + "name": "definition_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration": { + "name": "integration", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "health_check": { + "name": "health_check", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "config_revised_at": { + "name": "config_revised_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "can_remove": { + "name": "can_remove", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "can_refresh": { + "name": "can_refresh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "integration_uidx": { + "name": "integration_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant": { + "name": "grant", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret_item_id": { + "name": "client_secret_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_integration": { + "name": "origin_integration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_issuer": { + "name": "origin_issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_redirect_uri": { + "name": "origin_redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_client_uidx": { + "name": "oauth_client_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_session": { + "name": "oauth_session", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "client_slug": { + "name": "client_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "integration": { + "name": "integration", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_url": { + "name": "redirect_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pkce_verifier": { + "name": "pkce_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_session_uidx": { + "name": "oauth_session_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "collection": { + "name": "collection", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "plugin_storage_uidx": { + "name": "plugin_storage_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "collection", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.private_executor_cloud_settings": { + "name": "private_executor_cloud_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subject": { + "name": "subject", + "schema": "", + "columns": { + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "subject_uidx": { + "name": "subject_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool": { + "name": "tool", + "schema": "", + "columns": { + "integration": { + "name": "integration", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection": { + "name": "connection", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_schema": { + "name": "input_schema", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "output_schema": { + "name": "output_schema", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "result_encoding": { + "name": "result_encoding", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tool_uidx": { + "name": "tool_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policy": { + "name": "tool_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tool_policy_uidx": { + "name": "tool_policy_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/cloud/drizzle/meta/_journal.json b/apps/cloud/drizzle/meta/_journal.json index fa90570831..773b1b6091 100644 --- a/apps/cloud/drizzle/meta/_journal.json +++ b/apps/cloud/drizzle/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1785355354955, "tag": "0015_equal_the_leader", "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1787816208308, + "tag": "0016_burly_champions", + "breakpoints": true } ] } diff --git a/apps/cloud/src/db/executor-schema.ts b/apps/cloud/src/db/executor-schema.ts index e23d30d077..ec72bec188 100644 --- a/apps/cloud/src/db/executor-schema.ts +++ b/apps/cloud/src/db/executor-schema.ts @@ -153,6 +153,7 @@ export const tool = pgTable( input_schema: json("input_schema"), output_schema: json("output_schema"), annotations: json("annotations"), + result_encoding: text("result_encoding"), created_at: timestamp("created_at").notNull(), updated_at: timestamp("updated_at").notNull(), row_id: varchar("row_id", { length: 255 }) diff --git a/apps/local/drizzle/0005_colossal_vermin.sql b/apps/local/drizzle/0005_colossal_vermin.sql new file mode 100644 index 0000000000..31adc2e5d2 --- /dev/null +++ b/apps/local/drizzle/0005_colossal_vermin.sql @@ -0,0 +1 @@ +ALTER TABLE `tool` ADD `result_encoding` text; \ No newline at end of file diff --git a/apps/local/drizzle/meta/0005_snapshot.json b/apps/local/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000000..d2bba6bec4 --- /dev/null +++ b/apps/local/drizzle/meta/0005_snapshot.json @@ -0,0 +1,934 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "35089e12-1330-4d09-a254-00a8d9f323cf", + "prevId": "85f30981-cda9-45df-b757-b2836fb70e07", + "tables": { + "blob": { + "name": "blob", + "columns": { + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "blob_id_uidx": { + "name": "blob_id_uidx", + "columns": ["id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "connection": { + "name": "connection", + "columns": { + "integration": { + "name": "integration", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_ids": { + "name": "item_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oauth_client": { + "name": "oauth_client", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oauth_client_owner": { + "name": "oauth_client_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_item_id": { + "name": "refresh_item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oauth_scope": { + "name": "oauth_scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oauth_token_url": { + "name": "oauth_token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_state": { + "name": "provider_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "connection_uidx": { + "name": "connection_uidx", + "columns": ["tenant", "owner", "subject", "integration", "name"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "definition": { + "name": "definition", + "columns": { + "integration": { + "name": "integration", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection": { + "name": "connection", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "definition_uidx": { + "name": "definition_uidx", + "columns": ["tenant", "owner", "subject", "integration", "connection", "name"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "can_remove": { + "name": "can_remove", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "can_refresh": { + "name": "can_refresh", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_uidx": { + "name": "integration_uidx", + "columns": ["tenant", "slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_client": { + "name": "oauth_client", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "grant": { + "name": "grant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret_item_id": { + "name": "client_secret_item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_integration": { + "name": "origin_integration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_issuer": { + "name": "origin_issuer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_redirect_uri": { + "name": "origin_redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_client_uidx": { + "name": "oauth_client_uidx", + "columns": ["tenant", "owner", "subject", "slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_session": { + "name": "oauth_session", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_slug": { + "name": "client_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration": { + "name": "integration", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_url": { + "name": "redirect_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pkce_verifier": { + "name": "pkce_verifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_session_uidx": { + "name": "oauth_session_uidx", + "columns": ["tenant", "state"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "collection": { + "name": "collection", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "plugin_storage_uidx": { + "name": "plugin_storage_uidx", + "columns": ["tenant", "owner", "subject", "plugin_id", "collection", "key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tool": { + "name": "tool", + "columns": { + "integration": { + "name": "integration", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection": { + "name": "connection", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_schema": { + "name": "input_schema", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_schema": { + "name": "output_schema", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotations": { + "name": "annotations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_encoding": { + "name": "result_encoding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tool_uidx": { + "name": "tool_uidx", + "columns": ["tenant", "owner", "subject", "integration", "connection", "name"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tool_policy": { + "name": "tool_policy", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant": { + "name": "tenant", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tool_policy_uidx": { + "name": "tool_policy_uidx", + "columns": ["tenant", "owner", "subject", "id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/local/drizzle/meta/_journal.json b/apps/local/drizzle/meta/_journal.json index 8b4b73e4ab..6265e3b480 100644 --- a/apps/local/drizzle/meta/_journal.json +++ b/apps/local/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1784581228503, "tag": "0004_cultured_shadowcat", "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1787816206592, + "tag": "0005_colossal_vermin", + "breakpoints": true } ] } diff --git a/apps/local/src/db/executor-schema.ts b/apps/local/src/db/executor-schema.ts index baa4180300..8f8e355412 100644 --- a/apps/local/src/db/executor-schema.ts +++ b/apps/local/src/db/executor-schema.ts @@ -121,6 +121,7 @@ export const tool = sqliteTable( input_schema: text("input_schema"), output_schema: text("output_schema"), annotations: text("annotations"), + result_encoding: text("result_encoding"), created_at: integer("created_at").notNull(), updated_at: integer("updated_at").notNull(), row_id: text("row_id").primaryKey().notNull(), diff --git a/apps/local/src/db/sqlite-fumadb.ts b/apps/local/src/db/sqlite-fumadb.ts index 7ffb8128a1..3daf1c0cfb 100644 --- a/apps/local/src/db/sqlite-fumadb.ts +++ b/apps/local/src/db/sqlite-fumadb.ts @@ -106,6 +106,14 @@ export const createSqliteFumaDb = async ( await client.execute("ALTER TABLE oauth_client ADD COLUMN origin_redirect_uri TEXT"); } + const toolColumns = await client.execute("PRAGMA table_info(tool)"); + if ( + toolColumns.rows.length > 0 && + !toolColumns.rows.some((column) => column["name"] === "result_encoding") + ) { + await client.execute("ALTER TABLE tool ADD COLUMN result_encoding TEXT"); + } + const { db, fuma } = createExecutorFumaDb(drizzleDb, { tables: options.tables, namespace: options.namespace, diff --git a/e2e/scenarios/shape-memory.test.ts b/e2e/scenarios/shape-memory.test.ts index a1d2f5e123..1e2adec2ae 100644 --- a/e2e/scenarios/shape-memory.test.ts +++ b/e2e/scenarios/shape-memory.test.ts @@ -11,13 +11,15 @@ import { createServer } from "node:http"; import { expect } from "@effect/vitest"; import { Effect } from "effect"; import { composePluginApi } from "@executor-js/api/server"; +import { mcpHttpPlugin } from "@executor-js/plugin-mcp/api"; +import { makeUndeclaredStructuredMcpServer, serveMcpServer } from "@executor-js/plugin-mcp/testing"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared"; import { scenario } from "../src/scenario"; import { Api, Target } from "../src/services"; -const api = composePluginApi([openApiHttpPlugin()] as const); +const api = composePluginApi([openApiHttpPlugin(), mcpHttpPlugin()] as const); /** One GET operation whose 200 declares no response schema — the shape the * model would otherwise have to guess. */ @@ -176,3 +178,109 @@ return { ok: result.ok }; }), ), ); + +// --------------------------------------------------------------------------- +// The MCP journey — semantic data contract end to end. The server returns +// structuredContent it never declared a schema for. Cold, the tool describes +// as `data: unknown` (no CallToolResult envelope is synthesized); the model's +// code receives the payload directly (never `content[0].text`); one call +// teaches the payload shape and the next describe serves it, marked observed. +// --------------------------------------------------------------------------- + +const mcpDescribeCode = (slug: string) => ` +const details = await tools.describe.tool({ path: "${slug}.org.main.undeclared_structured_echo" }); +return { + outputTypeScript: details.outputTypeScript ?? null, + note: details.outputTypeScriptNote ?? null, + error: details.error ?? null, +}; +`; + +scenario( + "Muscle memory · an MCP payload is semantic data and its observed shape reaches describe", + {}, + Effect.scoped( + Effect.gen(function* () { + const target = yield* Target; + const { client: makeApiClient } = yield* Api; + const identity = yield* target.newIdentity(); + const client = yield* makeApiClient(api, identity); + const slug = IntegrationSlug.make(`shape_mcp_${randomBytes(4).toString("hex")}`); + + const server = yield* serveMcpServer(makeUndeclaredStructuredMcpServer); + + yield* client.mcp.addServer({ + payload: { + transport: "remote", + name: "Undeclared structured MCP", + endpoint: server.url, + slug, + }, + }); + + yield* Effect.ensuring( + Effect.gen(function* () { + yield* client.connections.create({ + payload: { + owner: "org", + name: ConnectionName.make("main"), + integration: slug, + template: AuthTemplateSlug.make("none"), + value: "", + }, + }); + + const describe = Effect.gen(function* () { + const executed = yield* client.executions.execute({ + payload: { code: mcpDescribeCode(String(slug)), autoApprove: true }, + }); + expect(executed.status, executed.text).toBe("completed"); + return JSON.parse(executed.text) as DescribeOutcome; + }); + + // Cold: no declared schema, no synthesized envelope, no note. + const cold = yield* describe; + expect(cold.error, "the tool resolves").toBeNull(); + expect(cold.outputTypeScript, "cold data is unknown").toContain("data: unknown;"); + expect(cold.outputTypeScript, "no envelope is synthesized").not.toContain( + "structuredContent", + ); + expect(cold.note, "cold describe carries no provenance note").toBeNull(); + + // The model's code receives the semantic payload directly. + const invoked = yield* client.executions.execute({ + payload: { + code: ` +const result = await tools.${slug}.org.main.undeclared_structured_echo({ value: "hi" }); +return { ok: result.ok, data: result.ok ? result.data : null }; +`, + autoApprove: true, + }, + }); + expect(invoked.status, invoked.text).toBe("completed"); + expect(JSON.parse(invoked.text), "data is the payload, not an envelope").toEqual({ + ok: true, + data: { value: "hi", length: 2, ok: true }, + }); + + // Warm: the observed payload shape is served, marked observed. + const warm = yield* describe; + expect(warm.outputTypeScript, "payload fields are typed").toContain("length: number"); + expect(warm.outputTypeScript, "payload fields are typed").toContain("value: string"); + expect(warm.outputTypeScript, "the shape no longer collapses").not.toContain( + "data: unknown;", + ); + expect(warm.note, "provenance is explicit").toContain("observed from 1 live response"); + }), + Effect.gen(function* () { + yield* client.connections + .remove({ + params: { owner: "org", integration: slug, name: ConnectionName.make("main") }, + }) + .pipe(Effect.ignore); + yield* client.mcp.removeServer({ params: { slug } }).pipe(Effect.ignore); + }), + ); + }), + ), +); diff --git a/packages/core/execution/src/skills.ts b/packages/core/execution/src/skills.ts index deeb31cb3b..6448bb01be 100644 --- a/packages/core/execution/src/skills.ts +++ b/packages/core/execution/src/skills.ts @@ -45,7 +45,8 @@ const EXECUTE_SKILL_BODY = [ '- When you already know the namespace, narrow with `tools.search({ namespace: "github", query: "issues" })`.', "- `tools.executor.coreTools.connections.list({})` returns saved connections with `{ address, integration, owner, name, ... }`. The `address` field includes the leading `tools.` root.", "- Tool calls return a value union: `{ ok: true, data }` for success or `{ ok: false, error: { code, message, status?, details?, retryable? } }` for expected tool/domain failures. Branch on `result.ok`.", - "- `data` is the upstream payload itself. HTTP-backed tools (OpenAPI) also set `http: { status, headers }` beside `data` — read `result.http?.headers` for pagination (Link) or rate-limit headers.", + "- `data` is the upstream payload itself — for MCP-backed tools too: the CallToolResult envelope is unwrapped, so `data` is the structured payload (or the parsed JSON a server sent as text, or the text itself, or the ordered content-block array for media/multi-part results). Never look for `data.content[0].text`.", + "- HTTP-backed tools (OpenAPI) also set `http: { status, headers }` beside `data` — read `result.http?.headers` for pagination (Link) or rate-limit headers. MCP-backed tools may set `content` (supplemental rich blocks such as images — pass them to `emit(...)` to show the user) and `meta` beside `data`.", "- Use `emit(value)` to append user-visible output. Plain values become MCP text content. MCP content blocks are forwarded as-is. `ToolFile` values are rendered by MIME. Emitting and returning compose: emitted items come first in the tool result, the returned value follows, and the envelope reports an `emitted` count so you can confirm the items landed.", '- File-returning tools may return `ToolFile` values: `{ _tag: "ToolFile", name?, mimeType, encoding: "base64", data, byteLength }`. A "file-returning tool" includes APIs that return file bytes inside a JSON field, such as Base64-encoded `content`; wrap those payloads in a `ToolFile` and `emit()` them. Emit any attachment with `emit(result.data)`.', "- Never decode or transcode bytes yourself — the sandbox has no `Buffer`, `atob`, `btoa`, `TextDecoder`, or `TextEncoder`. For base64-encoded bytes in a JSON field, wrap the payload in a `ToolFile` with its `mimeType` and `emit()` it; to forward them to an upload tool, pass the `ToolFile`'s base64 `data` as that tool's `bodyBase64` (both sides speak base64, so nothing is decoded).", diff --git a/packages/core/execution/src/tool-invoker.test.ts b/packages/core/execution/src/tool-invoker.test.ts index 536fa31f69..68c4aef2db 100644 --- a/packages/core/execution/src/tool-invoker.test.ts +++ b/packages/core/execution/src/tool-invoker.test.ts @@ -865,7 +865,7 @@ describe("tool discovery", () => { expect(described.description).toBe("List issues for a repository"); expect(described.inputTypeScript).toBe("{ owner: string; repo: string; }"); expect(described.outputTypeScript).toBe( - "{ ok: true; data: unknown; http?: ToolHttpMeta } | { ok: false; error: ToolError }", + "{ ok: true; data: unknown; content?: { type: string; [k: string]: unknown }[]; meta?: unknown; http?: ToolHttpMeta } | { ok: false; error: ToolError }", ); expect(described.typeScriptDefinitions).toEqual({ ToolError: @@ -898,7 +898,7 @@ describe("tool discovery", () => { // both inline and via the note. const warm = yield* describeTool(executor, "github.org.main.listRepositoryIssues"); expect(warm.outputTypeScript).toBe( - "{ ok: true; data: unknown[] /* observed; may be incomplete */; http?: ToolHttpMeta } | { ok: false; error: ToolError }", + "{ ok: true; data: unknown[] /* observed; may be incomplete */; content?: { type: string; [k: string]: unknown }[]; meta?: unknown; http?: ToolHttpMeta } | { ok: false; error: ToolError }", ); expect(warm.outputTypeScriptNote).toContain("observed from 1 live response"); }), @@ -983,7 +983,7 @@ describe("tool discovery", () => { const described = yield* describeTool(executor, "github.org.main.getRepositoryDetails"); expect(described.outputTypeScript).toBe( - "{ ok: true; data: { defaultBranch: string; }; http?: ToolHttpMeta } | { ok: false; error: ToolError }", + "{ ok: true; data: { defaultBranch: string; }; content?: { type: string; [k: string]: unknown }[]; meta?: unknown; http?: ToolHttpMeta } | { ok: false; error: ToolError }", ); expect(described.typeScriptDefinitions).toEqual({ ToolError: diff --git a/packages/core/execution/src/tool-invoker.ts b/packages/core/execution/src/tool-invoker.ts index 2df47644ed..497e6d3ea4 100644 --- a/packages/core/execution/src/tool-invoker.ts +++ b/packages/core/execution/src/tool-invoker.ts @@ -30,9 +30,11 @@ const TOOL_ERROR_TYPESCRIPT = const TOOL_HTTP_META_TYPESCRIPT = "{ status: number; headers: { [k: string]: string; } }"; const TOOL_FILE_TYPESCRIPT = '{ _tag: "ToolFile"; name?: string; mimeType: string; encoding: "base64"; data: string; byteLength: number; }'; - +// Inlined rather than a named definition: a provider's own schema may +// legitimately define `$defs.ToolContentBlock`, and the definitions map must +// never shadow provider types. const wrapOutputTypeScript = (outputTypeScript?: string, marker?: string): string => - `{ ok: true; data: ${outputTypeScript ?? "unknown"}${marker ?? ""}; http?: ToolHttpMeta } | { ok: false; error: ToolError }`; + `{ ok: true; data: ${outputTypeScript ?? "unknown"}${marker ?? ""}; content?: { type: string; [k: string]: unknown }[]; meta?: unknown; http?: ToolHttpMeta } | { ok: false; error: ToolError }`; /** Inline provenance for observed types — a model that copies only the type * string still sees the hint, since the compact render drops descriptions. */ diff --git a/packages/core/sdk/src/core-schema.ts b/packages/core/sdk/src/core-schema.ts index b03adf5dfc..d85b4c1251 100644 --- a/packages/core/sdk/src/core-schema.ts +++ b/packages/core/sdk/src/core-schema.ts @@ -323,6 +323,11 @@ export const coreTables = defineTables({ input_schema: nullableJsonColumn("input_schema"), output_schema: nullableJsonColumn("output_schema"), annotations: nullableJsonColumn("annotations"), + // How `executor.execute` decodes this tool's raw invocation value into + // semantic `data` (see tool-result-normalization.ts). NULL = "direct": + // the value already is the payload. Stamped at catalog sync, so a + // contract rollout converges with the catalog's own refresh cycle. + result_encoding: nullableTextColumn("result_encoding"), created_at: dateColumn("created_at"), updated_at: dateColumn("updated_at"), }, @@ -451,6 +456,7 @@ export const TOOL_INVOCATION_COLUMNS = [ "name", "description", "annotations", + "result_encoding", "created_at", "updated_at", ] as const satisfies readonly (keyof ToolRow)[]; diff --git a/packages/core/sdk/src/executor.ts b/packages/core/sdk/src/executor.ts index c8537be5dd..a805c17814 100644 --- a/packages/core/sdk/src/executor.ts +++ b/packages/core/sdk/src/executor.ts @@ -176,7 +176,12 @@ import { type EnterpriseManagedRollout, } from "./oauth-ema"; import { connectionIdentifier } from "./connection-name-identifier"; -import { annotateToolResultOutcome, isToolResult } from "./tool-result"; +import { annotateToolResultOutcome, isToolResult, ToolResult } from "./tool-result"; +import { + applyResultEncoding, + isToolResultEncoding, + type ToolResultEncoding, +} from "./tool-result-normalization"; import { makeShapeMemory, observedShapeToJsonSchema, SHAPE_MEMORY_PLUGIN_ID } from "./shape-memory"; import { isUnauthorizedToolFailure } from "./auth-tool-failure"; @@ -927,6 +932,7 @@ const rowToTool = ( description: row.description, inputSchema: decodeJsonColumn(row.input_schema), outputSchema: decodeJsonColumn(row.output_schema), + ...(isToolResultEncoding(row.result_encoding) ? { resultEncoding: row.result_encoding } : {}), annotations: annotations ?? (decodeJsonColumn(row.annotations) as ToolAnnotations | undefined), }; }; @@ -2914,6 +2920,7 @@ export const createExecutor = => { const handler = pickHandler(options); - return Effect.gen(function* () { - // oxlint-disable executor/no-instanceof-error, executor/no-unknown-error-message, executor/no-manual-tag-check -- boundary: normalize arbitrary unknown plugin failures into a human-readable message for ToolInvocationError/telemetry - const formatInvocationCauseMessage = (cause: unknown): string => { - if (cause instanceof Error && cause.message.length > 0) return cause.message; - // Non-Error / empty-message causes: `String(plainObject)` renders - // "[object Object]", which is what telemetry then shows as the only - // label for the failure. Prefer the tag, else stringify structurally. - if (typeof cause === "object" && cause !== null) { - const tag = (cause as { readonly _tag?: unknown })._tag; - if (typeof tag === "string") return tag; - return Inspectable.toStringUnknown(cause, 0); - } - return String(cause); - }; - // oxlint-enable executor/no-instanceof-error, executor/no-unknown-error-message, executor/no-manual-tag-check - const wrapInvocationError = ( - effect: Effect.Effect, - ): Effect.Effect => - effect.pipe( - Effect.mapError( - (cause) => - new ToolInvocationError({ - address, - message: formatInvocationCauseMessage(cause), - cause, - }), - ), + // Effect values are reusable, so per-invocation state lives inside a + // suspend: every run gets its own encoding slot, and two concurrent + // runs can never see each other's row read. + return Effect.suspend(() => { + // Set once the tool row loads; read by the invocation wrapper and the + // shape-observation tap. Static tools never reach either. + let resolvedEncoding: ToolResultEncoding = "direct"; + // Decode the raw invocation value into semantic `data` per the row's + // encoding: transport envelopes (MCP CallToolResult) never reach the + // sandbox, telemetry, or shape inference as `data`. + const decodeInvocationValue = (value: unknown): unknown => { + if (resolvedEncoding === "direct") return value; + return applyResultEncoding( + resolvedEncoding, + isToolResult(value) ? value : ToolResult.ok(value), ); + }; + return Effect.gen(function* () { + // oxlint-disable executor/no-instanceof-error, executor/no-unknown-error-message, executor/no-manual-tag-check -- boundary: normalize arbitrary unknown plugin failures into a human-readable message for ToolInvocationError/telemetry + const formatInvocationCauseMessage = (cause: unknown): string => { + if (cause instanceof Error && cause.message.length > 0) return cause.message; + // Non-Error / empty-message causes: `String(plainObject)` renders + // "[object Object]", which is what telemetry then shows as the only + // label for the failure. Prefer the tag, else stringify structurally. + if (typeof cause === "object" && cause !== null) { + const tag = (cause as { readonly _tag?: unknown })._tag; + if (typeof tag === "string") return tag; + return Inspectable.toStringUnknown(cause, 0); + } + return String(cause); + }; + // oxlint-enable executor/no-instanceof-error, executor/no-unknown-error-message, executor/no-manual-tag-check + const wrapInvocationError = ( + effect: Effect.Effect, + ): Effect.Effect => + effect.pipe( + Effect.mapError( + (cause) => + new ToolInvocationError({ + address, + message: formatInvocationCauseMessage(cause), + cause, + }), + ), + ); - // Static path — O(1) map lookup for plugin-contributed static tools - // (core-tools, plugin executor namespaces). Addressed by their fqid, - // not the 5-segment dynamic form. - const staticEntry = staticTools.get(String(address)); - if (staticEntry) { + // Static path — O(1) map lookup for plugin-contributed static tools + // (core-tools, plugin executor namespaces). Addressed by their fqid, + // not the 5-segment dynamic form. + const staticEntry = staticTools.get(String(address)); + if (staticEntry) { + const policyRules = yield* listActivePolicyRuleSet(); + const policy = yield* resolvePolicyFromRuleSet( + String(address), + policyRules, + staticEntry.tool.annotations?.requiresApproval, + ); + if (policy.action === "block") { + return yield* new ToolBlockedError({ + address, + pattern: policy.pattern ?? "*", + }); + } + yield* enforceApproval(staticEntry.tool.annotations, address, args, policy, handler); + return yield* wrapInvocationError( + staticEntry.tool.handler({ + ctx: staticEntry.ctx, + args, + elicit: buildElicit(address, args, handler), + }), + ); + } + + const parsed = parseToolAddress(String(address)); + if (!parsed) { + return yield* new ToolNotFoundError({ address }); + } + + // Find the tool row — projected: invoke needs routing/policy fields + // only, never the multi-KB input/output schema JSON (`tools.schema` + // is the schema-bearing surface). + const row = yield* core.findFirst("tool", { + where: (b: AnyCb) => + b.and( + byOwner(parsed.owner)(b), + b("integration", "=", String(parsed.integration)), + b("connection", "=", String(parsed.connection)), + b("name", "=", String(parsed.tool)), + ), + select: TOOL_INVOCATION_COLUMNS, + }); + if (!row) { + const searchMatches = yield* searchToolRowsForConnection(parsed); + const connectionTools = + searchMatches.length > 0 ? searchMatches : yield* findToolRowsForConnection(parsed); + return yield* new ToolNotFoundError({ + address, + suggestions: toolSuggestions(connectionTools), + }); + } + + // How this tool's raw invocation value decodes into semantic `data`. + // Shared with the shape-observation tap below via the closure. + resolvedEncoding = isToolResultEncoding(row.result_encoding) + ? row.result_encoding + : "direct"; + + // Resolve policy (owner-ranked). + const toolForPolicy = rowToTool(row); const policyRules = yield* listActivePolicyRuleSet(); + const annotations = decodeJsonColumn(row.annotations) as ToolAnnotations | undefined; const policy = yield* resolvePolicyFromRuleSet( - String(address), + normalizedPolicyId(toolForPolicy), policyRules, - staticEntry.tool.annotations?.requiresApproval, + annotations?.requiresApproval, ); if (policy.action === "block") { return yield* new ToolBlockedError({ @@ -4620,199 +4708,147 @@ export const createExecutor = - b.and( - byOwner(parsed.owner)(b), - b("integration", "=", String(parsed.integration)), - b("connection", "=", String(parsed.connection)), - b("name", "=", String(parsed.tool)), - ), - select: TOOL_INVOCATION_COLUMNS, - }); - if (!row) { - const searchMatches = yield* searchToolRowsForConnection(parsed); - const connectionTools = - searchMatches.length > 0 ? searchMatches : yield* findToolRowsForConnection(parsed); - return yield* new ToolNotFoundError({ - address, - suggestions: toolSuggestions(connectionTools), - }); - } - - // Resolve policy (owner-ranked). - const toolForPolicy = rowToTool(row); - const policyRules = yield* listActivePolicyRuleSet(); - const annotations = decodeJsonColumn(row.annotations) as ToolAnnotations | undefined; - const policy = yield* resolvePolicyFromRuleSet( - normalizedPolicyId(toolForPolicy), - policyRules, - annotations?.requiresApproval, - ); - if (policy.action === "block") { - return yield* new ToolBlockedError({ - address, - pattern: policy.pattern ?? "*", - }); - } - const runtime = runtimes.get(row.plugin_id); - if (!runtime) { - return yield* new PluginNotLoadedError({ - address, - pluginId: row.plugin_id, - }); - } - if (!runtime.plugin.invokeTool) { - return yield* new NoHandlerError({ - address, - pluginId: row.plugin_id, - }); - } + const runtime = runtimes.get(row.plugin_id); + if (!runtime) { + return yield* new PluginNotLoadedError({ + address, + pluginId: row.plugin_id, + }); + } + if (!runtime.plugin.invokeTool) { + return yield* new NoHandlerError({ + address, + pluginId: row.plugin_id, + }); + } - // Find the connection row. - const connectionRow = yield* findConnectionRow({ - owner: parsed.owner, - integration: parsed.integration, - name: parsed.connection, - }); - if (!connectionRow) { - return yield* new ConnectionNotFoundError({ + // Find the connection row. + const connectionRow = yield* findConnectionRow({ owner: parsed.owner, integration: parsed.integration, name: parsed.connection, }); - } + if (!connectionRow) { + return yield* new ConnectionNotFoundError({ + owner: parsed.owner, + integration: parsed.integration, + name: parsed.connection, + }); + } + + // Resolve annotations + enforce approval. + let resolvedAnnotations = annotations; + if (policy.action !== "approve" && runtime.plugin.resolveAnnotations) { + const map = yield* runtime.plugin + .resolveAnnotations({ + ctx: runtime.ctx, + integration: parsed.integration, + connection: parsed.connection, + toolRows: [row], + }) + .pipe(wrapInvocationError); + resolvedAnnotations = map[String(parsed.tool)] ?? annotations; + } + // When this call is about to pause for approval, validate args + // first: a call that can only fail (missing required path param / + // body) must be rejected here, not after the user grants an approval + // that then goes to waste. Non-pausing calls skip this — invokeTool + // raises the identical failure moments later without the extra pass. + if (approvalRequired(resolvedAnnotations, policy) && runtime.plugin.validateToolArgs) { + yield* runtime.plugin + .validateToolArgs({ ctx: runtime.ctx, toolRow: row, args }) + .pipe(wrapInvocationError); + } + yield* enforceApproval(resolvedAnnotations, address, args, policy, handler); - // Resolve annotations + enforce approval. - let resolvedAnnotations = annotations; - if (policy.action !== "approve" && runtime.plugin.resolveAnnotations) { - const map = yield* runtime.plugin - .resolveAnnotations({ - ctx: runtime.ctx, + // Resolve every named credential input (`variable → value`); `value` is + // the primary `token` for single-input + OAuth callers. + const values = yield* resolveConnectionValues(connectionRow); + const integrationRow = yield* findIntegrationRow(parsed.integration); + const grantedScopes = grantedScopesFromRow(connectionRow); + const invokeTool = runtime.plugin.invokeTool; + const invokeWith = ( + resolved: Record, + ): Effect.Effect => { + const credential: ToolInvocationCredential = { + owner: parsed.owner, integration: parsed.integration, connection: parsed.connection, - toolRows: [row], - }) - .pipe(wrapInvocationError); - resolvedAnnotations = map[String(parsed.tool)] ?? annotations; - } - // When this call is about to pause for approval, validate args - // first: a call that can only fail (missing required path param / - // body) must be rejected here, not after the user grants an approval - // that then goes to waste. Non-pausing calls skip this — invokeTool - // raises the identical failure moments later without the extra pass. - if (approvalRequired(resolvedAnnotations, policy) && runtime.plugin.validateToolArgs) { - yield* runtime.plugin - .validateToolArgs({ ctx: runtime.ctx, toolRow: row, args }) - .pipe(wrapInvocationError); - } - yield* enforceApproval(resolvedAnnotations, address, args, policy, handler); - - // Resolve every named credential input (`variable → value`); `value` is - // the primary `token` for single-input + OAuth callers. - const values = yield* resolveConnectionValues(connectionRow); - const integrationRow = yield* findIntegrationRow(parsed.integration); - const grantedScopes = grantedScopesFromRow(connectionRow); - const invokeTool = runtime.plugin.invokeTool; - const invokeWith = ( - resolved: Record, - ): Effect.Effect => { - const credential: ToolInvocationCredential = { - owner: parsed.owner, - integration: parsed.integration, - connection: parsed.connection, - template: AuthTemplateSlug.make(connectionRow.template), - value: resolved[PRIMARY_INPUT_VARIABLE] ?? null, - values: resolved, - config: integrationRow ? decodeJsonColumn(integrationRow.config) : undefined, - ...(grantedScopes ? { grantedScopes } : {}), + template: AuthTemplateSlug.make(connectionRow.template), + value: resolved[PRIMARY_INPUT_VARIABLE] ?? null, + values: resolved, + config: integrationRow ? decodeJsonColumn(integrationRow.config) : undefined, + ...(grantedScopes ? { grantedScopes } : {}), + }; + return wrapInvocationError( + invokeTool({ + ctx: runtime.ctx, + toolRow: row, + credential, + args, + elicit: buildElicit(address, args, handler), + invokeOptions: options, + }), + ).pipe(Effect.map(decodeInvocationValue)); }; - return wrapInvocationError( - invokeTool({ - ctx: runtime.ctx, - toolRow: row, - credential, - args, - elicit: buildElicit(address, args, handler), - invokeOptions: options, - }), - ); - }; - const first = yield* invokeWith(values); - // Reactive refresh. `expires_at` is only ever the AS's ADVERTISED - // lifetime; the upstream rejecting the token is the authoritative word - // on whether it is still good. The two diverge routinely: server-side - // revocation, an identity provider's idle-timeout policy shorter than - // the token lifetime, and connections whose AS omitted `expires_in` - // entirely (null expiry → the proactive check never fires, so this is - // their ONLY route back to a working token short of a reconnect). - // - // Deliberately narrow: exactly one retry, only on the 401 that means - // "this credential is not valid", and only for a connection holding a - // refresh token. A 403 is excluded — it means authenticated-but-not- - // permitted, and re-minting the same grant returns the same answer. - // If the retry also fails its result stands, so a genuinely dead grant - // still surfaces the upstream's own auth failure and its reconnect - // guidance rather than a masked one. - if (!isUnauthorizedToolFailure(first)) return first; - const refreshed = yield* forceRefreshConnectionValues(connectionRow).pipe( - // A failed re-mint is not this call's failure to report: the upstream - // already produced an auth failure with recovery guidance, which is - // strictly more actionable than a refresh-plumbing error. Keep it. - Effect.catchTag("CredentialResolutionError", () => Effect.succeed(null)), + const first = yield* invokeWith(values); + // Reactive refresh. `expires_at` is only ever the AS's ADVERTISED + // lifetime; the upstream rejecting the token is the authoritative word + // on whether it is still good. The two diverge routinely: server-side + // revocation, an identity provider's idle-timeout policy shorter than + // the token lifetime, and connections whose AS omitted `expires_in` + // entirely (null expiry → the proactive check never fires, so this is + // their ONLY route back to a working token short of a reconnect). + // + // Deliberately narrow: exactly one retry, only on the 401 that means + // "this credential is not valid", and only for a connection holding a + // refresh token. A 403 is excluded — it means authenticated-but-not- + // permitted, and re-minting the same grant returns the same answer. + // If the retry also fails its result stands, so a genuinely dead grant + // still surfaces the upstream's own auth failure and its reconnect + // guidance rather than a masked one. + if (!isUnauthorizedToolFailure(first)) return first; + const refreshed = yield* forceRefreshConnectionValues(connectionRow).pipe( + // A failed re-mint is not this call's failure to report: the upstream + // already produced an auth failure with recovery guidance, which is + // strictly more actionable than a refresh-plumbing error. Keep it. + Effect.catchTag("CredentialResolutionError", () => Effect.succeed(null)), + ); + if (!refreshed) return first; + yield* Effect.annotateCurrentSpan({ "executor.oauth.refresh.retried": true }); + return yield* invokeWith(refreshed); + }).pipe( + // Expected tool failures (`ToolResult.fail`) resolve through the + // success channel, so the tracer alone would record them as healthy + // spans. Stamp the outcome + error code so telemetry can distinguish + // "tool ran fine" from "user hit an upstream error / auth wall" + // without parsing response bodies. + Effect.tap(annotateToolResultOutcome), + // Muscle memory: fold successful dynamic-tool payloads into the + // observed output shape. Static tools are hand-typed already; + // failures teach nothing about the success shape. Runs inline — not + // forked — so the write survives Workers request teardown; `observe` + // never fails, is size-bounded, and stops writing once the shape + // stabilizes, so steady-state cost is one cache lookup. + Effect.tap((result) => { + if (staticTools.has(String(address))) return Effect.void; + const parsed = parseToolAddress(String(address)); + if (!parsed) return Effect.void; + const data = isToolResult(result) ? (result.ok ? result.data : undefined) : result; + if (data === undefined) return Effect.void; + return shapeMemory.observe(String(address), parsed.owner, resolvedEncoding, data); + }), + Effect.withSpan("executor.tool.execute", { + attributes: { + "mcp.tool.name": String(address), + "executor.tenant": tenant, + ...(subject != null ? { "executor.subject": subject } : {}), + }, + }), ); - if (!refreshed) return first; - yield* Effect.annotateCurrentSpan({ "executor.oauth.refresh.retried": true }); - return yield* invokeWith(refreshed); - }).pipe( - // Expected tool failures (`ToolResult.fail`) resolve through the - // success channel, so the tracer alone would record them as healthy - // spans. Stamp the outcome + error code so telemetry can distinguish - // "tool ran fine" from "user hit an upstream error / auth wall" - // without parsing response bodies. - Effect.tap(annotateToolResultOutcome), - // Muscle memory: fold successful dynamic-tool payloads into the - // observed output shape. Static tools are hand-typed already; - // failures teach nothing about the success shape. Runs inline — not - // forked — so the write survives Workers request teardown; `observe` - // never fails, is size-bounded, and stops writing once the shape - // stabilizes, so steady-state cost is one cache lookup. - Effect.tap((result) => { - if (staticTools.has(String(address))) return Effect.void; - const parsed = parseToolAddress(String(address)); - if (!parsed) return Effect.void; - const data = isToolResult(result) ? (result.ok ? result.data : undefined) : result; - if (data === undefined) return Effect.void; - return shapeMemory.observe(String(address), parsed.owner, "direct", data); - }), - Effect.withSpan("executor.tool.execute", { - attributes: { - "mcp.tool.name": String(address), - "executor.tenant": tenant, - ...(subject != null ? { "executor.subject": subject } : {}), - }, - }), - ); + }); }; // ------------------------------------------------------------------ diff --git a/packages/core/sdk/src/testing/tool-output-contract.test.ts b/packages/core/sdk/src/testing/tool-output-contract.test.ts index 200bfb4c96..5cb61235c2 100644 --- a/packages/core/sdk/src/testing/tool-output-contract.test.ts +++ b/packages/core/sdk/src/testing/tool-output-contract.test.ts @@ -4,42 +4,37 @@ import { typeCheckOutputTypeScript } from "./tool-output-contract"; describe("typeCheckOutputTypeScript", () => { it("accepts runtime output that matches the described TypeScript contract", () => { + // Semantic data contract: `data` IS the payload; supplemental blocks ride + // in `content` beside it. const diagnostics = typeCheckOutputTypeScript( { - outputTypeScript: "{ ok: true; data: ResultData }", + outputTypeScript: "{ ok: true; data: Payload; content?: readonly Block[] }", typeScriptDefinitions: { Payload: "{ answer: string }", - ResultData: - '{ content: readonly { type: "text"; text: string }[]; structuredContent: Payload }', + Block: '{ type: "text"; text: string }', }, }, { ok: true, - data: { - content: [{ type: "text", text: "done" }], - structuredContent: { answer: "done" }, - }, + data: { answer: "done" }, + content: [{ type: "text", text: "done" }], }, { - consumerSource: - "const answer: string = invokedOutput.data.structuredContent.answer; answer;", + consumerSource: "const answer: string = invokedOutput.data.answer; answer;", }, ); expect(diagnostics).toEqual([]); }); - it("reports when the described contract omits the runtime output wrapper", () => { + it("reports when runtime output does not match the described payload", () => { const diagnostics = typeCheckOutputTypeScript( { outputTypeScript: "{ ok: true; data: { answer: string } }", }, { ok: true, - data: { - content: [{ type: "text", text: "done" }], - structuredContent: { answer: "done" }, - }, + data: { reply: "done" }, }, ); diff --git a/packages/core/sdk/src/tool.ts b/packages/core/sdk/src/tool.ts index c3bdb64f65..3267665d6a 100644 --- a/packages/core/sdk/src/tool.ts +++ b/packages/core/sdk/src/tool.ts @@ -1,4 +1,5 @@ import type { ConnectionName, IntegrationSlug, Owner, ToolAddress, ToolName } from "./ids"; +import type { ToolResultEncoding } from "./tool-result-normalization"; /* Tools belong to a connection and are PERSISTED, like v1 — not resolved live on * every list. A plugin produces them at create/refresh (openapi from the @@ -20,7 +21,11 @@ export interface ToolDef { readonly name: ToolName; readonly description?: string; readonly inputSchema?: unknown; + /** The schema of the tool's SEMANTIC payload (`ToolResult.data` after any + * result encoding is applied), never a transport envelope. */ readonly outputSchema?: unknown; + /** How raw invocation values decode into `data`; omitted = "direct". */ + readonly resultEncoding?: ToolResultEncoding; readonly annotations?: ToolAnnotations; } @@ -36,6 +41,7 @@ export interface Tool { readonly description: string; readonly inputSchema?: unknown; readonly outputSchema?: unknown; + readonly resultEncoding?: ToolResultEncoding; readonly annotations?: ToolAnnotations; /** True for plugin-contributed static tools (for example Executor's own * configuration tools). Static tools have no backing connection even though diff --git a/packages/plugins/mcp/src/sdk/elicitation.test.ts b/packages/plugins/mcp/src/sdk/elicitation.test.ts index cc2bed354f..1eb620e4d3 100644 --- a/packages/plugins/mcp/src/sdk/elicitation.test.ts +++ b/packages/plugins/mcp/src/sdk/elicitation.test.ts @@ -136,14 +136,9 @@ describe("MCP elicitation (end-to-end)", () => { ); yield* executor.close(); - expect(accepted).toMatchObject({ - ok: true, - data: { content: [{ type: "text", text: "approved:hello" }] }, - }); - expect(declined).toMatchObject({ - ok: true, - data: { content: [{ type: "text", text: "denied:nope" }] }, - }); + // v2 result contract: a lone prose text block IS the data. + expect(accepted).toMatchObject({ ok: true, data: "approved:hello" }); + expect(declined).toMatchObject({ ok: true, data: "denied:nope" }); expect(acceptedMessages).toEqual(['Approve echo for "hello"?']); expect(declinedMessages).toEqual(['Approve echo for "nope"?']); expect(capturedAddress).toBe(String(gatedEcho.address)); @@ -180,67 +175,41 @@ describe("MCP elicitation (end-to-end)", () => { { onElicitation: "accept-all" }, ); - expect(result).toMatchObject({ - ok: true, - data: { content: [{ type: "text", text: "plain" }] }, - }); + expect(result).toMatchObject({ ok: true, data: "plain" }); }), ); - it.effect("registered tools without MCP outputSchema still describe CallToolResult", () => + it.effect("tools without a declared MCP output schema serve the observed payload shape", () => Effect.gen(function* () { const server = yield* serveElicitationTestServer; const executor = yield* makeTestExecutor(server.url); const tools = yield* executor.tools.list(); const simpleEcho = findTool(tools, "simple_echo"); - const schema = yield* executor.tools.schema(simpleEcho.address); - expect(schema?.outputSchema).toMatchObject({ - type: "object", - properties: { - content: { type: "array" }, - structuredContent: {}, - isError: { const: false }, - _meta: { type: "object" }, - }, - required: ["content"], - }); - const outputSchema = schema?.outputSchema as { - readonly properties: { - readonly content: { - readonly items: { - readonly anyOf: readonly unknown[]; - }; - }; - }; - }; - expect(outputSchema.properties.content.items.anyOf).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - properties: expect.objectContaining({ - type: { const: "text", type: "string" }, - text: { type: "string" }, - }), - required: ["type", "text"], - }), - ]), - ); - expect(schema?.outputTypeScript).toContain('type: "text"'); - expect(schema?.outputTypeScript).toContain("structuredContent?: unknown;"); + // Cold: nothing declared, nothing observed — no envelope schema is + // synthesized any more. + const cold = yield* executor.tools.schema(simpleEcho.address); + expect(cold?.outputSchema).toBeUndefined(); + expect(cold?.outputTypeScript).toBeUndefined(); const result = yield* executor.execute( simpleEcho.address, { value: "plain" }, { onElicitation: "accept-all" }, ); + expect(result).toMatchObject({ ok: true, data: "plain" }); + // Warm: the observed semantic payload (a prose string) is served. + const warm = yield* executor.tools.schema(simpleEcho.address); + expect(warm?.outputSchemaSource).toBe("observed"); + expect(warm?.outputSchema).toMatchObject({ type: "string" }); const data = expectToolResultOkData(result); - expectMatchesOutputSchema(schema?.outputSchema, data); - expect(typeCheckOutputTypeScript(schema, data)).toEqual([]); + expectMatchesOutputSchema(warm?.outputSchema, data); + expect(typeCheckOutputTypeScript(warm, data)).toEqual([]); }), ); - it.effect("successful tool invocation preserves structured MCP result fields", () => + it.effect("structured MCP results become semantic data with supplemental content beside", () => Effect.gen(function* () { const server = yield* serveElicitationTestServer; const executor = yield* makeTestExecutor(server.url); @@ -248,23 +217,17 @@ describe("MCP elicitation (end-to-end)", () => { const structuredEcho = findTool(tools, "structured_echo"); const schema = yield* executor.tools.schema(structuredEcho.address); + // The server's declared output schema types `data` directly — no + // CallToolResult wrapper. expect(schema?.outputSchema).toMatchObject({ type: "object", properties: { - content: { type: "array" }, - structuredContent: { - type: "object", - properties: { - value: { type: "string" }, - upper: { type: "string" }, - }, - }, - _meta: { type: "object" }, + value: { type: "string" }, + upper: { type: "string" }, }, - required: ["content", "structuredContent"], }); - expect(schema?.outputTypeScript).toContain("structuredContent"); expect(schema?.outputTypeScript).toContain("value: string"); + expect(schema?.outputTypeScript).not.toContain("structuredContent"); const result = yield* executor.execute( structuredEcho.address, @@ -272,13 +235,13 @@ describe("MCP elicitation (end-to-end)", () => { { onElicitation: "accept-all" }, ); + // structuredContent IS the data; the non-duplicate text block rides in + // `content`; envelope `_meta` moves to `meta`. expect(result).toMatchObject({ ok: true, - data: { - content: [{ type: "text", text: "plain" }], - structuredContent: { value: "plain", upper: "PLAIN" }, - _meta: { trace: "kept" }, - }, + data: { value: "plain", upper: "PLAIN" }, + content: [{ type: "text", text: "plain" }], + meta: { trace: "kept" }, }); const data = expectToolResultOkData(result); expectMatchesOutputSchema(schema?.outputSchema, data); @@ -286,7 +249,7 @@ describe("MCP elicitation (end-to-end)", () => { }), ); - it.effect("connections.refresh keeps MCP outputSchema nested under structuredContent", () => + it.effect("connections.refresh keeps the declared schema semantic", () => Effect.gen(function* () { const server = yield* serveElicitationTestServer; const executor = yield* makeTestExecutor(server.url); @@ -302,18 +265,10 @@ describe("MCP elicitation (end-to-end)", () => { expect(schema?.outputSchema).toMatchObject({ type: "object", properties: { - content: { type: "array" }, - structuredContent: { - type: "object", - properties: { - value: { type: "string" }, - upper: { type: "string" }, - }, - }, + value: { type: "string" }, + upper: { type: "string" }, }, - required: ["content", "structuredContent"], }); - expect(schema?.outputTypeScript).toContain("structuredContent"); expect(schema?.outputTypeScript).toContain("upper: string"); const result = yield* executor.execute( diff --git a/packages/plugins/mcp/src/sdk/image-content.test.ts b/packages/plugins/mcp/src/sdk/image-content.test.ts index 2aba5ae4a2..4131b4eefe 100644 --- a/packages/plugins/mcp/src/sdk/image-content.test.ts +++ b/packages/plugins/mcp/src/sdk/image-content.test.ts @@ -53,16 +53,16 @@ describe("MCP image content", () => { {}, { onElicitation: "accept-all" }, ); + // v2 contract: structuredContent IS the data; the image block rides + // beside it in `content` rather than being buried in an envelope. expect(imageOnly).toMatchObject({ ok: true, data: { - content: [imageBlock], - structuredContent: { - name: "mcp-image-fixture.png", - mimeType: TEST_IMAGE_MIME_TYPE, - byteLength: 70, - }, + name: "mcp-image-fixture.png", + mimeType: TEST_IMAGE_MIME_TYPE, + byteLength: 70, }, + content: [imageBlock], }); const mixed = yield* executor.execute( @@ -70,17 +70,22 @@ describe("MCP image content", () => { {}, { onElicitation: "accept-all" }, ); + // structuredContent IS the data; the prose caption and image ride in + // `content` beside it. expect(mixed).toMatchObject({ ok: true, data: { - content: [ - { - type: "text", - text: "Deterministic image fixture: mcp-image-fixture.png (image/png, 70 bytes)", - }, - imageBlock, - ], + name: "mcp-image-fixture.png", + mimeType: TEST_IMAGE_MIME_TYPE, + byteLength: 70, }, + content: [ + { + type: "text", + text: "Deterministic image fixture: mcp-image-fixture.png (image/png, 70 bytes)", + }, + imageBlock, + ], }); }), ); diff --git a/packages/plugins/mcp/src/sdk/multi-placement-auth.test.ts b/packages/plugins/mcp/src/sdk/multi-placement-auth.test.ts index 590c05273c..e2be169235 100644 --- a/packages/plugins/mcp/src/sdk/multi-placement-auth.test.ts +++ b/packages/plugins/mcp/src/sdk/multi-placement-auth.test.ts @@ -71,10 +71,7 @@ describe("MCP multi-placement auth", () => { { marker: "mixed" }, { onElicitation: "accept-all" }, ); - expect(result).toMatchObject({ - ok: true, - data: { content: [{ type: "text", text: "ok:mixed" }] }, - }); + expect(result).toMatchObject({ ok: true, data: "ok:mixed" }); const requests = (yield* server.requests).slice(before); expect(requests.length).toBeGreaterThan(0); diff --git a/packages/plugins/mcp/src/sdk/owner-isolation.test.ts b/packages/plugins/mcp/src/sdk/owner-isolation.test.ts index 7502bce0cf..b9142fa7c6 100644 --- a/packages/plugins/mcp/src/sdk/owner-isolation.test.ts +++ b/packages/plugins/mcp/src/sdk/owner-isolation.test.ts @@ -84,10 +84,7 @@ describe("MCP owner isolation", () => { { marker: "from-org" }, { onElicitation: "accept-all" }, ); - expect(orgResult).toMatchObject({ - ok: true, - data: { content: [{ type: "text", text: "ok:from-org" }] }, - }); + expect(orgResult).toMatchObject({ ok: true, data: "ok:from-org" }); expect( (yield* server.requests) .slice(beforeOrg) @@ -102,10 +99,7 @@ describe("MCP owner isolation", () => { { marker: "from-user" }, { onElicitation: "accept-all" }, ); - expect(userResult).toMatchObject({ - ok: true, - data: { content: [{ type: "text", text: "ok:from-user" }] }, - }); + expect(userResult).toMatchObject({ ok: true, data: "ok:from-user" }); const userRequests = (yield* server.requests).slice(beforeUser); expect(userRequests.some((request) => request.authorization === "Bearer token-user")).toBe( true, diff --git a/packages/plugins/mcp/src/sdk/plugin.ts b/packages/plugins/mcp/src/sdk/plugin.ts index d401f896cc..ffd04793bf 100644 --- a/packages/plugins/mcp/src/sdk/plugin.ts +++ b/packages/plugins/mcp/src/sdk/plugin.ts @@ -3,8 +3,6 @@ import type { HttpClient } from "effect/unstable/http"; import type { OAuthClientProvider } from "@modelcontextprotocol/client"; -import { callToolResultJsonSchema } from "./call-tool-result-schema.gen"; - import { authToolFailure, AuthTemplateSlug, @@ -396,35 +394,6 @@ const toIntegrationConfig = (input: McpServerInput): McpIntegrationConfigType => }; }; -type JsonSchemaObject = Record & { - readonly properties?: Record; -}; - -// Baked at generation time rather than derived from @modelcontextprotocol/core -// at module scope — importing core costs ~8.6MB of heap per Cloudflare isolate -// (see client-module.ts), and this schema is the only thing the plugin needs -// from it outside a live connection. -const McpCallToolResultJsonSchema: JsonSchemaObject = callToolResultJsonSchema; - -const mcpCallToolResultOutputSchema = (structuredContentSchema?: unknown): JsonSchemaObject => { - const defaultStructuredContentSchema = - McpCallToolResultJsonSchema.properties?.structuredContent ?? {}; - - return { - ...McpCallToolResultJsonSchema, - properties: { - ...McpCallToolResultJsonSchema.properties, - structuredContent: - structuredContentSchema === undefined - ? defaultStructuredContentSchema - : structuredContentSchema, - isError: { const: false }, - }, - required: - structuredContentSchema === undefined ? ["content"] : ["content", "structuredContent"], - }; -}; - /** Build the executor-facing ToolDef for one discovered MCP tool, stamping the * real MCP tool name + upstream annotations into the persisted annotations so * they survive to invokeTool with no plugin-side store. */ @@ -443,7 +412,12 @@ const toToolDef = (entry: McpToolManifestEntry): ToolDef => { name: ToolName.make(entry.toolId), description: entry.description ?? `MCP tool: ${entry.toolName}`, inputSchema: entry.inputSchema, - outputSchema: mcpCallToolResultOutputSchema(entry.outputSchema), + // The server's declared output schema types `structuredContent` — which + // IS the semantic payload once the v2 result encoding normalizes the + // CallToolResult envelope away. No declared schema → undefined, and the + // executor serves the runtime-observed payload shape instead. + outputSchema: entry.outputSchema, + resultEncoding: "mcp-call-tool-result-v2", annotations: annotations as ToolAnnotations, }; }; diff --git a/packages/plugins/mcp/src/sdk/slack-connect-file.integration.test.ts b/packages/plugins/mcp/src/sdk/slack-connect-file.integration.test.ts index 696fb12f5c..96c69fda88 100644 --- a/packages/plugins/mcp/src/sdk/slack-connect-file.integration.test.ts +++ b/packages/plugins/mcp/src/sdk/slack-connect-file.integration.test.ts @@ -167,14 +167,13 @@ describe("Slack Connect file fallback", () => { { onElicitation: "accept-all" }, ); + // v2 contract: a multi-block result is the ordered block array. expect(result).toMatchObject({ ok: true, - data: { - content: [ - { type: "text", text: expect.stringContaining(FILE_ID) }, - { type: "image", mimeType: "image/png" }, - ], - }, + data: [ + { type: "text", text: expect.stringContaining(FILE_ID) }, + { type: "image", mimeType: "image/png" }, + ], }); }), ), diff --git a/packages/plugins/mcp/src/testing/index.ts b/packages/plugins/mcp/src/testing/index.ts index 172f5ee85e..53fe0a7d39 100644 --- a/packages/plugins/mcp/src/testing/index.ts +++ b/packages/plugins/mcp/src/testing/index.ts @@ -9,6 +9,7 @@ export { makeGreetingMcpServer, makeImageMcpServer, makeMutableCatalogMcpServer, + makeUndeclaredStructuredMcpServer, serveMcpServer, serveMcpServerWithOAuth, type McpTestRequest, diff --git a/packages/plugins/mcp/src/testing/server.ts b/packages/plugins/mcp/src/testing/server.ts index dceac0db2a..ca16acfa71 100644 --- a/packages/plugins/mcp/src/testing/server.ts +++ b/packages/plugins/mcp/src/testing/server.ts @@ -514,6 +514,30 @@ export const makeElicitationMcpServer = () => { return server; }; +/** The muscle-memory case: a server returning structured data at runtime with + * NO declared output schema, like most real MCP servers. The catalog gives + * the client nothing to type the payload with until it is observed. */ +export const makeUndeclaredStructuredMcpServer = () => { + const server = new McpServer( + { name: "undeclared-structured-test-server", version: "1.0.0" }, + { capabilities: {} }, + ); + + server.registerTool( + "undeclared_structured_echo", + { + description: "Returns structured data it never declared a schema for", + inputSchema: { value: z.string() }, + }, + async ({ value }: { value: string }) => ({ + content: [{ type: "text" as const, text: value }], + structuredContent: { value, length: value.length, ok: true }, + }), + ); + + return server; +}; + /** * A server whose tool catalog mutates at runtime. `renameTool` renames the * advertised tool from `initialToolName` to `renamedToolName` via the SDK's