feat(core): Add availableInMCP column to agents (#34665)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Svetoslav Dekov
2026-07-24 17:09:04 +03:00
committed by GitHub
parent 5a97bd1a2e
commit fdd4e33f9c
41 changed files with 312 additions and 6 deletions
+2 -1
View File
@@ -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
@@ -48,6 +48,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -46,6 +46,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -61,6 +61,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -82,6 +82,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -56,6 +56,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -58,6 +58,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -51,6 +51,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -47,6 +47,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -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
@@ -74,6 +74,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -50,6 +50,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -50,6 +50,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -66,6 +66,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -50,6 +50,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -54,6 +54,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -73,6 +73,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
@@ -102,6 +102,7 @@ erDiagram
}
"public.agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
timestamp_3__with_time_zone createdAt
varchar_36_ id
json integrations
+3 -2
View File
@@ -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
@@ -57,6 +57,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -54,6 +54,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -66,6 +66,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -83,6 +83,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -60,6 +60,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -65,6 +65,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -56,6 +56,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -54,6 +54,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
+5 -3
View File
@@ -6,7 +6,7 @@
<summary><strong>Table Definition</strong></summary>
```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)
```
</details>
@@ -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
@@ -77,6 +77,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -57,6 +57,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -57,6 +57,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -69,6 +69,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -57,6 +57,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -61,6 +61,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -76,6 +76,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
+1
View File
@@ -108,6 +108,7 @@ erDiagram
}
"agents" {
varchar_36_ activeVersionId FK
boolean availableInMCP
datetime_3_ createdAt
varchar_36_ id PK
TEXT integrations
@@ -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 });
}
}
@@ -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,
];
@@ -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;
}
@@ -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 };
@@ -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<T>(fn: (context: TestMigrationContext) => Promise<T>): Promise<T> {
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<void> {
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<void> {
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<void> {
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<Array<{ availableInMCP: boolean | number }>>(
`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<Array<{ availableInMCP: boolean | number }>>(
`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<Array<{ id: string }>>(
`SELECT "id" FROM ${context.escape.tableName('agents')} WHERE "id" = :id`,
{ id: agentId },
);
expect(agentRows).toHaveLength(1);
const historyRows = await context.runQuery<Array<{ versionId: string }>>(
`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<Array<{ versionId: string }>>(
`SELECT "versionId" FROM ${context.escape.tableName('agent_history')} WHERE "agentId" = :id`,
{ id: agentId },
);
expect(historyRows).toHaveLength(1);
});
});
});
});