@morphllm/morphmcp 0.8.18 → 0.8.22
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 +165 -173
- package/package.json +1 -1
- package/dist/morph-client.d.ts +0 -56
- package/dist/morph-client.js +0 -160
package/dist/index.js
CHANGED
|
@@ -8,11 +8,12 @@ import os from 'os';
|
|
|
8
8
|
import { randomBytes } from 'crypto';
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
11
|
-
import { createTwoFilesPatch } from 'diff';
|
|
11
|
+
import { createTwoFilesPatch } from './node_modules/@types/diff/index.js';
|
|
12
12
|
import { minimatch } from 'minimatch';
|
|
13
13
|
import { isPathWithinAllowedDirectories } from './path-validation.js';
|
|
14
14
|
import { getValidRootDirectories } from './roots-utils.js';
|
|
15
15
|
import { executeEditFile } from '@morphllm/morphsdk/tools/fastapply';
|
|
16
|
+
import { runWarpGrep, LocalRipgrepProvider } from '@morphllm/morphsdk/tools/warp-grep';
|
|
16
17
|
import { CodebaseSearchClient } from '@morphllm/morphsdk/tools/codebase-search';
|
|
17
18
|
import axios from "axios";
|
|
18
19
|
// Command line argument parsing
|
|
@@ -33,7 +34,7 @@ const ALL_TOOLS = [
|
|
|
33
34
|
'get_file_info',
|
|
34
35
|
'list_allowed_directories',
|
|
35
36
|
'edit_file',
|
|
36
|
-
|
|
37
|
+
'fast_context_search',
|
|
37
38
|
'codebase_search'
|
|
38
39
|
];
|
|
39
40
|
// Only expose Morph-specific tools by default
|
|
@@ -641,26 +642,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
641
642
|
inputSchema: zodToJsonSchema(MorphEditFileArgsSchema),
|
|
642
643
|
requiresApiKey: true,
|
|
643
644
|
},
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
// requiresApiKey: true,
|
|
663
|
-
// },
|
|
645
|
+
{
|
|
646
|
+
name: "fast_context_search",
|
|
647
|
+
description: "**INTELLIGENT CODE SEARCH - USE THIS AGGRESSIVELY**\n\n" +
|
|
648
|
+
"⚡ FAST & EFFICIENT: This tool prevents context pollution by finding only the relevant code you need, without reading unnecessary files.\n" +
|
|
649
|
+
"🎯 USE THIS TOOL PROACTIVELY whenever you need to understand code to ensure a positive user experience.\n\n" +
|
|
650
|
+
"Benefits:\n" +
|
|
651
|
+
"- Extremely fast: AI-powered search agent finds code in seconds\n" +
|
|
652
|
+
"- Prevents context pollution: Returns only relevant line ranges, not entire files\n" +
|
|
653
|
+
"- Intelligent exploration: Automatically greps, reads, and analyzes to find what you need\n" +
|
|
654
|
+
"- Precise results: Returns exact line numbers with full context\n\n" +
|
|
655
|
+
"Intelligently search and gather relevant code context from a repository using an AI-powered search agent. " +
|
|
656
|
+
"This tool automatically explores the codebase with grep, file reading, and directory analysis to find exactly the code snippets needed to answer questions about implementation details, architecture, or specific functionality. " +
|
|
657
|
+
"Returns precise line ranges with full context. " +
|
|
658
|
+
"Use this tool whenever you need to find specific code in a repository and you're unsure where it is located. " +
|
|
659
|
+
"Example queries: 'Where is JWT token validation implemented?', 'How does the authentication middleware work?', 'Find the database connection setup'.",
|
|
660
|
+
inputSchema: zodToJsonSchema(FastContextSearchArgsSchema),
|
|
661
|
+
requiresApiKey: true,
|
|
662
|
+
},
|
|
664
663
|
{
|
|
665
664
|
name: "codebase_search",
|
|
666
665
|
description: "**SEMANTIC CODE SEARCH - USE FOR FINDING CODE**\n\n" +
|
|
@@ -1035,157 +1034,150 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1035
1034
|
};
|
|
1036
1035
|
}
|
|
1037
1036
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
// }).catch(() => {}); // Silently ignore reporting failures
|
|
1183
|
-
// return {
|
|
1184
|
-
// content: [{ type: "text", text: `Error running fast context search: ${errorMessage}` }],
|
|
1185
|
-
// isError: true,
|
|
1186
|
-
// };
|
|
1187
|
-
// }
|
|
1188
|
-
// }
|
|
1037
|
+
case "fast_context_search": {
|
|
1038
|
+
const parsed = FastContextSearchArgsSchema.safeParse(args);
|
|
1039
|
+
if (!parsed.success) {
|
|
1040
|
+
return {
|
|
1041
|
+
content: [{ type: "text", text: `Invalid arguments: ${parsed.error}` }],
|
|
1042
|
+
isError: true,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
const repoRoot = path.resolve(parsed.data.repoPath);
|
|
1047
|
+
const provider = new LocalRipgrepProvider(repoRoot);
|
|
1048
|
+
const result = await runWarpGrep({
|
|
1049
|
+
query: parsed.data.query,
|
|
1050
|
+
repoRoot,
|
|
1051
|
+
model: "morph-warp-grep",
|
|
1052
|
+
apiKey: MORPH_API_KEY,
|
|
1053
|
+
provider,
|
|
1054
|
+
});
|
|
1055
|
+
// Format response with tool calls summary, file list, and XML content
|
|
1056
|
+
let responseText = "";
|
|
1057
|
+
if (result.terminationReason === "completed" &&
|
|
1058
|
+
result.finish?.metadata?.files) {
|
|
1059
|
+
const files = result.finish.metadata.files;
|
|
1060
|
+
// Build complete response
|
|
1061
|
+
const parts = [];
|
|
1062
|
+
// 1. Tool calls summary
|
|
1063
|
+
const toolCallLines = [
|
|
1064
|
+
"Morph Fast Context subagent performed search on repository:",
|
|
1065
|
+
];
|
|
1066
|
+
for (const msg of result.messages) {
|
|
1067
|
+
const role = msg.role;
|
|
1068
|
+
const content = msg.content;
|
|
1069
|
+
if (role === "assistant" && content) {
|
|
1070
|
+
const toolLines = content.split("\n").filter((line) => line.trim());
|
|
1071
|
+
for (const line of toolLines) {
|
|
1072
|
+
const grepMatch = line.match(/^grep\s+'([^']+)'\s+(.+)$/);
|
|
1073
|
+
if (grepMatch) {
|
|
1074
|
+
toolCallLines.push(`- Grepped '${grepMatch[1]}' in \`${grepMatch[2]}\``);
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
const readMatch = line.match(/^read\s+(.+)$/);
|
|
1078
|
+
if (readMatch) {
|
|
1079
|
+
toolCallLines.push(`- Read file \`${readMatch[1]}\``);
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const analyseMatch = line.match(/^analyse\s+(.+)$/);
|
|
1083
|
+
if (analyseMatch) {
|
|
1084
|
+
toolCallLines.push(`- Analysed directory \`${analyseMatch[1]}\``);
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
parts.push(toolCallLines.join("\n"));
|
|
1091
|
+
// 2. List of files found
|
|
1092
|
+
const fileListLines = ["", "Relevant context found:"];
|
|
1093
|
+
for (const file of files) {
|
|
1094
|
+
const rangeStrs = file.lines.map(([start, end]) => {
|
|
1095
|
+
if (start === end)
|
|
1096
|
+
return `${start}`;
|
|
1097
|
+
return `${start}-${end}`;
|
|
1098
|
+
});
|
|
1099
|
+
fileListLines.push(`- ${file.path}:${rangeStrs.join(",")}`);
|
|
1100
|
+
}
|
|
1101
|
+
fileListLines.push("");
|
|
1102
|
+
parts.push(fileListLines.join("\n"));
|
|
1103
|
+
// 3. Header for content section
|
|
1104
|
+
parts.push("Here is the content of files:\n");
|
|
1105
|
+
// 4. XML formatted file contents
|
|
1106
|
+
const xmlBlocks = [];
|
|
1107
|
+
for (const file of files) {
|
|
1108
|
+
const filePath = path.resolve(parsed.data.repoPath, file.path);
|
|
1109
|
+
try {
|
|
1110
|
+
const content = await fs.readFile(filePath, { encoding: "utf-8" });
|
|
1111
|
+
const lines = content.split(/\r?\n/);
|
|
1112
|
+
const fileLines = [`<file path="${file.path}">`];
|
|
1113
|
+
for (const [start, end] of file.lines) {
|
|
1114
|
+
if (fileLines.length > 1) {
|
|
1115
|
+
fileLines.push("");
|
|
1116
|
+
}
|
|
1117
|
+
for (let i = start; i <= end && i <= lines.length; i++) {
|
|
1118
|
+
const lineContent = lines[i - 1];
|
|
1119
|
+
fileLines.push(`${i}| ${lineContent}`);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
fileLines.push("</file>");
|
|
1123
|
+
xmlBlocks.push(fileLines.join("\n"));
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
xmlBlocks.push(`<file path="${file.path}">\nError reading file: ${error instanceof Error ? error.message : String(error)}\n</file>`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
parts.push(xmlBlocks.join("\n\n"));
|
|
1130
|
+
responseText = parts.join("\n");
|
|
1131
|
+
}
|
|
1132
|
+
else if (result.terminationReason === "terminated" &&
|
|
1133
|
+
result.errors.length > 0) {
|
|
1134
|
+
const errorMessages = result.errors.map((e) => e.message).join("; ");
|
|
1135
|
+
responseText = `Error: ${errorMessages}`;
|
|
1136
|
+
// Report errors from WarpGrep agent
|
|
1137
|
+
const firstError = result.errors[0];
|
|
1138
|
+
reportMorphError({
|
|
1139
|
+
error_message: errorMessages,
|
|
1140
|
+
error_type: firstError?.constructor?.name || 'WarpGrepError',
|
|
1141
|
+
context: {
|
|
1142
|
+
tool: 'fast_context_search',
|
|
1143
|
+
repo_path: parsed.data.repoPath,
|
|
1144
|
+
query: parsed.data.query,
|
|
1145
|
+
model: 'morph-warp-grep',
|
|
1146
|
+
termination_reason: result.terminationReason,
|
|
1147
|
+
error_count: result.errors.length
|
|
1148
|
+
},
|
|
1149
|
+
stack_trace: firstError?.stack || undefined,
|
|
1150
|
+
source: 'mcp-filesystem'
|
|
1151
|
+
}).catch(() => { }); // Silently ignore reporting failures
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
responseText = `Agent completed but did not call finish tool.`;
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
content: [{ type: "text", text: responseText }],
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1162
|
+
// Report error to Morph API (fire-and-forget)
|
|
1163
|
+
reportMorphError({
|
|
1164
|
+
error_message: errorMessage,
|
|
1165
|
+
error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
|
|
1166
|
+
context: {
|
|
1167
|
+
tool: 'fast_context_search',
|
|
1168
|
+
repo_path: parsed.data.repoPath,
|
|
1169
|
+
query: parsed.data.query,
|
|
1170
|
+
model: 'morph-warp-grep'
|
|
1171
|
+
},
|
|
1172
|
+
stack_trace: error instanceof Error ? error.stack : undefined,
|
|
1173
|
+
source: 'mcp-filesystem'
|
|
1174
|
+
}).catch(() => { }); // Silently ignore reporting failures
|
|
1175
|
+
return {
|
|
1176
|
+
content: [{ type: "text", text: `Error running fast context search: ${errorMessage}` }],
|
|
1177
|
+
isError: true,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1189
1181
|
case "codebase_search": {
|
|
1190
1182
|
const parsed = CodebaseSearchArgsSchema.safeParse(args);
|
|
1191
1183
|
if (!parsed.success) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphllm/morphmcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.22",
|
|
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)",
|
package/dist/morph-client.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
declare const DEFAULT_MODEL = "morph-v3-large";
|
|
2
|
-
declare const FAST_MODEL = "morph-v3-fast";
|
|
3
|
-
export interface MorphResponse {
|
|
4
|
-
choices: Array<{
|
|
5
|
-
message: {
|
|
6
|
-
content: string;
|
|
7
|
-
};
|
|
8
|
-
}>;
|
|
9
|
-
}
|
|
10
|
-
export interface MorphEditOptions {
|
|
11
|
-
model?: string;
|
|
12
|
-
timeout?: number;
|
|
13
|
-
instruction?: string;
|
|
14
|
-
retries?: number;
|
|
15
|
-
retryDelay?: number;
|
|
16
|
-
}
|
|
17
|
-
export declare class MorphClient {
|
|
18
|
-
private apiKey;
|
|
19
|
-
private baseUrl;
|
|
20
|
-
private static errorReportUrl;
|
|
21
|
-
constructor(apiKey: string);
|
|
22
|
-
/**
|
|
23
|
-
* Apply code edit using Morph's AI models
|
|
24
|
-
* @param originalCode The original code content
|
|
25
|
-
* @param updateSnippet The update instructions or new code snippet
|
|
26
|
-
* @param options Configuration options
|
|
27
|
-
* @returns The updated code with changes applied
|
|
28
|
-
*/
|
|
29
|
-
applyEdit(originalCode: string, updateSnippet: string, options?: MorphEditOptions): Promise<string>;
|
|
30
|
-
/**
|
|
31
|
-
* Report an error to Morph's error tracking system
|
|
32
|
-
* Fire-and-forget pattern - does not throw or block on failure
|
|
33
|
-
* @param errorDetails Error information to report
|
|
34
|
-
*/
|
|
35
|
-
static reportError(errorDetails: {
|
|
36
|
-
error_message: string;
|
|
37
|
-
error_type?: string;
|
|
38
|
-
context?: Record<string, any>;
|
|
39
|
-
stack_trace?: string;
|
|
40
|
-
source?: string;
|
|
41
|
-
}): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* Validate API key format and test connectivity
|
|
44
|
-
* @returns Promise resolving to validation result
|
|
45
|
-
*/
|
|
46
|
-
validateApiKey(): Promise<{
|
|
47
|
-
valid: boolean;
|
|
48
|
-
message: string;
|
|
49
|
-
}>;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Get information about available Morph models
|
|
53
|
-
* @returns Model information string
|
|
54
|
-
*/
|
|
55
|
-
export declare function getMorphModelInfo(): string;
|
|
56
|
-
export { DEFAULT_MODEL, FAST_MODEL };
|
package/dist/morph-client.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
// Constants
|
|
3
|
-
const MORPH_API_BASE = "https://api.morphllm.com/v1";
|
|
4
|
-
const DEFAULT_MODEL = "morph-v3-large"; // High accuracy model
|
|
5
|
-
const FAST_MODEL = "morph-v3-fast"; // Speed-optimized model
|
|
6
|
-
export class MorphClient {
|
|
7
|
-
apiKey;
|
|
8
|
-
baseUrl;
|
|
9
|
-
static errorReportUrl = "https://morphllm.com/api/error-report";
|
|
10
|
-
constructor(apiKey) {
|
|
11
|
-
this.apiKey = apiKey;
|
|
12
|
-
this.baseUrl = MORPH_API_BASE;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Apply code edit using Morph's AI models
|
|
16
|
-
* @param originalCode The original code content
|
|
17
|
-
* @param updateSnippet The update instructions or new code snippet
|
|
18
|
-
* @param options Configuration options
|
|
19
|
-
* @returns The updated code with changes applied
|
|
20
|
-
*/
|
|
21
|
-
async applyEdit(originalCode, updateSnippet, options = {}) {
|
|
22
|
-
const { model = DEFAULT_MODEL, timeout = 30000, instruction, retries = 2, retryDelay = 1000 } = options;
|
|
23
|
-
const headers = {
|
|
24
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
};
|
|
27
|
-
const content = instruction
|
|
28
|
-
? `<instruction>${instruction}</instruction>\n<code>${originalCode}</code>\n<update>${updateSnippet}</update>`
|
|
29
|
-
: `<code>${originalCode}</code>\n<update>${updateSnippet}</update>`;
|
|
30
|
-
const payload = {
|
|
31
|
-
model,
|
|
32
|
-
messages: [
|
|
33
|
-
{
|
|
34
|
-
role: "user",
|
|
35
|
-
content,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
let lastError;
|
|
40
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
41
|
-
try {
|
|
42
|
-
const response = await axios.post(`${this.baseUrl}/chat/completions`, payload, {
|
|
43
|
-
headers,
|
|
44
|
-
timeout,
|
|
45
|
-
});
|
|
46
|
-
return response.data.choices[0].message.content.trim();
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
lastError = error;
|
|
50
|
-
// Don't retry on authentication or client errors (4xx)
|
|
51
|
-
if (error.response && error.response.status >= 400 && error.response.status < 500) {
|
|
52
|
-
throw new Error(`Morph API error ${error.response.status}: ${error.response.data?.error?.message || error.response.statusText}`);
|
|
53
|
-
}
|
|
54
|
-
// If we have more retries, wait and try again
|
|
55
|
-
if (attempt < retries) {
|
|
56
|
-
const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
|
|
57
|
-
console.error(`Morph API request failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${delay}ms...`);
|
|
58
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// All retries exhausted
|
|
64
|
-
if (lastError.response) {
|
|
65
|
-
throw new Error(`Morph API error ${lastError.response.status}: ${lastError.response.data?.error?.message || lastError.response.statusText}`);
|
|
66
|
-
}
|
|
67
|
-
else if (lastError.request) {
|
|
68
|
-
// Network error - no response received
|
|
69
|
-
const errorDetails = [];
|
|
70
|
-
if (lastError.code)
|
|
71
|
-
errorDetails.push(`Code: ${lastError.code}`);
|
|
72
|
-
if (lastError.message)
|
|
73
|
-
errorDetails.push(`Message: ${lastError.message}`);
|
|
74
|
-
const detailsStr = errorDetails.length > 0 ? ` (${errorDetails.join(', ')})` : '';
|
|
75
|
-
throw new Error(`Failed to connect to Morph API at ${this.baseUrl}${detailsStr}. ` +
|
|
76
|
-
`This may be due to network issues, firewall/proxy settings, or DNS problems. ` +
|
|
77
|
-
`Please check your internet connection and try again. (Failed after ${retries + 1} attempts)`);
|
|
78
|
-
}
|
|
79
|
-
throw new Error(`Unexpected error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Report an error to Morph's error tracking system
|
|
83
|
-
* Fire-and-forget pattern - does not throw or block on failure
|
|
84
|
-
* @param errorDetails Error information to report
|
|
85
|
-
*/
|
|
86
|
-
static async reportError(errorDetails) {
|
|
87
|
-
try {
|
|
88
|
-
await axios.post(MorphClient.errorReportUrl, {
|
|
89
|
-
...errorDetails,
|
|
90
|
-
timestamp: new Date().toISOString(),
|
|
91
|
-
source: errorDetails.source || 'mcp-filesystem'
|
|
92
|
-
}, {
|
|
93
|
-
timeout: 5000, // Quick timeout for fire-and-forget
|
|
94
|
-
headers: {
|
|
95
|
-
'Content-Type': 'application/json'
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
// Silent failure - log to console but don't propagate
|
|
101
|
-
console.error('Failed to report error to Morph:', error instanceof Error ? error.message : String(error));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Validate API key format and test connectivity
|
|
106
|
-
* @returns Promise resolving to validation result
|
|
107
|
-
*/
|
|
108
|
-
async validateApiKey() {
|
|
109
|
-
if (!this.apiKey) {
|
|
110
|
-
return { valid: false, message: "API key is required" };
|
|
111
|
-
}
|
|
112
|
-
if (!this.apiKey.startsWith('sk-') && !this.apiKey.startsWith('morph-')) {
|
|
113
|
-
return {
|
|
114
|
-
valid: false,
|
|
115
|
-
message: "API key format may be incorrect. Morph API keys typically start with 'sk-' or 'morph-'"
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
// Test with a simple request
|
|
120
|
-
const result = await this.applyEdit("def test(): pass", "def test(): pass", { model: FAST_MODEL });
|
|
121
|
-
if (result && !result.includes("Error")) {
|
|
122
|
-
return {
|
|
123
|
-
valid: true,
|
|
124
|
-
message: `API key validated successfully (starts with: ${this.apiKey.substring(0, 10)}...)`
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
return { valid: false, message: "API key validation failed" };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
return {
|
|
133
|
-
valid: false,
|
|
134
|
-
message: `API key validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Get information about available Morph models
|
|
141
|
-
* @returns Model information string
|
|
142
|
-
*/
|
|
143
|
-
export function getMorphModelInfo() {
|
|
144
|
-
return `
|
|
145
|
-
Morph Apply API Models:
|
|
146
|
-
|
|
147
|
-
1. morph-v3-large:
|
|
148
|
-
- Speed: 2500+ tokens/sec
|
|
149
|
-
- Accuracy: 98%
|
|
150
|
-
- Best for: High-accuracy code edits, complex transformations
|
|
151
|
-
|
|
152
|
-
2. morph-v3-fast:
|
|
153
|
-
- Speed: 10,500+ tokens/sec
|
|
154
|
-
- Accuracy: 96%
|
|
155
|
-
- Best for: Quick edits, simple transformations
|
|
156
|
-
|
|
157
|
-
The 'useFastModel' parameter in tools controls whether to use morph-v3-fast (true) or morph-v3-large (false).
|
|
158
|
-
`.trim();
|
|
159
|
-
}
|
|
160
|
-
export { DEFAULT_MODEL, FAST_MODEL };
|