local-mcp 3.0.219 → 3.0.220

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/index.js +72 -30
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -4,7 +4,8 @@
4
4
  * index.js — entry point del paquete npm local-mcp.
5
5
  *
6
6
  * Modos:
7
- * npx local-mcp → lanza MCP server en stdio (para Claude Desktop, Cursor, etc.)
7
+ * npx local-mcp → instala/actualiza runtime; en terminal interactiva muestra
8
+ * mensaje de listo y sale (Cursor/Claude lanzan stdio solos)
8
9
  * npx local-mcp setup → wizard de configuración de clientes
9
10
  * npx local-mcp update → fuerza re-descarga del runtime
10
11
  * npx local-mcp uninstall → elimina cache del runtime
@@ -139,6 +140,50 @@ async function main() {
139
140
  return true
140
141
  }
141
142
 
143
+ /** MCP hosts (Cursor, Claude) spawn us without a TTY — keep stdio bridge alive. */
144
+ const isMcpClientSpawn = !(process.stdin && process.stdin.isTTY)
145
+
146
+ function printInstallComplete(version) {
147
+ process.stderr.write('\n')
148
+ process.stderr.write(`✓ Local MCP v${version} — installation complete.\n\n`)
149
+ process.stderr.write(' • Menu bar: open LocalMCPTray from Applications (look for the LMCP icon).\n')
150
+ process.stderr.write(' • Cursor / Claude: enable the "local-mcp" MCP server in settings (they start it automatically).\n')
151
+ process.stderr.write(' • You can close this terminal — do not leave `npx local-mcp` running here.\n')
152
+ process.stderr.write(' • Status: http://localhost:8765/status\n\n')
153
+ }
154
+
155
+ async function ensureDarwinComponents() {
156
+ if (process.platform !== 'darwin') return
157
+ const { ensureTray, ensureTeamsProxy, ensureHelper, ensureJXARunner } = require('./download')
158
+ await Promise.all([
159
+ ensureTray().catch((e) => { process.stderr.write(`[LMCP] Tray install failed: ${e.message}\n`) }),
160
+ ensureTeamsProxy().catch((e) => { process.stderr.write(`[LMCP] teams-proxy install failed: ${e.message}\n`) }),
161
+ ensureHelper().catch((e) => { process.stderr.write(`[LMCP] helper install failed: ${e.message}\n`) }),
162
+ ensureJXARunner().catch((e) => { process.stderr.write(`[LMCP] jxa-runner install failed: ${e.message}\n`) }),
163
+ ])
164
+ }
165
+
166
+ function launchMcpStdio(binaryPath, version) {
167
+ if (!isMcpClientSpawn) {
168
+ printInstallComplete(version)
169
+ if (process.platform === 'darwin') {
170
+ const home = os.homedir()
171
+ const tray = path.join(home, 'Applications', 'LocalMCPTray.app')
172
+ const trayPath = fs.existsSync(tray) ? tray : '/Applications/LocalMCPTray.app'
173
+ try { execFileSync('open', ['-a', trayPath], { stdio: 'ignore' }) } catch {}
174
+ }
175
+ process.exit(0)
176
+ return
177
+ }
178
+ const spawnArgs = process.platform === 'darwin' ? ['stdio'] : []
179
+ const child = spawn(binaryPath, spawnArgs, { stdio: 'inherit', env: process.env })
180
+ child.on('error', (err) => {
181
+ process.stderr.write(`Error ejecutando LMCP: ${err.message}\n`)
182
+ process.exit(1)
183
+ })
184
+ child.on('exit', (code) => process.exit(code ?? 0))
185
+ }
186
+
142
187
  // On Windows: apply pending update before launching (same as Mac's UpdateManager)
143
188
  // The Go server downloads new versions in background and stages them as .pending.
144
189
  // On next Claude Desktop restart, we swap them in here.
