From fdd4e33f9ca7d4faeac7d393234b22a47faafbd8 Mon Sep 17 00:00:00 2001 From: Svetoslav Dekov Date: Fri, 24 Jul 2026 17:09:04 +0300 Subject: [PATCH] feat(core): Add availableInMCP column to agents (#34665) Co-authored-by: Claude Fable 5 --- docs/generated/postgres-schema/README.md | 3 +- .../public.agent_chat_subscriptions.md | 1 + .../public.agent_checkpoints.md | 1 + .../public.agent_eval_dataset.md | 1 + .../public.agent_execution_threads.md | 1 + .../postgres-schema/public.agent_files.md | 1 + .../postgres-schema/public.agent_history.md | 1 + .../public.agent_task_definition.md | 1 + .../public.agent_task_run_lock.md | 1 + .../postgres-schema/public.agents.md | 3 + .../public.agents_memory_entries.md | 1 + .../public.agents_memory_entry_cursors.md | 1 + .../public.agents_memory_entry_locks.md | 1 + .../public.agents_memory_entry_sources.md | 1 + .../public.agents_observation_cursors.md | 1 + .../public.agents_observation_locks.md | 1 + .../public.agents_observations.md | 1 + .../postgres-schema/public.project.md | 1 + docs/generated/sqlite-schema/README.md | 5 +- .../sqlite-schema/agent_chat_subscriptions.md | 1 + .../sqlite-schema/agent_checkpoints.md | 1 + .../sqlite-schema/agent_eval_dataset.md | 1 + .../sqlite-schema/agent_execution_threads.md | 1 + docs/generated/sqlite-schema/agent_files.md | 1 + docs/generated/sqlite-schema/agent_history.md | 1 + .../sqlite-schema/agent_task_definition.md | 1 + .../sqlite-schema/agent_task_run_lock.md | 1 + docs/generated/sqlite-schema/agents.md | 8 +- .../sqlite-schema/agents_memory_entries.md | 1 + .../agents_memory_entry_cursors.md | 1 + .../agents_memory_entry_locks.md | 1 + .../agents_memory_entry_sources.md | 1 + .../agents_observation_cursors.md | 1 + .../sqlite-schema/agents_observation_locks.md | 1 + .../sqlite-schema/agents_observations.md | 1 + docs/generated/sqlite-schema/project.md | 1 + ...1784897791636-AddAvailableInMcpToAgents.ts | 19 ++ .../db/src/migrations/postgresdb/index.ts | 2 + ...1784897791636-AddAvailableInMcpToAgents.ts | 9 + .../@n8n/db/src/migrations/sqlite/index.ts | 2 + ...636-add-available-in-mcp-to-agents.test.ts | 235 ++++++++++++++++++ 41 files changed, 312 insertions(+), 6 deletions(-) create mode 100644 packages/@n8n/db/src/migrations/common/1784897791636-AddAvailableInMcpToAgents.ts create mode 100644 packages/@n8n/db/src/migrations/sqlite/1784897791636-AddAvailableInMcpToAgents.ts create mode 100644 packages/cli/test/migration/1784897791636-add-available-in-mcp-to-agents.test.ts diff --git a/docs/generated/postgres-schema/README.md b/docs/generated/postgres-schema/README.md index 2eec9e7a69f..bddc5934894 100644 --- a/docs/generated/postgres-schema/README.md +++ b/docs/generated/postgres-schema/README.md @@ -21,7 +21,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand. | [public.agent_task_definition](public.agent_task_definition.md) | 7 | | BASE TABLE | | [public.agent_task_run_lock](public.agent_task_run_lock.md) | 6 | | BASE TABLE | | [public.agent_task_snapshot](public.agent_task_snapshot.md) | 8 | | BASE TABLE | -| [public.agents](public.agents.md) | 11 | | BASE TABLE | +| [public.agents](public.agents.md) | 12 | | BASE TABLE | | [public.agents_memory_entries](public.agents_memory_entries.md) | 13 | | BASE TABLE | | [public.agents_memory_entry_cursors](public.agents_memory_entry_cursors.md) | 6 | | BASE TABLE | | [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) | 6 | | BASE TABLE | @@ -489,6 +489,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_chat_subscriptions.md b/docs/generated/postgres-schema/public.agent_chat_subscriptions.md index 6c3c0a8d4c5..ef34bc50478 100644 --- a/docs/generated/postgres-schema/public.agent_chat_subscriptions.md +++ b/docs/generated/postgres-schema/public.agent_chat_subscriptions.md @@ -48,6 +48,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_checkpoints.md b/docs/generated/postgres-schema/public.agent_checkpoints.md index 01f507e0a23..2f84bd74092 100644 --- a/docs/generated/postgres-schema/public.agent_checkpoints.md +++ b/docs/generated/postgres-schema/public.agent_checkpoints.md @@ -46,6 +46,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_eval_dataset.md b/docs/generated/postgres-schema/public.agent_eval_dataset.md index 7b19f5e9a26..95eb26e6cbb 100644 --- a/docs/generated/postgres-schema/public.agent_eval_dataset.md +++ b/docs/generated/postgres-schema/public.agent_eval_dataset.md @@ -61,6 +61,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_execution_threads.md b/docs/generated/postgres-schema/public.agent_execution_threads.md index c578c613e17..a3d116befb7 100644 --- a/docs/generated/postgres-schema/public.agent_execution_threads.md +++ b/docs/generated/postgres-schema/public.agent_execution_threads.md @@ -82,6 +82,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_files.md b/docs/generated/postgres-schema/public.agent_files.md index 12041cef160..c6610397810 100644 --- a/docs/generated/postgres-schema/public.agent_files.md +++ b/docs/generated/postgres-schema/public.agent_files.md @@ -56,6 +56,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_history.md b/docs/generated/postgres-schema/public.agent_history.md index 378adb2e497..e50da32c98f 100644 --- a/docs/generated/postgres-schema/public.agent_history.md +++ b/docs/generated/postgres-schema/public.agent_history.md @@ -58,6 +58,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_task_definition.md b/docs/generated/postgres-schema/public.agent_task_definition.md index cabbddd733b..37668892f62 100644 --- a/docs/generated/postgres-schema/public.agent_task_definition.md +++ b/docs/generated/postgres-schema/public.agent_task_definition.md @@ -51,6 +51,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agent_task_run_lock.md b/docs/generated/postgres-schema/public.agent_task_run_lock.md index ab7c0613b04..9fe376312b1 100644 --- a/docs/generated/postgres-schema/public.agent_task_run_lock.md +++ b/docs/generated/postgres-schema/public.agent_task_run_lock.md @@ -47,6 +47,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents.md b/docs/generated/postgres-schema/public.agents.md index 358b5a4b071..957edeb1c18 100644 --- a/docs/generated/postgres-schema/public.agents.md +++ b/docs/generated/postgres-schema/public.agents.md @@ -5,6 +5,7 @@ | Name | Type | Default | Nullable | Children | Parents | Comment | | ---- | ---- | ------- | -------- | -------- | ------- | ------- | | activeVersionId | varchar(36) | | true | | [public.agent_history](public.agent_history.md) | | +| availableInMCP | boolean | false | false | | | Whether MCP clients granted agent scopes may operate on this agent | | createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | | | id | varchar(36) | | false | [public.agent_chat_subscriptions](public.agent_chat_subscriptions.md) [public.agent_checkpoints](public.agent_checkpoints.md) [public.agent_eval_dataset](public.agent_eval_dataset.md) [public.agent_execution_threads](public.agent_execution_threads.md) [public.agent_files](public.agent_files.md) [public.agent_history](public.agent_history.md) [public.agent_task_definition](public.agent_task_definition.md) [public.agent_task_run_lock](public.agent_task_run_lock.md) [public.agents_memory_entries](public.agents_memory_entries.md) [public.agents_memory_entry_cursors](public.agents_memory_entry_cursors.md) [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) [public.agents_memory_entry_sources](public.agents_memory_entry_sources.md) [public.agents_observation_cursors](public.agents_observation_cursors.md) [public.agents_observation_locks](public.agents_observation_locks.md) [public.agents_observations](public.agents_observations.md) | | | | integrations | json | '[]'::json | false | | | | @@ -23,6 +24,7 @@ | FK_940597dfe9753375309ce6aeea0 | FOREIGN KEY | FOREIGN KEY ("activeVersionId") REFERENCES agent_history("versionId") ON DELETE SET NULL | | FK_a30d560207c4071d98aa03c179c | FOREIGN KEY | FOREIGN KEY ("projectId") REFERENCES project(id) ON DELETE CASCADE | | PK_9c653f28ae19c5884d5baf6a1d9 | PRIMARY KEY | PRIMARY KEY (id) | +| agents_availableInMCP_not_null | n | NOT NULL "availableInMCP" | | agents_createdAt_not_null | n | NOT NULL "createdAt" | | agents_id_not_null | n | NOT NULL id | | agents_integrations_not_null | n | NOT NULL integrations | @@ -65,6 +67,7 @@ erDiagram "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_memory_entries.md b/docs/generated/postgres-schema/public.agents_memory_entries.md index 6d4c06d0ff8..5d7ab6dba53 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entries.md +++ b/docs/generated/postgres-schema/public.agents_memory_entries.md @@ -74,6 +74,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_cursors.md b/docs/generated/postgres-schema/public.agents_memory_entry_cursors.md index 66b4d7f2931..e1b7bd22256 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entry_cursors.md +++ b/docs/generated/postgres-schema/public.agents_memory_entry_cursors.md @@ -50,6 +50,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_locks.md b/docs/generated/postgres-schema/public.agents_memory_entry_locks.md index 6d5d4bb9474..d32a0d664e2 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entry_locks.md +++ b/docs/generated/postgres-schema/public.agents_memory_entry_locks.md @@ -50,6 +50,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_sources.md b/docs/generated/postgres-schema/public.agents_memory_entry_sources.md index addcc3b57af..dcfbb82d090 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entry_sources.md +++ b/docs/generated/postgres-schema/public.agents_memory_entry_sources.md @@ -66,6 +66,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_observation_cursors.md b/docs/generated/postgres-schema/public.agents_observation_cursors.md index e61d9d3a3d6..59455a4bb85 100644 --- a/docs/generated/postgres-schema/public.agents_observation_cursors.md +++ b/docs/generated/postgres-schema/public.agents_observation_cursors.md @@ -50,6 +50,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_observation_locks.md b/docs/generated/postgres-schema/public.agents_observation_locks.md index f3e4842de58..3a6e176166d 100644 --- a/docs/generated/postgres-schema/public.agents_observation_locks.md +++ b/docs/generated/postgres-schema/public.agents_observation_locks.md @@ -54,6 +54,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.agents_observations.md b/docs/generated/postgres-schema/public.agents_observations.md index 4d90eb39e64..22f37a6b446 100644 --- a/docs/generated/postgres-schema/public.agents_observations.md +++ b/docs/generated/postgres-schema/public.agents_observations.md @@ -73,6 +73,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/postgres-schema/public.project.md b/docs/generated/postgres-schema/public.project.md index ba179000143..a626e6d4dfb 100644 --- a/docs/generated/postgres-schema/public.project.md +++ b/docs/generated/postgres-schema/public.project.md @@ -102,6 +102,7 @@ erDiagram } "public.agents" { varchar_36_ activeVersionId FK + boolean availableInMCP timestamp_3__with_time_zone createdAt varchar_36_ id json integrations diff --git a/docs/generated/sqlite-schema/README.md b/docs/generated/sqlite-schema/README.md index 0f5cae0de59..aad7d858f17 100644 --- a/docs/generated/sqlite-schema/README.md +++ b/docs/generated/sqlite-schema/README.md @@ -21,7 +21,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand. | [agent_task_definition](agent_task_definition.md) | 7 | | table | | [agent_task_run_lock](agent_task_run_lock.md) | 6 | | table | | [agent_task_snapshot](agent_task_snapshot.md) | 8 | | table | -| [agents](agents.md) | 11 | | table | +| [agents](agents.md) | 12 | | table | | [agents_memory_entries](agents_memory_entries.md) | 13 | | table | | [agents_memory_entry_cursors](agents_memory_entry_cursors.md) | 6 | | table | | [agents_memory_entry_locks](agents_memory_entry_locks.md) | 6 | | table | @@ -155,8 +155,8 @@ erDiagram "agent_task_definition" }o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agent_task_run_lock" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agent_task_snapshot" |o--|| "agent_history" : "FOREIGN KEY (versionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" -"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agents" }o--o| "agent_history" : "FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE" +"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agents_memory_entries" }o--o| "agents_memory_entries" : "FOREIGN KEY (supersededBy) REFERENCES agents_memory_entries (id) ON UPDATE NO ACTION ON DELETE NO ACTION MATCH NONE" "agents_memory_entries" }o--|| "agents_resources" : "FOREIGN KEY (resourceId) REFERENCES agents_resources (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agents_memory_entries" }o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" @@ -476,6 +476,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_chat_subscriptions.md b/docs/generated/sqlite-schema/agent_chat_subscriptions.md index dbf2f877efa..714f4582614 100644 --- a/docs/generated/sqlite-schema/agent_chat_subscriptions.md +++ b/docs/generated/sqlite-schema/agent_chat_subscriptions.md @@ -57,6 +57,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_checkpoints.md b/docs/generated/sqlite-schema/agent_checkpoints.md index ed04354728a..fa22cf4b59b 100644 --- a/docs/generated/sqlite-schema/agent_checkpoints.md +++ b/docs/generated/sqlite-schema/agent_checkpoints.md @@ -54,6 +54,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_eval_dataset.md b/docs/generated/sqlite-schema/agent_eval_dataset.md index 3fb21fe79f8..eadd1193682 100644 --- a/docs/generated/sqlite-schema/agent_eval_dataset.md +++ b/docs/generated/sqlite-schema/agent_eval_dataset.md @@ -66,6 +66,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_execution_threads.md b/docs/generated/sqlite-schema/agent_execution_threads.md index 17c617dc5ef..587378ed8f5 100644 --- a/docs/generated/sqlite-schema/agent_execution_threads.md +++ b/docs/generated/sqlite-schema/agent_execution_threads.md @@ -83,6 +83,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_files.md b/docs/generated/sqlite-schema/agent_files.md index 43dd427be19..cb67a888cec 100644 --- a/docs/generated/sqlite-schema/agent_files.md +++ b/docs/generated/sqlite-schema/agent_files.md @@ -60,6 +60,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_history.md b/docs/generated/sqlite-schema/agent_history.md index 1d572325a53..3679e183648 100644 --- a/docs/generated/sqlite-schema/agent_history.md +++ b/docs/generated/sqlite-schema/agent_history.md @@ -65,6 +65,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_task_definition.md b/docs/generated/sqlite-schema/agent_task_definition.md index ded38141a54..6d4c5e2bee2 100644 --- a/docs/generated/sqlite-schema/agent_task_definition.md +++ b/docs/generated/sqlite-schema/agent_task_definition.md @@ -56,6 +56,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agent_task_run_lock.md b/docs/generated/sqlite-schema/agent_task_run_lock.md index 5c8184066e8..8fcaa3ce749 100644 --- a/docs/generated/sqlite-schema/agent_task_run_lock.md +++ b/docs/generated/sqlite-schema/agent_task_run_lock.md @@ -54,6 +54,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents.md b/docs/generated/sqlite-schema/agents.md index d925895eee7..338cdea363e 100644 --- a/docs/generated/sqlite-schema/agents.md +++ b/docs/generated/sqlite-schema/agents.md @@ -6,7 +6,7 @@ Table Definition ```sql -CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255) NOT NULL, "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION) +CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255) NOT NULL, "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION) ``` @@ -16,6 +16,7 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128 | Name | Type | Default | Nullable | Children | Parents | Comment | | ---- | ---- | ------- | -------- | -------- | ------- | ------- | | activeVersionId | varchar(36) | | true | | [agent_history](agent_history.md) | | +| availableInMCP | boolean | false | false | | | | | createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | | | id | varchar(36) | | false | [agent_chat_subscriptions](agent_chat_subscriptions.md) [agent_checkpoints](agent_checkpoints.md) [agent_eval_dataset](agent_eval_dataset.md) [agent_execution_threads](agent_execution_threads.md) [agent_files](agent_files.md) [agent_history](agent_history.md) [agent_task_definition](agent_task_definition.md) [agent_task_run_lock](agent_task_run_lock.md) [agents_memory_entries](agents_memory_entries.md) [agents_memory_entry_cursors](agents_memory_entry_cursors.md) [agents_memory_entry_locks](agents_memory_entry_locks.md) [agents_memory_entry_sources](agents_memory_entry_sources.md) [agents_observation_cursors](agents_observation_cursors.md) [agents_observation_locks](agents_observation_locks.md) [agents_observations](agents_observations.md) | | | | integrations | TEXT | '[]' | false | | | | @@ -31,8 +32,8 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128 | Name | Type | Definition | | ---- | ---- | ---------- | -| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE | -| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE | +| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE | +| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE | | id | PRIMARY KEY | PRIMARY KEY (id) | | sqlite_autoindex_agents_1 | PRIMARY KEY | PRIMARY KEY (id) | @@ -69,6 +70,7 @@ erDiagram "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_memory_entries.md b/docs/generated/sqlite-schema/agents_memory_entries.md index 2601608f3d9..8f78854a99e 100644 --- a/docs/generated/sqlite-schema/agents_memory_entries.md +++ b/docs/generated/sqlite-schema/agents_memory_entries.md @@ -77,6 +77,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_memory_entry_cursors.md b/docs/generated/sqlite-schema/agents_memory_entry_cursors.md index 8d8bdf303b0..064f6523987 100644 --- a/docs/generated/sqlite-schema/agents_memory_entry_cursors.md +++ b/docs/generated/sqlite-schema/agents_memory_entry_cursors.md @@ -57,6 +57,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_memory_entry_locks.md b/docs/generated/sqlite-schema/agents_memory_entry_locks.md index 56144bdceba..f2a40760528 100644 --- a/docs/generated/sqlite-schema/agents_memory_entry_locks.md +++ b/docs/generated/sqlite-schema/agents_memory_entry_locks.md @@ -57,6 +57,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_memory_entry_sources.md b/docs/generated/sqlite-schema/agents_memory_entry_sources.md index 357d217c916..4bb82b6664b 100644 --- a/docs/generated/sqlite-schema/agents_memory_entry_sources.md +++ b/docs/generated/sqlite-schema/agents_memory_entry_sources.md @@ -69,6 +69,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_observation_cursors.md b/docs/generated/sqlite-schema/agents_observation_cursors.md index 484e0a78f5b..0b238c3fd21 100644 --- a/docs/generated/sqlite-schema/agents_observation_cursors.md +++ b/docs/generated/sqlite-schema/agents_observation_cursors.md @@ -57,6 +57,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_observation_locks.md b/docs/generated/sqlite-schema/agents_observation_locks.md index f755493c2a9..995c689be1a 100644 --- a/docs/generated/sqlite-schema/agents_observation_locks.md +++ b/docs/generated/sqlite-schema/agents_observation_locks.md @@ -61,6 +61,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/agents_observations.md b/docs/generated/sqlite-schema/agents_observations.md index aaa26b0dbdd..cafb9cb0216 100644 --- a/docs/generated/sqlite-schema/agents_observations.md +++ b/docs/generated/sqlite-schema/agents_observations.md @@ -76,6 +76,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/docs/generated/sqlite-schema/project.md b/docs/generated/sqlite-schema/project.md index 9b2d929dec3..0e7ace1f8fb 100644 --- a/docs/generated/sqlite-schema/project.md +++ b/docs/generated/sqlite-schema/project.md @@ -108,6 +108,7 @@ erDiagram } "agents" { varchar_36_ activeVersionId FK + boolean availableInMCP datetime_3_ createdAt varchar_36_ id PK TEXT integrations diff --git a/packages/@n8n/db/src/migrations/common/1784897791636-AddAvailableInMcpToAgents.ts b/packages/@n8n/db/src/migrations/common/1784897791636-AddAvailableInMcpToAgents.ts new file mode 100644 index 00000000000..a1e6c961a59 --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1784897791636-AddAvailableInMcpToAgents.ts @@ -0,0 +1,19 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +export class AddAvailableInMcpToAgents1784897791636 implements ReversibleMigration { + async up({ schemaBuilder: { addColumns, column } }: MigrationContext) { + await addColumns( + 'agents', + [ + column('availableInMCP') + .bool.notNull.default(false) + .comment('Whether MCP clients granted agent scopes may operate on this agent'), + ], + { recreatesOnSqlite: true }, + ); + } + + async down({ schemaBuilder: { dropColumns } }: MigrationContext) { + await dropColumns('agents', ['availableInMCP'], { recreatesOnSqlite: true }); + } +} diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index fcdd15e889a..70ea9605657 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -229,6 +229,7 @@ import { CreateWorkflowReviewRequestTables1784000000052 } from '../common/178400 import { AddStoredAtToAgentExecution1784815940110 } from '../common/1784815940110-AddStoredAtToAgentExecution'; import { AddInstanceCredentials1784815940111 } from '../common/1784815940111-AddInstanceCredentials'; import { CreateAgentEvalTables1784815940112 } from '../common/1784815940112-CreateAgentEvalTables'; +import { AddAvailableInMcpToAgents1784897791636 } from '../common/1784897791636-AddAvailableInMcpToAgents'; import type { Migration } from '../migration-types'; export const postgresMigrations: Migration[] = [ @@ -463,4 +464,5 @@ export const postgresMigrations: Migration[] = [ AddStoredAtToAgentExecution1784815940110, AddInstanceCredentials1784815940111, CreateAgentEvalTables1784815940112, + AddAvailableInMcpToAgents1784897791636, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/1784897791636-AddAvailableInMcpToAgents.ts b/packages/@n8n/db/src/migrations/sqlite/1784897791636-AddAvailableInMcpToAgents.ts new file mode 100644 index 00000000000..ba82fe6f29f --- /dev/null +++ b/packages/@n8n/db/src/migrations/sqlite/1784897791636-AddAvailableInMcpToAgents.ts @@ -0,0 +1,9 @@ +import { AddAvailableInMcpToAgents1784897791636 as BaseMigration } from '../common/1784897791636-AddAvailableInMcpToAgents'; + +/** + * Adding a column recreates `agents` on SQLite, and several agent tables have + * CASCADE FKs to it. Disable FKs for the migration's duration. + */ +export class AddAvailableInMcpToAgents1784897791636 extends BaseMigration { + withFKsDisabled = true as const; +} diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 3679349e891..f95c4afed2d 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -58,6 +58,7 @@ import { AddProjectIdToInstanceAiThread1784000000028 } from './1784000000028-Add import { AllowAzureStoredAt1784000000034 } from './1784000000034-AllowAzureStoredAt'; import { DropAgentDescriptionFromAgents1784000000037 } from './1784000000037-DropAgentDescriptionFromAgents'; import { AddRecurringCronScheduleKind1784000000045 } from './1784000000045-AddRecurringCronScheduleKind'; +import { AddAvailableInMcpToAgents1784897791636 } from './1784897791636-AddAvailableInMcpToAgents'; import { UniqueWorkflowNames1620821879465 } from '../common/1620821879465-UniqueWorkflowNames'; import { UpdateWorkflowCredentials1630330987096 } from '../common/1630330987096-UpdateWorkflowCredentials'; import { AddNodeIds1658930531669 } from '../common/1658930531669-AddNodeIds'; @@ -445,6 +446,7 @@ const sqliteMigrations: Migration[] = [ AddStoredAtToAgentExecution1784815940110, AddInstanceCredentials1784815940111, CreateAgentEvalTables1784815940112, + AddAvailableInMcpToAgents1784897791636, ]; export { sqliteMigrations }; diff --git a/packages/cli/test/migration/1784897791636-add-available-in-mcp-to-agents.test.ts b/packages/cli/test/migration/1784897791636-add-available-in-mcp-to-agents.test.ts new file mode 100644 index 00000000000..57a085542a7 --- /dev/null +++ b/packages/cli/test/migration/1784897791636-add-available-in-mcp-to-agents.test.ts @@ -0,0 +1,235 @@ +import { + createTestMigrationContext, + initDbUpToMigration, + runSingleMigration, + undoLastSingleMigration, + type TestMigrationContext, +} from '@n8n/backend-test-utils'; +import { DbConnection } from '@n8n/db'; +import { Container } from '@n8n/di'; +import { DataSource } from '@n8n/typeorm'; +import { randomUUID } from 'node:crypto'; + +const MIGRATION_NAME = 'AddAvailableInMcpToAgents1784897791636'; + +describe('AddAvailableInMcpToAgents Migration', () => { + let dataSource: DataSource; + + async function withContext(fn: (context: TestMigrationContext) => Promise): Promise { + const context = createTestMigrationContext(dataSource); + try { + return await fn(context); + } finally { + await context.queryRunner.release(); + } + } + + beforeAll(async () => { + const dbConnection = Container.get(DbConnection); + await dbConnection.init(); + dataSource = Container.get(DataSource); + }); + + beforeEach(async () => { + await withContext(async (context) => { + await context.queryRunner.clearDatabase(); + }); + await initDbUpToMigration(MIGRATION_NAME); + }); + + afterAll(async () => { + const dbConnection = Container.get(DbConnection); + await dbConnection.close(); + }); + + async function insertProject(context: TestMigrationContext, id: string): Promise { + const tableName = context.escape.tableName('project'); + await context.runQuery( + `INSERT INTO ${tableName} ("id", "name", "type", "createdAt", "updatedAt") + VALUES (:id, :name, :type, :createdAt, :updatedAt)`, + { + id, + name: `project-${id.slice(0, 8)}`, + type: 'personal', + createdAt: new Date(), + updatedAt: new Date(), + }, + ); + } + + async function insertAgent( + context: TestMigrationContext, + data: { id: string; projectId: string }, + ): Promise { + const tableName = context.escape.tableName('agents'); + await context.runQuery( + `INSERT INTO ${tableName} + ("id", "name", "projectId", "integrations", "tools", "skills", "createdAt", "updatedAt") + VALUES (:id, :name, :projectId, :integrations, :tools, :skills, :createdAt, :updatedAt)`, + { + id: data.id, + name: `agent-${data.id.slice(0, 8)}`, + projectId: data.projectId, + integrations: '[]', + tools: '{}', + skills: '{}', + createdAt: new Date(), + updatedAt: new Date(), + }, + ); + } + + async function insertAgentHistory( + context: TestMigrationContext, + data: { versionId: string; agentId: string }, + ): Promise { + const tableName = context.escape.tableName('agent_history'); + await context.runQuery( + `INSERT INTO ${tableName} ("versionId", "agentId", "author", "createdAt", "updatedAt") + VALUES (:versionId, :agentId, :author, :createdAt, :updatedAt)`, + { + versionId: data.versionId, + agentId: data.agentId, + author: 'Test User', + createdAt: new Date(), + updatedAt: new Date(), + }, + ); + } + + async function getColumnMeta(context: TestMigrationContext, columnName: string) { + if (context.isSqlite) { + const rows: Array<{ name: string; notnull: number; dflt_value: string | null }> = + await context.queryRunner.query(`PRAGMA table_info(${context.escape.tableName('agents')})`); + return rows.find((r) => r.name === columnName); + } + const rows: Array<{ + column_name: string; + is_nullable: string; + column_default: string | null; + }> = await context.queryRunner.query( + 'SELECT column_name, is_nullable, column_default FROM information_schema.columns WHERE table_name = $1 AND column_name = $2', + [`${context.tablePrefix}agents`, columnName], + ); + return rows[0]; + } + + describe('up', () => { + it('should add availableInMCP as NOT NULL with default false', async () => { + await runSingleMigration(MIGRATION_NAME); + dataSource = Container.get(DataSource); + + await withContext(async (context) => { + const col = await getColumnMeta(context, 'availableInMCP'); + expect(col).toBeDefined(); + if (context.isSqlite) { + expect((col as { notnull: number }).notnull).toBe(1); + } else { + expect((col as { is_nullable: string }).is_nullable).toBe('NO'); + } + }); + }); + + it('should backfill false on existing agents', async () => { + const projectId = randomUUID(); + const agentId = randomUUID(); + + await withContext(async (context) => { + await insertProject(context, projectId); + await insertAgent(context, { id: agentId, projectId }); + }); + + await runSingleMigration(MIGRATION_NAME); + dataSource = Container.get(DataSource); + + await withContext(async (context) => { + const rows = await context.runQuery>( + `SELECT "availableInMCP" FROM ${context.escape.tableName('agents')} WHERE "id" = :id`, + { id: agentId }, + ); + expect(rows).toHaveLength(1); + expect(Boolean(rows[0].availableInMCP)).toBe(false); + }); + }); + + it('should default availableInMCP to false on inserts that omit it', async () => { + await runSingleMigration(MIGRATION_NAME); + dataSource = Container.get(DataSource); + + const projectId = randomUUID(); + const agentId = randomUUID(); + + await withContext(async (context) => { + await insertProject(context, projectId); + await insertAgent(context, { id: agentId, projectId }); + + const rows = await context.runQuery>( + `SELECT "availableInMCP" FROM ${context.escape.tableName('agents')} WHERE "id" = :id`, + { id: agentId }, + ); + expect(Boolean(rows[0].availableInMCP)).toBe(false); + }); + }); + + it('should preserve rows in tables with CASCADE FKs to agents', async () => { + // On SQLite the migration recreates `agents` (drop + rename), which + // would cascade-delete child rows unless FKs are disabled for the + // migration's duration (withFKsDisabled on the sqlite subclass). + const projectId = randomUUID(); + const agentId = randomUUID(); + const versionId = randomUUID(); + + await withContext(async (context) => { + await insertProject(context, projectId); + await insertAgent(context, { id: agentId, projectId }); + await insertAgentHistory(context, { versionId, agentId }); + }); + + await runSingleMigration(MIGRATION_NAME); + dataSource = Container.get(DataSource); + + await withContext(async (context) => { + const agentRows = await context.runQuery>( + `SELECT "id" FROM ${context.escape.tableName('agents')} WHERE "id" = :id`, + { id: agentId }, + ); + expect(agentRows).toHaveLength(1); + + const historyRows = await context.runQuery>( + `SELECT "versionId" FROM ${context.escape.tableName('agent_history')} WHERE "agentId" = :id`, + { id: agentId }, + ); + expect(historyRows).toHaveLength(1); + expect(historyRows[0].versionId).toBe(versionId); + }); + }); + }); + + describe('down', () => { + it('should remove the column and preserve child rows', async () => { + const projectId = randomUUID(); + const agentId = randomUUID(); + const versionId = randomUUID(); + + await withContext(async (context) => { + await insertProject(context, projectId); + await insertAgent(context, { id: agentId, projectId }); + await insertAgentHistory(context, { versionId, agentId }); + }); + + await runSingleMigration(MIGRATION_NAME); + await undoLastSingleMigration(); + dataSource = Container.get(DataSource); + + await withContext(async (context) => { + expect(await getColumnMeta(context, 'availableInMCP')).toBeUndefined(); + + const historyRows = await context.runQuery>( + `SELECT "versionId" FROM ${context.escape.tableName('agent_history')} WHERE "agentId" = :id`, + { id: agentId }, + ); + expect(historyRows).toHaveLength(1); + }); + }); + }); +});