@morphllm/morphmcp 0.8.41 → 0.8.43

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 +170 -29
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -80,26 +80,21 @@ if (args.length === 0 && !ENABLE_WORKSPACE_MODE) {
80
80
  // =============================================================================
81
81
  const AUTO_UPDATE_INTERVAL_MS = parseInt(process.env.AUTO_UPDATE_INTERVAL_MS || '1800000', 10); // Default: 30 minutes
82
82
  const DISABLE_AUTO_UPDATE = process.env.DISABLE_AUTO_UPDATE === 'true';
83
+ const EXIT_ON_SDK_UPDATE = process.env.EXIT_ON_SDK_UPDATE !== 'false'; // Default: true - exit after SDK update so client reconnects with fresh SDK
83
84
  // Get current installed version from package.json
84
85
  async function getInstalledVersion(packageName) {
86
+ // Get package root (parent of dist/ where package.json and node_modules live)
87
+ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
88
+ const packageRoot = path.resolve(scriptDir, '..');
89
+ const packageJsonPath = path.join(packageRoot, 'node_modules', ...packageName.split('/'), 'package.json');
85
90
  try {
86
- // For @morphllm/morphsdk, check the node_modules
87
- const packageJsonPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'node_modules', ...packageName.split('/'), 'package.json');
88
91
  const content = await fs.readFile(packageJsonPath, 'utf-8');
89
92
  const pkg = JSON.parse(content);
90
93
  return pkg.version || null;
91
94
  }
92
95
  catch {
93
- // Try alternative path (when running from dist)
94
- try {
95
- const altPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'node_modules', ...packageName.split('/'), 'package.json');
96
- const content = await fs.readFile(altPath, 'utf-8');
97
- const pkg = JSON.parse(content);
98
- return pkg.version || null;
99
- }
100
- catch {
101
- return null;
102
- }
96
+ // Package not installed locally - this is fine, we'll fetch latest
97
+ return null;
103
98
  }
104
99
  }
105
100
  // Fetch latest version from npm registry
@@ -132,6 +127,56 @@ async function checkPackageUpdate(packageName) {
132
127
  updateAvailable,
133
128
  };
134
129
  }
130
+ // Clear npm cache for morph packages specifically
131
+ async function clearMorphCache() {
132
+ try {
133
+ const homeDir = os.homedir();
134
+ const npxCacheDir = path.join(homeDir, '.npm', '_npx');
135
+ // Find and remove morph-related npx cache entries
136
+ try {
137
+ const entries = await fs.readdir(npxCacheDir);
138
+ for (const entry of entries) {
139
+ const entryPath = path.join(npxCacheDir, entry, 'node_modules', '@morphllm');
140
+ try {
141
+ await fs.access(entryPath);
142
+ // Found a morph cache entry - remove the entire npx cache folder
143
+ const cacheFolder = path.join(npxCacheDir, entry);
144
+ await fs.rm(cacheFolder, { recursive: true, force: true });
145
+ console.error(`🗑️ Cleared npx cache: ${entry}`);
146
+ }
147
+ catch {
148
+ // Not a morph cache entry, skip
149
+ }
150
+ }
151
+ }
152
+ catch (error) {
153
+ // npx cache dir doesn't exist or can't be read
154
+ if (error.code !== 'ENOENT') {
155
+ reportMorphError({
156
+ error_message: `Failed to read npx cache dir: ${error instanceof Error ? error.message : String(error)}`,
157
+ error_type: 'CacheClearError',
158
+ context: {
159
+ action: 'read_npx_cache_dir',
160
+ npx_cache_dir: npxCacheDir
161
+ },
162
+ source: 'mcp-filesystem-autoupdate'
163
+ }).catch(() => { });
164
+ }
165
+ }
166
+ }
167
+ catch (error) {
168
+ // Report cache clear failures
169
+ reportMorphError({
170
+ error_message: `Cache clear failed: ${error instanceof Error ? error.message : String(error)}`,
171
+ error_type: 'CacheClearError',
172
+ context: {
173
+ action: 'clear_morph_cache'
174
+ },
175
+ stack_trace: error instanceof Error ? error.stack : undefined,
176
+ source: 'mcp-filesystem-autoupdate'
177
+ }).catch(() => { });
178
+ }
179
+ }
135
180
  // Run npm/bun update and wait for completion
