Merge remote-tracking branch 'origin/main' into fix/stdio-restart-recovery
Some checks failed
CI/CD / lint (push) Successful in 1m12s
CI/CD / test (push) Successful in 1m25s
CI/CD / typecheck (push) Successful in 3m2s
CI/CD / smoke (push) Failing after 1m55s
CI/CD / build (push) Successful in 4m51s
CI/CD / publish (push) Has been skipped

This commit is contained in:
Michal
2026-08-15 23:32:46 +01:00
3 changed files with 23 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
import type { McpdClient } from './http/mcpd-client.js'; import type { McpdClient } from './http/mcpd-client.js';
import { DISCOVERY_TIMEOUT_MS } from './http/mcpd-client.js'; import { DISCOVERY_TIMEOUT_MS, TOOLCALL_TIMEOUT_MS } from './http/mcpd-client.js';
import type { McpRouter } from './router.js'; import type { McpRouter } from './router.js';
import { McpdUpstream } from './upstream/mcpd.js'; import { McpdUpstream } from './upstream/mcpd.js';
@@ -152,6 +152,10 @@ function syncUpstreams(router: McpRouter, mcpdClient: McpdClient, servers: McpdS
// unreachable upstream cannot stall session init for the full tool-call window. // unreachable upstream cannot stall session init for the full tool-call window.
const discoveryClient = mcpdClient.withTimeout(DISCOVERY_TIMEOUT_MS); const discoveryClient = mcpdClient.withTimeout(DISCOVERY_TIMEOUT_MS);
// Everything else an upstream receives is a proxied tools/call, which can
// legitimately run past the 30s default (browser solvers, PDF extraction).
const toolClient = mcpdClient.withTimeout(TOOLCALL_TIMEOUT_MS);
// Remove stale upstreams // Remove stale upstreams
const currentNames = new Set(router.getUpstreamNames()); const currentNames = new Set(router.getUpstreamNames());
const serverNames = new Set(servers.map((s) => s.name)); const serverNames = new Set(servers.map((s) => s.name));
@@ -164,7 +168,7 @@ function syncUpstreams(router: McpRouter, mcpdClient: McpdClient, servers: McpdS
// Add/update upstreams for each server // Add/update upstreams for each server
for (const server of servers) { for (const server of servers) {
if (!currentNames.has(server.name)) { if (!currentNames.has(server.name)) {
const upstream = new McpdUpstream(server.id, server.name, mcpdClient, server.description, discoveryClient); const upstream = new McpdUpstream(server.id, server.name, toolClient, server.description, discoveryClient);
router.addUpstream(upstream); router.addUpstream(upstream);
} }
registered.push(server.name); registered.push(server.name);

View File

@@ -62,6 +62,17 @@ export const LONG_RUNNING_TIMEOUT_MS = Number(process.env['MCPLOCAL_LONG_TIMEOUT
*/ */
export const DISCOVERY_TIMEOUT_MS = Number(process.env['MCPLOCAL_DISCOVERY_TIMEOUT_MS']) || 8_000; export const DISCOVERY_TIMEOUT_MS = Number(process.env['MCPLOCAL_DISCOVERY_TIMEOUT_MS']) || 8_000;
/**
* Budget for proxied tools/call. The 30s DEFAULT_TIMEOUT_MS is a guaranteed
* failure for tools that legitimately run long — web_url_read through a
* browser solver, a large PDF extraction, a slow retail site — all of which
* died as `mcpd proxy error: mcpd did not respond within 30000ms` while mcpd
* was still working. Distinct from LONG_RUNNING_TIMEOUT_MS (chat/inference,
* minutes) and DISCOVERY_TIMEOUT_MS (list calls, must stay short so a dead
* upstream cannot stall session init). Override via `MCPLOCAL_TOOLCALL_TIMEOUT_MS`.
*/
export const TOOLCALL_TIMEOUT_MS = Number(process.env['MCPLOCAL_TOOLCALL_TIMEOUT_MS']) || 120_000;
export class McpdClient { export class McpdClient {
private readonly baseUrl: string; private readonly baseUrl: string;
private readonly token: string; private readonly token: string;

View File

@@ -176,9 +176,13 @@ export function registerProjectMcpEndpoint(app: FastifyInstance, mcpdClient: Mcp
chain.push(createAgentsPlugin()); chain.push(createAgentsPlugin());
router.setPlugin(composePlugins(chain)); router.setPlugin(composePlugins(chain));
// Fetch project instructions and set on router // Fetch project instructions and set on router. Must ride the CALLER's
// token (requestClient) like every other downstream call here: in HTTP
// mode mcpdClient's own token is empty, mcpd answers 401, and the catch
// below swallowed it — every session initialized with no instructions
// while nothing looked broken (#113).
try { try {
const instructions = await mcpdClient.get<{ prompt: string; servers: Array<{ name: string; description: string }> }>( const instructions = await requestClient.get<{ prompt: string; servers: Array<{ name: string; description: string }> }>(
`/api/v1/projects/${encodeURIComponent(projectName)}/instructions`, `/api/v1/projects/${encodeURIComponent(projectName)}/instructions`,
); );
const parts: string[] = []; const parts: string[] = [];