@anyshift/mcp-proxy 0.6.2 → 0.6.4
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/index.js +76 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
19
19
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
20
20
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
21
21
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
22
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
22
23
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
23
24
|
import { createJqTool } from './jq/index.js';
|
|
24
25
|
import { createTimeseriesTool } from './timeseries/index.js';
|
|
@@ -119,11 +120,16 @@ const CHILD_ARGS = process.env.MCP_PROXY_CHILD_ARGS
|
|
|
119
120
|
: [];
|
|
120
121
|
/**
|
|
121
122
|
* MCP_PROXY_REMOTE_URL (OPTIONAL)
|
|
122
|
-
*
|
|
123
|
-
* When set, the proxy connects as an SSE client to the remote server
|
|
123
|
+
* URL of a remote MCP server to connect to instead of spawning a child process
|
|
124
124
|
* Example: "http://internal-server:3100/sse"
|
|
125
125
|
*/
|
|
126
126
|
const REMOTE_URL = process.env.MCP_PROXY_REMOTE_URL;
|
|
127
|
+
/**
|
|
128
|
+
* MCP_PROXY_REMOTE_TRANSPORT (OPTIONAL, default: "sse")
|
|
129
|
+
* Transport protocol for connecting to a remote MCP server.
|
|
130
|
+
* Values: "sse" (legacy GET-based SSE), "streamable-http" (POST-based, newer MCP spec)
|
|
131
|
+
*/
|
|
132
|
+
const REMOTE_TRANSPORT = process.env.MCP_PROXY_REMOTE_TRANSPORT || 'sse';
|
|
127
133
|
/**
|
|
128
134
|
* MCP_PROXY_REMOTE_HEADERS (OPTIONAL)
|
|
129
135
|
* JSON string of custom headers to send with SSE/POST requests to the remote server
|
|
@@ -295,7 +301,7 @@ for (const [key, value] of Object.entries(process.env)) {
|
|
|
295
301
|
// ============================================================================
|
|
296
302
|
if (ENABLE_LOGGING) {
|
|
297
303
|
console.debug('[mcp-proxy] Configuration:');
|
|
298
|
-
console.debug(` Mode: ${REMOTE_URL ?
|
|
304
|
+
console.debug(` Mode: ${REMOTE_URL ? `remote (${REMOTE_TRANSPORT})` : CHILD_COMMAND ? 'wrapper' : 'standalone (JQ only)'}`);
|
|
299
305
|
if (REMOTE_URL) {
|
|
300
306
|
console.debug(` Remote URL: ${REMOTE_URL}`);
|
|
301
307
|
console.debug(` Remote headers: ${REMOTE_HEADERS ? 'configured' : 'none'}`);
|
|
@@ -360,20 +366,26 @@ async function main() {
|
|
|
360
366
|
}
|
|
361
367
|
headers['X-Client-Credentials'] = REMOTE_CREDENTIALS;
|
|
362
368
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
369
|
+
let childTransport;
|
|
370
|
+
if (REMOTE_TRANSPORT === 'streamable-http') {
|
|
371
|
+
childTransport = new StreamableHTTPClientTransport(new URL(REMOTE_URL), {
|
|
372
|
+
requestInit: { headers },
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
childTransport = new SSEClientTransport(new URL(REMOTE_URL), {
|
|
377
|
+
eventSourceInit: {
|
|
378
|
+
fetch: (url, init) => {
|
|
379
|
+
const merged = new Headers(init?.headers);
|
|
380
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
381
|
+
merged.set(key, value);
|
|
382
|
+
}
|
|
383
|
+
return fetch(url, { ...init, headers: merged });
|
|
384
|
+
},
|
|
373
385
|
},
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
386
|
+
requestInit: { headers },
|
|
387
|
+
});
|
|
388
|
+
}
|
|
377
389
|
childClient = new Client({
|
|
378
390
|
name: 'mcp-proxy-client',
|
|
379
391
|
version: '1.0.0'
|
|
@@ -417,6 +429,51 @@ async function main() {
|
|
|
417
429
|
console.debug('[mcp-proxy] Standalone mode - no child MCP');
|
|
418
430
|
}
|
|
419
431
|
// ------------------------------------------------------------------------
|
|
432
|
+
// 2.5. STORE ORIGINAL TOOL SCHEMAS FOR SMART PARAMETER SANITIZATION
|
|
433
|
+
// ------------------------------------------------------------------------
|
|
434
|
+
// Create a map of original tool schemas (before proxy param injection)
|
|
435
|
+
// This allows us to detect if description/isRetryAttempt/originalToolId were
|
|
436
|
+
// originally part of the child tool's schema (to handle parameter name collisions)
|
|
437
|
+
const originalToolSchemas = new Map();
|
|
438
|
+
if (childToolsResponse.tools) {
|
|
439
|
+
for (const tool of childToolsResponse.tools) {
|
|
440
|
+
const originalParams = new Set();
|
|
441
|
+
if (tool.inputSchema?.properties) {
|
|
442
|
+
for (const paramName of Object.keys(tool.inputSchema.properties)) {
|
|
443
|
+
originalParams.add(paramName);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
originalToolSchemas.set(tool.name, originalParams);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Sanitize tool arguments before forwarding to child MCP.
|
|
451
|
+
* Removes proxy parameters (description, isRetryAttempt, originalToolId)
|
|
452
|
+
* ONLY if they weren't originally part of the child tool's schema.
|
|
453
|
+
* This handles parameter name collisions gracefully.
|
|
454
|
+
*/
|
|
455
|
+
function sanitizeToolArgs(toolName, args) {
|
|
456
|
+
const originalParams = originalToolSchemas.get(toolName);
|
|
457
|
+
if (!originalParams) {
|
|
458
|
+
// If we don't have schema info, pass through all args (safer than stripping)
|
|
459
|
+
return args;
|
|
460
|
+
}
|
|
461
|
+
const sanitized = {};
|
|
462
|
+
const proxyParamNames = Object.keys(PROXY_PARAMS);
|
|
463
|
+
for (const [key, value] of Object.entries(args)) {
|
|
464
|
+
// Keep the parameter if:
|
|
465
|
+
// 1. It's NOT a proxy parameter, OR
|
|
466
|
+
// 2. It WAS in the original tool's schema (collision case)
|
|
467
|
+
if (!proxyParamNames.includes(key) || originalParams.has(key)) {
|
|
468
|
+
sanitized[key] = value;
|
|
469
|
+
}
|
|
470
|
+
else if (ENABLE_LOGGING) {
|
|
471
|
+
console.debug(`[mcp-proxy] Stripping proxy parameter '${key}' from ${toolName} call`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return sanitized;
|
|
475
|
+
}
|
|
476
|
+
// ------------------------------------------------------------------------
|
|
420
477
|
// 3. CREATE PROXY SERVER
|
|
421
478
|
// ------------------------------------------------------------------------
|
|
422
479
|
const server = new Server({
|
|
@@ -641,9 +698,11 @@ async function main() {
|
|
|
641
698
|
if (ENABLE_LOGGING) {
|
|
642
699
|
console.debug(`[mcp-proxy] Forwarding to child MCP: ${toolName}`);
|
|
643
700
|
}
|
|
701
|
+
// Sanitize arguments: remove proxy parameters that weren't in original tool schema
|
|
702
|
+
const sanitizedArgs = sanitizeToolArgs(toolName, toolArgs);
|
|
644
703
|
result = await childClient.callTool({
|
|
645
704
|
name: toolName,
|
|
646
|
-
arguments:
|
|
705
|
+
arguments: sanitizedArgs
|
|
647
706
|
});
|
|
648
707
|
// Check if child MCP returned an error
|
|
649
708
|
const childReturnedError = !!result.isError;
|