local-mcp 3.0.70 → 3.0.72
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.
- package/README.md +6 -0
- package/download.js +46 -1
- package/index.js +5 -3
- package/package.json +1 -1
- package/setup.js +91 -14
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ curl -fsSL https://local-mcp.com/install?ref=npm | bash
|
|
|
16
16
|
|
|
17
17
|
Auto-detects and configures: Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, and Zed. Restart your AI client once.
|
|
18
18
|
|
|
19
|
+
> **Cursor users:** LMCP tools only appear in **Agent mode**. Open a new Composer window (⌘I) and make sure the mode selector shows **Agent** (not Chat or Edit). Without Agent mode, the tools are installed but invisible.
|
|
20
|
+
|
|
19
21
|
**Or via npx:**
|
|
20
22
|
|
|
21
23
|
```bash
|
|
@@ -114,6 +116,10 @@ System: `diagnose` `run_diagnostics` `version_info` `get_config` `update_local_m
|
|
|
114
116
|
| VS Code | `~/.vscode/mcp.json` |
|
|
115
117
|
| Zed | `~/.config/zed/settings.json` |
|
|
116
118
|
|
|
119
|
+
### Cursor users
|
|
120
|
+
|
|
121
|
+
After install, switch to **Agent mode** in the Cursor chat panel — MCP tools only appear in Agent mode, not in Ask or Edit mode. Click the mode selector at the bottom of the chat input.
|
|
122
|
+
|
|
117
123
|
## Commands
|
|
118
124
|
|
|
119
125
|
```bash
|
package/download.js
CHANGED
|
@@ -310,4 +310,49 @@ function _writeTrayLaunchAgent(trayApp) {
|
|
|
310
310
|
} catch { /* no crítico */ }
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Descarga el co-proceso m365-proxy si no está instalado o es una versión diferente.
|
|
315
|
+
* El binary se instala en ~/.local/share/local-mcp/bin/m365-proxy
|
|
316
|
+
* @returns {Promise<string|null>} Ruta al binary, o null si falla
|
|
317
|
+
*/
|
|
318
|
+
async function ensureM365Proxy() {
|
|
319
|
+
const info = await getLatestBinary()
|
|
320
|
+
const version = info.version
|
|
321
|
+
const arch = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64'
|
|
322
|
+
const binPath = path.join(CACHE_DIR, 'm365-proxy')
|
|
323
|
+
const verFile = path.join(CACHE_DIR, '.m365-proxy-version')
|
|
324
|
+
|
|
325
|
+
// Ya instalado y actualizado
|
|
326
|
+
if (fs.existsSync(binPath) && fs.existsSync(verFile)) {
|
|
327
|
+
if (fs.readFileSync(verFile, 'utf8').trim() === version) return binPath
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const url = `https://download.local-mcp.com/m365-proxy-${version}-${arch}.tar.gz`
|
|
331
|
+
process.stderr.write(`\nDescargando m365-proxy v${version}...\n`)
|
|
332
|
+
|
|
333
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true })
|
|
334
|
+
const tarPath = path.join(CACHE_DIR, `m365-proxy-${version}.tar.gz`)
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
await downloadFile(url, tarPath)
|
|
338
|
+
const stat = fs.statSync(tarPath)
|
|
339
|
+
if (stat.size < 1000) { fs.unlinkSync(tarPath); throw new Error(`Descarga incompleta (${stat.size} bytes)`) }
|
|
340
|
+
|
|
341
|
+
execFileSync('tar', ['-xzf', tarPath, '-C', CACHE_DIR], { stdio: 'pipe' })
|
|
342
|
+
fs.unlinkSync(tarPath)
|
|
343
|
+
|
|
344
|
+
if (!fs.existsSync(binPath)) throw new Error('m365-proxy no encontrado tras extraer')
|
|
345
|
+
fs.chmodSync(binPath, 0o755)
|
|
346
|
+
try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.m365-proxy', binPath], { stdio: 'pipe' }) } catch { /* no crítico */ }
|
|
347
|
+
|
|
348
|
+
fs.writeFileSync(verFile, version, 'utf8')
|
|
349
|
+
process.stderr.write(` m365-proxy instalado en ${binPath}\n`)
|
|
350
|
+
return binPath
|
|
351
|
+
} catch (err) {
|
|
352
|
+
try { if (fs.existsSync(tarPath)) fs.unlinkSync(tarPath) } catch {}
|
|
353
|
+
process.stderr.write(` (m365-proxy no disponible: ${err.message})\n`)
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = { ensureBinary, ensureRuntime, ensureTray, ensureTeamsProxy, ensureM365Proxy, CACHE_DIR, TRAY_DIR }
|
package/index.js
CHANGED
|
@@ -122,9 +122,10 @@ async function main() {
|
|
|
122
122
|
: ''
|
|
123
123
|
if (cachedVersion === pkg.version) {
|
|
124
124
|
// Version matches — use fast path
|
|
125
|
-
const { ensureTray, ensureTeamsProxy } = require('./download')
|
|
125
|
+
const { ensureTray, ensureTeamsProxy, ensureM365Proxy } = require('./download')
|
|
126
126
|
ensureTray().catch(() => {})
|
|
127
127
|
ensureTeamsProxy().catch(() => {})
|
|
128
|
+
ensureM365Proxy().catch(() => {})
|
|
128
129
|
const child = spawn(stableBin, [], { stdio: 'inherit', env: process.env })
|
|
129
130
|
child.on('error', () => process.exit(1))
|
|
130
131
|
child.on('exit', (code) => process.exit(code ?? 0))
|
|
@@ -143,11 +144,12 @@ async function main() {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
// Slow path: download/repair binary
|
|
146
|
-
const { ensureRuntime, ensureTray, ensureTeamsProxy } = require('./download')
|
|
147
|
+
const { ensureRuntime, ensureTray, ensureTeamsProxy, ensureM365Proxy } = require('./download')
|
|
147
148
|
|
|
148
|
-
// Actualizar tray + teams-proxy en background
|
|
149
|
+
// Actualizar tray + teams-proxy + m365-proxy en background
|
|
149
150
|
ensureTray().catch(() => {})
|
|
150
151
|
ensureTeamsProxy().catch(() => {})
|
|
152
|
+
ensureM365Proxy().catch(() => {})
|
|
151
153
|
|
|
152
154
|
let runtime
|
|
153
155
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.72",
|
|
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
|
@@ -151,6 +151,18 @@ async function runSetup(opts = {}) {
|
|
|
151
151
|
fs.copyFileSync(binPath, tmpPath)
|
|
152
152
|
fs.chmodSync(tmpPath, 0o755)
|
|
153
153
|
try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
|
|
154
|
+
// curl route-3 installs the binary one level deeper:
|
|
155
|
+
// ~/.local/share/local-mcp/bin/local-mcp-server/local-mcp-server
|
|
156
|
+
// leaving stablePath itself as a DIRECTORY. fs.renameSync throws EISDIR
|
|
157
|
+
// silently, so the npm stable binary is never written and the old curl
|
|
158
|
+
// server stays running forever (LMC-158).
|
|
159
|
+
try {
|
|
160
|
+
const st = fs.statSync(stablePath)
|
|
161
|
+
if (st.isDirectory()) {
|
|
162
|
+
process.stderr.write(' Migrating from curl install: removing old versioned directory at stable path\n')
|
|
163
|
+
fs.rmSync(stablePath, { recursive: true, force: true })
|
|
164
|
+
}
|
|
165
|
+
} catch { /* stablePath doesn't exist — nothing to do */ }
|
|
154
166
|
fs.renameSync(tmpPath, stablePath)
|
|
155
167
|
// Write version file so fast path knows when to update
|
|
156
168
|
const pkg = require('./package.json')
|
|
@@ -170,13 +182,32 @@ async function runSetup(opts = {}) {
|
|
|
170
182
|
const detected = CLIENTS.filter(c => c.detect())
|
|
171
183
|
|
|
172
184
|
if (detected.length === 0) {
|
|
173
|
-
|
|
174
|
-
console.log('Install Claude Desktop, Cursor, Windsurf, or VS Code and run this command again.\n')
|
|
175
|
-
console.log('Or add this manually to your AI client config:')
|
|
176
|
-
const manualEntry = stableArgs !== undefined
|
|
185
|
+
const entry = stableArgs !== undefined
|
|
177
186
|
? { command: stableCommand, args: stableArgs }
|
|
178
187
|
: { command: stableCommand }
|
|
179
|
-
|
|
188
|
+
const snippet = JSON.stringify({ mcpServers: { 'local-mcp': entry } }, null, 2)
|
|
189
|
+
.split('\n').map(l => ' ' + l).join('\n')
|
|
190
|
+
|
|
191
|
+
console.log('╔══════════════════════════════════════════════════════════╗')
|
|
192
|
+
console.log('║ LMCP installed — no AI client detected yet ║')
|
|
193
|
+
console.log('╚══════════════════════════════════════════════════════════╝\n')
|
|
194
|
+
console.log(' To get started, add LMCP to your AI client config:\n')
|
|
195
|
+
console.log(' ── Claude Desktop ────────────────────────────────────────')
|
|
196
|
+
console.log(' File: ~/Library/Application Support/Claude/claude_desktop_config.json\n')
|
|
197
|
+
console.log(snippet)
|
|
198
|
+
console.log('\n ── Cursor ────────────────────────────────────────────────')
|
|
199
|
+
console.log(' File: ~/.cursor/mcp.json\n')
|
|
200
|
+
console.log(snippet)
|
|
201
|
+
console.log('\n ── VS Code (Cline / GitHub Copilot) ──────────────────────')
|
|
202
|
+
console.log(' File: ~/.vscode/mcp.json\n')
|
|
203
|
+
console.log(snippet)
|
|
204
|
+
console.log('\n Then restart your AI client and try:')
|
|
205
|
+
console.log(' "Summarize my unread emails"\n')
|
|
206
|
+
console.log(' ── Already installed an AI client? ───────────────────────')
|
|
207
|
+
console.log(' Run this to auto-configure it:')
|
|
208
|
+
console.log(' npx local-mcp setup\n')
|
|
209
|
+
console.log(' Full setup guide: https://local-mcp.com/setup')
|
|
210
|
+
console.log('─────────────────────────────────────────────────────────────\n')
|
|
180
211
|
return
|
|
181
212
|
}
|
|
182
213
|
|
|
@@ -215,27 +246,51 @@ async function runSetup(opts = {}) {
|
|
|
215
246
|
// Health check — verify binary works before showing success
|
|
216
247
|
const healthOk = _runHealthCheck()
|
|
217
248
|
|
|
249
|
+
const hasCursor = configured.includes('Cursor')
|
|
250
|
+
|
|
218
251
|
console.log('\n──────────────────────────────────────')
|
|
219
252
|
if (configured.length > 0) {
|
|
220
253
|
if (healthOk) {
|
|
221
254
|
console.log(`✅ LMCP configured for: ${configured.join(', ')}\n`)
|
|
222
|
-
console.log(' ✓ Server binary verified —
|
|
255
|
+
console.log(' ✓ Server binary verified — 95 tools ready\n')
|
|
223
256
|
} else {
|
|
224
257
|
console.log(`⚠ LMCP configured for: ${configured.join(', ')}`)
|
|
225
258
|
console.log(' Server binary could not be verified — it may still work after restart.\n')
|
|
226
259
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
|
|
261
|
+
if (hasCursor) {
|
|
262
|
+
console.log('┌─────────────────────────────────────────────────────┐')
|
|
263
|
+
console.log('│ CURSOR — 3 steps to activate: │')
|
|
264
|
+
console.log('│ │')
|
|
265
|
+
console.log('│ 1. Quit Cursor completely (Cmd+Q) │')
|
|
266
|
+
console.log('│ 2. Reopen Cursor │')
|
|
267
|
+
console.log('│ 3. Cursor Settings (⚙) → MCP → find "local-mcp" │')
|
|
268
|
+
console.log('│ → toggle it ON if not already enabled │')
|
|
269
|
+
console.log('│ │')
|
|
270
|
+
console.log('│ Then ask: "Summarize my unread emails" │')
|
|
271
|
+
console.log('│ │')
|
|
272
|
+
console.log('│ Guide: local-mcp.com/guides/cursor-mcp-setup │')
|
|
273
|
+
console.log('└─────────────────────────────────────────────────────┘\n')
|
|
274
|
+
} else {
|
|
275
|
+
const primaryClient = configured[0]
|
|
276
|
+
console.log('┌─────────────────────────────────────────────────────┐')
|
|
277
|
+
console.log('│ NEXT STEP — This is important: │')
|
|
278
|
+
console.log('│ │')
|
|
279
|
+
console.log(`│ Quit and reopen ${primaryClient.padEnd(35)}│`)
|
|
280
|
+
console.log('│ LMCP will appear automatically. │')
|
|
281
|
+
console.log('│ │')
|
|
282
|
+
console.log('│ Then try: "Summarize my unread emails" │')
|
|
283
|
+
console.log('└─────────────────────────────────────────────────────┘\n')
|
|
284
|
+
}
|
|
235
285
|
}
|
|
236
286
|
if (failed.length > 0) {
|
|
237
287
|
console.log(`⚠ Could not configure: ${failed.join(', ')} — check permissions and try again.\n`)
|
|
238
288
|
}
|
|
289
|
+
console.log(' Setup guide & troubleshooting: https://local-mcp.com/setup\n')
|
|
290
|
+
|
|
291
|
+
// Migrate legacy curl LaunchAgent before launching tray — must run first
|
|
292
|
+
// so the tray's DaemonSupervisor doesn't race against the old server.
|
|
293
|
+
_migrateCurlLaunchAgent()
|
|
239
294
|
|
|
240
295
|
// Instalar y lanzar el tray (menu bar app) — arm64 only, non-fatal
|
|
241
296
|
await _installTray()
|
|
@@ -362,6 +417,28 @@ async function _promptEmail() {
|
|
|
362
417
|
})
|
|
363
418
|
}
|
|
364
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Detects and removes the legacy com.local-mcp.server LaunchAgent left behind
|
|
422
|
+
* by older curl installs. In the current architecture the tray app's
|
|
423
|
+
* DaemonSupervisor manages the server process — the standalone LaunchAgent is
|
|
424
|
+
* no longer needed and causes the old binary to keep running after an npm
|
|
425
|
+
* migration (LMC-158).
|
|
426
|
+
*/
|
|
427
|
+
function _migrateCurlLaunchAgent() {
|
|
428
|
+
const plistPath = path.join(HOME, 'Library', 'LaunchAgents', 'com.local-mcp.server.plist')
|
|
429
|
+
if (!fs.existsSync(plistPath)) return
|
|
430
|
+
try {
|
|
431
|
+
const content = fs.readFileSync(plistPath, 'utf8')
|
|
432
|
+
// Only act on plists that reference an LMCP server binary
|
|
433
|
+
if (!content.includes('local-mcp-server') && !content.includes('Local MCP')) return
|
|
434
|
+
process.stderr.write('\n Migrating legacy server LaunchAgent to tray supervision...\n')
|
|
435
|
+
// launchctl unload kills the running process AND prevents it from restarting
|
|
436
|
+
try { execFileSync('launchctl', ['unload', plistPath], { stdio: 'pipe' }) } catch { /* may already be unloaded */ }
|
|
437
|
+
try { fs.unlinkSync(plistPath) } catch { /* non-fatal */ }
|
|
438
|
+
process.stderr.write(' ✓ Legacy server LaunchAgent removed — server will be restarted by tray\n\n')
|
|
439
|
+
} catch { /* non-fatal — don't block install on unexpected plist content */ }
|
|
440
|
+
}
|
|
441
|
+
|
|
365
442
|
function _pingInstall(clients, method, email = '') {
|
|
366
443
|
try {
|
|
367
444
|
const https = require('https')
|