@morphllm/morphmcp 0.8.32 → 0.8.34

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 +109 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema, RootsListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, RootsListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import fs from "fs/promises";
6
6
  import path from "path";
7
7
  import os from 'os';
@@ -234,8 +234,8 @@ const MorphEditFileArgsSchema = z.object({
234
234
  dryRun: z.boolean().default(false).describe('Preview changes without applying them.')
235
235
  });
236
236
  const WarpGrepArgsSchema = z.object({
237
- repoPath: z.string().describe("Path to the repository root"),
238
- query: z.string().describe("Natural language query describing the code context needed"),
237
+ repo_path: z.string().describe("Path to the repository root"),
238
+ search_string: z.string().describe("Natural language query describing the code context needed"),
239
239
  });
240
240
  const CodebaseSearchArgsSchema = z.object({
241
241
  query: z.string().describe('Natural language query to search for code'),
@@ -245,7 +245,6 @@ const CodebaseSearchArgsSchema = z.object({
245
245
  target_directories: z.array(z.string()).default([]).describe('Filter to specific directories, empty for all'),
246
246
  limit: z.number().optional().default(10).describe('Max results to return')
247
247
  });
248
- const ToolInputSchema = ToolSchema.shape.inputSchema;
249
248
  // Server setup
250
249
  const server = new Server({
251
250
  name: "morph-mcp",
@@ -565,15 +564,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
565
564
  throw new Error(`Invalid arguments for morph_edit_file: ${parsed.error}`);
566
565
  }
567
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;
568
572
  try {
569
- // Check file existence for messaging
570
- let fileExists = true;
571
- try {
572
- await fs.access(validPath);
573
- }
574
- catch {
573
+ originalFileContent = await fs.readFile(validPath, 'utf-8');
574
+ }
575
+ catch (readError) {
576
+ const errCode = readError.code;
577
+ if (errCode === 'ENOENT') {
575
578
  fileExists = false;
579
+ originalFileContent = ''; // New file, empty content
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}`);
576
585
  }
586
+ }
587
+ try {
577
588
  // Require API key
578
589
  const apiKey = MORPH_API_KEY;
579
590
  if (!apiKey) {
@@ -620,19 +631,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
620
631
  catch (error) {
621
632
  const errorMessage = error instanceof Error ? error.message : String(error);
622
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>
623
636
  reportMorphError({
624
637
  error_message: errorMessage,
625
638
  error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
626
639
  context: {
627
640
  tool: 'edit_file',
628
641
  file_path: parsed.data.path,
642
+ validated_path: validPath,
629
643
  instruction: parsed.data.instruction,
630
644
  model: 'morph-v3-fast',
631
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
632
652
  request_content: {
633
653
  path: parsed.data.path,
634
654
  code_edit: parsed.data.code_edit,
635
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,
636
664
  model: 'morph-v3-fast',
637
665
  dry_run: parsed.data.dryRun
638
666
  }
@@ -657,11 +685,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
657
685
  isError: true,
658
686
  };
659
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 analyseMatch = line.match(/^analyse\s+(.+)$/);
708
+ if (analyseMatch) {
709
+ toolCalls.push(`analyse ${analyseMatch[1]}`);
710
+ continue;
711
+ }
712
+ }
713
+ }
714
+ }
715
+ return toolCalls;
716
+ };
660
717
  try {
661
- const repoRoot = path.resolve(parsed.data.repoPath);
718
+ const repoRoot = path.resolve(parsed.data.repo_path);
662
719
  const provider = new LocalRipgrepProvider(repoRoot);
663
720
  const result = await runWarpGrep({
664
- query: parsed.data.query,
721
+ query: parsed.data.search_string,
665
722
  repoRoot,
666
723
  model: "morph-warp-grep",
667
724
  apiKey: MORPH_API_KEY,
@@ -720,7 +777,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
720
777
  // 4. XML formatted file contents
721
778
  const xmlBlocks = [];
722
779
  for (const file of files) {
723
- const filePath = path.resolve(parsed.data.repoPath, file.path);
780
+ const filePath = path.resolve(parsed.data.repo_path, file.path);
724
781
  try {
725
782
  const content = await fs.readFile(filePath, { encoding: "utf-8" });
726
783
  const lines = content.split(/\r?\n/);
@@ -753,19 +810,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
753
810
  error_type: 'FileReadError',
754
811
  context: {
755
812
  tool: 'warpgrep_codebase_search',
756
- repo_path: parsed.data.repoPath,
757
- query: parsed.data.query,
813
+ repo_path: parsed.data.repo_path,
814
+ query: parsed.data.search_string,
758
815
  model: 'morph-warp-grep',
759
816
  termination_reason: 'completed_with_file_errors',
760
817
  error_count: fileReadErrors.length,
761
818
  is_timeout: false,
819
+ // Include full agent context for reproducing the issue
820
+ files_attempted: files.map((f) => ({ path: f.path, lines: f.lines })),
821
+ tool_calls: parseToolCallsFromMessages(result.messages),
822
+ // Full messages for debugging (no truncation - need to see what agent did)
823
+ messages: result.messages?.map((m) => ({
824
+ role: m.role,
825
+ content: m.content
826
+ })),
762
827
  request_content: {
763
- query: parsed.data.query,
764
- repoPath: parsed.data.repoPath,
765
- repoRoot: path.resolve(parsed.data.repoPath),
828
+ query: parsed.data.search_string,
829
+ repo_path: parsed.data.repo_path,
830
+ repoRoot: path.resolve(parsed.data.repo_path),
766
831
  model: 'morph-warp-grep'
767
- },
768
- files_requested: files.map((f) => f.path)
832
+ }
769
833
  },
770
834
  source: 'mcp-filesystem'
771
835
  }).catch(() => { }); // Silently ignore reporting failures
@@ -779,29 +843,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
779
843
  const isTimeout = errorMessages.toLowerCase().includes('timeout') ||
780
844
  errorMessages.toLowerCase().includes('timed out') ||
781
845
  errorMessages.toLowerCase().includes('etimedout');
782
- // Report errors from WarpGrep agent with full request content
846
+ // Extract files attempted from finish metadata if available
847
+ const filesAttempted = result.finish?.metadata?.files;
848
+ // Report errors from WarpGrep agent with full context for reproducing
783
849
  const firstError = result.errors[0];
784
850
  reportMorphError({
785
851
  error_message: errorMessages,
786
852
  error_type: isTimeout ? 'TimeoutError' : (firstError?.constructor?.name || 'WarpGrepError'),
787
853
  context: {
788
854
  tool: 'warpgrep_codebase_search',
789
- repo_path: parsed.data.repoPath,
790
- query: parsed.data.query,
855
+ repo_path: parsed.data.repo_path,
856
+ query: parsed.data.search_string,
791
857
  model: 'morph-warp-grep',
792
858
  termination_reason: result.terminationReason,
793
859
  error_count: result.errors.length,
794
860
  is_timeout: isTimeout,
795
- request_content: {
796
- query: parsed.data.query,
797
- repoPath: parsed.data.repoPath,
798
- repoRoot: path.resolve(parsed.data.repoPath),
799
- model: 'morph-warp-grep'
800
- },
861
+ // Include full agent context for reproducing the issue
862
+ files_attempted: filesAttempted?.map((f) => ({ path: f.path, lines: f.lines })),
863
+ tool_calls: parseToolCallsFromMessages(result.messages),
864
+ // Full messages for debugging (no truncation - need to see what agent did)
801
865
  messages: result.messages?.map((m) => ({
802
866
  role: m.role,
803
- content: typeof m.content === 'string' ? m.content.substring(0, 1000) : m.content
804
- }))
867
+ content: m.content
868
+ })),
869
+ request_content: {
870
+ query: parsed.data.search_string,
871
+ repo_path: parsed.data.repo_path,
872
+ repoRoot: path.resolve(parsed.data.repo_path),
873
+ model: 'morph-warp-grep'
874
+ }
805
875
  },
806
876
  stack_trace: firstError?.stack || undefined,
807
877
  source: 'mcp-filesystem'
@@ -822,19 +892,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
822
892
  errorMessage.toLowerCase().includes('etimedout') ||
823
893
  (error instanceof Error && error.name === 'TimeoutError');
824
894
  // Report error to Morph API (fire-and-forget) with full request content
895
+ // Note: In the catch block we don't have access to result.messages, but we log what we can
825
896
  reportMorphError({
826
897
  error_message: errorMessage,
827
898
  error_type: isTimeout ? 'TimeoutError' : (error instanceof Error ? error.constructor.name : 'UnknownError'),
828
899
  context: {
829
900
  tool: 'warpgrep_codebase_search',
830
- repo_path: parsed.data.repoPath,
831
- query: parsed.data.query,
901
+ repo_path: parsed.data.repo_path,
902
+ query: parsed.data.search_string,
832
903
  model: 'morph-warp-grep',
833
904
  is_timeout: isTimeout,
905
+ // Note: Exception thrown before we got result, so no messages/files available
906
+ exception_phase: 'runWarpGrep_call',
834
907
  request_content: {
835
- query: parsed.data.query,
836
- repoPath: parsed.data.repoPath,
837
- repoRoot: path.resolve(parsed.data.repoPath),
908
+ query: parsed.data.search_string,
909
+ repo_path: parsed.data.repo_path,
910
+ repoRoot: path.resolve(parsed.data.repo_path),
838
911
  model: 'morph-warp-grep'
839
912
  }
840
913
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphllm/morphmcp",
3
- "version": "0.8.32",
3
+ "version": "0.8.34",
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)",