@anyshift/mcp-proxy 0.4.2-dev0 → 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.
@@ -5,18 +5,10 @@ import { generateQueryAssistSchema, generateJsonlQueryAssistSchema } from './sch
5
5
  import { parseDynatraceDqlResponse, isDynatraceDqlTool, } from './dynatrace.js';
6
6
  // Default minimum character count to trigger file writing
7
7
  const DEFAULT_MIN_CHARS = 1000;
8
- /**
9
- * Generate the message to include when data is written to file, guiding the LLM on how to use the result.
10
- * The message conditionally includes the timeseries tool based on configuration.
11
- */
12
- function getFileWrittenMessage(enableTimeseries) {
13
- const toolsList = enableTimeseries
14
- ? '"execute_jq_query" tool (for JSON/JSONL data extraction and transformation) or "detect_timeseries_anomalies" tool (for time series analysis)'
15
- : '"execute_jq_query" tool (for JSON/JSONL data extraction and transformation)';
16
- return `To read this file, use the ${toolsList}.
8
+ // Message to include when data is written to file, guiding the LLM on how to use the result
9
+ const FILE_WRITTEN_MESSAGE = `To read this file, use the "execute_jq_query" tool (for JSON/JSONL data extraction and transformation) or "detect_timeseries_anomalies" tool (for time series analysis, if available).
17
10
 
18
11
  IMPORTANT for supporting facts: This tool_id CANNOT be used as the proxy_tool_id in your output's supporting_facts evidence. You must read the file using one of the tools above and use THAT tool's tool_id as the proxy_tool_id to support facts in your output.`;
19
- }
20
12
  /**
21
13
  * Detect whether content is JSON, JSONL, or plain text
22
14
  */
@@ -263,7 +255,7 @@ async function handleDynatraceDqlResponse(config, tool_id, parsedDql) {
263
255
  wroteToFile: true,
264
256
  filePath: filepath,
265
257
  fileSchema,
266
- message: getFileWrittenMessage(config.enableTimeseries ?? false),
258
+ message: FILE_WRITTEN_MESSAGE,
267
259
  };
268
260
  }
269
261
  catch (error) {
@@ -401,7 +393,7 @@ export async function handleToolResponse(config, toolName, args, responseData) {
401
393
  wroteToFile: true,
402
394
  filePath: filepath,
403
395
  fileSchema,
404
- message: getFileWrittenMessage(config.enableTimeseries ?? false),
396
+ message: FILE_WRITTEN_MESSAGE,
405
397
  };
406
398
  }
407
399
  catch (error) {
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 is now optional - if not provided, proxy runs in standalone mode (JQ only)
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 (CHILD_COMMAND) {
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 (CHILD_COMMAND) {
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,
@@ -324,8 +409,7 @@ async function main() {
324
409
  toolAbbreviations: {}, // No service-specific abbreviations (generic proxy)
325
410
  schemaMaxDepth: SCHEMA_MAX_DEPTH,
326
411
  schemaMaxPaths: SCHEMA_MAX_PATHS,
327
- schemaMaxKeys: SCHEMA_MAX_KEYS,
328
- enableTimeseries: ENABLE_TIMESERIES
412
+ schemaMaxKeys: SCHEMA_MAX_KEYS
329
413
  };
330
414
  const fileWriter = createFileWriter(fileWriterConfig);
331
415
  // JQ tool configuration
@@ -34,8 +34,6 @@ export interface FileWriterConfig {
34
34
  schemaMaxPaths?: number;
35
35
  /** Maximum keys to analyze per object (default: 50) */
36
36
  schemaMaxKeys?: number;
37
- /** Whether the timeseries anomaly detection tool is enabled (affects file-written message) */
38
- enableTimeseries?: boolean;
39
37
  }
40
38
  /**
41
39
  * Configuration for the JQ tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyshift/mcp-proxy",
3
- "version": "0.4.2-dev0",
3
+ "version": "0.5.0",
4
4
  "description": "Generic MCP proxy that adds truncation, file writing, and JQ capabilities to any MCP server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",