@anyshift/mcp-proxy 0.3.4 → 0.3.5

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 CHANGED
@@ -23,14 +23,15 @@ import { createJqTool } from './jq/index.js';
23
23
  import { truncateResponseIfNeeded } from './truncation/index.js';
24
24
  import { createFileWriter } from './fileWriter/index.js';
25
25
  import { generateToolId } from './utils/filename.js';
26
+ import { PROXY_PARAMS } from './types/index.js';
26
27
  // ============================================================================
27
28
  // HELPER FUNCTIONS
28
29
  // ============================================================================
29
30
  /**
30
- * Inject 'description' parameter into a tool's inputSchema
31
- * This ensures LLMs explain why they're calling each tool
31
+ * Inject proxy-specific parameters into a tool's inputSchema
32
+ * Uses centralized PROXY_PARAMS definitions
32
33
  */
33
- function injectDescriptionParam(tool) {
34
+ function injectProxyParams(tool) {
34
35
  // Clone the tool to avoid mutating the original
35
36
  const modifiedTool = { ...tool };
36
37
  if (!modifiedTool.inputSchema) {
@@ -43,15 +44,26 @@ function injectDescriptionParam(tool) {
43
44
  }
44
45
  // Clone properties
45
46
  modifiedTool.inputSchema.properties = { ...modifiedTool.inputSchema.properties };
46
- // Only add if not already present
47
- if (!modifiedTool.inputSchema.properties.description) {
48
- modifiedTool.inputSchema.properties.description = {
49
- type: 'string',
50
- description: 'Brief explanation of why you are calling this tool and what you expect to learn/achieve'
51
- };
47
+ // Inject all proxy params if not already present
48
+ for (const [key, value] of Object.entries(PROXY_PARAMS)) {
49
+ if (!modifiedTool.inputSchema.properties[key]) {
50
+ modifiedTool.inputSchema.properties[key] = value;
51
+ }
52
52
  }
53
53
  return modifiedTool;
54
54
  }
55
+ /**
56
+ * Add retry metadata to a unified response if present in tool args
57
+ */
58
+ function addRetryMetadata(response, toolArgs) {
59
+ if (toolArgs.isRetryAttempt) {
60
+ response.isRetryAttempt = true;
61
+ if (toolArgs.originalToolId) {
62
+ response.originalToolId = toolArgs.originalToolId;
63
+ }
64
+ }
65
+ return response;
66
+ }
55
67
  /**
56
68
  * ENVIRONMENT VARIABLE CONTRACT
57
69
  * =============================
@@ -299,8 +311,8 @@ async function main() {
299
311
  // ------------------------------------------------------------------------
300
312
  // 5. REGISTER ALL TOOLS (CHILD + PROXY) WITH DESCRIPTION INJECTION
301
313
  // ------------------------------------------------------------------------
302
- // Inject 'description' parameter into all child tools
303
- const enhancedChildTools = childToolsResponse.tools.map(injectDescriptionParam);
314
+ // Inject proxy parameters (description, isRetryAttempt, originalToolId) into all child tools
315
+ const enhancedChildTools = childToolsResponse.tools.map(injectProxyParams);
304
316
  const allTools = [
305
317
  ...enhancedChildTools,
306
318
  ...(jqTool ? [jqTool.toolDefinition] : []) // JQ tool already has description param
@@ -323,6 +335,9 @@ async function main() {
323
335
  if (toolArgs.description) {
324
336
  console.debug(`[mcp-proxy] Description: ${toolArgs.description}`);
325
337
  }
338
+ if (toolArgs.isRetryAttempt) {
339
+ console.debug(`[mcp-proxy] Retry of: ${toolArgs.originalToolId}`);
340
+ }
326
341
  }
327
342
  try {
328
343
  let result;
@@ -336,11 +351,11 @@ async function main() {
336
351
  });
337
352
  // JQ tool returns directly, wrap in unified format
338
353
  const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
339
- const unifiedResponse = {
354
+ const unifiedResponse = addRetryMetadata({
340
355
  tool_id,
341
356
  wroteToFile: false,
342
357
  outputContent: result.content?.[0]?.text
343
- };
358
+ }, toolArgs);
344
359
  return {
345
360
  content: [{
346
361
  type: 'text',
@@ -352,11 +367,11 @@ async function main() {
352
367
  // Forward all other tools to child MCP (if child exists)
353
368
  if (!childClient) {
354
369
  const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
355
- const errorResponse = {
370
+ const errorResponse = addRetryMetadata({
356
371
  tool_id,
357
372
  wroteToFile: false,
358
373
  error: `Tool ${toolName} not available in standalone mode (no child MCP)`
359
- };
374
+ }, toolArgs);
360
375
  return {
361
376
  content: [{
362
377
  type: 'text',
@@ -372,11 +387,32 @@ async function main() {
372
387
  name: toolName,
373
388
  arguments: toolArgs
374
389
  });
390
+ // Check if child MCP returned an error
391
+ const childReturnedError = !!result.isError;
375
392
  // Process result through file writer to get unified response
376
393
  if (result.content && Array.isArray(result.content) && result.content.length > 0) {
377
394
  const item = result.content[0];
378
395
  if (item.type === 'text' && typeof item.text === 'string') {
379
396
  const originalLength = item.text.length;
397
+ // If child returned error, pass through directly without file writing
398
+ if (childReturnedError) {
399
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
400
+ const errorResponse = addRetryMetadata({
401
+ tool_id,
402
+ wroteToFile: false,
403
+ error: item.text
404
+ }, toolArgs);
405
+ if (ENABLE_LOGGING) {
406
+ console.debug(`[mcp-proxy] Child MCP returned error for ${toolName}: ${item.text.substring(0, 100)}...`);
407
+ }
408
+ return {
409
+ content: [{
410
+ type: 'text',
411
+ text: JSON.stringify(errorResponse, null, 2)
412
+ }],
413
+ isError: true
414
+ };
415
+ }
380
416
  // Get unified response from file writer
381
417
  const unifiedResponse = await fileWriter.handleResponse(toolName, toolArgs, {
382
418
  content: [{ type: 'text', text: item.text }]
@@ -408,7 +444,8 @@ async function main() {
408
444
  }
409
445
  }
410
446
  }
411
- // Return unified response as JSON
447
+ // Add retry metadata and return unified response as JSON
448
+ addRetryMetadata(unifiedResponse, toolArgs);
412
449
  return {
413
450
  content: [{
414
451
  type: 'text',
@@ -420,26 +457,27 @@ async function main() {
420
457
  }
421
458
  // Fallback: return result with generated tool_id
422
459
  const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
423
- const fallbackResponse = {
460
+ const fallbackResponse = addRetryMetadata({
424
461
  tool_id,
425
462
  wroteToFile: false,
426
463
  outputContent: result
427
- };
464
+ }, toolArgs);
428
465
  return {
429
466
  content: [{
430
467
  type: 'text',
431
468
  text: JSON.stringify(fallbackResponse, null, 2)
432
- }]
469
+ }],
470
+ isError: childReturnedError
433
471
  };
434
472
  }
435
473
  catch (error) {
436
474
  console.error(`[mcp-proxy] Error executing tool ${toolName}:`, error);
437
475
  const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
438
- const errorResponse = {
476
+ const errorResponse = addRetryMetadata({
439
477
  tool_id,
440
478
  wroteToFile: false,
441
479
  error: `Error executing ${toolName}: ${error.message || String(error)}`
442
- };
480
+ }, toolArgs);
443
481
  return {
444
482
  content: [{
445
483
  type: 'text',
@@ -14,6 +14,18 @@ export declare function createJqTool(config: JqConfig): {
14
14
  inputSchema: {
15
15
  type: string;
16
16
  properties: {
17
+ description: {
18
+ readonly type: "string";
19
+ readonly description: "Brief explanation of why you are calling this tool and what you expect to learn/achieve";
20
+ };
21
+ isRetryAttempt: {
22
+ readonly type: "boolean";
23
+ readonly description: "Set to true if this call is a follow-up attempt after a previous failure. This includes: (1) exact retries for transient errors, (2) corrected attempts fixing typos or parameters based on error feedback. Any call attempting to achieve the same goal as a failed call is a retry.";
24
+ };
25
+ originalToolId: {
26
+ readonly type: "string";
27
+ readonly description: "The tool_id from the FIRST failed attempt in this retry chain. Always reference the original tool_id, not intermediate failures. Example: if tool_id_1 fails, tool_id_2 (retry) fails, tool_id_3 (retry) succeeds - both tool_id_2 and tool_id_3 should reference tool_id_1. Required when isRetryAttempt is true.";
28
+ };
17
29
  jq_query: {
18
30
  type: string;
19
31
  description: string;
@@ -22,10 +34,6 @@ export declare function createJqTool(config: JqConfig): {
22
34
  type: string;
23
35
  description: string;
24
36
  };
25
- description: {
26
- type: string;
27
- description: string;
28
- };
29
37
  };
30
38
  required: string[];
31
39
  };
package/dist/jq/tool.d.ts CHANGED
@@ -6,14 +6,20 @@ export declare const ExecuteJqQuerySchema: z.ZodObject<{
6
6
  jq_query: z.ZodString;
7
7
  file_path: z.ZodString;
8
8
  description: z.ZodOptional<z.ZodString>;
9
+ isRetryAttempt: z.ZodOptional<z.ZodBoolean>;
10
+ originalToolId: z.ZodOptional<z.ZodString>;
9
11
  }, "strip", z.ZodTypeAny, {
10
12
  jq_query: string;
11
13
  file_path: string;
12
14
  description?: string | undefined;
15
+ isRetryAttempt?: boolean | undefined;
16
+ originalToolId?: string | undefined;
13
17
  }, {
14
18
  jq_query: string;
15
19
  file_path: string;
16
20
  description?: string | undefined;
21
+ isRetryAttempt?: boolean | undefined;
22
+ originalToolId?: string | undefined;
17
23
  }>;
18
24
  /**
19
25
  * Tool definition for JQ query execution with enhanced prompts
@@ -25,6 +31,18 @@ export declare const JQ_TOOL_DEFINITION: {
25
31
  inputSchema: {
26
32
  type: string;
27
33
  properties: {
34
+ description: {
35
+ readonly type: "string";
36
+ readonly description: "Brief explanation of why you are calling this tool and what you expect to learn/achieve";
37
+ };
38
+ isRetryAttempt: {
39
+ readonly type: "boolean";
40
+ readonly description: "Set to true if this call is a follow-up attempt after a previous failure. This includes: (1) exact retries for transient errors, (2) corrected attempts fixing typos or parameters based on error feedback. Any call attempting to achieve the same goal as a failed call is a retry.";
41
+ };
42
+ originalToolId: {
43
+ readonly type: "string";
44
+ readonly description: "The tool_id from the FIRST failed attempt in this retry chain. Always reference the original tool_id, not intermediate failures. Example: if tool_id_1 fails, tool_id_2 (retry) fails, tool_id_3 (retry) succeeds - both tool_id_2 and tool_id_3 should reference tool_id_1. Required when isRetryAttempt is true.";
45
+ };
28
46
  jq_query: {
29
47
  type: string;
30
48
  description: string;
@@ -33,10 +51,6 @@ export declare const JQ_TOOL_DEFINITION: {
33
51
  type: string;
34
52
  description: string;
35
53
  };
36
- description: {
37
- type: string;
38
- description: string;
39
- };
40
54
  };
41
55
  required: string[];
42
56
  };
package/dist/jq/tool.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { PROXY_PARAMS } from '../types/index.js';
2
3
  /**
3
4
  * Zod schema for JQ query execution
4
5
  */
@@ -9,10 +10,9 @@ export const ExecuteJqQuerySchema = z.object({
9
10
  file_path: z
10
11
  .string()
11
12
  .describe('Absolute path starting with "/" pointing to the JSON or JSONL file to process. Must be a valid, existing file with .json or .jsonl extension. The file will be validated for existence and readability before processing.'),
12
- description: z
13
- .string()
14
- .optional()
15
- .describe('Brief explanation of why you are calling this tool and what you expect to learn/achieve'),
13
+ description: z.string().optional().describe(PROXY_PARAMS.description.description),
14
+ isRetryAttempt: z.boolean().optional().describe(PROXY_PARAMS.isRetryAttempt.description),
15
+ originalToolId: z.string().optional().describe(PROXY_PARAMS.originalToolId.description),
16
16
  });
17
17
  /**
18
18
  * Tool definition for JQ query execution with enhanced prompts
@@ -111,10 +111,7 @@ export const JQ_TOOL_DEFINITION = {
111
111
  type: 'string',
112
112
  description: 'Absolute path starting with "/" pointing to the JSON or JSONL file to process. Must be a valid, existing file with .json or .jsonl extension. The file will be validated for existence and readability before processing.',
113
113
  },
114
- description: {
115
- type: 'string',
116
- description: 'Brief explanation of why you are calling this tool and what you expect to learn/achieve',
117
- },
114
+ ...PROXY_PARAMS,
118
115
  },
119
116
  required: ['jq_query', 'file_path'],
120
117
  },
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Centralized proxy parameter definitions
3
+ * Used by both injectProxyParams and JQ tool definitions
4
+ */
5
+ export declare const PROXY_PARAMS: {
6
+ readonly description: {
7
+ readonly type: "string";
8
+ readonly description: "Brief explanation of why you are calling this tool and what you expect to learn/achieve";
9
+ };
10
+ readonly isRetryAttempt: {
11
+ readonly type: "boolean";
12
+ readonly description: "Set to true if this call is a follow-up attempt after a previous failure. This includes: (1) exact retries for transient errors, (2) corrected attempts fixing typos or parameters based on error feedback. Any call attempting to achieve the same goal as a failed call is a retry.";
13
+ };
14
+ readonly originalToolId: {
15
+ readonly type: "string";
16
+ readonly description: "The tool_id from the FIRST failed attempt in this retry chain. Always reference the original tool_id, not intermediate failures. Example: if tool_id_1 fails, tool_id_2 (retry) fails, tool_id_3 (retry) succeeds - both tool_id_2 and tool_id_3 should reference tool_id_1. Required when isRetryAttempt is true.";
17
+ };
18
+ };
1
19
  /**
2
20
  * Configuration for the file writer module
3
21
  */
@@ -73,4 +91,8 @@ export interface UnifiedToolResponse {
73
91
  outputContent?: unknown;
74
92
  /** Error message if the tool call failed */
75
93
  error?: string;
94
+ /** Whether this was a retry attempt */
95
+ isRetryAttempt?: boolean;
96
+ /** The original tool_id this call is retrying */
97
+ originalToolId?: string;
76
98
  }
@@ -1 +1,18 @@
1
- export {};
1
+ /**
2
+ * Centralized proxy parameter definitions
3
+ * Used by both injectProxyParams and JQ tool definitions
4
+ */
5
+ export const PROXY_PARAMS = {
6
+ description: {
7
+ type: 'string',
8
+ description: 'Brief explanation of why you are calling this tool and what you expect to learn/achieve'
9
+ },
10
+ isRetryAttempt: {
11
+ type: 'boolean',
12
+ description: 'Set to true if this call is a follow-up attempt after a previous failure. This includes: (1) exact retries for transient errors, (2) corrected attempts fixing typos or parameters based on error feedback. Any call attempting to achieve the same goal as a failed call is a retry.'
13
+ },
14
+ originalToolId: {
15
+ type: 'string',
16
+ description: 'The tool_id from the FIRST failed attempt in this retry chain. Always reference the original tool_id, not intermediate failures. Example: if tool_id_1 fails, tool_id_2 (retry) fails, tool_id_3 (retry) succeeds - both tool_id_2 and tool_id_3 should reference tool_id_1. Required when isRetryAttempt is true.'
17
+ }
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyshift/mcp-proxy",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
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",