local-mcp 2.7.0 → 2.9.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 +38 -41
  2. package/package.json +1 -1
  3. package/setup.js +8 -11
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, execFileSync } = 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,37 @@ async function main() {
71
71
  return
72
72
  }
73
73
 
74
- // Modo default: stdio MCP server
75
- const { ensureRuntime, ensureTray } = require('./download')
74
+ // ── Modo default: stdio MCP server ──────────────────────────────────────────
75
+ const { CACHE_DIR } = require('./download')
76
+ const stableBin = path.join(CACHE_DIR, 'local-mcp-server')
76
77
 
77
- // Actualizar tray en background en cada startup (sin bloquear el arranque del servidor)
78
- ensureTray().catch(() => { /* no crítico */ })
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
+ // Download tray + teams-proxy in background (non-blocking)
85
+ const { ensureTray, ensureTeamsProxy } = require('./download')
86
+ ensureTray().catch(() => {})
87
+ ensureTeamsProxy().catch(() => {})
88
+ const child = spawn(stableBin, [], { stdio: 'inherit', env: process.env })
89
+ child.on('error', () => process.exit(1))
90
+ child.on('exit', (code) => process.exit(code ?? 0))
91
+ return
92
+ }
93
+ // If it's a symlink (legacy), remove it and fall through to download
94
+ if (stat.isSymbolicLink()) fs.unlinkSync(stableBin)
95
+ } catch {
96
+ // stat failed — binary missing, fall through to download
97
+ }
98
+
99
+ // Slow path: download/repair binary
100
+ const { ensureRuntime, ensureTray, ensureTeamsProxy } = require('./download')
101
+
102
+ // Actualizar tray + teams-proxy en background
103
+ ensureTray().catch(() => {})
104
+ ensureTeamsProxy().catch(() => {})
79
105
 
80
106
  let runtime
81
107
  try {
@@ -88,55 +114,26 @@ async function main() {
88
114
 
89
115
  const { binPath } = runtime
90
116
 
91
- // ── Stable binary copy + Claude Desktop migration ──────────────────────────
92
- // Copy binary to a stable path so auto-updates (which clean versioned dirs)
93
- // never break the binary that Claude Desktop points to.
94
- const { CACHE_DIR } = require('./download')
95
- const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
117
+ // Copy binary to stable path (survives version cleanup)
96
118
  try {
97
- const tmpPath = stablePath + '.tmp'
119
+ const tmpPath = stableBin + '.tmp'
98
120
  fs.copyFileSync(binPath, tmpPath)
99
121
  fs.chmodSync(tmpPath, 0o755)
100
- // Re-sign with stable identity
101
122
  try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
102
- // Atomic replace
103
- fs.renameSync(tmpPath, stablePath)
123
+ fs.renameSync(tmpPath, stableBin)
104
124
  // Also copy settings.html
105
125
  const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
106
126
  const settingsDst = path.join(CACHE_DIR, 'settings.html')
107
127
  if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
128
+ } catch {}
108
129
 
109
- // Migrar claude_desktop_config.json si todavía usa npx
110
- const claudeConfig = path.join(
111
- os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'
112
- )
113
- if (fs.existsSync(claudeConfig)) {
114
- const cfg = JSON.parse(fs.readFileSync(claudeConfig, 'utf8'))
115
- const srv = cfg.mcpServers?.['local-mcp']
116
- if (srv && srv.command === 'npx') {
117
- cfg.mcpServers['local-mcp'] = { command: stablePath }
118
- fs.writeFileSync(claudeConfig, JSON.stringify(cfg, null, 2))
119
- process.stderr.write(`\n✓ Config de Claude Desktop actualizado.\n`)
120
- process.stderr.write(` Reiniciá Claude Desktop para que Pilot MCP corra sin node como padre.\n`)
121
- process.stderr.write(` (Esto elimina los diálogos de permisos repetidos.)\n\n`)
122
- }
123
- }
124
- } catch { /* no crítico — seguimos con spawn normal */ }
125
-
126
- // Spawn para esta sesión (siguiente sesión ya será binario directo)
127
- const child = spawn(binPath, [], {
128
- stdio: 'inherit',
129
- env: process.env,
130
- })
131
-
130
+ // Run the binary
131
+ const child = spawn(binPath, [], { stdio: 'inherit', env: process.env })
132
132
  child.on('error', (err) => {
133
133
  process.stderr.write(`Error ejecutando Pilot MCP: ${err.message}\n`)
134
134
  process.exit(1)
135
135
  })
136
-
137
- child.on('exit', (code) => {
138
- process.exit(code ?? 0)
139
- })
136
+ child.on('exit', (code) => process.exit(code ?? 0))
140
137
  }
141
138
 
142
139
  main().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "2.7.0",
3
+ "version": "2.9.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,35 +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
- // Copy binary to stable path (not symlink survives version cleanup)
123
+ // Copy binary to stable path (npx fast-path will exec it directly)
124
124
  const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
125
125
  try {
126
126
  const tmpPath = stablePath + '.tmp'
127
127
  fs.copyFileSync(binPath, tmpPath)
128
128
  fs.chmodSync(tmpPath, 0o755)
129
- try { require('child_process').execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
129
+ try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
130
130
  fs.renameSync(tmpPath, stablePath)
131
- // Also copy settings.html
132
131
  const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
133
132
  const settingsDst = path.join(CACHE_DIR, 'settings.html')
134
133
  if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
135
- } catch { /* no crítico — fallback a npx */ }
136
- if (fs.existsSync(stablePath)) {
137
- stableCommand = stablePath
138
- stableArgs = undefined
139
- process.stderr.write('✓ Runtime ready\n\n')
140
- }
134
+ } catch {}
135
+ process.stderr.write('✓ Runtime ready\n\n')
141
136
  } catch (err) {
142
137
  process.stderr.write(` (Runtime download failed, will download on first run: ${err.message})\n\n`)
143
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
144
141
 
145
142
  // Detectar clientes
146
143
  const detected = CLIENTS.filter(c => c.detect())