@morphllm/morphmcp 0.8.42 → 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 +164 -23
  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);
@@ -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.42",
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",