local-mcp 3.0.340 → 3.0.342
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/download.js +9 -3
- package/index.js +40 -14
- package/package.json +1 -1
package/download.js
CHANGED
|
@@ -325,9 +325,15 @@ async function ensureTray() {
|
|
|
325
325
|
return _ensureTrayWindows()
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
// Respect explicit uninstall — don't resurrect tray if user ran `uninstall
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
// Respect explicit uninstall — don't resurrect tray if user ran `uninstall`.
|
|
329
|
+
// Checks BOTH the runtime-dir copy and the durable App Support copy: a user who
|
|
330
|
+
// "cleaned up" with `rm -rf ~/.local/share/local-mcp` removes the runtime-dir copy,
|
|
331
|
+
// and only the durable one survives to stop a resurrection loop (FB-D1FA79).
|
|
332
|
+
const uninstalledMarkers = [
|
|
333
|
+
path.join(CACHE_DIR, '..', '.uninstalled'),
|
|
334
|
+
path.join(os.homedir(), 'Library', 'Application Support', 'Local MCP', '.uninstalled'),
|
|
335
|
+
]
|
|
336
|
+
if (uninstalledMarkers.some((p) => { try { return fs.existsSync(p) } catch { return false } })) return null
|
|
331
337
|
|
|
332
338
|
const info = await getLatestBinary()
|
|
333
339
|
const version = info.version
|
package/index.js
CHANGED
|
@@ -18,6 +18,38 @@ const os = require('os')
|
|
|
18
18
|
const fs = require('fs')
|
|
19
19
|
const https = require('https')
|
|
20
20
|
|
|
21
|
+
// ── Uninstall sentinels ───────────────────────────────────────────────────────
|
|
22
|
+
// A `.uninstalled` sentinel marks a machine the user removed, so no respawn (an
|
|
23
|
+
// AI client that still has `local-mcp` in its config re-spawning `npx local-mcp`)
|
|
24
|
+
// resurrects it. We keep TWO copies and treat EITHER as "uninstalled":
|
|
25
|
+
// 1. ~/.local/share/local-mcp/.uninstalled (runtime dir — legacy)
|
|
26
|
+
// 2. ~/Library/Application Support/Local MCP/.uninstalled (durable)
|
|
27
|
+
// #2 exists because all three guards (the two below + ensureTray in download.js)
|
|
28
|
+
// used to read ONLY the runtime-dir copy — so a user "cleaning up" with
|
|
29
|
+
// `rm -rf ~/.local/share/local-mcp` blinded every guard at once, and a surviving
|
|
30
|
+
// project-scoped `.mcp.json` (which no uninstaller can reach) kept reinstalling the
|
|
31
|
+
// machine (FB-D1FA79). The App Support copy survives that cleanup. A reinstall
|
|
32
|
+
// clears BOTH; an uninstall writes BOTH.
|
|
33
|
+
function uninstallSentinelPaths() {
|
|
34
|
+
const { CACHE_DIR } = require('./download')
|
|
35
|
+
const list = [path.join(path.dirname(CACHE_DIR), '.uninstalled')]
|
|
36
|
+
if (process.platform === 'darwin') {
|
|
37
|
+
list.push(path.join(os.homedir(), 'Library', 'Application Support', 'Local MCP', '.uninstalled'))
|
|
38
|
+
}
|
|
39
|
+
return list
|
|
40
|
+
}
|
|
41
|
+
function anyUninstallSentinel() {
|
|
42
|
+
return uninstallSentinelPaths().some((p) => { try { return fs.existsSync(p) } catch { return false } })
|
|
43
|
+
}
|
|
44
|
+
function writeUninstallSentinels() {
|
|
45
|
+
for (const p of uninstallSentinelPaths()) {
|
|
46
|
+
try { fs.mkdirSync(path.dirname(p), { recursive: true }) } catch {}
|
|
47
|
+
try { fs.writeFileSync(p, String(Date.now())) } catch {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function clearUninstallSentinels() {
|
|
51
|
+
for (const p of uninstallSentinelPaths()) { try { fs.unlinkSync(p) } catch {} }
|
|
52
|
+
}
|
|
21
53
|
|
|
22
54
|
// Platform support: macOS only (Windows/Linux on waitlist at local-mcp.com)
|
|
23
55
|
const SUPPORTED_PLATFORMS = ['darwin']
|
|
@@ -67,8 +99,7 @@ function detectManualUninstall() {
|
|
|
67
99
|
async function main() {
|
|
68
100
|
if (cmd === 'setup') {
|
|
69
101
|
// Clear uninstall sentinel — user explicitly wants to (re-)install
|
|
70
|
-
|
|
71
|
-
try { fs.unlinkSync(path.join(path.dirname(CACHE_DIR), '.uninstalled')) } catch {}
|
|
102
|
+
clearUninstallSentinels()
|
|
72
103
|
const { runSetup } = require('./setup')
|
|
73
104
|
await runSetup()
|
|
74
105
|
process.exit(0)
|
|
@@ -112,10 +143,9 @@ async function main() {
|
|
|
112
143
|
}
|
|
113
144
|
|
|
114
145
|
// Write sentinel BEFORE deleting cache dir, so it survives cache wipes.
|
|
115
|
-
// ensureTray() checks this and skips re-installation.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
try { fs.writeFileSync(path.join(sentinelDir, '.uninstalled'), String(Date.now())) } catch {}
|
|
146
|
+
// ensureTray() checks this and skips re-installation. Writes BOTH the runtime-dir
|
|
147
|
+
// copy and the durable App Support copy (see uninstallSentinelPaths).
|
|
148
|
+
writeUninstallSentinels()
|
|
119
149
|
|
|
120
150
|
if (fs.existsSync(CACHE_DIR)) {
|
|
121
151
|
fs.rmSync(CACHE_DIR, { recursive: true, force: true })
|
|
@@ -153,11 +183,10 @@ async function main() {
|
|
|
153
183
|
// measured 2026-06-12). ensureTray() already honored the sentinel, but the stdio
|
|
154
184
|
// server path never did. Refuse to start so a respawn can't resurrect the install.
|
|
155
185
|
// Legit reinstalls clear the sentinel first: `npx local-mcp setup` (above) and
|
|
156
|
-
// install.sh (curl/DMG, which rm's it before copying the bundle).
|
|
186
|
+
// install.sh (curl/DMG, which rm's it before copying the bundle). Checks BOTH the
|
|
187
|
+
// runtime-dir and durable App Support copies (see uninstallSentinelPaths).
|
|
157
188
|
{
|
|
158
|
-
|
|
159
|
-
const sentinel = path.join(path.dirname(_cacheDir), '.uninstalled')
|
|
160
|
-
if (fs.existsSync(sentinel)) {
|
|
189
|
+
if (anyUninstallSentinel()) {
|
|
161
190
|
process.stderr.write(
|
|
162
191
|
'[LMCP] Local MCP was uninstalled — not starting.\n' +
|
|
163
192
|
' Reinstall: npx local-mcp setup\n' +
|
|
@@ -176,11 +205,8 @@ async function main() {
|
|
|
176
205
|
// (FB-A30CDC). The AI client's stale MCP config entry is left alone; it just respawns us
|
|
177
206
|
// and we short-circuit here, which is harmless.
|
|
178
207
|
{
|
|
179
|
-
const { CACHE_DIR: _cacheDir2 } = require('./download')
|
|
180
208
|
if (detectManualUninstall()) {
|
|
181
|
-
|
|
182
|
-
try { fs.mkdirSync(sentinelDir, { recursive: true }) } catch {}
|
|
183
|
-
try { fs.writeFileSync(path.join(sentinelDir, '.uninstalled'), String(Date.now())) } catch {}
|
|
209
|
+
writeUninstallSentinels()
|
|
184
210
|
const trayPlist = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.local-mcp.tray.plist')
|
|
185
211
|
if (fs.existsSync(trayPlist)) {
|
|
186
212
|
try { execSync(`launchctl unload -w "${trayPlist}" 2>/dev/null`, { stdio: 'pipe' }) } catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.342",
|
|
4
4
|
"description": "Let ChatGPT, Claude, Cursor & any MCP client actually use your Mac — read & reply to email, manage your calendar, text over iMessage, find files, work with Teams, Slack & Office. On your Mac, no API keys, free.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|