@agimon-ai/mcp-proxy 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
+ import { coerceArgs, formatZodError, jsonSchemaToZod } from "@agimon-ai/foundation-validator";
1
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
3
  import { CallToolRequestSchema, ElicitRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4
+ import { z } from "zod";
3
5
  import { existsSync } from "node:fs";
4
6
  import { access, mkdir, readFile, readdir, rm, stat, unlink, watch, writeFile } from "node:fs/promises";
5
7
  import yaml from "js-yaml";
6
- import { z } from "zod";
7
8
  import { createHash, randomBytes, randomUUID } from "node:crypto";
8
9
  import { homedir, tmpdir } from "node:os";
9
10
  import { dirname, isAbsolute, join, resolve } from "node:path";
@@ -24,7 +25,7 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
24
25
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
26
 
26
27
  //#region package.json
27
- var version = "0.7.0";
28
+ var version = "0.7.2";
28
29
 
29
30
  //#endregion
30
31
  //#region src/utils/mcpConfigSchema.ts
@@ -1337,6 +1338,9 @@ var DefinitionsCacheService = class {
1337
1338
  async getServersForTool(toolName) {
1338
1339
  return (await this.getServerDefinitions()).filter((serverDefinition) => serverDefinition.tools.some((tool) => tool.name === toolName)).map((serverDefinition) => serverDefinition.serverName);
1339
1340
  }
1341
+ async getToolSchema(serverName, toolName) {
1342
+ return (await this.getDefinitions()).servers[serverName]?.tools.find((t) => t.name === toolName)?.inputSchema;
1343
+ }
1340
1344
  async getServersForResource(uri) {
1341
1345
  return (await this.getServerDefinitions()).filter((serverDefinition) => serverDefinition.resources.some((resource) => resource.uri === uri)).map((serverDefinition) => serverDefinition.serverName);
1342
1346
  }
@@ -1782,7 +1786,7 @@ var McpClientManagerService = class {
1782
1786
  async createConnection(serverName, config) {
1783
1787
  const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
1784
1788
  const client = new Client({
1785
- name: `@agimon-ai/mcp-proxy-client`,
1789
+ name: "@agimon-ai/mcp-proxy-client",
1786
1790
  version: "0.1.0"
1787
1791
  }, { capabilities: {} });
1788
1792
  const mcpClient = new McpClient(serverName, config.transport, client, this.logger, {
@@ -2966,7 +2970,7 @@ var PrefetchService = class {
2966
2970
 
2967
2971
  //#endregion
2968
2972
  //#region src/templates/toolkit-description.liquid?raw
2969
- var toolkit_description_default = "<toolkit id=\"{{ serverId }}\">\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
2973
+ var toolkit_description_default = "<toolkit id=\"{{ serverId }}\">\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group original-mcp-server=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
2970
2974
 
2971
2975
  //#endregion
2972
2976
  //#region src/tools/DescribeToolsTool.ts
@@ -2982,6 +2986,11 @@ function formatSkillInstructions(name, instructions) {
2982
2986
  return `<command-message>The "${name}" skill is loading</command-message>\n${instructions}`;
2983
2987
  }
2984
2988
  /**
2989
+ * Input schema for the DescribeToolsTool
2990
+ * @property toolNames - Array of tool names to get detailed information about
2991
+ */
2992
+ const DescribeToolsToolInputSchema = z.object({ toolNames: z.array(z.string().min(1)).min(1).describe("List of tool names to get detailed information about") });
2993
+ /**
2985
2994
  * DescribeToolsTool provides progressive disclosure of MCP tools and skills.
2986
2995
  *
2987
2996
  * This tool lists available tools from all connected MCP servers and skills
@@ -3238,25 +3247,15 @@ var DescribeToolsTool = class DescribeToolsTool {
3238
3247
  *
3239
3248
  * @returns Tool definition with description and input schema
3240
3249
  */
3250
+ getInputSchema() {
3251
+ return DescribeToolsToolInputSchema;
3252
+ }
3241
3253
  async getDefinition() {
3242
3254
  const { content } = await this.buildToolkitDescription();
3243
3255
  return {
3244
3256
  name: DescribeToolsTool.TOOL_NAME,
3245
3257
  description: content,
3246
- inputSchema: {
3247
- type: "object",
3248
- properties: { toolNames: {
3249
- type: "array",
3250
- items: {
3251
- type: "string",
3252
- minLength: 1
3253
- },
3254
- description: "List of tool names to get detailed information about",
3255
- minItems: 1
3256
- } },
3257
- required: ["toolNames"],
3258
- additionalProperties: false
3259
- }
3258
+ inputSchema: z.toJSONSchema(DescribeToolsToolInputSchema, { reused: "inline" })
3260
3259
  };
3261
3260
  }
3262
3261
  /**
@@ -3270,9 +3269,9 @@ var DescribeToolsTool = class DescribeToolsTool {
3270
3269
  * @param input - Object containing toolNames array
3271
3270
  * @returns CallToolResult with tool/skill descriptions or error
3272
3271
  */
3273
- async execute(input) {
3272
+ async execute(rawInput) {
3274
3273
  try {
3275
- const { toolNames } = input;
3274
+ const { toolNames } = DescribeToolsToolInputSchema.parse(rawInput);
3276
3275
  const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
3277
3276
  if (!toolNames || toolNames.length === 0) return {
3278
3277
  content: [{
@@ -3446,6 +3445,10 @@ function getUniqueSortedCapabilities(tools) {
3446
3445
 
3447
3446
  //#endregion
3448
3447
  //#region src/tools/SearchListToolsTool.ts
3448
+ const SearchListToolsToolInputSchema = z.object({
3449
+ capability: z.string().optional().describe("Optional capability filter. Matches explicit capability tags first, then server summaries, server names, tool names, and tool descriptions."),
3450
+ serverName: z.string().optional().describe("Optional server name filter.")
3451
+ });
3449
3452
  var SearchListToolsTool = class SearchListToolsTool {
3450
3453
  static TOOL_NAME = "list_tools";
3451
3454
  constructor(_clientManager, definitionsCacheService) {
@@ -3455,6 +3458,9 @@ var SearchListToolsTool = class SearchListToolsTool {
3455
3458
  formatToolName(toolName, serverName, toolToServers) {
3456
3459
  return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
3457
3460
  }
3461
+ getInputSchema() {
3462
+ return SearchListToolsToolInputSchema;
3463
+ }
3458
3464
  async getDefinition() {
3459
3465
  const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
3460
3466
  const capabilitySummary = serverDefinitions.length > 0 ? serverDefinitions.map((server) => {
@@ -3465,23 +3471,11 @@ var SearchListToolsTool = class SearchListToolsTool {
3465
3471
  return {
3466
3472
  name: SearchListToolsTool.TOOL_NAME,
3467
3473
  description: `Search proxied MCP tools by server capability summary.\n\nAvailable capabilities:\n${capabilitySummary}`,
3468
- inputSchema: {
3469
- type: "object",
3470
- properties: {
3471
- capability: {
3472
- type: "string",
3473
- description: "Optional capability filter. Matches explicit capability tags first, then server summaries, server names, tool names, and tool descriptions."
3474
- },
3475
- serverName: {
3476
- type: "string",
3477
- description: "Optional server name filter."
3478
- }
3479
- },
3480
- additionalProperties: false
3481
- }
3474
+ inputSchema: z.toJSONSchema(SearchListToolsToolInputSchema, { reused: "inline" })
3482
3475
  };
3483
3476
  }
3484
- async execute(input) {
3477
+ async execute(rawInput) {
3478
+ const input = SearchListToolsToolInputSchema.parse(rawInput);
3485
3479
  const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
3486
3480
  const capabilityFilter = input.capability?.trim().toLowerCase();
3487
3481
  const serverNameFilter = input.serverName?.trim().toLowerCase();
@@ -3524,6 +3518,39 @@ var SearchListToolsTool = class SearchListToolsTool {
3524
3518
  //#endregion
3525
3519
  //#region src/tools/UseToolTool.ts
3526
3520
  /**
3521
+ * UseToolTool - Progressive disclosure tool for calling MCP tools and skills
3522
+ *
3523
+ * DESIGN PATTERNS:
3524
+ * - Tool pattern with getDefinition() and execute() methods
3525
+ * - Dependency injection for client manager and skill service
3526
+ * - Progressive disclosure pattern
3527
+ * - Proxy pattern for forwarding tool calls
3528
+ *
3529
+ * CODING STANDARDS:
3530
+ * - Implement Tool interface from ../types
3531
+ * - Use TOOL_NAME constant with snake_case
3532
+ * - Return CallToolResult with content array
3533
+ * - Handle errors with isError flag
3534
+ *
3535
+ * AVOID:
3536
+ * - Complex business logic in execute method
3537
+ * - Unhandled promise rejections
3538
+ * - Missing error handling
3539
+ *
3540
+ * NAMING CONVENTIONS:
3541
+ * - Tools from MCP servers use serverName__toolName format when clashing
3542
+ * - Skills use skill__skillName format (skill__ prefix)
3543
+ */
3544
+ /**
3545
+ * Input schema for UseToolTool
3546
+ * @property toolName - Name of the tool or skill to execute
3547
+ * @property toolArgs - Arguments to pass to the tool (from describe_tools schema)
3548
+ */
3549
+ const UseToolToolInputSchema = z.object({
3550
+ toolName: z.string().min(1).describe("Name of the tool to execute"),
3551
+ toolArgs: z.record(z.string(), z.unknown()).optional().describe("Arguments to pass to the tool, as discovered from describe_tools")
3552
+ });
3553
+ /**
3527
3554
  * UseToolTool executes MCP tools and skills with proper error handling.
3528
3555
  *
3529
3556
  * This tool supports three invocation patterns:
@@ -3563,6 +3590,9 @@ var UseToolTool = class UseToolTool {
3563
3590
  *
3564
3591
  * @returns The tool definition conforming to MCP spec
3565
3592
  */
3593
+ getInputSchema() {
3594
+ return UseToolToolInputSchema;
3595
+ }
3566
3596
  getDefinition() {
3567
3597
  return {
3568
3598
  name: UseToolTool.TOOL_NAME,
@@ -3572,22 +3602,7 @@ var UseToolTool = class UseToolTool {
3572
3602
 
3573
3603
  IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverId}".
3574
3604
  `,
3575
- inputSchema: {
3576
- type: "object",
3577
- properties: {
3578
- toolName: {
3579
- type: "string",
3580
- description: "Name of the tool to execute",
3581
- minLength: 1
3582
- },
3583
- toolArgs: {
3584
- type: "object",
3585
- description: "Arguments to pass to the tool, as discovered from describe_tools"
3586
- }
3587
- },
3588
- required: ["toolName"],
3589
- additionalProperties: false
3590
- }
3605
+ inputSchema: z.toJSONSchema(UseToolToolInputSchema, { reused: "inline" })
3591
3606
  };
3592
3607
  }
3593
3608
  /**
@@ -3600,6 +3615,16 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
3600
3615
  * @param skill - The skill that was requested
3601
3616
  * @returns CallToolResult with guidance message
3602
3617
  */
3618
+ /**
3619
+ * Coerce toolArgs using the downstream tool's cached JSON Schema.
3620
+ * Converts the schema to Zod and runs coerceArgs so that string-encoded
3621
+ * objects/arrays are parsed before being forwarded to the downstream server.
3622
+ */
3623
+ async coerceToolArgs(serverName, toolName, toolArgs) {
3624
+ const jsonSchema = await this.definitionsCacheService.getToolSchema(serverName, toolName);
3625
+ if (!jsonSchema) return toolArgs;
3626
+ return coerceArgs(toolArgs, jsonSchemaToZod(jsonSchema));
3627
+ }
3603
3628
  executeSkill(skill) {
3604
3629
  return { content: [{
3605
3630
  type: "text",
@@ -3645,9 +3670,9 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
3645
3670
  * @param input - The tool/skill name and optional arguments
3646
3671
  * @returns CallToolResult with execution output or error
3647
3672
  */
3648
- async execute(input) {
3673
+ async execute(rawInput) {
3649
3674
  try {
3650
- const { toolName: inputToolName, toolArgs = {} } = input;
3675
+ const { toolName: inputToolName, toolArgs = {} } = UseToolToolInputSchema.parse(rawInput);
3651
3676
  if (inputToolName.startsWith(SKILL_PREFIX)) {
3652
3677
  const skillName = inputToolName.slice(SKILL_PREFIX.length);
3653
3678
  if (this.skillService) {
@@ -3676,7 +3701,8 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
3676
3701
  isError: true
3677
3702
  };
3678
3703
  const reqTimeout = this.clientManager.getServerRequestTimeout(serverName);
3679
- return await client.callTool(actualToolName, toolArgs, reqTimeout ? { timeout: reqTimeout } : void 0);
3704
+ const coercedArgs = await this.coerceToolArgs(serverName, actualToolName, toolArgs);
3705
+ return await client.callTool(actualToolName, coercedArgs, reqTimeout ? { timeout: reqTimeout } : void 0);
3680
3706
  } catch (error) {
3681
3707
  return {
3682
3708
  content: [{
@@ -3713,7 +3739,8 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
3713
3739
  const targetServerName = matchingServers[0];
3714
3740
  const client = await this.clientManager.ensureConnected(targetServerName);
3715
3741
  const targetReqTimeout = this.clientManager.getServerRequestTimeout(targetServerName);
3716
- return await client.callTool(actualToolName, toolArgs, targetReqTimeout ? { timeout: targetReqTimeout } : void 0);
3742
+ const coercedArgs = await this.coerceToolArgs(targetServerName, actualToolName, toolArgs);
3743
+ return await client.callTool(actualToolName, coercedArgs, targetReqTimeout ? { timeout: targetReqTimeout } : void 0);
3717
3744
  } catch (error) {
3718
3745
  return {
3719
3746
  content: [{
@@ -4394,19 +4421,38 @@ var StdioTransportHandler = class {
4394
4421
  * - Transport handler pattern implementing TransportHandler interface
4395
4422
  * - STDIO transport with MCP request forwarding to HTTP backend
4396
4423
  * - Graceful cleanup with error isolation
4424
+ * - Reconnection with exponential backoff on connection loss
4397
4425
  *
4398
4426
  * CODING STANDARDS:
4399
4427
  * - Use StdioServerTransport for stdio communication
4400
4428
  * - Reuse a single StreamableHTTP client connection
4401
4429
  * - Wrap async operations with try-catch and descriptive errors
4430
+ * - Reconnect transparently on connection reset
4402
4431
  *
4403
4432
  * AVOID:
4404
4433
  * - Starting HTTP server lifecycle in this transport entry point
4405
4434
  * - Recreating HTTP client per request
4406
4435
  * - Swallowing cleanup failures silently
4407
4436
  */
4437
+ const CONNECTION_ERROR_PATTERNS = [
4438
+ "econnrefused",
4439
+ "econnreset",
4440
+ "enotfound",
4441
+ "connection refused",
4442
+ "fetch failed",
4443
+ "socket hang up",
4444
+ "network error",
4445
+ "failed to fetch"
4446
+ ];
4447
+ function isConnectionError(error) {
4448
+ if (!(error instanceof Error)) return false;
4449
+ const message = error.message.toLowerCase();
4450
+ return CONNECTION_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
4451
+ }
4408
4452
  /**
4409
4453
  * Transport that serves MCP over stdio and forwards MCP requests to an HTTP endpoint.
4454
+ * Automatically reconnects to the HTTP backend with exponential backoff when the
4455
+ * connection is lost (e.g. backend crash + restart).
4410
4456
  */
4411
4457
  var StdioHttpTransportHandler = class {
4412
4458
  endpoint;
@@ -4414,24 +4460,19 @@ var StdioHttpTransportHandler = class {
4414
4460
  stdioTransport = null;
4415
4461
  httpClient = null;
4416
4462
  logger;
4463
+ MAX_RECONNECT_ATTEMPTS = 5;
4464
+ RECONNECT_BASE_MS = 1e3;
4465
+ RECONNECT_MAX_MS = 3e4;
4417
4466
  constructor(config, logger = console) {
4418
4467
  this.endpoint = config.endpoint;
4419
4468
  this.logger = logger;
4420
4469
  }
4421
4470
  async start() {
4422
4471
  try {
4423
- const httpClientTransport = new StreamableHTTPClientTransport(this.endpoint);
4424
- const client = new Client({
4425
- name: "@agimon-ai/mcp-proxy-stdio-http-proxy",
4426
- version: "0.1.0"
4427
- }, { capabilities: { elicitation: {} } });
4428
- await client.connect(httpClientTransport);
4472
+ const client = await this.createAndConnectClient();
4429
4473
  this.httpClient = client;
4430
- this.stdioProxyServer = this.createProxyServer(client);
4431
- const proxyServer = this.stdioProxyServer;
4432
- client.setRequestHandler(ElicitRequestSchema, async (request) => {
4433
- return await proxyServer.elicitInput(request.params);
4434
- });
4474
+ this.stdioProxyServer = this.createProxyServer();
4475
+ this.registerElicitationHandler(client);
4435
4476
  this.stdioTransport = new StdioServerTransport();
4436
4477
  await this.stdioProxyServer.connect(this.stdioTransport);
4437
4478
  this.logger.info(`@agimon-ai/mcp-proxy MCP stdio proxy connected to ${this.endpoint.toString()}`);
@@ -4473,7 +4514,52 @@ var StdioHttpTransportHandler = class {
4473
4514
  ]);
4474
4515
  if (cleanupErrors.length > 0) throw new Error(`Failed to stop stdio-http proxy transport: ${cleanupErrors.join("; ")}`);
4475
4516
  }
4476
- createProxyServer(client) {
4517
+ async createAndConnectClient() {
4518
+ const httpClientTransport = new StreamableHTTPClientTransport(this.endpoint);
4519
+ const client = new Client({
4520
+ name: "@agimon-ai/mcp-proxy-stdio-http-proxy",
4521
+ version: "0.1.0"
4522
+ }, { capabilities: { elicitation: {} } });
4523
+ await client.connect(httpClientTransport);
4524
+ return client;
4525
+ }
4526
+ registerElicitationHandler(client) {
4527
+ const proxyServer = this.stdioProxyServer;
4528
+ if (!proxyServer) return;
4529
+ client.setRequestHandler(ElicitRequestSchema, async (request) => {
4530
+ return await proxyServer.elicitInput(request.params);
4531
+ });
4532
+ }
4533
+ async reconnectWithBackoff() {
4534
+ for (let attempt = 0; attempt < this.MAX_RECONNECT_ATTEMPTS; attempt++) {
4535
+ const delay = Math.min(this.RECONNECT_BASE_MS * 2 ** attempt, this.RECONNECT_MAX_MS);
4536
+ await new Promise((resolve$1) => setTimeout(resolve$1, delay));
4537
+ try {
4538
+ await this.httpClient?.close().catch(() => void 0);
4539
+ const client = await this.createAndConnectClient();
4540
+ this.httpClient = client;
4541
+ this.registerElicitationHandler(client);
4542
+ this.logger.info(`Reconnected to HTTP backend at ${this.endpoint.toString()} (attempt ${attempt + 1})`);
4543
+ return;
4544
+ } catch {
4545
+ this.logger.info(`Reconnect attempt ${attempt + 1}/${this.MAX_RECONNECT_ATTEMPTS} to ${this.endpoint.toString()} failed`);
4546
+ }
4547
+ }
4548
+ throw new Error(`Failed to reconnect to HTTP backend at ${this.endpoint.toString()} after ${this.MAX_RECONNECT_ATTEMPTS} attempts`);
4549
+ }
4550
+ async withReconnect(fn) {
4551
+ if (!this.httpClient) throw new Error("HTTP client not connected");
4552
+ try {
4553
+ return await fn();
4554
+ } catch (error) {
4555
+ if (isConnectionError(error)) {
4556
+ await this.reconnectWithBackoff();
4557
+ return await fn();
4558
+ }
4559
+ throw error;
4560
+ }
4561
+ }
4562
+ createProxyServer() {
4477
4563
  const proxyServer = new Server({
4478
4564
  name: "@agimon-ai/mcp-proxy-stdio-http-proxy",
4479
4565
  version: "0.1.0"
@@ -4484,48 +4570,48 @@ var StdioHttpTransportHandler = class {
4484
4570
  } });
4485
4571
  proxyServer.setRequestHandler(ListToolsRequestSchema, async () => {
4486
4572
  try {
4487
- return await client.listTools();
4573
+ return await this.withReconnect(() => this.httpClient.listTools());
4488
4574
  } catch (error) {
4489
4575
  throw new Error(`Failed forwarding tools/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
4490
4576
  }
4491
4577
  });
4492
4578
  proxyServer.setRequestHandler(CallToolRequestSchema, async (request) => {
4493
4579
  try {
4494
- return await client.callTool({
4580
+ return await this.withReconnect(() => this.httpClient.callTool({
4495
4581
  name: request.params.name,
4496
4582
  arguments: request.params.arguments
4497
- });
4583
+ }));
4498
4584
  } catch (error) {
4499
4585
  throw new Error(`Failed forwarding tools/call (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
4500
4586
  }
4501
4587
  });
4502
4588
  proxyServer.setRequestHandler(ListResourcesRequestSchema, async () => {
4503
4589
  try {
4504
- return await client.listResources();
4590
+ return await this.withReconnect(() => this.httpClient.listResources());
4505
4591
  } catch (error) {
4506
4592
  throw new Error(`Failed forwarding resources/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
4507
4593
  }
4508
4594
  });
4509
4595
  proxyServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
4510
4596
  try {
4511
- return await client.readResource({ uri: request.params.uri });
4597
+ return await this.withReconnect(() => this.httpClient.readResource({ uri: request.params.uri }));
4512
4598
  } catch (error) {
4513
4599
  throw new Error(`Failed forwarding resources/read (${request.params.uri}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
4514
4600
  }
4515
4601
  });
4516
4602
  proxyServer.setRequestHandler(ListPromptsRequestSchema, async () => {
4517
4603
  try {
4518
- return await client.listPrompts();
4604
+ return await this.withReconnect(() => this.httpClient.listPrompts());
4519
4605
  } catch (error) {
4520
4606
  throw new Error(`Failed forwarding prompts/list to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
4521
4607
  }
4522
4608
  });
4523
4609
  proxyServer.setRequestHandler(GetPromptRequestSchema, async (request) => {
4524
4610
  try {
4525
- return await client.getPrompt({
4611
+ return await this.withReconnect(() => this.httpClient.getPrompt({
4526
4612
  name: request.params.name,
4527
4613
  arguments: request.params.arguments
4528
- });
4614
+ }));
4529
4615
  } catch (error) {
4530
4616
  throw new Error(`Failed forwarding prompts/get (${request.params.name}) to HTTP backend: ${error instanceof Error ? error.message : String(error)}`);
4531
4617
  }
@@ -4888,21 +4974,27 @@ async function createSessionServer(shared) {
4888
4974
  })() : [await describeTools.getDefinition(), useToolWithCache.getDefinition()] }));
4889
4975
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
4890
4976
  const { name, arguments: args } = request.params;
4891
- if (name === DescribeToolsTool.TOOL_NAME) try {
4892
- return await describeTools.execute(args);
4893
- } catch (error) {
4894
- throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
4895
- }
4896
- if (name === UseToolTool.TOOL_NAME) try {
4897
- return await useToolWithCache.execute(args);
4898
- } catch (error) {
4899
- throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
4900
- }
4901
- if (name === SearchListToolsTool.TOOL_NAME && proxyMode === "search") try {
4902
- return await searchListTools.execute(args);
4903
- } catch (error) {
4904
- throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
4905
- }
4977
+ const executeWithCoercion = async (tool, toolName) => {
4978
+ const coerced = coerceArgs(args ?? {}, tool.getInputSchema());
4979
+ try {
4980
+ return await tool.execute(coerced);
4981
+ } catch (error) {
4982
+ if (error instanceof z.ZodError) return {
4983
+ content: [{
4984
+ type: "text",
4985
+ text: formatZodError(error, {
4986
+ schemaName: toolName,
4987
+ schema: tool.getInputSchema()
4988
+ })
4989
+ }],
4990
+ isError: true
4991
+ };
4992
+ throw new Error(`Failed to execute ${toolName}: ${error instanceof Error ? error.message : String(error)}`);
4993
+ }
4994
+ };
4995
+ if (name === DescribeToolsTool.TOOL_NAME) return await executeWithCoercion(describeTools, name);
4996
+ if (name === UseToolTool.TOOL_NAME) return await executeWithCoercion(useToolWithCache, name);
4997
+ if (name === SearchListToolsTool.TOOL_NAME && proxyMode === "search") return await executeWithCoercion(searchListTools, name);
4906
4998
  if (proxyMode === "flat") return await useToolWithCache.execute({
4907
4999
  toolName: name,
4908
5000
  toolArgs: args || {}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agimon-ai/mcp-proxy",
3
3
  "description": "MCP proxy server package",
4
- "version": "0.7.1",
4
+ "version": "0.7.3",
5
5
  "license": "AGPL-3.0",
6
6
  "keywords": [
7
7
  "mcp",
@@ -27,10 +27,11 @@
27
27
  "gray-matter": "^4.0.3",
28
28
  "js-yaml": "^4.1.0",
29
29
  "liquidjs": "^10.21.0",
30
- "zod": "^3.24.1",
31
- "@agimon-ai/foundation-process-registry": "0.5.1",
32
- "@agimon-ai/foundation-port-registry": "0.5.1",
33
- "@agimon-ai/log-sink-mcp": "0.5.1"
30
+ "zod": "4.3.6",
31
+ "@agimon-ai/foundation-process-registry": "0.5.2",
32
+ "@agimon-ai/foundation-port-registry": "0.5.2",
33
+ "@agimon-ai/log-sink-mcp": "0.5.3",
34
+ "@agimon-ai/foundation-validator": "0.2.0"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/js-yaml": "^4.0.9",