@@ -178,17 +223,8 @@ async function main() {
178
223
  : ''
179
224
  if (cachedVersion && semverGte(cachedVersion, pkg.version)) {
180
225
  // Cached binary is same or newer — use fast path, no download needed
181
- if (process.platform === 'darwin') {
182
- const { ensureTray, ensureTeamsProxy, ensureHelper, ensureJXARunner } = require('./download')
183
- ensureTray().catch((e) => { process.stderr.write(`[LMCP] Tray install failed: ${e.message}\n`) })
184
- ensureTeamsProxy().catch((e) => { process.stderr.write(`[LMCP] teams-proxy install failed: ${e.message}\n`) })
185
- ensureHelper().catch((e) => { process.stderr.write(`[LMCP] helper install failed: ${e.message}\n`) })
186
- ensureJXARunner().catch((e) => { process.stderr.write(`[LMCP] jxa-runner install failed: ${e.message}\n`) })
187
- }
188
- const args = process.platform === 'darwin' ? ['stdio'] : []
189
- const child = spawn(stableBin, args, { stdio: 'inherit', env: process.env })
190
- child.on('error', () => process.exit(1))
191
- child.on('exit', (code) => process.exit(code ?? 0))
226
+ await ensureDarwinComponents()
227
+ launchMcpStdio(stableBin, cachedVersion || pkg.version)
192
228
 
193
229
  // Background update check — fires 10s after startup, at most once per 24h.
194
230
  // Covers machines where the tray is dead and Sparkle can't update the binary.
@@ -207,10 +243,30 @@ async function main() {
207
243
  try {
208
244
  const { version: latestVersion } = JSON.parse(buf)
209
245
  if (latestVersion && !semverGte(_cachedVersion, latestVersion)) {
210
- // New version available — download silently for next restart
246
+ // New version available — download for next restart only while no server is running.
247
+ // Replacing stableBin mid-session changes its mtime and used to cause sibling
248
+ // stdio proxies to be SIGTERM'd → Cursor transport_closed (LMCA-1044).
249
+ let activeServers = 0
250
+ try {
251
+ activeServers = parseInt(
252
+ execSync('pgrep -fc local-mcp-server 2>/dev/null || echo 0', { encoding: 'utf8' }).trim(),
253
+ 10
254
+ ) || 0
255
+ } catch {}
256
+ if (activeServers > 0) return
257
+
211
258
  const { ensureRuntime } = require('./download')
212
259
  ensureRuntime().then(({ binPath, version: downloadedVersion }) => {
213
260
  try {
261
+ let stillActive = 0
262
+ try {
263
+ stillActive = parseInt(
264
+ execSync('pgrep -fc local-mcp-server 2>/dev/null || echo 0', { encoding: 'utf8' }).trim(),
265
+ 10
266
+ ) || 0
267
+ } catch {}
268
+ if (stillActive > 0) return
269
+
214
270
  const tmpPath = stableBin + '.tmp'
215
271
  fs.copyFileSync(binPath, tmpPath)
216
272
  fs.chmodSync(tmpPath, 0o755)
@@ -241,15 +297,7 @@ async function main() {
241
297
  }
242
298
 
243
299
  // Slow path: download/repair binary
244
- const { ensureRuntime, ensureTray, ensureTeamsProxy, ensureHelper, ensureJXARunner } = require('./download')
245
-
246
- // Update tray + proxies in background (macOS only).
247
- if (process.platform === 'darwin') {
248
- ensureTray().catch((e) => { process.stderr.write(`[LMCP] Tray install failed: ${e.message}\n`) })
249
- ensureTeamsProxy().catch((e) => { process.stderr.write(`[LMCP] teams-proxy install failed: ${e.message}\n`) })
250
- ensureHelper().catch((e) => { process.stderr.write(`[LMCP] helper install failed: ${e.message}\n`) })
251
- ensureJXARunner().catch((e) => { process.stderr.write(`[LMCP] jxa-runner install failed: ${e.message}\n`) })
252
- }
300
+ const { ensureRuntime } = require('./download')
253
301
 
254
302
  let runtime
255
303
  try {
@@ -281,14 +329,8 @@ async function main() {
281
329
  if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
282
330
  } catch {}
283
331
 
284
- // Run the binary (macOS binary requires 'stdio' arg to enter MCP stdio mode)
285
- const spawnArgs = process.platform === 'darwin' ? ['stdio'] : []
286
- const child = spawn(binPath, spawnArgs, { stdio: 'inherit', env: process.env })
287
- child.on('error', (err) => {
288
- process.stderr.write(`Error ejecutando LMCP: ${err.message}\n`)
289
- process.exit(1)
290
- })
291
- child.on('exit', (code) => process.exit(code ?? 0))
332
+ await ensureDarwinComponents()
333
+ launchMcpStdio(stableBin, downloadedVersion || pkg.version)
292
334
  }
293
335
 
294
336
  main().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.219",
3
+ "version": "3.0.220",
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": {