local-mcp 3.0.137 → 3.0.138

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 (3) hide show
  1. package/index.js +14 -0
  2. package/package.json +1 -1
  3. package/setup.js +73 -4
package/index.js CHANGED
@@ -24,6 +24,20 @@ if (process.platform !== 'darwin') {
24
24
  process.exit(1)
25
25
  }
26
26
 
27
+ // Node version guard — Claude Desktop can silently launch LMCP with an old
28
+ // nvm default (e.g. v11) that makes `npx -y` fail before this file loads.
29
+ // If this code IS running on an old Node, surface the error clearly so the
30
+ // user sees it in Claude Desktop's MCP logs instead of a cryptic spawn error.
31
+ if (parseInt(process.version.slice(1)) < 16) {
32
+ const msg = `LMCP requires Node 16+. Running ${process.version}.\n` +
33
+ 'If you use nvm, run: nvm alias default 22\n' +
34
+ 'Then restart Claude Desktop / Cursor.'
35
+ process.stderr.write(msg + '\n')
36
+ // Exit with a non-zero code so the MCP host marks the server as failed
37
+ // (vs hanging forever with no output).
38
+ process.exit(1)
39
+ }
40
+
27
41
  const cmd = process.argv[2]
28
42
 
29
43
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.137",
3
+ "version": "3.0.138",
4
4
  "description": "LMCP — connect Claude Desktop, Cursor, Windsurf to Mail, Calendar, Contacts, Teams, OneDrive on macOS. Privacy-first: all data stays on your Mac.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -16,6 +16,49 @@ const { execSync, execFileSync } = require('child_process')
16
16
  const HOME = os.homedir()
17
17
  const NPX_COMMAND = 'npx'
18
18
  const NPX_ARGS = ['-y', 'local-mcp@latest']
19
+
20
+ // Resolve absolute path to npx from the current Node binary so Claude Desktop
21
+ // doesn't pick up an old npx from its own PATH (common with nvm users whose
22
+ // nvm default points to Node v11/v12 — those npx versions don't support -y).
23
+ // Falls back to plain 'npx' if resolution fails (non-nvm setups, symlinks, etc.)
24
+ function _resolveNpxPath() {
25
+ try {
26
+ const candidate = path.join(path.dirname(process.execPath), 'npx')
27
+ if (fs.existsSync(candidate)) return candidate
28
+ } catch {}
29
+ return NPX_COMMAND
30
+ }
31
+
32
+ // Write a shell launcher script that wraps npx with a Node version check and
33
+ // curl-based telemetry. This gives us visibility into failures that happen
34
+ // BEFORE Node loads our code (e.g. old nvm default in Claude Desktop's PATH).
35
+ // Returns the path to the launcher, or null if writing fails.
36
+ function _writeLaunchScript(npxAbsPath, cacheDir) {
37
+ try {
38
+ fs.mkdirSync(cacheDir, { recursive: true })
39
+ const launcherPath = path.join(cacheDir, 'lmcp-launch.sh')
40
+ const script = [
41
+ '#!/bin/bash',
42
+ '# LMCP launcher — generated by npx local-mcp setup, do not edit.',
43
+ '# Checks Node version before launching npx; sends telemetry if too old.',
44
+ 'NODE_VER=$(node --version 2>/dev/null || echo "none")',
45
+ 'NODE_MAJOR=$(echo "$NODE_VER" | sed \'s/v//\' | cut -d. -f1)',
46
+ 'if [ "${NODE_MAJOR:-0}" -lt 16 ] 2>/dev/null; then',
47
+ ' # curl telemetry — no Node dependency, fire-and-forget',
48
+ ` curl -sf --max-time 3 -X POST "https://${BACKEND_HOST}/install-event" \\`,
49
+ ' -H "Content-Type: application/json" \\',
50
+ ' -d "{\\\"stage\\\":\\\"node_too_old\\\",\\\"version\\\":\\\"$NODE_VER\\\"}" &>/dev/null &',
51
+ ' printf \'LMCP requires Node 16+. Current: %s\\nFix: nvm alias default 22 — then restart Claude Desktop.\\n\' "$NODE_VER" >&2',
52
+ ' exit 1',
53
+ 'fi',
54
+ `exec "${npxAbsPath}" -y local-mcp@latest "$@"`,
55
+ ].join('\n') + '\n'
56
+ fs.writeFileSync(launcherPath, script, { mode: 0o755 })
57
+ return launcherPath
58
+ } catch {
59
+ return null
60
+ }
61
+ }
19
62
  const STABLE_LINK = path.join(os.homedir(), '.local', 'share', 'local-mcp', 'bin', 'local-mcp-server')
