Compare commits
3 Commits
feat/agent
...
cli-v0.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ecd86fa357 | |||
|
|
ed54aa1464 | ||
| a783869e7c |
@@ -155,7 +155,7 @@ describe('Smoke: Audit events', () => {
|
|||||||
try {
|
try {
|
||||||
await chat.initialize();
|
await chat.initialize();
|
||||||
const tools = await chat.listTools();
|
const tools = await chat.listTools();
|
||||||
chat.check('Gated (only begin_session)', tools.length, (v) => v >= 1);
|
chat.check('Gated tools/list responds', tools.length, (v) => v >= 1);
|
||||||
|
|
||||||
// Trigger gate decision
|
// Trigger gate decision
|
||||||
await chat.callTool('begin_session', { description: 'audit smoke test' });
|
await chat.callTool('begin_session', { description: 'audit smoke test' });
|
||||||
|
|||||||
@@ -167,11 +167,21 @@ describe('Smoke: Backup completeness', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('backup servers have command field for docker-image STDIO servers', () => {
|
it('backup preserves the command of docker-image STDIO servers', async () => {
|
||||||
if (!available) return;
|
if (!available) return;
|
||||||
const dockerStdio = bundle.servers.filter((s) => s.dockerImage && s.transport === 'STDIO');
|
// `command` is optional for dockerImage servers (the image entrypoint is
|
||||||
for (const s of dockerStdio) {
|
// used when it is empty — calcmcp, dav-michal, jmap-michal all run that
|
||||||
expect(s.command, `${s.name} (dockerImage STDIO) should have command`).toBeTruthy();
|
// way), so requiring it universally asserted a false invariant. What the
|
||||||
|
// backup MUST do is carry the command through for servers that have one.
|
||||||
|
const live = (await mcpdRequest<Server[]>('GET', '/api/v1/servers')).data;
|
||||||
|
const withCommand = live.filter(
|
||||||
|
(s) => s.dockerImage && s.transport === 'STDIO' && Array.isArray(s.command) && s.command.length > 0,
|
||||||
|
);
|
||||||
|
expect(withCommand.length, 'no live dockerImage STDIO server has a command — fidelity unverifiable').toBeGreaterThan(0);
|
||||||
|
for (const s of withCommand) {
|
||||||
|
const backed = bundle.servers.find((b) => b.name === s.name);
|
||||||
|
expect(backed, `${s.name} missing from backup`).toBeTruthy();
|
||||||
|
expect(backed!.command, `${s.name}: backup dropped its command`).toEqual(s.command);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -108,23 +108,38 @@ describe('Smoke: ProxyModel pipeline', () => {
|
|||||||
expect(true).toBe(true);
|
expect(true).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gated session: tools/list returns only begin_session', async () => {
|
it('gated session: tools/list returns begin_session plus only gate-exempt tools', async () => {
|
||||||
if (!serverResponding) return;
|
if (!serverResponding) return;
|
||||||
|
|
||||||
|
// Since the ungated-global system project (PR #137), servers attached to
|
||||||
|
// it ride every project PRE-gate: a fresh gated session sees their tools
|
||||||
|
// alongside begin_session. Anything else leaking through is a gate bug.
|
||||||
|
let exemptServers: string[] = [];
|
||||||
|
try {
|
||||||
|
const raw = await mcpctl('get project ungated-global -o json');
|
||||||
|
const parsed = JSON.parse(raw) as Array<{ servers?: string[] }>;
|
||||||
|
exemptServers = parsed[0]?.servers ?? [];
|
||||||
|
} catch {
|
||||||
|
// Project deleted by an admin (it is deletable) — nothing is exempt.
|
||||||
|
}
|
||||||
|
|
||||||
const session = new SmokeMcpSession(PROJECT_NAME);
|
const session = new SmokeMcpSession(PROJECT_NAME);
|
||||||
const chat = new ChatReporter(session);
|
const chat = new ChatReporter(session);
|
||||||
chat.section('Gating: fresh session sees only begin_session');
|
chat.section('Gating: fresh session sees begin_session + gate-exempt tools only');
|
||||||
try {
|
try {
|
||||||
await chat.initialize();
|
await chat.initialize();
|
||||||
await chat.sendNotification('notifications/initialized');
|
await chat.sendNotification('notifications/initialized');
|
||||||
const tools = await chat.listTools();
|
const tools = await chat.listTools();
|
||||||
|
const names = tools.map((t) => t.name);
|
||||||
|
const isExempt = (n: string): boolean => exemptServers.some((s) => n.startsWith(`${s}_`));
|
||||||
|
const leaked = names.filter((n) => n !== 'begin_session' && !isExempt(n));
|
||||||
|
|
||||||
chat.check('Exactly 1 tool', tools.length, (v) => v === 1);
|
chat.check('begin_session present', names.includes('begin_session'), (v) => v === true);
|
||||||
chat.check('Tool is begin_session', tools[0]?.name ?? '', (v) => v === 'begin_session');
|
chat.check('No gated tools leaked', leaked.length, (v) => v === 0);
|
||||||
chat.check('Has inputSchema', !!tools[0]?.inputSchema, (v) => v === true);
|
chat.check('Has inputSchema', !!tools.find((t) => t.name === 'begin_session')?.inputSchema, (v) => v === true);
|
||||||
|
|
||||||
expect(tools).toHaveLength(1);
|
expect(names).toContain('begin_session');
|
||||||
expect(tools[0]!.name).toBe('begin_session');
|
expect(leaked, 'tools visible before begin_session that are not gate-exempt').toEqual([]);
|
||||||
} finally {
|
} finally {
|
||||||
await chat.close();
|
await chat.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ interface Tool {
|
|||||||
|
|
||||||
|
|
||||||
/** Tools served by the gate plugin — intercepted before the pipeline, so exempt. */
|
/** Tools served by the gate plugin — intercepted before the pipeline, so exempt. */
|
||||||
const GATE_TOOLS = new Set(['begin_session', 'read_prompts', 'propose_prompt', 'propose_skill']);
|
// mcplocal-virtual tools: dispatched by the router before the pipeline, so
|
||||||
|
// they never produce paginatable results and rightly skip the drill-down
|
||||||
|
// contract. get_current_time is the ambient-context clock.
|
||||||
|
const GATE_TOOLS = new Set(['begin_session', 'read_prompts', 'propose_prompt', 'propose_skill', 'get_current_time']);
|
||||||
|
|
||||||
describe('Smoke: tool drill-down contract', () => {
|
describe('Smoke: tool drill-down contract', () => {
|
||||||
let available = false;
|
let available = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user