ungated-global: system project whose servers ride every project, pre-gate
Some checks failed
CI/CD / lint (pull_request) Successful in 1m12s
CI/CD / test (pull_request) Successful in 1m26s
CI/CD / typecheck (pull_request) Successful in 3m22s
CI/CD / smoke (pull_request) Failing after 1m57s
CI/CD / build (pull_request) Successful in 5m15s
CI/CD / publish (pull_request) Has been skipped
Some checks failed
CI/CD / lint (pull_request) Successful in 1m12s
CI/CD / test (pull_request) Successful in 1m26s
CI/CD / typecheck (pull_request) Successful in 3m22s
CI/CD / smoke (pull_request) Failing after 1m57s
CI/CD / build (pull_request) Successful in 5m15s
CI/CD / publish (pull_request) Has been skipped
New seed-once system project `ungated-global`: every server attached to it is merged into every project's MCP endpoint in front of the gate (listed and callable before begin_session), and its project prompt is prepended to every project's instructions. - mcpd: bootstrapUngatedGlobal seeds the project once-ever (marker key `ungatedGlobalSeeded` in mcpctl-system-settings), with default server `calcmcp` (patched CalcMCP fork, mysources.co.uk/michal/calcmcp:1.1.0) and a "never do math yourself, always use calcmcp" prompt. Deletions are durable: `mcpctl delete server calcmcp -p ungated-global` stays deleted. - mcpd: GET /projects/:id/servers appends global servers flagged `ungatedGlobal: true` (direct attachment wins, no self-merge); GET /projects/:id/instructions prepends the global prompt and lists the global servers. - mcplocal: discovery surfaces the flagged names; the gate plugin exempts them — gated tools/list returns begin_session + exempt tools, calls to them pass through without ungating, and initialize instructions list them under "Available immediately (no begin_session required)". - templates/calcmcp.yaml so a deleted default can be re-created by hand. - docs/ungated-global.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
112
src/mcpd/tests/bootstrap-ungated-global.test.ts
Normal file
112
src/mcpd/tests/bootstrap-ungated-global.test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
bootstrapUngatedGlobal,
|
||||
UNGATED_GLOBAL_PROJECT_NAME,
|
||||
SETTING_UNGATED_GLOBAL_SEEDED,
|
||||
} from '../src/bootstrap/ungated-global.js';
|
||||
import { SYSTEM_SETTINGS_SECRET } from '../src/bootstrap/system-settings.js';
|
||||
import type { PrismaClient } from '@prisma/client';
|
||||
|
||||
function mockPrisma(opts: { settingsData?: Record<string, unknown> | null } = {}): PrismaClient {
|
||||
const secretRow = opts.settingsData
|
||||
? { id: 'secret-1', name: SYSTEM_SETTINGS_SECRET, data: opts.settingsData, version: 3 }
|
||||
: null;
|
||||
|
||||
return {
|
||||
secret: {
|
||||
findUnique: vi.fn(async () => secretRow),
|
||||
update: vi.fn(async () => ({})),
|
||||
create: vi.fn(async () => ({})),
|
||||
},
|
||||
user: {
|
||||
upsert: vi.fn(async () => ({ id: 'sys-user-id', email: 'system@mcpctl.local' })),
|
||||
},
|
||||
project: {
|
||||
upsert: vi.fn(async (args: { where: { name: string } }) => ({ id: 'proj-ug', name: args.where.name })),
|
||||
},
|
||||
mcpServer: {
|
||||
upsert: vi.fn(async (args: { where: { name: string } }) => ({ id: 'srv-calc', name: args.where.name })),
|
||||
},
|
||||
projectServer: {
|
||||
upsert: vi.fn(async () => ({})),
|
||||
},
|
||||
} as unknown as PrismaClient;
|
||||
}
|
||||
|
||||
describe('bootstrapUngatedGlobal', () => {
|
||||
let prisma: PrismaClient;
|
||||
|
||||
beforeEach(() => {
|
||||
prisma = mockPrisma();
|
||||
});
|
||||
|
||||
it('seeds the ungated-global project with the calcmcp server and attaches it', async () => {
|
||||
await bootstrapUngatedGlobal(prisma);
|
||||
|
||||
expect(prisma.project.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { name: UNGATED_GLOBAL_PROJECT_NAME },
|
||||
create: expect.objectContaining({ gated: false, prompt: expect.stringContaining('calcmcp') }),
|
||||
}),
|
||||
);
|
||||
expect(prisma.mcpServer.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { name: 'calcmcp' },
|
||||
create: expect.objectContaining({
|
||||
dockerImage: expect.stringContaining('calcmcp'),
|
||||
transport: 'STDIO',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(prisma.projectServer.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { projectId_serverId: { projectId: 'proj-ug', serverId: 'srv-calc' } },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('marks the seed as done in the system settings secret', async () => {
|
||||
await bootstrapUngatedGlobal(prisma);
|
||||
|
||||
// Settings secret didn't exist → created with the marker key
|
||||
expect(prisma.secret.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
name: SYSTEM_SETTINGS_SECRET,
|
||||
data: expect.objectContaining({ [SETTING_UNGATED_GLOBAL_SEEDED]: true }),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('does NOT re-seed when the marker is set (deletions stay deleted)', async () => {
|
||||
prisma = mockPrisma({ settingsData: { [SETTING_UNGATED_GLOBAL_SEEDED]: true } });
|
||||
|
||||
await bootstrapUngatedGlobal(prisma);
|
||||
|
||||
expect(prisma.project.upsert).not.toHaveBeenCalled();
|
||||
expect(prisma.mcpServer.upsert).not.toHaveBeenCalled();
|
||||
expect(prisma.projectServer.upsert).not.toHaveBeenCalled();
|
||||
expect(prisma.secret.update).not.toHaveBeenCalled();
|
||||
expect(prisma.secret.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('preserves existing settings keys when writing the marker', async () => {
|
||||
prisma = mockPrisma({ settingsData: { allowSelfPasswordChange: false } });
|
||||
|
||||
await bootstrapUngatedGlobal(prisma);
|
||||
|
||||
expect(prisma.secret.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { name: SYSTEM_SETTINGS_SECRET },
|
||||
data: expect.objectContaining({
|
||||
data: {
|
||||
allowSelfPasswordChange: false,
|
||||
[SETTING_UNGATED_GLOBAL_SEEDED]: true,
|
||||
},
|
||||
version: 4,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user