@anyshift/mcp-proxy 0.6.8 → 0.6.10

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.
Files changed (2) hide show
  1. package/dist/index.js +280 -230
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * - Wrapper Mode: When MCP_PROXY_CHILD_COMMAND is set, wraps another MCP server
15
15
  * - Standalone Mode: When MCP_PROXY_CHILD_COMMAND is not set, provides only JQ tool
16
16
  */
17
+ import * as Sentry from '@sentry/node';
17
18
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
18
19
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
20
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -29,6 +30,21 @@ import { createFileWriter } from './fileWriter/index.js';
29
30
  import { generateToolId } from './utils/filename.js';
30
31
  import { PROXY_PARAMS } from './types/index.js';
31
32
  // ============================================================================
33
+ // SENTRY INITIALIZATION
34
+ // ============================================================================
35
+ const MCP_NAME = process.env.MCP_PROXY_MCP_NAME || 'unknown';
36
+ const SENTRY_DSN = process.env.SENTRY_DSN;
37
+ const SENTRY_ENV = process.env.SENTRY_ENV;
38
+ const SENTRY_TRACE_HEADER = process.env.SENTRY_TRACE_HEADER;
39
+ const SENTRY_BAGGAGE_HEADER = process.env.SENTRY_BAGGAGE_HEADER;
40
+ if (SENTRY_DSN) {
41
+ Sentry.init({
42
+ dsn: SENTRY_DSN,
43
+ environment: SENTRY_ENV,
44
+ tracesSampleRate: 1.0,
45
+ });
46
+ }
47
+ // ============================================================================
32
48
  // HELPER FUNCTIONS
33
49
  // ============================================================================
34
50
  /**
@@ -293,10 +309,12 @@ if (ENABLE_DOCS && DOCS_INDEXED_PATHS.length === 0) {
293
309
  */
294
310
  const childEnv = {};
