Files
TREK/server/tests/helpers/mcp-gate.ts
T
Maurice 1a9855cf70 refactor(mcp): the demo-mode write block runs once, in the registry's tool gate
Every write tool used to open with its own demo check, 196 copies across 30
tool files, most of them injecting AuthService for nothing else. The
registry now takes a toolGate (built from the container) that runs before
each registered tool handler, and TREK's gate refuses a demo account on
every tool not annotated read-only with the same canned result. The
per-tool checks, the AuthService injections, AuthService.isDemoUser and the
AuthModule imports that only fed them are gone; ESLint refuses a demo check
in a tool file, and a registry-wide test calls every write tool as the
demo account.
2026-10-10 20:15:57 +02:00

26 lines
1.2 KiB
TypeScript

/**
* Calls one decorated MCP tool method the way a session reaches it: behind the
* demo gate the registry puts in front of every registered tool. For suites
* that build a controller with stubs and call its methods directly, so they
* keep their stubs instead of attaching a whole registry (which would also run
* the `when` gates those stubs cannot answer).
*/
import { getEntry, type ClassRef } from '../../src/nest-mcp/metadata';
import { trekDemoToolGate } from '../../src/mcp/nest-mcp-policy';
import type { McpContext } from '../../src/nest-mcp';
export async function callGatedTool(
instance: object,
methodName: string,
args: unknown,
ctx: McpContext,
isDemoUser: (userId: number) => Promise<boolean>,
): Promise<unknown> {
const entry = getEntry((instance as { constructor: ClassRef }).constructor, methodName);
if (entry?.kind !== 'tool') throw new Error(`${methodName} is not a decorated tool`);
const refusal = await trekDemoToolGate(isDemoUser)(entry.options, ctx);
if (refusal !== undefined) return refusal;
const method = (instance as Record<string, (a: unknown, c: McpContext) => unknown>)[methodName];
return method.call(instance, args, ctx);
}