136
181
  function runPackageUpdate(packageName) {
137
182
  return new Promise((resolve) => {
@@ -140,9 +185,26 @@ function runPackageUpdate(packageName) {
140
185
  const args = packageManager === 'bun'
141
186
  ? ['add', packageName + '@latest']
142
187
  : ['install', packageName + '@latest'];
143
- const packageDir = path.dirname(new URL(import.meta.url).pathname);
188
+ // Get package root (parent of dist/ where package.json lives)
189
+ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
190
+ const packageRoot = path.resolve(scriptDir, '..');
191
+ console.error(`🔧 Running: ${packageManager} ${args.join(' ')} in ${packageRoot}`);
192
+ // Check if we can write to the package root
193
+ fs.access(packageRoot, 2 /* W_OK */).catch(() => {
194
+ console.error(`⚠️ Warning: Package root may not be writable: ${packageRoot}`);
195
+ reportMorphError({
196
+ error_message: `Package root not writable: ${packageRoot}`,
197
+ error_type: 'AutoUpdatePermissionError',
198
+ context: {
199
+ package_name: packageName,
200
+ package_root: packageRoot,
201
+ action: 'check_write_permission'
202
+ },
203
+ source: 'mcp-filesystem-autoupdate'
204
+ }).catch(() => { });
205
+ });
144
206
  const child = spawn(packageManager, args, {
145
- cwd: packageDir,
207
+ cwd: packageRoot,
146
208
  stdio: 'pipe', // Capture output for debugging
147
209
  env: { ...process.env, npm_config_fund: 'false', npm_config_audit: 'false' }, // Skip fund/audit for speed
148
210
  });
@@ -150,16 +212,59 @@ function runPackageUpdate(packageName) {
150
212
  child.stderr?.on('data', (data) => { stderr += data.toString(); });
151
213
  child.on('error', (err) => {
152
214
  console.error(`Update error: ${err.message}`);
215
+ // Report spawn errors (e.g., npm not found)
216
+ reportMorphError({
217
+ error_message: `Failed to spawn ${packageManager}: ${err.message}`,
218
+ error_type: 'AutoUpdateSpawnError',
219
+ context: {
220
+ package_name: packageName,
221
+ package_manager: packageManager,
222
+ package_root: packageRoot,
223
+ error_code: err.code,
224
+ action: 'spawn_package_manager',
225
+ // Include PATH for debugging
226
+ path_env: process.env.PATH?.split(':').slice(0, 5).join(':') + '...'
227
+ },
228
+ stack_trace: err.stack,
229
+ source: 'mcp-filesystem-autoupdate'
230
+ }).catch(() => { });
153
231
  resolve(false);
154
232
  });
155
233
  child.on('close', (code) => {
156
- if (code !== 0 && stderr) {
157
- console.error(`Update failed: ${stderr.slice(0, 200)}`);
234
+ if (code !== 0) {
235
+ console.error(`Update failed (exit ${code}): ${stderr.slice(0, 300)}`);
236
+ // Report non-zero exit
237
+ reportMorphError({
238
+ error_message: `${packageManager} exited with code ${code}`,
239
+ error_type: 'AutoUpdateExitError',
240
+ context: {
241
+ package_name: packageName,
242
+ package_manager: packageManager,
243
+ package_root: packageRoot,
244
+ exit_code: code,
245
+ stderr: stderr.slice(0, 1000),
246
+ action: 'npm_install'
247
+ },
248
+ source: 'mcp-filesystem-autoupdate'
249
+ }).catch(() => { });
158
250
  }
159
251
  resolve(code === 0);
160
252
  });
161
253
  // Timeout after 60 seconds
162
254
  setTimeout(() => {
255
+ console.error(`Update timed out after 60s`);
256
+ reportMorphError({
257
+ error_message: `${packageManager} install timed out after 60s`,
258
+ error_type: 'AutoUpdateTimeoutError',
259
+ context: {
260
+ package_name: packageName,
261
+ package_manager: packageManager,
262
+ package_root: packageRoot,
263
+ stderr: stderr.slice(0, 1000),
264
+ action: 'npm_install_timeout'
265
+ },
266
+ source: 'mcp-filesystem-autoupdate'
267
+ }).catch(() => { });
163
268
  child.kill();
164
269
  resolve(false);
165
270
  }, 60000);
@@ -167,9 +272,9 @@ function runPackageUpdate(packageName) {
167
272
  }
168
273
  // Main auto-update check function
169
274
  async function checkAndUpdatePackages() {
275
+ // Only check SDK - MCP is updated via npx @morphllm/morphmcp@latest
170
276
  const packagesToCheck = [
171
277
  '@morphllm/morphsdk',
172
- '@morphllm/morphmcp',
173
278
  ];
174
279
  for (const packageName of packagesToCheck) {
175
280
  try {
@@ -224,17 +329,51 @@ async function checkUpdateThenReportError(errorDetails) {
224
329
  }
225
330
  // Start auto-update interval
226
331
  let autoUpdateInterval = null;
332
+ let sdkWasUpdatedAtStartup = false;
333
+ // Blocking startup check - runs before server starts
334
+ async function checkSdkAtStartup() {
335
+ if (DISABLE_AUTO_UPDATE) {
336
+ return;
337
+ }
338
+ try {
339
+ const info = await checkPackageUpdate('@morphllm/morphsdk');
340
+ if (info.updateAvailable) {
341
+ console.error(`⚠️ SDK outdated: ${info.currentVersion} → ${info.latestVersion}`);
342
+ console.error(`📦 Updating SDK...`);
343
+ // Clear morph-related npm/npx cache first
344
+ await clearMorphCache();
345
+ const updateSucceeded = await runPackageUpdate('@morphllm/morphsdk');
346
+ if (updateSucceeded) {
347
+ console.error(`✅ SDK updated to ${info.latestVersion}`);
348
+ sdkWasUpdatedAtStartup = true;
349
+ if (EXIT_ON_SDK_UPDATE) {
350
+ console.error(`🔄 Restarting to use updated SDK...`);
351
+ // Exit with code 0 so MCP client will reconnect automatically
352
+ process.exit(0);
353
+ }
354
+ else {
355
+ console.error(`⚠️ RESTART REQUIRED: The updated SDK will be used on next restart.`);
356
+ }
357
+ }
358
+ else {
359
+ console.error(`❌ SDK update failed - continuing with ${info.currentVersion}`);
360
+ }
361
+ }
362
+ else {
363
+ console.error(`SDK version: ${info.currentVersion} (up to date)`);
364
+ }
365
+ }
366
+ catch (error) {
367
+ console.error(`SDK version check failed: ${error instanceof Error ? error.message : String(error)}`);
368
+ }
369
+ }
227
370
  function startAutoUpdate() {
228
371
  if (DISABLE_AUTO_UPDATE) {
229
372
  console.error('Auto-update disabled via DISABLE_AUTO_UPDATE=true');
230
373
  return;
231
374
  }
232
375
  console.error(`Auto-update enabled: checking every ${Math.round(AUTO_UPDATE_INTERVAL_MS / 60000)} minutes`);
233
- // Check immediately on startup (after a short delay to not block initialization)
234
- setTimeout(() => {
235
- checkAndUpdatePackages().catch(() => { });
236
- }, 5000);
237
- // Then check periodically
376
+ // Periodic checks (startup check is done separately before server starts)
238
377
  autoUpdateInterval = setInterval(() => {
239
378
  checkAndUpdatePackages().catch(() => { });
240
379
  }, AUTO_UPDATE_INTERVAL_MS);
@@ -981,7 +1120,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
981
1120
  tool: 'warpgrep_codebase_search',
982
1121
  repo_path: parsed.data.repo_path,
983
1122
  query: parsed.data.search_string,
984
- model: 'morph-warp-grep-v1-1111',
1123
+ model: 'morph-warp-grep-v1-1111v0',
985
1124
  termination_reason: 'completed_with_file_errors',
986
1125
  error_count: fileReadErrors.length,
987
1126
  is_timeout: false,
@@ -997,7 +1136,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
997
1136
  query: parsed.data.search_string,
998
1137
  repo_path: parsed.data.repo_path,
999
1138
  repoRoot: path.resolve(parsed.data.repo_path),
1000
- model: 'morph-warp-grep-v1-1111'
1139
+ model: 'morph-warp-grep-v1-1111v0'
1001
1140
  }
1002
1141
  },
1003
1142
  source: 'mcp-filesystem'
@@ -1023,7 +1162,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1023
1162
  tool: 'warpgrep_codebase_search',
1024
1163
  repo_path: parsed.data.repo_path,
1025
1164
  query: parsed.data.search_string,
1026
- model: 'morph-warp-grep-v1-1111',
1165
+ model: 'morph-warp-grep-v1-1111v0',
1027
1166
  termination_reason: result.terminationReason,
1028
1167
  error_count: result.errors.length,
1029
1168
  is_timeout: isTimeout,
@@ -1039,7 +1178,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1039
1178
  query: parsed.data.search_string,
1040
1179
  repo_path: parsed.data.repo_path,
1041
1180
  repoRoot: path.resolve(parsed.data.repo_path),
1042
- model: 'morph-warp-grep-v1-1111'
1181
+ model: 'morph-warp-grep-v1-1111v0'
1043
1182
  }
1044
1183
  },
1045
1184
  stack_trace: firstError?.stack || undefined,
@@ -1069,7 +1208,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1069
1208
  tool: 'warpgrep_codebase_search',
1070
1209
  repo_path: parsed.data.repo_path,
1071
1210
  query: parsed.data.search_string,
1072
- model: 'morph-warp-grep-v1-1111',
1211
+ model: 'morph-warp-grep-v1-1111v0',
1073
1212
  is_timeout: isTimeout,
1074
1213
  // Note: Exception thrown before we got result, so no messages/files available
1075
1214
  exception_phase: 'runWarpGrep_call',
@@ -1077,7 +1216,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1077
1216
  query: parsed.data.search_string,
1078
1217
  repo_path: parsed.data.repo_path,
1079
1218
  repoRoot: path.resolve(parsed.data.repo_path),
1080
- model: 'morph-warp-grep-v1-1111'
1219
+ model: 'morph-warp-grep-v1-1111v0'
1081
1220
  }
1082
1221
  },
1083
1222
  stack_trace: error instanceof Error ? error.stack : undefined,
@@ -1241,13 +1380,15 @@ server.oninitialized = async () => {
1241
1380
  };
1242
1381
  // Start server
1243
1382
  async function runServer() {
1383
+ // BLOCKING: Check and update SDK before starting server
1384
+ await checkSdkAtStartup();
1244
1385
  const transport = new StdioServerTransport();
1245
1386
  await server.connect(transport);
1246
1387
  console.error("Secure MCP Filesystem Server running on stdio");
1247
1388
  if (allowedDirectories.length === 0) {
1248
1389
  console.error("Started without allowed directories - waiting for client to provide roots via MCP protocol");
1249
1390
  }
1250
- // Start auto-update system
1391
+ // Start periodic auto-update system
1251
1392
  startAutoUpdate();
1252
1393
  }
1253
1394
  runServer().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphllm/morphmcp",
3
- "version": "0.8.41",
3
+ "version": "0.8.43",
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)",
@@ -29,14 +29,14 @@
29
29
  "build:obfuscated": "npm run build && npm run obfuscate",
30
30
  "obfuscate": "npx javascript-obfuscator dist --output dist --target node --compact true --string-array true --exclude '**/dist/__tests__/**'",
31
31
  "prepare": "npm run build",
32
- "postinstall": "npm install @morphllm/morphsdk@latest --no-save --silent 2>/dev/null || true",
32
+ "postinstall": "echo '📦 Updating Morph SDK...' && npm install @morphllm/morphsdk@latest --no-save && echo '✅ SDK updated'",
33
33
  "watch": "tsc --watch",
34
34
  "test": "jest --config=jest.config.cjs --coverage"
35
35
  },
36
36
  "dependencies": {
37
37
  "@google/generative-ai": "^0.21.0",
38
38
  "@modelcontextprotocol/sdk": "^1.12.3",
39
- "@morphllm/morphsdk": "latest",
39
+ "@morphllm/morphsdk": "^0.2.58",
40
40
  "@vscode/ripgrep": "^1.15.14",
41
41
  "axios": "^1.6.0",
42
42
  "chalk": "^5.3.0",