295
311
  for (const [key, value] of Object.entries(process.env)) {
296
- // Skip proxy configuration variables
297
312
  if (key.startsWith('MCP_PROXY_')) {
298
313
  continue;
299
314
  }
315
+ if (key === 'SENTRY_DSN' || key === 'SENTRY_ENV' || key === 'MCP_PROXY_MCP_NAME') {
316
+ continue;
317
+ }
300
318
  // Only include defined values
301
319
  if (value !== undefined) {
302
320
  childEnv[key] = value;
@@ -555,8 +573,21 @@ async function main() {
555
573
  // ------------------------------------------------------------------------
556
574
  // 6. HANDLE TOOL LIST REQUESTS
557
575
  // ------------------------------------------------------------------------
576
+ const readOnlyMcp = process.env.READ_ONLY_MCP === 'true';
558
577
  server.setRequestHandler(ListToolsRequestSchema, async () => {
559
- return { tools: allTools };
578
+ if (!readOnlyMcp) {
579
+ return { tools: allTools };
580
+ }
581
+ // Annotate all tools with readOnlyHint: true to enable parallel tool
582
+ // execution in clients that support it (e.g. Claude Code).
583
+ const annotatedTools = allTools.map(tool => ({
584
+ ...tool,
585
+ annotations: {
586
+ ...tool.annotations,
587
+ readOnlyHint: true,
588
+ },
589
+ }));
590
+ return { tools: annotatedTools };
560
591
  });
561
592
  // ------------------------------------------------------------------------
562
593
  // 7. HANDLE TOOL CALL REQUESTS (WITH UNIFIED RESPONSE FORMAT)
@@ -573,76 +604,225 @@ async function main() {
573
604
  console.debug(`[mcp-proxy] Retry of: ${toolArgs.originalToolId}`);
574
605
  }
575
606
  }
576
- try {
577
- let result;
578
- // Handle JQ tool locally (if enabled)
579
- if (toolName === 'execute_jq_query' && jqTool) {
580
- if (ENABLE_LOGGING) {
581
- console.debug('[mcp-proxy] Executing JQ tool locally');
582
- }
583
- result = await jqTool.handler({
584
- params: { arguments: toolArgs }
585
- });
586
- // JQ tool returns directly, wrap in unified format
587
- const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
588
- const unifiedResponse = createContentResponse(tool_id, result.content?.[0]?.text, toolArgs);
589
- return {
590
- content: [{
591
- type: 'text',
592
- text: JSON.stringify(unifiedResponse, null, 2)
593
- }],
594
- isError: result.isError
595
- };
596
- }
597
- // Handle Timeseries anomaly detection tool locally (if enabled)
598
- if (toolName === 'detect_timeseries_anomalies' && timeseriesTool) {
599
- if (ENABLE_LOGGING) {
600
- console.debug('[mcp-proxy] Executing Timeseries anomaly detection tool locally');
601
- }
602
- result = await timeseriesTool.handler({
603
- params: { arguments: toolArgs }
604
- });
605
- // Timeseries tool returns directly, wrap in unified format
606
- const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
607
- let outputContent = null;
608
- if (result.content?.[0]?.text) {
609
- try {
610
- outputContent = JSON.parse(result.content[0].text);
611
- }
612
- catch {
613
- // If JSON parsing fails, use the raw text
614
- outputContent = result.content[0].text;
607
+ const executeToolCall = async () => {
608
+ try {
609
+ let result;
610
+ // Handle JQ tool locally (if enabled)
611
+ if (toolName === 'execute_jq_query' && jqTool) {
612
+ if (ENABLE_LOGGING) {
613
+ console.debug('[mcp-proxy] Executing JQ tool locally');
615
614
  }
615
+ result = await Sentry.startSpan({ name: toolName, op: 'mcp.tool_call.local' }, async () => {
616
+ return await jqTool.handler({
617
+ params: { arguments: toolArgs }
618
+ });
619
+ });
620
+ // JQ tool returns directly, wrap in unified format
621
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
622
+ const unifiedResponse = createContentResponse(tool_id, result.content?.[0]?.text, toolArgs);
623
+ return {
624
+ content: [{
625
+ type: 'text',
626
+ text: JSON.stringify(unifiedResponse, null, 2)
627
+ }],
628
+ isError: result.isError
629
+ };
616
630
  }
617
- const unifiedResponse = {
618
- tool_id,
619
- wroteToFile: false,
620
- outputContent
621
- };
622
- return {
623
- content: [{
624
- type: 'text',
625
- text: JSON.stringify(unifiedResponse, null, 2)
626
- }],
627
- isError: result.isError
628
- };
629
- }
630
- // Handle Docs tools locally (if enabled)
631
- // Route through file writer for large doc handling (same as child MCP tools)
632
- const docsToolNames = ['docs_glob', 'docs_grep', 'docs_read'];
633
- if (docsToolNames.includes(toolName) && docsTools.length > 0) {
634
- const docTool = docsTools.find(t => t.toolDefinition.name === toolName);
635
- if (docTool) {
631
+ // Handle Timeseries anomaly detection tool locally (if enabled)
632
+ if (toolName === 'detect_timeseries_anomalies' && timeseriesTool) {
636
633
  if (ENABLE_LOGGING) {
637
- console.debug(`[mcp-proxy] Executing ${toolName} locally`);
634
+ console.debug('[mcp-proxy] Executing Timeseries anomaly detection tool locally');
638
635
  }
639
- try {
640
- result = await docTool.handler({
636
+ result = await Sentry.startSpan({ name: toolName, op: 'mcp.tool_call.local' }, async () => {
637
+ return await timeseriesTool.handler({
641
638
  params: { arguments: toolArgs }
642
639
  });
643
- const contentStr = result.content?.[0]?.text || '';
640
+ });
641
+ // Timeseries tool returns directly, wrap in unified format
642
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
643
+ let outputContent = null;
644
+ if (result.content?.[0]?.text) {
645
+ try {
646
+ outputContent = JSON.parse(result.content[0].text);
647
+ }
648
+ catch {
649
+ // If JSON parsing fails, use the raw text
650
+ outputContent = result.content[0].text;
651
+ }
652
+ }
653
+ const unifiedResponse = {
654
+ tool_id,
655
+ wroteToFile: false,
656
+ outputContent
657
+ };
658
+ return {
659
+ content: [{
660
+ type: 'text',
661
+ text: JSON.stringify(unifiedResponse, null, 2)
662
+ }],
663
+ isError: result.isError
664
+ };
665
+ }
666
+ // Handle Docs tools locally (if enabled)
667
+ // Route through file writer for large doc handling (same as child MCP tools)
668
+ const docsToolNames = ['docs_glob', 'docs_grep', 'docs_read'];
669
+ if (docsToolNames.includes(toolName) && docsTools.length > 0) {
670
+ const docTool = docsTools.find(t => t.toolDefinition.name === toolName);
671
+ if (docTool) {
672
+ if (ENABLE_LOGGING) {
673
+ console.debug(`[mcp-proxy] Executing ${toolName} locally`);
674
+ }
675
+ try {
676
+ result = await Sentry.startSpan({ name: toolName, op: 'mcp.tool_call.local' }, async () => {
677
+ return await docTool.handler({
678
+ params: { arguments: toolArgs }
679
+ });
680
+ });
681
+ const contentStr = result.content?.[0]?.text || '';
682
+ const originalLength = contentStr.length;
683
+ // Route through file writer (same pipeline as child MCP tools)
684
+ const unifiedResponse = await fileWriter.handleResponse(toolName, toolArgs, {
685
+ content: [{ type: 'text', text: contentStr }]
686
+ });
687
+ if (ENABLE_LOGGING) {
688
+ if (unifiedResponse.wroteToFile) {
689
+ console.debug(`[mcp-proxy] File written for ${toolName} (${originalLength} chars) → ${unifiedResponse.filePath}`);
690
+ }
691
+ else {
692
+ console.debug(`[mcp-proxy] Response for ${toolName} (${originalLength} chars) returned directly`);
693
+ }
694
+ }
695
+ // If not written to file, apply truncation to outputContent
696
+ if (!unifiedResponse.wroteToFile && unifiedResponse.outputContent) {
697
+ const outputStr = typeof unifiedResponse.outputContent === 'string'
698
+ ? unifiedResponse.outputContent
699
+ : JSON.stringify(unifiedResponse.outputContent);
700
+ const truncated = truncateResponseIfNeeded(truncationConfig, outputStr);
701
+ if (truncated.length < outputStr.length) {
702
+ if (ENABLE_LOGGING) {
703
+ console.debug(`[mcp-proxy] Truncated response: ${outputStr.length} → ${truncated.length} chars`);
704
+ }
705
+ // Keep as string for docs (markdown content)
706
+ unifiedResponse.outputContent = truncated;
707
+ }
708
+ }
709
+ // Add retry metadata and return unified response
710
+ addRetryMetadata(unifiedResponse, toolArgs);
711
+ return {
712
+ content: [{
713
+ type: 'text',
714
+ text: JSON.stringify(unifiedResponse, null, 2)
715
+ }],
716
+ isError: false
717
+ };
718
+ }
719
+ catch (error) {
720
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
721
+ return {
722
+ content: [{
723
+ type: 'text',
724
+ text: JSON.stringify(createErrorResponse(tool_id, error.message || String(error), toolArgs), null, 2)
725
+ }],
726
+ isError: true
727
+ };
728
+ }
729
+ }
730
+ }
731
+ // Forward all other tools to child MCP (if child exists)
732
+ if (!childClient) {
733
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
734
+ return {
735
+ content: [{
736
+ type: 'text',
737
+ text: JSON.stringify(createErrorResponse(tool_id, `Tool ${toolName} not available in standalone mode (no child MCP)`, toolArgs), null, 2)
738
+ }],
739
+ isError: true
740
+ };
741
+ }
742
+ if (ENABLE_LOGGING) {
743
+ console.debug(`[mcp-proxy] Forwarding to child MCP: ${toolName}`);
744
+ }
745
+ // Sanitize arguments: remove proxy parameters that weren't in original tool schema
746
+ const sanitizedArgs = sanitizeToolArgs(toolName, toolArgs);
747
+ result = await Sentry.startSpan({ name: toolName, op: 'mcp.tool_call.remote' }, () => childClient.callTool({
748
+ name: toolName,
749
+ arguments: sanitizedArgs
750
+ }, undefined, {
751
+ timeout: REQUEST_TIMEOUT_MS
752
+ }));
753
+ // Check if child MCP returned an error
754
+ const childReturnedError = !!result.isError;
755
+ // Preserve _meta from child response (e.g. parsed_commands) for passthrough
756
+ const childMeta = result._meta && typeof result._meta === 'object' && Object.keys(result._meta).length > 0
757
+ ? result._meta
758
+ : undefined;
759
+ // Process result through file writer to get unified response
760
+ if (result.content && Array.isArray(result.content) && result.content.length > 0) {
761
+ // Extract text content and embedded resources from all content items
762
+ const textContents = [];
763
+ const resources = [];
764
+ for (const item of result.content) {
765
+ if (item.type === 'text' && typeof item.text === 'string') {
766
+ textContents.push(item.text);
767
+ }
768
+ else if (item.type === 'resource' && item.resource) {
769
+ // Handle EmbeddedResource - extract content from resource field
770
+ const resource = item.resource;
771
+ const resourceObj = {};
772
+ if (resource.uri)
773
+ resourceObj.uri = resource.uri;
774
+ if (resource.mimeType)
775
+ resourceObj.mimeType = resource.mimeType;
776
+ // Content can be in 'text' (string) or 'blob' (base64 binary)
777
+ if (resource.text) {
778
+ resourceObj.content = resource.text;
779
+ }
780
+ else if (resource.blob) {
781
+ // For binary content, keep as base64 or decode based on mimeType
782
+ resourceObj.content = typeof resource.blob === 'string'
783
+ ? resource.blob
784
+ : Buffer.from(resource.blob).toString('base64');
785
+ }
786
+ resources.push(resourceObj);
787
+ }
788
+ }
789
+ // Build combined content object
790
+ let combinedContent;
791
+ if (resources.length > 0) {
792
+ // Has resources - combine text and resources into single object
793
+ combinedContent = {
794
+ text: textContents.length === 1 ? textContents[0] : textContents,
795
+ resources: resources.length === 1 ? resources[0] : resources
796
+ };
797
+ if (ENABLE_LOGGING) {
798
+ console.debug(`[mcp-proxy] Combined ${textContents.length} text items and ${resources.length} resources`);
799
+ }
800
+ }
801
+ else if (textContents.length > 0) {
802
+ // Text only - use first text content (original behavior)
803
+ combinedContent = textContents[0];
804
+ }
805
+ if (combinedContent !== undefined) {
806
+ const contentStr = typeof combinedContent === 'string'
807
+ ? combinedContent
808
+ : JSON.stringify(combinedContent);
644
809
  const originalLength = contentStr.length;
645
- // Route through file writer (same pipeline as child MCP tools)
810
+ // If child returned error, pass through directly without file writing
811
+ if (childReturnedError) {
812
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
813
+ if (ENABLE_LOGGING) {
814
+ console.debug(`[mcp-proxy] Child MCP returned error for ${toolName}: ${contentStr.substring(0, 100)}...`);
815
+ }
816
+ return {
817
+ content: [{
818
+ type: 'text',
819
+ text: JSON.stringify(createErrorResponse(tool_id, contentStr, toolArgs), null, 2)
820
+ }],
821
+ isError: true,
822
+ ...(childMeta ? { _meta: childMeta } : {})
823
+ };
824
+ }
825
+ // Get unified response from file writer
646
826
  const unifiedResponse = await fileWriter.handleResponse(toolName, toolArgs, {
647
827
  content: [{ type: 'text', text: contentStr }]
648
828
  });
@@ -664,191 +844,56 @@ async function main() {
664
844
  if (ENABLE_LOGGING) {
665
845
  console.debug(`[mcp-proxy] Truncated response: ${outputStr.length} → ${truncated.length} chars`);
666
846
  }
667
- // Keep as string for docs (markdown content)
668
- unifiedResponse.outputContent = truncated;
847
+ // Re-parse if it was JSON, otherwise keep as string
848
+ try {
849
+ unifiedResponse.outputContent = JSON.parse(truncated);
850
+ }
851
+ catch {
852
+ unifiedResponse.outputContent = truncated;
853
+ }
669
854
  }
670
855
  }
671
- // Add retry metadata and return unified response
856
+ // Add retry metadata and return unified response as JSON
672
857
  addRetryMetadata(unifiedResponse, toolArgs);
673
858
  return {
674
859
  content: [{
675
860
  type: 'text',
676
861
  text: JSON.stringify(unifiedResponse, null, 2)
677
862
  }],
678
- isError: false
679
- };
680
- }
681
- catch (error) {
682
- const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
683
- return {
684
- content: [{
685
- type: 'text',
686
- text: JSON.stringify(createErrorResponse(tool_id, error.message || String(error), toolArgs), null, 2)
687
- }],
688
- isError: true
863
+ isError: !!unifiedResponse.error,
864
+ ...(childMeta ? { _meta: childMeta } : {})
689
865
  };
690
866
  }
691
867
  }
868
+ // Fallback: return result with generated tool_id
869
+ const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
870
+ return {
871
+ content: [{
872
+ type: 'text',
873
+ text: JSON.stringify(createContentResponse(tool_id, result, toolArgs), null, 2)
874
+ }],
875
+ isError: childReturnedError,
876
+ ...(childMeta ? { _meta: childMeta } : {})
877
+ };
692
878
  }
693
- // Forward all other tools to child MCP (if child exists)
694
- if (!childClient) {
879
+ catch (error) {
880
+ console.error(`[mcp-proxy] Error executing tool ${toolName}:`, error);
695
881
  const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
696
882
  return {
697
883
  content: [{
698
884
  type: 'text',
699
- text: JSON.stringify(createErrorResponse(tool_id, `Tool ${toolName} not available in standalone mode (no child MCP)`, toolArgs), null, 2)
885
+ text: JSON.stringify(createErrorResponse(tool_id, `Error executing ${toolName}: ${error.message || String(error)}`, toolArgs), null, 2)
700
886
  }],
701
887
  isError: true
702
888
  };
703
889
  }
704
- if (ENABLE_LOGGING) {
705
- console.debug(`[mcp-proxy] Forwarding to child MCP: ${toolName}`);
706
- }
707
- // Sanitize arguments: remove proxy parameters that weren't in original tool schema
708
- const sanitizedArgs = sanitizeToolArgs(toolName, toolArgs);
709
- result = await childClient.callTool({
710
- name: toolName,
711
- arguments: sanitizedArgs
712
- }, undefined, {
713
- timeout: REQUEST_TIMEOUT_MS
714
- });
715
- // Check if child MCP returned an error
716
- const childReturnedError = !!result.isError;
717
- // Preserve _meta from child response (e.g. parsed_commands) for passthrough
718
- const childMeta = result._meta && typeof result._meta === 'object' && Object.keys(result._meta).length > 0
719
- ? result._meta
720
- : undefined;
721
- // Process result through file writer to get unified response
722
- if (result.content && Array.isArray(result.content) && result.content.length > 0) {
723
- // Extract text content and embedded resources from all content items
724
- const textContents = [];
725
- const resources = [];
726
- for (const item of result.content) {
727
- if (item.type === 'text' && typeof item.text === 'string') {
728
- textContents.push(item.text);
729
- }
730
- else if (item.type === 'resource' && item.resource) {
731
- // Handle EmbeddedResource - extract content from resource field
732
- const resource = item.resource;
733
- const resourceObj = {};
734
- if (resource.uri)
735
- resourceObj.uri = resource.uri;
736
- if (resource.mimeType)
737
- resourceObj.mimeType = resource.mimeType;
738
- // Content can be in 'text' (string) or 'blob' (base64 binary)
739
- if (resource.text) {
740
- resourceObj.content = resource.text;
741
- }
742
- else if (resource.blob) {
743
- // For binary content, keep as base64 or decode based on mimeType
744
- resourceObj.content = typeof resource.blob === 'string'
745
- ? resource.blob
746
- : Buffer.from(resource.blob).toString('base64');
747
- }
748
- resources.push(resourceObj);
749
- }
750
- }
751
- // Build combined content object
752
- let combinedContent;
753
- if (resources.length > 0) {
754
- // Has resources - combine text and resources into single object
755
- combinedContent = {
756
- text: textContents.length === 1 ? textContents[0] : textContents,
757
- resources: resources.length === 1 ? resources[0] : resources
758
- };
759
- if (ENABLE_LOGGING) {
760
- console.debug(`[mcp-proxy] Combined ${textContents.length} text items and ${resources.length} resources`);
761
- }
762
- }
763
- else if (textContents.length > 0) {
764
- // Text only - use first text content (original behavior)
765
- combinedContent = textContents[0];
766
- }
767
- if (combinedContent !== undefined) {
768
- const contentStr = typeof combinedContent === 'string'
769
- ? combinedContent
770
- : JSON.stringify(combinedContent);
771
- const originalLength = contentStr.length;
772
- // If child returned error, pass through directly without file writing
773
- if (childReturnedError) {
774
- const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
775
- if (ENABLE_LOGGING) {
776
- console.debug(`[mcp-proxy] Child MCP returned error for ${toolName}: ${contentStr.substring(0, 100)}...`);
777
- }
778
- return {
779
- content: [{
780
- type: 'text',
781
- text: JSON.stringify(createErrorResponse(tool_id, contentStr, toolArgs), null, 2)
782
- }],
783
- isError: true,
784
- ...(childMeta ? { _meta: childMeta } : {})
785
- };
786
- }
787
- // Get unified response from file writer
788
- const unifiedResponse = await fileWriter.handleResponse(toolName, toolArgs, {
789
- content: [{ type: 'text', text: contentStr }]
790
- });
791
- if (ENABLE_LOGGING) {
792
- if (unifiedResponse.wroteToFile) {
793
- console.debug(`[mcp-proxy] File written for ${toolName} (${originalLength} chars) → ${unifiedResponse.filePath}`);
794
- }
795
- else {
796
- console.debug(`[mcp-proxy] Response for ${toolName} (${originalLength} chars) returned directly`);
797
- }
798
- }
799
- // If not written to file, apply truncation to outputContent
800
- if (!unifiedResponse.wroteToFile && unifiedResponse.outputContent) {
801
- const outputStr = typeof unifiedResponse.outputContent === 'string'
802
- ? unifiedResponse.outputContent
803
- : JSON.stringify(unifiedResponse.outputContent);
804
- const truncated = truncateResponseIfNeeded(truncationConfig, outputStr);
805
- if (truncated.length < outputStr.length) {
806
- if (ENABLE_LOGGING) {
807
- console.debug(`[mcp-proxy] Truncated response: ${outputStr.length} → ${truncated.length} chars`);
808
- }
809
- // Re-parse if it was JSON, otherwise keep as string
810
- try {
811
- unifiedResponse.outputContent = JSON.parse(truncated);
812
- }
813
- catch {
814
- unifiedResponse.outputContent = truncated;
815
- }
816
- }
817
- }
818
- // Add retry metadata and return unified response as JSON
819
- addRetryMetadata(unifiedResponse, toolArgs);
820
- return {
821
- content: [{
822
- type: 'text',
823
- text: JSON.stringify(unifiedResponse, null, 2)
824
- }],
825
- isError: !!unifiedResponse.error,
826
- ...(childMeta ? { _meta: childMeta } : {})
827
- };
828
- }
829
- }
830
- // Fallback: return result with generated tool_id
831
- const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
832
- return {
833
- content: [{
834
- type: 'text',
835
- text: JSON.stringify(createContentResponse(tool_id, result, toolArgs), null, 2)
836
- }],
837
- isError: childReturnedError,
838
- ...(childMeta ? { _meta: childMeta } : {})
839
- };
840
- }
841
- catch (error) {
842
- console.error(`[mcp-proxy] Error executing tool ${toolName}:`, error);
843
- const tool_id = generateToolId(toolName, toolArgs, fileWriterConfig.toolAbbreviations);
844
- return {
845
- content: [{
846
- type: 'text',
847
- text: JSON.stringify(createErrorResponse(tool_id, `Error executing ${toolName}: ${error.message || String(error)}`, toolArgs), null, 2)
848
- }],
849
- isError: true
850
- };
890
+ };
891
+ if (SENTRY_DSN && (SENTRY_TRACE_HEADER || SENTRY_BAGGAGE_HEADER)) {
892
+ const result = await Sentry.continueTrace({ sentryTrace: SENTRY_TRACE_HEADER || '', baggage: SENTRY_BAGGAGE_HEADER }, () => Sentry.startSpan({ name: `${MCP_NAME}/${toolName}`, op: 'mcp.tool_call' }, executeToolCall));
893
+ await Sentry.flush(2000);
894
+ return result;
851
895
  }
896
+ return executeToolCall();
852
897
  });
853
898
  // ------------------------------------------------------------------------
854
899
  // 8. CONNECT PROXY TO STDIO
@@ -857,8 +902,13 @@ async function main() {
857
902
  await server.connect(transport);
858
903
  console.debug('[mcp-proxy] Proxy server ready on stdio');
859
904
  console.debug('[mcp-proxy] Waiting for MCP protocol messages...');
860
- // Prevent EPIPE from crashing the process
861
905
  process.on('SIGPIPE', () => { });
906
+ const shutdown = async () => {
907
+ await Sentry.close(2000);
908
+ process.exit(0);
909
+ };
910
+ process.on('SIGTERM', shutdown);
911
+ process.on('SIGINT', shutdown);
862
912
  }
863
913
  // ============================================================================
864
914
  // START THE PROXY
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyshift/mcp-proxy",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
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",
@@ -16,6 +16,7 @@
16
16
  "@modelcontextprotocol/sdk": "^1.24.0",
17
17
  "glob": "^13.0.0",
18
18
  "papaparse": "^5.5.3",
19
+ "@sentry/node": "^9.0.0",
19
20
  "zod": "^3.24.2"
20
21
  },
21
22
  "devDependencies": {