local-mcp 3.0.92 → 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 +2 -5
- package/download.js +37 -35
- 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
|
@@ -311,72 +311,71 @@ function _writeTrayLaunchAgent(trayApp) {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* @returns {Promise<string|null>}
|
|
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
|
|
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, '
|
|
323
|
-
const verFile = path.join(CACHE_DIR, '.
|
|
322
|
+
const binPath = path.join(CACHE_DIR, 'slack-proxy')
|
|
323
|
+
const verFile = path.join(CACHE_DIR, '.slack-proxy-version')
|
|
324
324
|
|
|
325
|
-
//
|
|
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/
|
|
331
|
-
process.stderr.write(`\
|
|
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, `
|
|
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(`
|
|
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('
|
|
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.
|
|
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(`
|
|
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(` (
|
|
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
|
|
360
|
-
*
|
|
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
|
|
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
|
|
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`)
|
|
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, `
|
|
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('
|
|
388
|
+
if (!fs.existsSync(binPath)) throw new Error('local-mcp-helper not found after extraction')
|
|
390
389
|
fs.chmodSync(binPath, 0o755)
|
|
391
|
-
|
|
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(
|
|
394
|
-
process.stderr.write(`
|
|
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(` (
|
|
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,
|
|
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,
|
|
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.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": {
|