local-mcp 3.0.91 → 3.0.95

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # LMCP
2
2
 
3
- > Give your AI assistant native access to Mac apps — 112 tools for Mail, Calendar, Contacts, iMessage, Teams, Slack, WhatsApp, OneDrive, Notes, OmniFocus, Safari, Word, Excel, PowerPoint, Stocks, NordVPN, and more. Everything runs locally. Your data never leaves your machine.
3
+ > Give your AI assistant native access to Mac apps — 107 tools for Mail, Calendar, Contacts, iMessage, Teams, Slack, WhatsApp, OneDrive, Notes, OmniFocus, Safari, Word, Excel, PowerPoint, Stocks, NordVPN, and more. Everything runs locally. Your data never leaves your machine.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/local-mcp)](https://www.npmjs.com/package/local-mcp)
6
6
  [![macOS 12+](https://img.shields.io/badge/macOS-12%2B-blue)](https://local-mcp.com?ref=npm)
@@ -60,13 +60,12 @@ LMCP runs a native MCP server that bridges your Mac apps to any AI client:
60
60
  | **Finder** | Search files via Spotlight, list directory contents |
61
61
  | **Safari** | Bookmarks, open tabs, click/type/fill forms, run JavaScript |
62
62
  | **NordVPN** | Status, server recommendations, diagnostics |
63
- | **M365 Directory** | Search org directory, people insights — requires Microsoft 365 account |
64
63
 
65
- ## 112 MCP Tools
64
+ ## 107 MCP Tools
66
65
 
67
66
  Email (9): `list_accounts` `list_emails` `read_email` `send_email` `reply_email` `search_emails` `move_email` `save_attachment` `create_email_folder`
68
67
 
69
- Calendar (4): `list_calendar_names` `list_calendar_events` `create_calendar_event` `delete_calendar_event`
68
+ Calendar (5): `list_calendar_names` `list_calendar_events` `create_calendar_event` `update_calendar_event` `delete_calendar_event`
70
69
 
71
70
  Contacts (3): `list_contacts` `search_contacts` `get_contact`
72
71
 
@@ -100,8 +99,6 @@ Safari (13): `list_safari_bookmarks` `safari_list_tabs` `safari_read_tab` `safar
100
99
 
101
100
  NordVPN (3): `nordvpn_status` `nordvpn_servers` `nordvpn_diagnose`
102
101
 
103
- M365 Directory (5): `connect_m365_account` `disconnect_m365_account` `search_m365_directory` `get_m365_person` `list_m365_people_insights`
104
-
105
102
  System (7): `run_diagnostics` `update_local_mcp` `update_self_diagnosis` `daily_brief` `report_problem` `request_feature` `request_integration`
106
103
 
107
104
  ## Safety
package/download.js CHANGED
@@ -311,72 +311,71 @@ function _writeTrayLaunchAgent(trayApp) {
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
314
+ * Downloads the slack-proxy co-process if not installed or on a different version.
315
+ * Binary is installed at ~/.local/share/local-mcp/bin/slack-proxy
316
+ * @returns {Promise<string|null>} Path to the binary, or null if it fails
317
317
  */
318
- async function ensureM365Proxy() {
318
+ async function ensureSlackProxy() {
319
319
  const info = await getLatestBinary()
320
320
  const version = info.version
321
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')
322
+ const binPath = path.join(CACHE_DIR, 'slack-proxy')
323
+ const verFile = path.join(CACHE_DIR, '.slack-proxy-version')
324
324
 
325
- // Ya instalado y actualizado
325
+ // Already installed and up to date
326
326
  if (fs.existsSync(binPath) && fs.existsSync(verFile)) {
327
327
  if (fs.readFileSync(verFile, 'utf8').trim() === version) return binPath
328
328
  }
329
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`)
330
+ const url = `https://download.local-mcp.com/slack-proxy-${version}-${arch}.tar.gz`
331
+ process.stderr.write(`\nDownloading slack-proxy v${version}...\n`)
332
332
 
333
333
  fs.mkdirSync(CACHE_DIR, { recursive: true })
334
- const tarPath = path.join(CACHE_DIR, `m365-proxy-${version}.tar.gz`)
334
+ const tarPath = path.join(CACHE_DIR, `slack-proxy-${version}.tar.gz`)
335
335
 
336
336
  try {
337
337
  await downloadFile(url, tarPath)
338
338
  const stat = fs.statSync(tarPath)
339
- if (stat.size < 1000) { fs.unlinkSync(tarPath); throw new Error(`Descarga incompleta (${stat.size} bytes)`) }
339
+ if (stat.size < 1000) { fs.unlinkSync(tarPath); throw new Error(`Incomplete download (${stat.size} bytes)`) }
340
340
 
341
341
  execFileSync('tar', ['-xzf', tarPath, '-C', CACHE_DIR], { stdio: 'pipe' })
342
342
  fs.unlinkSync(tarPath)
343
343
 
344
- if (!fs.existsSync(binPath)) throw new Error('m365-proxy no encontrado tras extraer')
344
+ if (!fs.existsSync(binPath)) throw new Error('slack-proxy not found after extraction')
345
345
  fs.chmodSync(binPath, 0o755)
346
- try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.m365-proxy', binPath], { stdio: 'pipe' }) } catch { /* no crítico */ }
346
+ try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.slack-proxy', binPath], { stdio: 'pipe' }) } catch { /* non-critical */ }
347
347
 
348
348
  fs.writeFileSync(verFile, version, 'utf8')
349
- process.stderr.write(` m365-proxy instalado en ${binPath}\n`)
349
+ process.stderr.write(` slack-proxy installed at ${binPath}\n`)
350
350
  return binPath
351
351
  } catch (err) {
352
352
  try { if (fs.existsSync(tarPath)) fs.unlinkSync(tarPath) } catch {}
353
- process.stderr.write(` (m365-proxy no disponible: ${err.message})\n`)
353
+ process.stderr.write(` (slack-proxy not available: ${err.message})\n`)
354
354
  return null
355
355
  }
356
356
  }
357
357
 
358
358
  /**
359
- * Downloads the slack-proxy co-process if not installed or on a different version.
360
- * Binary is installed at ~/.local/share/local-mcp/bin/slack-proxy
359
+ * Downloads local-mcp-helper on first install only.
360
+ * CRITICAL: never replaces an existing helper binary — the helper owns
361
+ * Calendar/Contacts/Reminders TCC grants via its cdhash. Replacing the
362
+ * binary silently resets those grants and users would have to re-authorize.
361
363
  * @returns {Promise<string|null>} Path to the binary, or null if it fails
362
364
  */
363
- async function ensureSlackProxy() {
365
+ async function ensureHelper() {
366
+ const binPath = path.join(CACHE_DIR, 'local-mcp-helper')
367
+
368
+ // Already installed — never replace. Stable cdhash = stable TCC grants.
369
+ if (fs.existsSync(binPath)) return binPath
370
+
364
371
  const info = await getLatestBinary()
365
372
  const version = info.version
366
373
  const arch = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64'
367
- const binPath = path.join(CACHE_DIR, 'slack-proxy')
368
- const verFile = path.join(CACHE_DIR, '.slack-proxy-version')
369
-
370
- // Already installed and up to date
371
- if (fs.existsSync(binPath) && fs.existsSync(verFile)) {
372
- if (fs.readFileSync(verFile, 'utf8').trim() === version) return binPath
373
- }
374
-
375
- const url = `https://download.local-mcp.com/slack-proxy-${version}-${arch}.tar.gz`
376
- process.stderr.write(`\nDownloading slack-proxy v${version}...\n`)
374
+ const url = `https://download.local-mcp.com/local-mcp-helper-${version}-${arch}.tar.gz`
375
+ process.stderr.write(`\nDownloading local-mcp-helper v${version}...\n`)
377
376
 
378
377
  fs.mkdirSync(CACHE_DIR, { recursive: true })
379
- const tarPath = path.join(CACHE_DIR, `slack-proxy-${version}.tar.gz`)
378
+ const tarPath = path.join(CACHE_DIR, `local-mcp-helper-${version}.tar.gz`)
380
379
 
381
380
  try {
382
381
  await downloadFile(url, tarPath)
@@ -386,18 +385,21 @@ async function ensureSlackProxy() {
386
385
  execFileSync('tar', ['-xzf', tarPath, '-C', CACHE_DIR], { stdio: 'pipe' })
387
386
  fs.unlinkSync(tarPath)
388
387
 
389
- if (!fs.existsSync(binPath)) throw new Error('slack-proxy not found after extraction')
388
+ if (!fs.existsSync(binPath)) throw new Error('local-mcp-helper not found after extraction')
390
389
  fs.chmodSync(binPath, 0o755)
391
- try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.slack-proxy', binPath], { stdio: 'pipe' }) } catch { /* non-critical */ }
390
+ // Do NOT codesign: the pre-built binary's cdhash must remain exactly as
391
+ // distributed. Codesigning a fresh binary produces the same cdhash only
392
+ // when the content is identical, but we want the CI-signed cdhash to be
393
+ // the canonical identity used in TCC grants.
392
394
 
393
- fs.writeFileSync(verFile, version, 'utf8')
394
- process.stderr.write(` slack-proxy installed at ${binPath}\n`)
395
+ fs.writeFileSync(path.join(CACHE_DIR, '.helper-version'), version, 'utf8')
396
+ process.stderr.write(` local-mcp-helper installed at ${binPath}\n`)
395
397
  return binPath
396
398
  } catch (err) {
397
399
  try { if (fs.existsSync(tarPath)) fs.unlinkSync(tarPath) } catch {}
398
- process.stderr.write(` (slack-proxy not available: ${err.message})\n`)
400
+ process.stderr.write(` (local-mcp-helper not available: ${err.message})\n`)
399
401
  return null
400
402
  }
401
403
  }
402
404
 
403
- module.exports = { ensureBinary, ensureRuntime, ensureTray, ensureTeamsProxy, ensureM365Proxy, ensureSlackProxy, CACHE_DIR, TRAY_DIR }
405
+ module.exports = { ensureBinary, ensureRuntime, ensureTray, ensureTeamsProxy, ensureSlackProxy, ensureHelper, CACHE_DIR, TRAY_DIR }
package/index.js CHANGED
@@ -136,10 +136,10 @@ async function main() {
136
136
  : ''
137
137
  if (cachedVersion && semverGte(cachedVersion, pkg.version)) {
138
138
  // Cached binary is same or newer — use fast path, no download needed
139
- const { ensureTray, ensureTeamsProxy, ensureM365Proxy } = require('./download')
139
+ const { ensureTray, ensureTeamsProxy, ensureHelper } = require('./download')
140
140
  ensureTray().catch(() => {})
141
141
  ensureTeamsProxy().catch(() => {})
142
- ensureM365Proxy().catch(() => {})
142
+ ensureHelper().catch(() => {})
143
143
  const child = spawn(stableBin, [], { stdio: 'inherit', env: process.env })
144
144
  child.on('error', () => process.exit(1))
145
145
  child.on('exit', (code) => process.exit(code ?? 0))
@@ -158,12 +158,12 @@ async function main() {
158
158
  }
159
159
 
160
160
  // Slow path: download/repair binary
161
- const { ensureRuntime, ensureTray, ensureTeamsProxy, ensureM365Proxy } = require('./download')
161
+ const { ensureRuntime, ensureTray, ensureTeamsProxy, ensureHelper } = require('./download')
162
162
 
163
- // Actualizar tray + teams-proxy + m365-proxy en background
163
+ // Update tray + proxies in background. Helper is downloaded only on first install.
164
164
  ensureTray().catch(() => {})
165
165
  ensureTeamsProxy().catch(() => {})
166
- ensureM365Proxy().catch(() => {})
166
+ ensureHelper().catch(() => {})
167
167
 
168
168
  let runtime
169
169
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.91",
3
+ "version": "3.0.95",
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": {