@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.
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +39 -20
- package/dist/index.d.mts +38 -20
- package/dist/index.mjs +1 -1
- package/dist/{src-DyWwAp8e.cjs → src-Dp2m9_I_.cjs} +182 -90
- package/dist/{src-NOWQwaRe.mjs → src-Y-cyyxaw.mjs} +182 -90
- package/package.json +6 -5
|
@@ -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.
|
|
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:
|
|
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
|
|
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(
|
|
3272
|
+
async execute(rawInput) {
|
|
3274
3273
|
try {
|
|
3275
|
-
const { toolNames } =
|
|
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(
|
|
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(
|
|
3673
|
+
async execute(rawInput) {
|
|
3649
3674
|
try {
|
|
3650
|
-
const { toolName: inputToolName, toolArgs = {} } =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
4431
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
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.
|
|
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": "
|
|
31
|
-
"@agimon-ai/foundation-process-registry": "0.5.
|
|
32
|
-
"@agimon-ai/foundation-port-registry": "0.5.
|
|
33
|
-
"@agimon-ai/log-sink-mcp": "0.5.
|
|
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",
|