local-mcp 3.0.236 → 3.0.238

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 +22 -0
  2. package/package.json +1 -1
  3. package/setup.js +55 -32
package/index.js CHANGED
@@ -228,6 +228,28 @@ async function main() {
228
228
  }
229
229
  }
230
230
 
231
+ // ADR-0004 phase 3 (darwin): prefer the notarized server embedded in
232
+ // LocalMCPTray.app. Ensure the .app is installed, then launch its embedded
233
+ // server directly — skipping the standalone download + ad-hoc re-sign that
234
+ // resets Automation TCC (Mail/Notes/Messages/Safari) on every update. Only
235
+ // if the .app can't be provided do we fall through to the legacy standalone
236
+ // binary (which still ad-hoc signs as a last resort so the user isn't stranded).
237
+ if (process.platform === 'darwin') {
238
+ await ensureDarwinComponents()
239
+ const embedded = findEmbeddedServer()
240
+ if (embedded) {
241
+ let v = pkg.version
242
+ try {
243
+ v = fs.readFileSync(
244
+ path.join(os.homedir(), '.local', 'share', 'local-mcp', 'tray', '.version'),
245
+ 'utf8'
246
+ ).trim() || pkg.version
247
+ } catch {}
248
+ launchMcpStdio(embedded, v)
249
+ return
250
+ }
251
+ }
252
+
231
253
  // Fast path: if stable binary exists AND its version is >= our npm package version, exec directly.
232
254
  // Using >= (not ===) prevents Claude Desktop from downgrading a binary that the tray's
233
255
  // UpdateManager has already upgraded. LMC-375: machine 5F8445AE downgraded 3.0.72→3.0.70
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.236",
3
+ "version": "3.0.238",
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
@@ -369,41 +369,64 @@ async function runSetup(opts = {}) {
369
369
  let stableCommand = NPX_COMMAND // fallback; replaced below once CACHE_DIR is known
370
370
  let stableArgs = NPX_ARGS
371
371
  let binaryVersion = ''
372
+ // ADR-0004 phase 3 (darwin): if the notarized server is already embedded in
373
+ // LocalMCPTray.app, skip pre-downloading + ad-hoc re-signing the standalone
374
+ // binary — the launcher (→ npx → index.js) prefers the embedded one anyway,
375
+ // and the ad-hoc copy is what resets Automation TCC. The tray is ensured later
376
+ // in setup; on a fresh install where the .app isn't present yet this is null
377
+ // and we keep the legacy pre-download as a fallback.
378
+ const _embeddedServer = process.platform === 'darwin'
379
+ ? [
380
+ path.join(os.homedir(), 'Applications', 'LocalMCPTray.app', 'Contents', 'MacOS', 'local-mcp-server'),
381
+ path.join('/Applications', 'LocalMCPTray.app', 'Contents', 'MacOS', 'local-mcp-server'),
382
+ ].find(p => { try { return fs.statSync(p).isFile() } catch { return false } })
383
+ : null
372
384
  try {
373
385
  const { ensureBinary, CACHE_DIR } = require('./download')
374
- process.stderr.write('Downloading LMCP runtime...\n')
375
- const { binPath, version: bv } = await ensureBinary()
376
- binaryVersion = bv || ''
377
- // Copy binary to stable path (npx fast-path will exec it directly)
378
- const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
379
- try {
380
- const tmpPath = stablePath + '.tmp'
381
- fs.copyFileSync(binPath, tmpPath)
382
- fs.chmodSync(tmpPath, 0o755)
383
- try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
384
- // curl route-3 installs the binary one level deeper:
385
- // ~/.local/share/local-mcp/bin/local-mcp-server/local-mcp-server
386
- // leaving stablePath itself as a DIRECTORY. fs.renameSync throws EISDIR
387
- // silently, so the npm stable binary is never written and the old curl
388
- // server stays running forever (LMC-158).
386
+ if (_embeddedServer) {
387
+ // Embedded server present no standalone download needed. Still write the
388
+ // launcher and record the embedded version for the npx fast-path skip.
389
389
  try {
390
- const st = fs.statSync(stablePath)
391
- if (st.isDirectory()) {
392
- process.stderr.write(' Migrating from curl install: removing old versioned directory at stable path\n')
393
- fs.rmSync(stablePath, { recursive: true, force: true })
394
- }
395
- } catch { /* stablePath doesn't exist — nothing to do */ }
396
- fs.renameSync(tmpPath, stablePath)
397
- // Write the actual binary version (from /runtime/latest), not the npm package
398
- // version. npm and Swift binary versions can diverge (npm-only fixes bump npm
399
- // without a Swift rebuild). Writing pkg.version here caused the tray to think
400
- // a newer binary was on disk and restart-loop (LMC-TODO).
401
- fs.writeFileSync(path.join(CACHE_DIR, '.server-version'), binaryVersion || '')
402
- const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
403
- const settingsDst = path.join(CACHE_DIR, 'settings.html')
404
- if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
405
- } catch {}
406
- process.stderr.write('✓ Runtime ready\n\n')
390
+ binaryVersion = fs.readFileSync(
391
+ path.join(os.homedir(), '.local', 'share', 'local-mcp', 'tray', '.version'), 'utf8'
392
+ ).trim()
393
+ } catch {}
394
+ process.stderr.write('✓ Using server embedded in LocalMCPTray.app\n\n')
395
+ } else {
396
+ process.stderr.write('Downloading LMCP runtime...\n')
397
+ const { binPath, version: bv } = await ensureBinary()
398
+ binaryVersion = bv || ''
399
+ // Copy binary to stable path (npx fast-path will exec it directly)
400
+ const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
401
+ try {
402
+ const tmpPath = stablePath + '.tmp'
403
+ fs.copyFileSync(binPath, tmpPath)
404
+ fs.chmodSync(tmpPath, 0o755)
405
+ try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
406
+ // curl route-3 installs the binary one level deeper:
407
+ // ~/.local/share/local-mcp/bin/local-mcp-server/local-mcp-server
408
+ // leaving stablePath itself as a DIRECTORY. fs.renameSync throws EISDIR
409
+ // silently, so the npm stable binary is never written and the old curl
410
+ // server stays running forever (LMC-158).
411
+ try {
412
+ const st = fs.statSync(stablePath)
413
+ if (st.isDirectory()) {
414
+ process.stderr.write(' Migrating from curl install: removing old versioned directory at stable path\n')
415
+ fs.rmSync(stablePath, { recursive: true, force: true })
416
+ }
417
+ } catch { /* stablePath doesn't exist — nothing to do */ }
418
+ fs.renameSync(tmpPath, stablePath)
419
+ // Write the actual binary version (from /runtime/latest), not the npm package
420
+ // version. npm and Swift binary versions can diverge (npm-only fixes bump npm
421
+ // without a Swift rebuild). Writing pkg.version here caused the tray to think
422
+ // a newer binary was on disk and restart-loop (LMC-TODO).
423
+ fs.writeFileSync(path.join(CACHE_DIR, '.server-version'), binaryVersion || '')
424
+ const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
425
+ const settingsDst = path.join(CACHE_DIR, 'settings.html')
426
+ if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
427
+ } catch {}
428
+ process.stderr.write('✓ Runtime ready\n\n')
429
+ }
407
430
  // Write the launcher script now that we have CACHE_DIR.
408
431
  // The launcher uses curl (no Node dependency) to send telemetry if Node is
409
432
  // too old, then exec's npx. Claude Desktop runs this script instead of npx