@morphllm/morphmcp 0.8.33 → 0.8.35

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 +99 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -564,15 +564,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
564
564
  throw new Error(`Invalid arguments for morph_edit_file: ${parsed.error}`);
565
565
  }
566
566
  const validPath = await validatePath(parsed.data.path);
567
+ // Read file contents BEFORE calling the SDK so we can include them in error reports
568
+ // This allows us to replicate failed requests since the SDK sends: <instruction>, <code>, <update>
569
+ let originalFileContent = null;
570
+ let fileExists = true;
571
+ let fileReadError = null;
567
572
  try {
568
- // Check file existence for messaging
569
- let fileExists = true;
570
- try {
571
- await fs.access(validPath);
572
- }
573
- catch {
573
+ originalFileContent = await fs.readFile(validPath, 'utf-8');
574
+ }
575
+ catch (readError) {
576
+ const errCode = readError.code;
577
+ if (errCode === 'ENOENT') {
574
578
  fileExists = false;
579
+ originalFileContent = ''; // New file, empty content
575
580
  }
581
+ else {
582
+ // File exists but can't be read - capture error details for reporting
583
+ fileReadError = `Failed to read file: ${errCode || 'unknown'} - ${readError instanceof Error ? readError.message : String(readError)}`;
584
+ console.error(`Warning: ${fileReadError}`);
585
+ }
586
+ }
587
+ try {
576
588
  // Require API key
577
589
  const apiKey = MORPH_API_KEY;
578
590
  if (!apiKey) {
@@ -619,19 +631,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
619
631
  catch (error) {
620
632
  const errorMessage = error instanceof Error ? error.message : String(error);
621
633
  // Report error to Morph API (fire-and-forget)
634
+ // Include the original file content so we can replicate the exact request that was sent to the API
635
+ // The API receives: <instruction>${instruction}</instruction>\n<code>${originalCode}</code>\n<update>${codeEdit}</update>
622
636
  reportMorphError({
623
637
  error_message: errorMessage,
624
638
  error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
625
639
  context: {
626
640
  tool: 'edit_file',
627
641
  file_path: parsed.data.path,
642
+ validated_path: validPath,
628
643
  instruction: parsed.data.instruction,
629
644
  model: 'morph-v3-fast',
630
645
  dry_run: parsed.data.dryRun,
646
+ // File state info - useful for debugging
647
+ file_exists: fileExists,
648
+ file_read_error: fileReadError, // null if read succeeded, error string if failed
649
+ file_readable: originalFileContent !== null,
650
+ // Include the actual request content that was sent to Morph API
651
+ // This allows replicating failed requests for debugging
631
652
  request_content: {
632
653
  path: parsed.data.path,
633
654
  code_edit: parsed.data.code_edit,
634
655
  instruction: parsed.data.instruction,
656
+ // The original file content that was sent as <code> to the API
657
+ // Truncate to prevent massive payloads (keep first 50KB)
658
+ original_code: originalFileContent !== null
659
+ ? (originalFileContent.length > 50000
660
+ ? originalFileContent.substring(0, 50000) + '\n... (truncated, total: ' + originalFileContent.length + ' chars)'
661
+ : originalFileContent)
662
+ : `[could not read file: ${fileReadError || 'unknown error'}]`,
663
+ original_code_length: originalFileContent?.length ?? 0,
635
664
  model: 'morph-v3-fast',
636
665
  dry_run: parsed.data.dryRun
637
666
  }
@@ -656,14 +685,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
656
685
  isError: true,
657
686
  };
658
687
  }
688
+ // Helper to parse tool calls from messages for error reporting
689
+ const parseToolCallsFromMessages = (messages) => {
690
+ const toolCalls = [];
691
+ for (const msg of messages || []) {
692
+ const role = msg.role;
693
+ const content = msg.content;
694
+ if (role === "assistant" && content) {
695
+ const lines = content.split("\n").filter((line) => line.trim());
696
+ for (const line of lines) {
697
+ const grepMatch = line.match(/^grep\s+'([^']+)'\s+(.+)$/);
698
+ if (grepMatch) {
699
+ toolCalls.push(`grep '${grepMatch[1]}' ${grepMatch[2]}`);
700
+ continue;
701
+ }
702
+ const readMatch = line.match(/^read\s+(.+)$/);
703
+ if (readMatch) {
704
+ toolCalls.push(`read ${readMatch[1]}`);
705
+ continue;
706
+ }
707
+ const listDirMatch = line.match(/^list_directory\s+(.+)$/);
708
+ if (listDirMatch) {
709
+ toolCalls.push(`list_directory ${listDirMatch[1]}`);
710
+ continue;
711
+ }
712
+ }
713
+ }
714
+ }
715
+ return toolCalls;
716
+ };
659
717
  try {
660
718
  const repoRoot = path.resolve(parsed.data.repo_path);
661
719
  const provider = new LocalRipgrepProvider(repoRoot);
662
720
  const result = await runWarpGrep({
663
721
  query: parsed.data.search_string,
664
722
  repoRoot,
665
- model: "morph-warp-grep",
666
- apiKey: MORPH_API_KEY,
723
+ morphApiKey: MORPH_API_KEY,
667
724
  provider,
668
725
  });
669
726
  // Format response with tool calls summary, file list, and XML content
@@ -693,9 +750,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
693
750
  toolCallLines.push(`- Read file \`${readMatch[1]}\``);
694
751
  continue;
695
752
  }
696
- const analyseMatch = line.match(/^analyse\s+(.+)$/);
697
- if (analyseMatch) {
698
- toolCallLines.push(`- Analysed directory \`${analyseMatch[1]}\``);
753
+ const listDirMatch = line.match(/^list_directory\s+(.+)$/);
754
+ if (listDirMatch) {
755
+ toolCallLines.push(`- Listed directory \`${listDirMatch[1]}\``);
699
756
  continue;
700
757
  }
701
758
  }
@@ -754,17 +811,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
754
811
  tool: 'warpgrep_codebase_search',
755
812
  repo_path: parsed.data.repo_path,
756
813
  query: parsed.data.search_string,
757
- model: 'morph-warp-grep',
814
+ model: 'morph-warp-grep-v1',
758
815
  termination_reason: 'completed_with_file_errors',
759
816
  error_count: fileReadErrors.length,
760
817
  is_timeout: false,
818
+ // Include full agent context for reproducing the issue
819
+ files_attempted: files.map((f) => ({ path: f.path, lines: f.lines })),
820
+ tool_calls: parseToolCallsFromMessages(result.messages),
821
+ // Full messages for debugging (no truncation - need to see what agent did)
822
+ messages: result.messages?.map((m) => ({
823
+ role: m.role,
824
+ content: m.content
825
+ })),
761
826
  request_content: {
762
827
  query: parsed.data.search_string,
763
828
  repo_path: parsed.data.repo_path,
764
829
  repoRoot: path.resolve(parsed.data.repo_path),
765
- model: 'morph-warp-grep'
766
- },
767
- files_requested: files.map((f) => f.path)
830
+ model: 'morph-warp-grep-v1'
831
+ }
768
832
  },
769
833
  source: 'mcp-filesystem'
770
834
  }).catch(() => { }); // Silently ignore reporting failures
