@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.
- package/dist/index.js +109 -36
- 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,
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
757
|
-
query: parsed.data.
|
|
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.
|
|
764
|
-
|
|
765
|
-
repoRoot: path.resolve(parsed.data.
|
|
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
|
-
//
|
|
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.
|
|
790
|
-
query: parsed.data.
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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:
|
|
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.
|
|
831
|
-
query: parsed.data.
|
|
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.
|
|
836
|
-
|
|
837
|
-
repoRoot: path.resolve(parsed.data.
|
|
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.
|
|
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)",
|