local-mcp 2.6.0 → 2.8.0

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 +40 -42
  2. package/package.json +1 -1
  3. package/setup.js +16 -12
package/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * npx local-mcp status → muestra estado del servidor local
12
12
  */
13
13
 
14
- const { spawn } = require('child_process')
14
+ const { spawn, execFileSync, execSync } = require('child_process')
15
15
  const path = require('path')
16
16
  const os = require('os')
17
17
  const fs = require('fs')
@@ -71,11 +71,32 @@ async function main() {
71
71
  return
72
72
  }
73
73
 
74
- // Modo default: stdio MCP server
74
+ // ── Modo default: stdio MCP server ──────────────────────────────────────────
75
+ const { CACHE_DIR } = require('./download')
76
+ const stableBin = path.join(CACHE_DIR, 'local-mcp-server')
77
+
78
+ // Fast path: if stable binary exists and is a real file (not a broken symlink), exec it directly.
79
+ // This makes npx startup near-instant when everything is healthy.
80
+ try {
81
+ const stat = fs.lstatSync(stableBin)
82
+ // Only use fast path if it's a real file (not a symlink — symlinks can break)
83
+ if (stat.isFile() && (stat.mode & 0o111)) {
84
+ const child = spawn(stableBin, [], { stdio: 'inherit', env: process.env })
85
+ child.on('error', () => process.exit(1))
86
+ child.on('exit', (code) => process.exit(code ?? 0))
87
+ return
88
+ }
89
+ // If it's a symlink (legacy), remove it and fall through to download
90
+ if (stat.isSymbolicLink()) fs.unlinkSync(stableBin)
91
+ } catch {
92
+ // stat failed — binary missing, fall through to download
93
+ }
94
+
95
+ // Slow path: download/repair binary
75
96
  const { ensureRuntime, ensureTray } = require('./download')
76
97
 
77
- // Actualizar tray en background en cada startup (sin bloquear el arranque del servidor)
78
- ensureTray().catch(() => { /* no crítico */ })
98
+ // Actualizar tray en background
99
+ ensureTray().catch(() => {})
79
100
 
80
101
  let runtime
81
102
  try {
@@ -88,49 +109,26 @@ async function main() {
88
109
 
89
110
  const { binPath } = runtime
90
111
 
91
- // ── Symlink estable + migración de config de Claude Desktop ──────────────────
92
- // El binario corre como hijo de node → TCC muestra "node" en los diálogos de
93
- // permisos (Messages, Mail, etc.) en lugar de "local-mcp-server".
94
- // Solución: crear un symlink estable y apuntar Claude Desktop directamente
95
- // al binario. En la próxima sesión, Claude Desktop llama al binario sin node
96
- // como padre → TCC ve "com.local-mcp.server" directamente.
97
- const { CACHE_DIR } = require('./download')
98
- const stableLink = path.join(CACHE_DIR, 'local-mcp-server')
112
+ // Copy binary to stable path (survives version cleanup)
99
113
  try {
100
- if (fs.existsSync(stableLink)) fs.unlinkSync(stableLink)
101
- fs.symlinkSync(binPath, stableLink)
102
-
103
- // Migrar claude_desktop_config.json si todavía usa npx
104
- const claudeConfig = path.join(
105
- os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'
106
- )
107
- if (fs.existsSync(claudeConfig)) {
108
- const cfg = JSON.parse(fs.readFileSync(claudeConfig, 'utf8'))
109
- const srv = cfg.mcpServers?.['local-mcp']
110
- if (srv && srv.command === 'npx') {
111
- cfg.mcpServers['local-mcp'] = { command: stableLink }
112
- fs.writeFileSync(claudeConfig, JSON.stringify(cfg, null, 2))
113
- process.stderr.write(`\n✓ Config de Claude Desktop actualizado.\n`)
114
- process.stderr.write(` Reiniciá Claude Desktop para que Pilot MCP corra sin node como padre.\n`)
115
- process.stderr.write(` (Esto elimina los diálogos de permisos repetidos.)\n\n`)
116
- }
117
- }
118
- } catch { /* no crítico — seguimos con spawn normal */ }
119
-
120
- // Spawn para esta sesión (siguiente sesión ya será binario directo)
121
- const child = spawn(binPath, [], {
122
- stdio: 'inherit',
123
- env: process.env,
124
- })
125
-
114
+ const tmpPath = stableBin + '.tmp'
115
+ fs.copyFileSync(binPath, tmpPath)
116
+ fs.chmodSync(tmpPath, 0o755)
117
+ try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
118
+ fs.renameSync(tmpPath, stableBin)
119
+ // Also copy settings.html
120
+ const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
121
+ const settingsDst = path.join(CACHE_DIR, 'settings.html')
122
+ if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
123
+ } catch {}
124
+
125
+ // Run the binary
126
+ const child = spawn(binPath, [], { stdio: 'inherit', env: process.env })
126
127
  child.on('error', (err) => {
127
128
  process.stderr.write(`Error ejecutando Pilot MCP: ${err.message}\n`)
128
129
  process.exit(1)
129
130
  })
130
-
131
- child.on('exit', (code) => {
132
- process.exit(code ?? 0)
133
- })
131
+ child.on('exit', (code) => process.exit(code ?? 0))
134
132
  }
135
133
 
136
134
  main().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "Pilot MCP — 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
@@ -112,28 +112,32 @@ async function runSetup(opts = {}) {
112
112
  console.log('║ Pilot MCP — Setup Wizard ║')
113
113
  console.log('╚══════════════════════════════════════╝\n')
114
114
 
115
- // Descargar el binario ahora para poder escribir el stable link en la config
116
- // Esto elimina el 2do reinicio que de otro modo sería necesario para migrar npx → binario
115
+ // Pre-download binary so first launch is instant
116
+ // All clients use npx as launcher (self-healing if binary is missing/broken)
117
117
  let stableCommand = NPX_COMMAND
118
118
  let stableArgs = NPX_ARGS
119
119
  try {
120
120
  const { ensureBinary, CACHE_DIR } = require('./download')
121
121
  process.stderr.write('Downloading Pilot MCP runtime...\n')
122
122
  const { binPath } = await ensureBinary()
123
- // Crear stable symlink
124
- const stableLink = path.join(CACHE_DIR, 'local-mcp-server')
123
+ // Copy binary to stable path (npx fast-path will exec it directly)
124
+ const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
125
125
  try {
126
- if (fs.existsSync(stableLink)) fs.unlinkSync(stableLink)
127
- fs.symlinkSync(binPath, stableLink)
128
- } catch { /* no crítico — fallback a npx */ }
129
- if (fs.existsSync(stableLink)) {
130
- stableCommand = stableLink
131
- stableArgs = undefined
132
- process.stderr.write('✓ Runtime ready\n\n')
133
- }
126
+ const tmpPath = stablePath + '.tmp'
127
+ fs.copyFileSync(binPath, tmpPath)
128
+ fs.chmodSync(tmpPath, 0o755)
129
+ try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
130
+ fs.renameSync(tmpPath, stablePath)
131
+ const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
132
+ const settingsDst = path.join(CACHE_DIR, 'settings.html')
133
+ if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
134
+ } catch {}
135
+ process.stderr.write('✓ Runtime ready\n\n')
134
136
  } catch (err) {
135
137
  process.stderr.write(` (Runtime download failed, will download on first run: ${err.message})\n\n`)
136
138
  }
139
+ // Always use npx as the command — it has a fast-path that execs the binary directly
140
+ // but can self-heal if the binary is missing or broken
137
141
 
138
142
  // Detectar clientes
139
143
  const detected = CLIENTS.filter(c => c.detect())