feat: McpToken — HTTP-mode mcplocal, CLI verbs, audit plumbing #50
@@ -62,21 +62,31 @@ export function registerProjectMcpEndpoint(app: FastifyInstance, mcpdClient: Mcp
|
||||
return existing.router;
|
||||
}
|
||||
|
||||
// HTTP-mode mcplocal has no pod-level credentials — the default
|
||||
// `mcpdClient.token` is an empty string. Every downstream call from this
|
||||
// request (upstream discovery, LLM config fetch, prompt index for
|
||||
// begin_session) has to use the CALLER's McpToken as the bearer, or mcpd
|
||||
// rejects with 401. Build one per-request client here and thread it
|
||||
// everywhere instead of sprinkling `.withToken(authToken)` at each call site.
|
||||
const requestClient = authToken ? mcpdClient.withToken(authToken) : mcpdClient;
|
||||
|
||||
// Create new router or refresh existing one
|
||||
const router = existing?.router ?? new McpRouter();
|
||||
await refreshProjectUpstreams(router, mcpdClient, projectName, authToken);
|
||||
|
||||
// Resolve project LLM model: local override → mcpd recommendation → global default
|
||||
const localOverride = loadProjectLlmOverride(projectName);
|
||||
const mcpdConfig = await fetchProjectLlmConfig(mcpdClient, projectName);
|
||||
const mcpdConfig = await fetchProjectLlmConfig(requestClient, projectName);
|
||||
const resolvedModel = localOverride?.model ?? mcpdConfig.llmModel ?? undefined;
|
||||
|
||||
// If project llmProvider is "none", disable LLM for this project
|
||||
const llmDisabled = mcpdConfig.llmProvider === 'none' || localOverride?.provider === 'none';
|
||||
const effectiveRegistry = llmDisabled ? null : (providerRegistry ?? null);
|
||||
|
||||
// Configure prompt resources with SA-scoped client for RBAC
|
||||
const saClient = mcpdClient.withHeaders({ 'X-Service-Account': `project:${projectName}` });
|
||||
// Configure prompt resources with SA-scoped client for RBAC.
|
||||
// Keep the X-Service-Account header for mcpd-side audit tagging, but carry
|
||||
// the caller's bearer so auth passes (the principal resolves as McpToken:<sha>).
|
||||
const saClient = requestClient.withHeaders({ 'X-Service-Account': `project:${projectName}` });
|
||||
router.setPromptConfig(saClient, projectName);
|
||||
|
||||
// System prompt fetcher for LLM consumers (uses router's cached fetcher)
|
||||
|
||||
@@ -30,9 +30,13 @@ function mockMcpdClient() {
|
||||
delete: vi.fn(),
|
||||
forward: vi.fn(async () => ({ status: 200, body: [] })),
|
||||
withHeaders: vi.fn(),
|
||||
withToken: vi.fn(),
|
||||
withTimeout: vi.fn(),
|
||||
};
|
||||
// withHeaders returns a new client-like object (returns self for simplicity)
|
||||
// Chainable withX returns the same client for simplicity
|
||||
(client.withHeaders as ReturnType<typeof vi.fn>).mockReturnValue(client);
|
||||
(client.withToken as ReturnType<typeof vi.fn>).mockReturnValue(client);
|
||||
(client.withTimeout as ReturnType<typeof vi.fn>).mockReturnValue(client);
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user