local-mcp 3.0.92 → 3.0.96
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 +2 -5
- package/download.js +53 -38
- package/index.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LMCP
|
|
2
2
|
|
|
3
|
-
> Give your AI assistant native access to Mac apps —
|
|
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
|
[](https://www.npmjs.com/package/local-mcp)
|
|
6
6
|
[](https://local-mcp.com?ref=npm)
|
|
@@ -60,9 +60,8 @@ 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
|
-
##
|
|
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
|
|
|
@@ -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
|
@@ -15,7 +15,10 @@ const { execFileSync } = require('child_process')
|
|
|
15
15
|
const BACKEND_URL = 'https://office-mcp-production.up.railway.app'
|
|
16
16
|
const CACHE_DIR = path.join(os.homedir(), '.local', 'share', 'local-mcp', 'bin')
|
|
17
17
|
const TRAY_DIR = path.join(os.homedir(), '.local', 'share', 'local-mcp', 'tray') // metadata dir
|
|
18
|
-
|
|
18
|
+
// Install to ~/Applications (user-owned) to avoid macOS App Management TCC prompt.
|
|
19
|
+
// /Applications requires kTCCServiceAppManagement permission which shows "node would like
|
|
20
|
+
// to access data from other apps" on every tray update — confusing and unnecessary.
|
|
21
|
+
const TRAY_APP = path.join(os.homedir(), 'Applications', 'LocalMCPTray.app')
|
|
19
22
|
|
|
20
23
|
function _getMachineId() {
|
|
21
24
|
const { execSync } = require('child_process')
|
|
@@ -227,6 +230,12 @@ async function ensureTray() {
|
|
|
227
230
|
try { execFileSync('rm', ['-rf', oldTrayApp], { stdio: 'pipe' }) } catch { /* ignorar */ }
|
|
228
231
|
}
|
|
229
232
|
|
|
233
|
+
// Migrar instalación vieja de /Applications → ~/Applications (evita diálogo App Management)
|
|
234
|
+
const systemTrayApp = '/Applications/LocalMCPTray.app'
|
|
235
|
+
if (fs.existsSync(systemTrayApp) && trayApp !== systemTrayApp) {
|
|
236
|
+
try { execFileSync('rm', ['-rf', systemTrayApp], { stdio: 'pipe' }) } catch { /* ignorar */ }
|
|
237
|
+
}
|
|
238
|
+
|
|
230
239
|
// Ya instalado y actualizado
|
|
231
240
|
if (fs.existsSync(trayApp) && fs.existsSync(verFile)) {
|
|
232
241
|
if (fs.readFileSync(verFile, 'utf8').trim() === version) return trayApp
|
|
@@ -253,8 +262,12 @@ async function ensureTray() {
|
|
|
253
262
|
try { execFileSync('rm', ['-rf', trayApp], { stdio: 'pipe' }) } catch { /* ignorar */ }
|
|
254
263
|
}
|
|
255
264
|
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
// Crear ~/Applications si no existe (ubicación estándar para apps de usuario)
|
|
266
|
+
const userAppsDir = path.join(os.homedir(), 'Applications')
|
|
267
|
+
fs.mkdirSync(userAppsDir, { recursive: true })
|
|
268
|
+
|
|
269
|
+
process.stderr.write(` Instalando tray en ~/Applications...\n`)
|
|
270
|
+
execFileSync('tar', ['-xzf', tarPath, '-C', userAppsDir], { stdio: 'pipe' })
|
|
258
271
|
fs.unlinkSync(tarPath)
|
|
259
272
|
|
|
260
273
|
if (!fs.existsSync(trayApp)) {
|
|
@@ -311,72 +324,71 @@ function _writeTrayLaunchAgent(trayApp) {
|
|
|
311
324
|
}
|
|
312
325
|
|
|
313
326
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* @returns {Promise<string|null>}
|
|
327
|
+
* Downloads the slack-proxy co-process if not installed or on a different version.
|
|
328
|
+
* Binary is installed at ~/.local/share/local-mcp/bin/slack-proxy
|
|
329
|
+
* @returns {Promise<string|null>} Path to the binary, or null if it fails
|
|
317
330
|
*/
|
|
318
|
-
async function
|
|
331
|
+
async function ensureSlackProxy() {
|
|
319
332
|
const info = await getLatestBinary()
|
|
320
333
|
const version = info.version
|
|
321
334
|
const arch = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64'
|
|
322
|
-
const binPath = path.join(CACHE_DIR, '
|
|
323
|
-
const verFile = path.join(CACHE_DIR, '.
|
|
335
|
+
const binPath = path.join(CACHE_DIR, 'slack-proxy')
|
|
336
|
+
const verFile = path.join(CACHE_DIR, '.slack-proxy-version')
|
|
324
337
|
|
|
325
|
-
//
|
|
338
|
+
// Already installed and up to date
|
|
326
339
|
if (fs.existsSync(binPath) && fs.existsSync(verFile)) {
|
|
327
340
|
if (fs.readFileSync(verFile, 'utf8').trim() === version) return binPath
|
|
328
341
|
}
|
|
329
342
|
|
|
330
|
-
const url = `https://download.local-mcp.com/
|
|
331
|
-
process.stderr.write(`\
|
|
343
|
+
const url = `https://download.local-mcp.com/slack-proxy-${version}-${arch}.tar.gz`
|
|
344
|
+
process.stderr.write(`\nDownloading slack-proxy v${version}...\n`)
|
|
332
345
|
|
|
333
346
|
fs.mkdirSync(CACHE_DIR, { recursive: true })
|
|
334
|
-
const tarPath = path.join(CACHE_DIR, `
|
|
347
|
+
const tarPath = path.join(CACHE_DIR, `slack-proxy-${version}.tar.gz`)
|
|
335
348
|
|
|
336
349
|
try {
|
|
337
350
|
await downloadFile(url, tarPath)
|
|
338
351
|
const stat = fs.statSync(tarPath)
|
|
339
|
-
if (stat.size < 1000) { fs.unlinkSync(tarPath); throw new Error(`
|
|
352
|
+
if (stat.size < 1000) { fs.unlinkSync(tarPath); throw new Error(`Incomplete download (${stat.size} bytes)`) }
|
|
340
353
|
|
|
341
354
|
execFileSync('tar', ['-xzf', tarPath, '-C', CACHE_DIR], { stdio: 'pipe' })
|
|
342
355
|
fs.unlinkSync(tarPath)
|
|
343
356
|
|
|
344
|
-
if (!fs.existsSync(binPath)) throw new Error('
|
|
357
|
+
if (!fs.existsSync(binPath)) throw new Error('slack-proxy not found after extraction')
|
|
345
358
|
fs.chmodSync(binPath, 0o755)
|
|
346
|
-
try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.
|
|
359
|
+
try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.slack-proxy', binPath], { stdio: 'pipe' }) } catch { /* non-critical */ }
|
|
347
360
|
|
|
348
361
|
fs.writeFileSync(verFile, version, 'utf8')
|
|
349
|
-
process.stderr.write(`
|
|
362
|
+
process.stderr.write(` slack-proxy installed at ${binPath}\n`)
|
|
350
363
|
return binPath
|
|
351
364
|
} catch (err) {
|
|
352
365
|
try { if (fs.existsSync(tarPath)) fs.unlinkSync(tarPath) } catch {}
|
|
353
|
-
process.stderr.write(` (
|
|
366
|
+
process.stderr.write(` (slack-proxy not available: ${err.message})\n`)
|
|
354
367
|
return null
|
|
355
368
|
}
|
|
356
369
|
}
|
|
357
370
|
|
|
358
371
|
/**
|
|
359
|
-
* Downloads
|
|
360
|
-
*
|
|
372
|
+
* Downloads local-mcp-helper on first install only.
|
|
373
|
+
* CRITICAL: never replaces an existing helper binary — the helper owns
|
|
374
|
+
* Calendar/Contacts/Reminders TCC grants via its cdhash. Replacing the
|
|
375
|
+
* binary silently resets those grants and users would have to re-authorize.
|
|
361
376
|
* @returns {Promise<string|null>} Path to the binary, or null if it fails
|
|
362
377
|
*/
|
|
363
|
-
async function
|
|
378
|
+
async function ensureHelper() {
|
|
379
|
+
const binPath = path.join(CACHE_DIR, 'local-mcp-helper')
|
|
380
|
+
|
|
381
|
+
// Already installed — never replace. Stable cdhash = stable TCC grants.
|
|
382
|
+
if (fs.existsSync(binPath)) return binPath
|
|
383
|
+
|
|
364
384
|
const info = await getLatestBinary()
|
|
365
385
|
const version = info.version
|
|
366
386
|
const arch = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64'
|
|
367
|
-
const
|
|
368
|
-
|
|
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`)
|
|
387
|
+
const url = `https://download.local-mcp.com/local-mcp-helper-${version}-${arch}.tar.gz`
|
|
388
|
+
process.stderr.write(`\nDownloading local-mcp-helper v${version}...\n`)
|
|
377
389
|
|
|
378
390
|
fs.mkdirSync(CACHE_DIR, { recursive: true })
|
|
379
|
-
const tarPath = path.join(CACHE_DIR, `
|
|
391
|
+
const tarPath = path.join(CACHE_DIR, `local-mcp-helper-${version}.tar.gz`)
|
|
380
392
|
|
|
381
393
|
try {
|
|
382
394
|
await downloadFile(url, tarPath)
|
|
@@ -386,18 +398,21 @@ async function ensureSlackProxy() {
|
|
|
386
398
|
execFileSync('tar', ['-xzf', tarPath, '-C', CACHE_DIR], { stdio: 'pipe' })
|
|
387
399
|
fs.unlinkSync(tarPath)
|
|
388
400
|
|
|
389
|
-
if (!fs.existsSync(binPath)) throw new Error('
|
|
401
|
+
if (!fs.existsSync(binPath)) throw new Error('local-mcp-helper not found after extraction')
|
|
390
402
|
fs.chmodSync(binPath, 0o755)
|
|
391
|
-
|
|
403
|
+
// Do NOT codesign: the pre-built binary's cdhash must remain exactly as
|
|
404
|
+
// distributed. Codesigning a fresh binary produces the same cdhash only
|
|
405
|
+
// when the content is identical, but we want the CI-signed cdhash to be
|
|
406
|
+
// the canonical identity used in TCC grants.
|
|
392
407
|
|
|
393
|
-
fs.writeFileSync(
|
|
394
|
-
process.stderr.write(`
|
|
408
|
+
fs.writeFileSync(path.join(CACHE_DIR, '.helper-version'), version, 'utf8')
|
|
409
|
+
process.stderr.write(` local-mcp-helper installed at ${binPath}\n`)
|
|
395
410
|
return binPath
|
|
396
411
|
} catch (err) {
|
|
397
412
|
try { if (fs.existsSync(tarPath)) fs.unlinkSync(tarPath) } catch {}
|
|
398
|
-
process.stderr.write(` (
|
|
413
|
+
process.stderr.write(` (local-mcp-helper not available: ${err.message})\n`)
|
|
399
414
|
return null
|
|
400
415
|
}
|
|
401
416
|
}
|
|
402
417
|
|
|
403
|
-
module.exports = { ensureBinary, ensureRuntime, ensureTray, ensureTeamsProxy,
|
|
418
|
+
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,
|
|
139
|
+
const { ensureTray, ensureTeamsProxy, ensureHelper } = require('./download')
|
|
140
140
|
ensureTray().catch(() => {})
|
|
141
141
|
ensureTeamsProxy().catch(() => {})
|
|
142
|
-
|
|
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,
|
|
161
|
+
const { ensureRuntime, ensureTray, ensureTeamsProxy, ensureHelper } = require('./download')
|
|
162
162
|
|
|
163
|
-
//
|
|
163
|
+
// Update tray + proxies in background. Helper is downloaded only on first install.
|
|
164
164
|
ensureTray().catch(() => {})
|
|
165
165
|
ensureTeamsProxy().catch(() => {})
|
|
166
|
-
|
|
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.
|
|
3
|
+
"version": "3.0.96",
|
|
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": {
|