local-mcp 3.0.413 → 3.0.414
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 -3
- package/download.js +93 -29
- package/package.json +4 -4
- package/setup.js +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Local MCP
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Use your Mac from ChatGPT & Claude.ai on the web** — plus 191+ local tools for Claude Desktop, Cursor, Windsurf, VS Code & Zed. Connect any AI to Mail, Calendar, Contacts, iMessage, Teams, Slack, WhatsApp, Signal, OneDrive, Google Drive, Microsoft 365, Notes, Reminders, OmniFocus, Word/Excel/PowerPoint and more. Runs on your computer, no API keys, free — local tools read your data on your machine, and your AI provider receives the content needed for each request.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/local-mcp)
|
|
6
6
|
[](https://local-mcp.com?ref=npm)
|
|
7
7
|
[](https://local-mcp.com?ref=npm)
|
|
8
|
-
[](https://local-mcp.com/en/privacy)
|
|
9
9
|
[](https://glama.ai/mcp/servers/lanchuske/local-mcp-releases?ref=npm)
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Runs on your computer. No tokens. No API keys. Free.**
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
@@ -71,6 +71,7 @@ LMCP runs a native MCP server that bridges your Mac apps to any AI client:
|
|
|
71
71
|
| **NordVPN** | Status, server recommendations, diagnostics |
|
|
72
72
|
| **Image Playground** | Generate images on-device from a text prompt — Apple Intelligence, no cloud, no API key (requires macOS 15.4+) |
|
|
73
73
|
| **Skills / Recipes** | Save a working multi-tool flow as a reusable skill, rerun it, **export to share with others & import theirs**, see run history, manage in Settings → Skills |
|
|
74
|
+
| **Shortcuts** | List, run (preview → confirm), and inspect the user's own macOS Shortcuts — HomeKit, Focus modes, and any personal automation |
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
|
|
@@ -134,6 +135,8 @@ Agent mesh (9): `agent_mesh_status` `agent_mesh_create` `agent_mesh_invite` `age
|
|
|
134
135
|
|
|
135
136
|
System (12): `lmcp_state` `get_config` `get_datetime` `daily_brief` `run_diagnostics` `get_audit_log` `lmcp_install_upgrade` `lmcp_upgrade_diagnostics` `report_problem` `request_feature` `report_friction` `run_terminal_command`
|
|
136
137
|
|
|
138
|
+
Shortcuts (3): `shortcuts_list` `shortcuts_run` `shortcuts_view`
|
|
139
|
+
|
|
137
140
|
_Coming soon (not included in the 162 count above): **Evernote** (support is being rebuilt)._
|
|
138
141
|
|
|
139
142
|
|
package/download.js
CHANGED
|
@@ -543,17 +543,86 @@ function _isUsableTrayBundle(trayApp) {
|
|
|
543
543
|
catch { return false }
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
// Version compare mirroring mac-swift/Sources/LocalMCPServer/Version.swift's isNewer —
|
|
547
|
+
// #3599 criterio 6: un mismo criterio "más nuevo gana" en los dos lados, cada uno probado
|
|
548
|
+
// por separado (no hay código compartido entre Swift y Node). Incluye el mismo desempate
|
|
549
|
+
// que Version.swift: en un empate numérico, un release gana a un pre-release
|
|
550
|
+
// ("3.0.297" > "3.0.297-test").
|
|
551
|
+
function _versionIsNewer(a, b) {
|
|
552
|
+
const parse = (v) => {
|
|
553
|
+
const s = String(v || '')
|
|
554
|
+
const nums = s.split(/[-+]/, 1)[0].split('.').map((n) => parseInt(n, 10) || 0)
|
|
555
|
+
return { nums, isPrerelease: s.includes('-') || s.includes('+') }
|
|
556
|
+
}
|
|
557
|
+
const pa = parse(a), pb = parse(b)
|
|
558
|
+
for (let i = 0; i < Math.max(pa.nums.length, pb.nums.length); i++) {
|
|
559
|
+
const x = pa.nums[i] || 0, y = pb.nums[i] || 0
|
|
560
|
+
if (x !== y) return x > y
|
|
561
|
+
}
|
|
562
|
+
if (pa.isPrerelease !== pb.isPrerelease) return !pa.isPrerelease && pb.isPrerelease
|
|
563
|
+
return false
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const _CFBUNDLE_VERSION_RE = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]*)<\/string>/
|
|
567
|
+
|
|
568
|
+
// CFBundleShortVersionString of an installed .app, or null when unreadable. Tries a plain
|
|
569
|
+
// UTF8 text read FIRST — an xml1 plist (what every test fixture here writes, and what some
|
|
570
|
+
// build toolchains leave on disk) needs no external tool and reads on ANY platform, unlike
|
|
571
|
+
// `plutil` (macOS-only, absent on the ubuntu-latest runner these tests also run on, #3599
|
|
572
|
+
// review). Only reaches for `plutil -convert xml1` when the direct read finds no match —
|
|
573
|
+
// the real fallback for a genuinely binary-encoded Info.plist.
|
|
574
|
+
function _bundleVersion(appPath, exec = execFileSync) {
|
|
575
|
+
const infoPath = path.join(appPath, 'Contents', 'Info.plist')
|
|
576
|
+
try {
|
|
577
|
+
const m = _CFBUNDLE_VERSION_RE.exec(fs.readFileSync(infoPath, 'utf8'))
|
|
578
|
+
if (m) return m[1]
|
|
579
|
+
} catch { /* missing, unreadable, or not text — fall through to plutil */ }
|
|
580
|
+
try {
|
|
581
|
+
const xml = exec('/usr/bin/plutil', ['-convert', 'xml1', '-o', '-', infoPath], { stdio: 'pipe' }).toString()
|
|
582
|
+
const m = _CFBUNDLE_VERSION_RE.exec(xml)
|
|
583
|
+
return m ? m[1] : null
|
|
584
|
+
} catch {
|
|
585
|
+
return null
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Whether a LocalMCPTray process is running FROM this exact bundle path — matched on the
|
|
590
|
+
// full executable path so a sibling copy elsewhere never counts. Unreadable (pgrep
|
|
591
|
+
// missing/blocked) fails CLOSED to "running": a guard that cannot see must not say "safe
|
|
592
|
+
// to delete" (same rule as index.js's countRunningServers, #1394 §C).
|
|
593
|
+
function _isTrayRunningFrom(appPath, exec = execFileSync) {
|
|
594
|
+
const bin = path.join(appPath, 'Contents', 'MacOS', 'LocalMCPTray')
|
|
595
|
+
try {
|
|
596
|
+
exec('pgrep', ['-f', bin], { stdio: 'pipe' })
|
|
597
|
+
return true // exit 0: matched — a tray IS running from this bundle
|
|
598
|
+
} catch (err) {
|
|
599
|
+
if (err && err.status === 1) return false // ran fine, matched nothing
|
|
600
|
+
return true // could not tell — fail closed
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
546
604
|
// Retira las copias que `trayApp` supersede — SOLO si `trayApp` ya tiene un bundle usable.
|
|
547
605
|
// Sin esa guarda, la migración borra la única copia que el Mac tiene antes de que exista su
|
|
548
606
|
// reemplazo, y una descarga fallida deja la máquina sin cliente (#2654, hermano de #2643).
|
|
549
|
-
//
|
|
550
|
-
|
|
607
|
+
// #3599 AC5: tampoco retira una copia cuya versión no se pueda leer, ni una LEÍBLEMENTE más
|
|
608
|
+
// nueva que `trayApp`, ni una que un proceso vivo esté sirviendo — mismo criterio fail-closed
|
|
609
|
+
// que el lado Swift (TrayInstallHealth.shouldRemoveOtherCopy): no haber podido medir no es
|
|
610
|
+
// haber medido que es seguro borrar. Devuelve lo retirado para que los tests puedan afirmar
|
|
611
|
+
// sobre el efecto, no sobre el orden.
|
|
612
|
+
function _retireSupersededTrays(trayApp, supersededPaths, rm = _rmrf,
|
|
613
|
+
bundleVersion = _bundleVersion,
|
|
614
|
+
isTrayRunningFrom = _isTrayRunningFrom) {
|
|
551
615
|
if (!trayApp || !_isUsableTrayBundle(trayApp)) return []
|
|
616
|
+
const trayVersion = bundleVersion(trayApp)
|
|
552
617
|
const retired = []
|
|
553
618
|
for (const p of supersededPaths) {
|
|
554
619
|
if (!p) continue
|
|
555
620
|
if (_sameLocation(p, trayApp)) continue
|
|
556
621
|
if (!fs.existsSync(p)) continue
|
|
622
|
+
const otherVersion = bundleVersion(p)
|
|
623
|
+
if (!trayVersion || !otherVersion) continue // unreadable — fail closed, keep it
|
|
624
|
+
if (_versionIsNewer(otherVersion, trayVersion)) continue
|
|
625
|
+
if (isTrayRunningFrom(p)) continue
|
|
557
626
|
try { rm(p); retired.push(p) } catch { /* ignorar */ }
|
|
558
627
|
}
|
|
559
628
|
return retired
|
|
@@ -588,6 +657,8 @@ async function ensureTray(deps = {}) {
|
|
|
588
657
|
extract = extractTar,
|
|
589
658
|
writeLaunchAgent = _writeTrayLaunchAgent,
|
|
590
659
|
isTTY = () => process.stdin.isTTY,
|
|
660
|
+
bundleVersion = _bundleVersion,
|
|
661
|
+
isTrayRunningFrom = _isTrayRunningFrom,
|
|
591
662
|
} = deps
|
|
592
663
|
|
|
593
664
|
// Windows/Linux: the tray + background daemon are installed by the native
|
|
@@ -608,36 +679,28 @@ async function ensureTray(deps = {}) {
|
|
|
608
679
|
]
|
|
609
680
|
if (markers.some((p) => { try { return fs.existsSync(p) } catch { return false } })) return null
|
|
610
681
|
|
|
682
|
+
// #3627: a usable tray ANYWHERE a user would look (/Applications or ~/Applications) is this
|
|
683
|
+
// wrapper's stop condition, at ANY version — keeping it current from here on is the tray's
|
|
684
|
+
// own job (UpdateManager: staged, verified, self-update). This used to fall through to a
|
|
685
|
+
// download+"migrate" that moved a same-version /Applications copy to ~/Applications and
|
|
686
|
+
// deleted the original; on the published 3.0.413 wrapper (no version/process guard on the
|
|
687
|
+
// delete) that reproduced with a fresh DMG install too, not just the QA prerelease that
|
|
688
|
+
// surfaced it (#3627, #3599). A bundle that exists but fails `_isUsableTrayBundle` (a
|
|
689
|
+
// partial extraction, #1338) is NOT covered — that still needs repairing below.
|
|
690
|
+
if (_isUsableTrayBundle(trayApp)) return trayApp
|
|
691
|
+
if (_isUsableTrayBundle(systemTrayApp)) return systemTrayApp
|
|
692
|
+
|
|
611
693
|
const info = await getLatest()
|
|
612
694
|
const version = info.version
|
|
613
695
|
const verFile = path.join(trayDir, '.version')
|
|
614
696
|
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
// camino que descarga: ahí una falla dejaba la máquina sin cliente. (#2654)
|
|
697
|
+
// La migración legacy (~/.local/share → ~/Applications) retira una copia que puede ser la
|
|
698
|
+
// ÚNICA del Mac — nunca en el camino que descarga: ahí una falla dejaba la máquina sin
|
|
699
|
+
// cliente. (#2654) Reached only when neither main location has a usable bundle (guard
|
|
700
|
+
// above), so `systemTrayApp` in this list is a stale/broken leftover, never a live one.
|
|
620
701
|
const superseded = [path.join(trayDir, 'LocalMCPTray.app'), systemTrayApp]
|
|
621
|
-
const retireSuperseded = () =>
|
|
622
|
-
|
|
623
|
-
// Ya instalado y actualizado. "Instalado" es el bundle USABLE, no el directorio: si no,
|
|
624
|
-
// una extracción a medias se early-returnea para siempre y nadie la repara (#1338).
|
|
625
|
-
if (_isUsableTrayBundle(trayApp) && fs.existsSync(verFile)) {
|
|
626
|
-
if (fs.readFileSync(verFile, 'utf8').trim() === version) {
|
|
627
|
-
retireSuperseded()
|
|
628
|
-
return trayApp
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// In an active MCP session (non-TTY) the tray is running and self-updates via UpdateManager.
|
|
633
|
-
// Downloading a new tray bundle and rm-rf'ing the old one mid-session kills the Unix socket
|
|
634
|
-
// that local-mcp-server uses, crashing the session. Skip the update; it'll apply next login.
|
|
635
|
-
// Un bundle sin ejecutable no tiene tray corriendo que esta descarga pueda romper, así que
|
|
636
|
-
// ese caso NO se early-returnea: se repara.
|
|
637
|
-
if (!isTTY() && _isUsableTrayBundle(trayApp)) {
|
|
638
|
-
retireSuperseded()
|
|
639
|
-
return trayApp
|
|
640
|
-
}
|
|
702
|
+
const retireSuperseded = () =>
|
|
703
|
+
_retireSupersededTrays(trayApp, superseded, _rmrf, bundleVersion, isTrayRunningFrom)
|
|
641
704
|
|
|
642
705
|
const url = `https://download.local-mcp.com/local-mcp-tray-${version}-darwin-universal.tar.gz`
|
|
643
706
|
process.stderr.write(`\nDescargando tray v${version}...\n`)
|
|
@@ -668,8 +731,8 @@ async function ensureTray(deps = {}) {
|
|
|
668
731
|
extract(tarPath, userAppsDir)
|
|
669
732
|
fs.unlinkSync(tarPath)
|
|
670
733
|
|
|
671
|
-
// Mismo criterio que usa el
|
|
672
|
-
//
|
|
734
|
+
// Mismo criterio que usa el guard de arriba y el retiro — un directorio sin ejecutable no
|
|
735
|
+
// cuenta como instalado, o `verFile` (abajo) queda apuntando a un bundle que no sirve.
|
|
673
736
|
if (!_isUsableTrayBundle(trayApp)) {
|
|
674
737
|
throw new Error(`Tray no usable tras extraer: ${trayApp}`)
|
|
675
738
|
}
|
|
@@ -794,4 +857,5 @@ module.exports = {
|
|
|
794
857
|
_probeWinHardwareId, _resetWinHardwareIdCacheForTests,
|
|
795
858
|
_readWinMachineIdCache, _writeWinMachineIdCache, WIN_MACHINE_ID_CACHE_NAME,
|
|
796
859
|
_retireSupersededTrays, _isUsableTrayBundle, SYSTEM_TRAY_APP,
|
|
860
|
+
_bundleVersion, _isTrayRunningFrom, _versionIsNewer,
|
|
797
861
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.414",
|
|
4
4
|
"description": "Let ChatGPT, Claude, Cursor & any MCP client actually use your Mac or Windows PC — read & reply to email, manage your calendar, text over iMessage, find files, work with Teams, Slack & Office. Runs locally, no API keys, free.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -64,14 +64,14 @@
|
|
|
64
64
|
"automation"
|
|
65
65
|
],
|
|
66
66
|
"author": "LMCP <hello@local-mcp.com>",
|
|
67
|
-
"license": "
|
|
67
|
+
"license": "UNLICENSED",
|
|
68
68
|
"homepage": "https://local-mcp.com",
|
|
69
69
|
"repository": {
|
|
70
70
|
"type": "git",
|
|
71
|
-
"url": "https://github.com/
|
|
71
|
+
"url": "https://github.com/lanchuske/local-mcp-releases.git"
|
|
72
72
|
},
|
|
73
73
|
"bugs": {
|
|
74
|
-
"url": "https://github.com/
|
|
74
|
+
"url": "https://github.com/lanchuske/local-mcp-releases/issues"
|
|
75
75
|
},
|
|
76
76
|
"mcpName": "com.local-mcp/local-mcp"
|
|
77
77
|
}
|
package/setup.js
CHANGED
|
@@ -530,7 +530,7 @@ async function runSetup(opts = {}) {
|
|
|
530
530
|
if (configured.length > 0) {
|
|
531
531
|
if (healthOk) {
|
|
532
532
|
console.log(`✅ LMCP configured for: ${configured.join(', ')}\n`)
|
|
533
|
-
console.log(' ✓ Server binary verified —
|
|
533
|
+
console.log(' ✓ Server binary verified — 191+ tools ready\n')
|
|
534
534
|
} else {
|
|
535
535
|
console.log(`⚠ LMCP configured for: ${configured.join(', ')}`)
|
|
536
536
|
console.log(' Server binary could not be verified — it may still work after restart.\n')
|