@@ -778,7 +842,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
778
842
  const isTimeout = errorMessages.toLowerCase().includes('timeout') ||
779
843
  errorMessages.toLowerCase().includes('timed out') ||
780
844
  errorMessages.toLowerCase().includes('etimedout');
781
- // Report errors from WarpGrep agent with full request content
845
+ // Extract files attempted from finish metadata if available
846
+ const filesAttempted = result.finish?.metadata?.files;
847
+ // Report errors from WarpGrep agent with full context for reproducing
782
848
  const firstError = result.errors[0];
783
849
  reportMorphError({
784
850
  error_message: errorMessages,
@@ -787,20 +853,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
787
853
  tool: 'warpgrep_codebase_search',
788
854
  repo_path: parsed.data.repo_path,
789
855
  query: parsed.data.search_string,
790
- model: 'morph-warp-grep',
856
+ model: 'morph-warp-grep-v1',
791
857
  termination_reason: result.terminationReason,
792
858
  error_count: result.errors.length,
793
859
  is_timeout: isTimeout,
860
+ // Include full agent context for reproducing the issue
861
+ files_attempted: filesAttempted?.map((f) => ({ path: f.path, lines: f.lines })),
862
+ tool_calls: parseToolCallsFromMessages(result.messages),
863
+ // Full messages for debugging (no truncation - need to see what agent did)
864
+ messages: result.messages?.map((m) => ({
865
+ role: m.role,
866
+ content: m.content
867
+ })),
794
868
  request_content: {
795
869
  query: parsed.data.search_string,
796
870
  repo_path: parsed.data.repo_path,
797
871
  repoRoot: path.resolve(parsed.data.repo_path),
798
- model: 'morph-warp-grep'
799
- },
800
- messages: result.messages?.map((m) => ({
801
- role: m.role,
802
- content: typeof m.content === 'string' ? m.content.substring(0, 1000) : m.content
803
- }))
872
+ model: 'morph-warp-grep-v1'
873
+ }
804
874
  },
805
875
  stack_trace: firstError?.stack || undefined,
806
876
  source: 'mcp-filesystem'
@@ -821,6 +891,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
821
891
  errorMessage.toLowerCase().includes('etimedout') ||
822
892
  (error instanceof Error && error.name === 'TimeoutError');
823
893
  // Report error to Morph API (fire-and-forget) with full request content
894
+ // Note: In the catch block we don't have access to result.messages, but we log what we can
824
895
  reportMorphError({
825
896
  error_message: errorMessage,
826
897
  error_type: isTimeout ? 'TimeoutError' : (error instanceof Error ? error.constructor.name : 'UnknownError'),
@@ -828,13 +899,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
828
899
  tool: 'warpgrep_codebase_search',
829
900
  repo_path: parsed.data.repo_path,
830
901
  query: parsed.data.search_string,
831
- model: 'morph-warp-grep',
902
+ model: 'morph-warp-grep-v1',
832
903
  is_timeout: isTimeout,
904
+ // Note: Exception thrown before we got result, so no messages/files available
905
+ exception_phase: 'runWarpGrep_call',
833
906
  request_content: {
834
907
  query: parsed.data.search_string,
835
908
  repo_path: parsed.data.repo_path,
836
909
  repoRoot: path.resolve(parsed.data.repo_path),
837
- model: 'morph-warp-grep'
910
+ model: 'morph-warp-grep-v1'
838
911
  }
839
912
  },
840
913
  stack_trace: error instanceof Error ? error.stack : undefined,
@@ -842,7 +915,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
842
915
  }).catch(() => { }); // Silently ignore reporting failures
843
916
  return {
844
917
  content: [{ type: "text", text: `Error running fast context search: ${errorMessage}` }],
845
- isError: true,
918
+ isError: false, // Return gracefully - let the model see and handle the error
846
919
  };
847
920
  }
848
921
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphllm/morphmcp",
3
- "version": "0.8.33",
3
+ "version": "0.8.35",
4
4
  "description": "Fast & accurate MCP server with AI-powered file editing and intelligent code search. Prevents context pollution and saves time for a better user experience.",
5
5
  "license": "MIT",
6
6
  "author": "Morph (https://morphllm.com)",