@anyshift/mcp-proxy 0.4.2 → 0.5.0
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 +91 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
18
18
|
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
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
21
22
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
22
23
|
import { createJqTool } from './jq/index.js';
|
|
23
24
|
import { createTimeseriesTool } from './timeseries/index.js';
|
|
@@ -115,6 +116,27 @@ const CHILD_COMMAND = process.env.MCP_PROXY_CHILD_COMMAND;
|
|
|
115
116
|
const CHILD_ARGS = process.env.MCP_PROXY_CHILD_ARGS
|
|
116
117
|
? process.env.MCP_PROXY_CHILD_ARGS.split(',').map(s => s.trim()).filter(Boolean)
|
|
117
118
|
: [];
|
|
119
|
+
/**
|
|
120
|
+
* MCP_PROXY_REMOTE_URL (OPTIONAL)
|
|
121
|
+
* SSE URL of a remote MCP server to connect to instead of spawning a child process
|
|
122
|
+
* When set, the proxy connects as an SSE client to the remote server
|
|
123
|
+
* Example: "http://internal-server:3100/sse"
|
|
124
|
+
*/
|
|
125
|
+
const REMOTE_URL = process.env.MCP_PROXY_REMOTE_URL;
|
|
126
|
+
/**
|
|
127
|
+
* MCP_PROXY_REMOTE_HEADERS (OPTIONAL)
|
|
128
|
+
* JSON string of custom headers to send with SSE/POST requests to the remote server
|
|
129
|
+
* Example: '{"X-Client-Credentials":"{\"prod\":{\"AWS_ACCESS_KEY_ID\":\"AKIA...\"}}"}'
|
|
130
|
+
*/
|
|
131
|
+
const REMOTE_HEADERS = process.env.MCP_PROXY_REMOTE_HEADERS;
|
|
132
|
+
/**
|
|
133
|
+
* MCP_PROXY_REMOTE_CREDENTIALS (OPTIONAL)
|
|
134
|
+
* JSON string of credentials to send as X-Client-Credentials header to the remote server.
|
|
135
|
+
* This is a convenience alternative to embedding credentials inside MCP_PROXY_REMOTE_HEADERS,
|
|
136
|
+
* which requires triple-escaping nested JSON.
|
|
137
|
+
* Example: '{"prod":{"AWS_ACCESS_KEY_ID":"AKIA...","AWS_SECRET_ACCESS_KEY":"..."}}'
|
|
138
|
+
*/
|
|
139
|
+
const REMOTE_CREDENTIALS = process.env.MCP_PROXY_REMOTE_CREDENTIALS;
|
|
118
140
|
/**
|
|
119
141
|
* MCP_PROXY_MAX_TOKENS (OPTIONAL, default: 10000)
|
|
120
142
|
* Maximum tokens before truncating responses
|
|
@@ -191,14 +213,18 @@ const ENABLE_LOGGING = process.env.MCP_PROXY_ENABLE_LOGGING === 'true';
|
|
|
191
213
|
// VALIDATION
|
|
192
214
|
// ============================================================================
|
|
193
215
|
// Validate configuration
|
|
194
|
-
// CHILD_COMMAND
|
|
195
|
-
if (!CHILD_COMMAND) {
|
|
196
|
-
console.debug('[mcp-proxy] No child command specified - running in standalone mode (JQ only)');
|
|
216
|
+
// CHILD_COMMAND and REMOTE_URL are optional - if neither provided, proxy runs in standalone mode (JQ only)
|
|
217
|
+
if (!CHILD_COMMAND && !REMOTE_URL) {
|
|
218
|
+
console.debug('[mcp-proxy] No child command or remote URL specified - running in standalone mode (JQ only)');
|
|
197
219
|
if (!ENABLE_JQ) {
|
|
198
220
|
console.error('ERROR: Standalone mode requires JQ to be enabled (MCP_PROXY_ENABLE_JQ must not be false)');
|
|
199
221
|
process.exit(1);
|
|
200
222
|
}
|
|
201
223
|
}
|
|
224
|
+
if (CHILD_COMMAND && REMOTE_URL) {
|
|
225
|
+
console.error('ERROR: Cannot use both MCP_PROXY_CHILD_COMMAND and MCP_PROXY_REMOTE_URL');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
202
228
|
if (WRITE_TO_FILE && !OUTPUT_PATH) {
|
|
203
229
|
console.error('ERROR: MCP_PROXY_OUTPUT_PATH is required when MCP_PROXY_WRITE_TO_FILE=true');
|
|
204
230
|
console.error('Example: export MCP_PROXY_OUTPUT_PATH="/tmp/mcp-results"');
|
|
@@ -237,8 +263,13 @@ for (const [key, value] of Object.entries(process.env)) {
|
|
|
237
263
|
// ============================================================================
|
|
238
264
|
if (ENABLE_LOGGING) {
|
|
239
265
|
console.debug('[mcp-proxy] Configuration:');
|
|
240
|
-
console.debug(` Mode: ${CHILD_COMMAND ? 'wrapper' : 'standalone (JQ only)'}`);
|
|
241
|
-
if (
|
|
266
|
+
console.debug(` Mode: ${REMOTE_URL ? 'remote (SSE)' : CHILD_COMMAND ? 'wrapper' : 'standalone (JQ only)'}`);
|
|
267
|
+
if (REMOTE_URL) {
|
|
268
|
+
console.debug(` Remote URL: ${REMOTE_URL}`);
|
|
269
|
+
console.debug(` Remote headers: ${REMOTE_HEADERS ? 'configured' : 'none'}`);
|
|
270
|
+
console.debug(` Remote credentials: ${REMOTE_CREDENTIALS ? 'configured' : 'none'}`);
|
|
271
|
+
}
|
|
272
|
+
else if (CHILD_COMMAND) {
|
|
242
273
|
console.debug(` Child command: ${CHILD_COMMAND}`);
|
|
243
274
|
console.debug(` Child args: [${CHILD_ARGS.join(', ')}]`);
|
|
244
275
|
}
|
|
@@ -265,7 +296,61 @@ async function main() {
|
|
|
265
296
|
// ------------------------------------------------------------------------
|
|
266
297
|
let childClient = null;
|
|
267
298
|
let childToolsResponse = { tools: [] };
|
|
268
|
-
if (
|
|
299
|
+
if (REMOTE_URL) {
|
|
300
|
+
// Remote mode: connect to a remote MCP server over SSE
|
|
301
|
+
console.debug(`[mcp-proxy] Connecting to remote MCP: ${REMOTE_URL}`);
|
|
302
|
+
let headers = {};
|
|
303
|
+
if (REMOTE_HEADERS) {
|
|
304
|
+
try {
|
|
305
|
+
headers = JSON.parse(REMOTE_HEADERS);
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
console.error('ERROR: MCP_PROXY_REMOTE_HEADERS contains invalid JSON');
|
|
309
|
+
console.error(` Value: ${REMOTE_HEADERS.substring(0, 100)}${REMOTE_HEADERS.length > 100 ? '...' : ''}`);
|
|
310
|
+
console.error(` Parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (REMOTE_CREDENTIALS) {
|
|
315
|
+
try {
|
|
316
|
+
JSON.parse(REMOTE_CREDENTIALS); // Validate JSON before sending
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
console.error('ERROR: MCP_PROXY_REMOTE_CREDENTIALS contains invalid JSON');
|
|
320
|
+
console.error(` Value: ${REMOTE_CREDENTIALS.substring(0, 100)}${REMOTE_CREDENTIALS.length > 100 ? '...' : ''}`);
|
|
321
|
+
console.error(` Parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
headers['X-Client-Credentials'] = REMOTE_CREDENTIALS;
|
|
325
|
+
}
|
|
326
|
+
const childTransport = new SSEClientTransport(new URL(REMOTE_URL), {
|
|
327
|
+
eventSourceInit: {
|
|
328
|
+
fetch: (url, init) => {
|
|
329
|
+
// Safely convert init.headers (could be Headers object, array, or plain object)
|
|
330
|
+
const initHeaders = init?.headers
|
|
331
|
+
? Object.fromEntries(new Headers(init.headers))
|
|
332
|
+
: {};
|
|
333
|
+
return fetch(url, {
|
|
334
|
+
...init,
|
|
335
|
+
headers: { ...initHeaders, ...headers },
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
requestInit: { headers },
|
|
340
|
+
});
|
|
341
|
+
childClient = new Client({
|
|
342
|
+
name: 'mcp-proxy-client',
|
|
343
|
+
version: '1.0.0'
|
|
344
|
+
}, {
|
|
345
|
+
capabilities: {}
|
|
346
|
+
});
|
|
347
|
+
await childClient.connect(childTransport);
|
|
348
|
+
console.debug('[mcp-proxy] Connected to remote MCP');
|
|
349
|
+
// Discover tools from remote MCP
|
|
350
|
+
childToolsResponse = await childClient.listTools();
|
|
351
|
+
console.debug(`[mcp-proxy] Discovered ${childToolsResponse.tools.length} tools from remote MCP`);
|
|
352
|
+
}
|
|
353
|
+
else if (CHILD_COMMAND) {
|
|
269
354
|
console.debug(`[mcp-proxy] Spawning child MCP: ${CHILD_COMMAND}`);
|
|
270
355
|
const childTransport = new StdioClientTransport({
|
|
271
356
|
command: CHILD_COMMAND,
|