20
63
  const BACKEND_HOST = 'office-mcp-production.up.railway.app'
21
64
 
@@ -212,9 +255,21 @@ async function runSetup(opts = {}) {
212
255
  console.log('║ LMCP — Setup Wizard ║')
213
256
  console.log('╚══════════════════════════════════════╝\n')
214
257
 
258
+ // Warn if running on an old Node version. Claude Desktop resolves 'npx' from
259
+ // its own PATH (not the user's shell), so an old nvm default (v11/v12) causes
260
+ // a silent "You must supply a command" failure on first launch.
261
+ const nodeMajor = parseInt(process.version.slice(1))
262
+ if (nodeMajor < 16) {
263
+ console.error(`\n⚠️ Warning: you're running Node ${process.version}.`)
264
+ console.error(' Claude Desktop may fail to start LMCP because it finds an old npx on its PATH.')
265
+ console.error(' Fix: nvm alias default 22 (then relaunch Claude Desktop)\n')
266
+ }
267
+
215
268
  // Pre-download binary so first launch is instant
216
- // All clients use npx as launcher (self-healing if binary is missing/broken)
217
- let stableCommand = NPX_COMMAND
269
+ // All clients use a shell launcher that wraps npx (self-healing if binary is
270
+ // missing/broken, and surfaces telemetry + clear errors if Node is too old).
271
+ let npxAbsPath = _resolveNpxPath() // baked into the launcher script
272
+ let stableCommand = NPX_COMMAND // fallback; replaced below once CACHE_DIR is known
218
273
  let stableArgs = NPX_ARGS
219
274
  let binaryVersion = ''
220
275
  try {
@@ -252,11 +307,25 @@ async function runSetup(opts = {}) {
252
307
  if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
253
308
  } catch {}
254
309
  process.stderr.write('✓ Runtime ready\n\n')
310
+ // Write the launcher script now that we have CACHE_DIR.
311
+ // The launcher uses curl (no Node dependency) to send telemetry if Node is
312
+ // too old, then exec's npx. Claude Desktop runs this script instead of npx
313
+ // directly, so failures are visible in MCP logs AND in our backend events.
314
+ const launcherPath = _writeLaunchScript(npxAbsPath, CACHE_DIR)
315
+ if (launcherPath) stableCommand = launcherPath
255
316
  } catch (err) {
256
317
  process.stderr.write(` (Runtime download failed, will download on first run: ${err.message})\n\n`)
318
+ // Still try to write a launcher even if binary download failed — the version
319
+ // check + telemetry path is independent of the binary.
320
+ try {
321
+ const { CACHE_DIR } = require('./download')
322
+ const launcherPath = _writeLaunchScript(npxAbsPath, CACHE_DIR)
323
+ if (launcherPath) stableCommand = launcherPath
324
+ } catch {}
257
325
  }
258
- // Always use npx as the command it has a fast-path that execs the binary directly
259
- // but can self-heal if the binary is missing or broken
326
+ // stableCommand is now the shell launcher script path. It wraps npx with a
327
+ // Node version check + curl telemetry before exec'ing. If launcher creation
328
+ // failed it falls back to plain 'npx'.
260
329
 
261
330
  // Detectar clientes
262
331
  const detected = CLIENTS.filter(c => c.detect())