local-mcp 3.0.235 → 3.0.237
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/index.js +41 -0
- package/package.json +1 -1
- package/setup.js +55 -32
package/index.js
CHANGED
|
@@ -161,7 +161,26 @@ async function main() {
|
|
|
161
161
|
])
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// ADR-0004 phase 1: the notarized server embedded in LocalMCPTray.app, if present.
|
|
165
|
+
// Preferring it over the downloaded standalone binary (which gets re-signed
|
|
166
|
+
// ad-hoc on install) keeps Automation TCC cert-based — so Mail/Notes/Messages/
|
|
167
|
+
// Safari permissions survive client updates instead of resetting every time.
|
|
168
|
+
function findEmbeddedServer() {
|
|
169
|
+
if (process.platform !== 'darwin') return null
|
|
170
|
+
const candidates = [
|
|
171
|
+
path.join(os.homedir(), 'Applications', 'LocalMCPTray.app', 'Contents', 'MacOS', 'local-mcp-server'),
|
|
172
|
+
path.join('/Applications', 'LocalMCPTray.app', 'Contents', 'MacOS', 'local-mcp-server'),
|
|
173
|
+
]
|
|
174
|
+
for (const p of candidates) {
|
|
175
|
+
try { if (fs.existsSync(p) && fs.statSync(p).isFile()) return p } catch {}
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
function launchMcpStdio(binaryPath, version) {
|
|
181
|
+
// Prefer the embedded, notarized server when the .app provides it (ADR-0004).
|
|
182
|
+
const embedded = findEmbeddedServer()
|
|
183
|
+
if (embedded) binaryPath = embedded
|
|
165
184
|
if (!isMcpClientSpawn) {
|
|
166
185
|
printInstallComplete(version)
|
|
167
186
|
if (process.platform === 'darwin') {
|
|
@@ -209,6 +228,28 @@ async function main() {
|
|
|
209
228
|
}
|
|
210
229
|
}
|
|
211
230
|
|
|
231
|
+
// ADR-0004 phase 3 (darwin): prefer the notarized server embedded in
|
|
232
|
+
// LocalMCPTray.app. Ensure the .app is installed, then launch its embedded
|
|
233
|
+
// server directly — skipping the standalone download + ad-hoc re-sign that
|
|
234
|
+
// resets Automation TCC (Mail/Notes/Messages/Safari) on every update. Only
|
|
235
|
+
// if the .app can't be provided do we fall through to the legacy standalone
|
|
236
|
+
// binary (which still ad-hoc signs as a last resort so the user isn't stranded).
|
|
237
|
+
if (process.platform === 'darwin') {
|
|
238
|
+
await ensureDarwinComponents()
|
|
239
|
+
const embedded = findEmbeddedServer()
|
|
240
|
+
if (embedded) {
|
|
241
|
+
let v = pkg.version
|
|
242
|
+
try {
|
|
243
|
+
v = fs.readFileSync(
|
|
244
|
+
path.join(os.homedir(), '.local', 'share', 'local-mcp', 'tray', '.version'),
|
|
245
|
+
'utf8'
|
|
246
|
+
).trim() || pkg.version
|
|
247
|
+
} catch {}
|
|
248
|
+
launchMcpStdio(embedded, v)
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
212
253
|
// Fast path: if stable binary exists AND its version is >= our npm package version, exec directly.
|
|
213
254
|
// Using >= (not ===) prevents Claude Desktop from downgrading a binary that the tray's
|
|
214
255
|
// UpdateManager has already upgraded. LMC-375: machine 5F8445AE downgraded 3.0.72→3.0.70
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.237",
|
|
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": {
|
package/setup.js
CHANGED
|
@@ -369,41 +369,64 @@ async function runSetup(opts = {}) {
|
|
|
369
369
|
let stableCommand = NPX_COMMAND // fallback; replaced below once CACHE_DIR is known
|
|
370
370
|
let stableArgs = NPX_ARGS
|
|
371
371
|
let binaryVersion = ''
|
|
372
|
+
// ADR-0004 phase 3 (darwin): if the notarized server is already embedded in
|
|
373
|
+
// LocalMCPTray.app, skip pre-downloading + ad-hoc re-signing the standalone
|
|
374
|
+
// binary — the launcher (→ npx → index.js) prefers the embedded one anyway,
|
|
375
|
+
// and the ad-hoc copy is what resets Automation TCC. The tray is ensured later
|
|
376
|
+
// in setup; on a fresh install where the .app isn't present yet this is null
|
|
377
|
+
// and we keep the legacy pre-download as a fallback.
|
|
378
|
+
const _embeddedServer = process.platform === 'darwin'
|
|
379
|
+
? [
|
|
380
|
+
path.join(os.homedir(), 'Applications', 'LocalMCPTray.app', 'Contents', 'MacOS', 'local-mcp-server'),
|
|
381
|
+
path.join('/Applications', 'LocalMCPTray.app', 'Contents', 'MacOS', 'local-mcp-server'),
|
|
382
|
+
].find(p => { try { return fs.statSync(p).isFile() } catch { return false } })
|
|
383
|
+
: null
|
|
372
384
|
try {
|
|
373
385
|
const { ensureBinary, CACHE_DIR } = require('./download')
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// Copy binary to stable path (npx fast-path will exec it directly)
|
|
378
|
-
const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
|
|
379
|
-
try {
|
|
380
|
-
const tmpPath = stablePath + '.tmp'
|
|
381
|
-
fs.copyFileSync(binPath, tmpPath)
|
|
382
|
-
fs.chmodSync(tmpPath, 0o755)
|
|
383
|
-
try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
|
|
384
|
-
// curl route-3 installs the binary one level deeper:
|
|
385
|
-
// ~/.local/share/local-mcp/bin/local-mcp-server/local-mcp-server
|
|
386
|
-
// leaving stablePath itself as a DIRECTORY. fs.renameSync throws EISDIR
|
|
387
|
-
// silently, so the npm stable binary is never written and the old curl
|
|
388
|
-
// server stays running forever (LMC-158).
|
|
386
|
+
if (_embeddedServer) {
|
|
387
|
+
// Embedded server present — no standalone download needed. Still write the
|
|
388
|
+
// launcher and record the embedded version for the npx fast-path skip.
|
|
389
389
|
try {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
390
|
+
binaryVersion = fs.readFileSync(
|
|
391
|
+
path.join(os.homedir(), '.local', 'share', 'local-mcp', 'tray', '.version'), 'utf8'
|
|
392
|
+
).trim()
|
|
393
|
+
} catch {}
|
|
394
|
+
process.stderr.write('✓ Using server embedded in LocalMCPTray.app\n\n')
|
|
395
|
+
} else {
|
|
396
|
+
process.stderr.write('Downloading LMCP runtime...\n')
|
|
397
|
+
const { binPath, version: bv } = await ensureBinary()
|
|
398
|
+
binaryVersion = bv || ''
|
|
399
|
+
// Copy binary to stable path (npx fast-path will exec it directly)
|
|
400
|
+
const stablePath = path.join(CACHE_DIR, 'local-mcp-server')
|
|
401
|
+
try {
|
|
402
|
+
const tmpPath = stablePath + '.tmp'
|
|
403
|
+
fs.copyFileSync(binPath, tmpPath)
|
|
404
|
+
fs.chmodSync(tmpPath, 0o755)
|
|
405
|
+
try { execFileSync('codesign', ['--force', '--sign', '-', '--identifier', 'com.local-mcp.server', tmpPath], { stdio: 'pipe' }) } catch {}
|
|
406
|
+
// curl route-3 installs the binary one level deeper:
|
|
407
|
+
// ~/.local/share/local-mcp/bin/local-mcp-server/local-mcp-server
|
|
408
|
+
// leaving stablePath itself as a DIRECTORY. fs.renameSync throws EISDIR
|
|
409
|
+
// silently, so the npm stable binary is never written and the old curl
|
|
410
|
+
// server stays running forever (LMC-158).
|
|
411
|
+
try {
|
|
412
|
+
const st = fs.statSync(stablePath)
|
|
413
|
+
if (st.isDirectory()) {
|
|
414
|
+
process.stderr.write(' Migrating from curl install: removing old versioned directory at stable path\n')
|
|
415
|
+
fs.rmSync(stablePath, { recursive: true, force: true })
|
|
416
|
+
}
|
|
417
|
+
} catch { /* stablePath doesn't exist — nothing to do */ }
|
|
418
|
+
fs.renameSync(tmpPath, stablePath)
|
|
419
|
+
// Write the actual binary version (from /runtime/latest), not the npm package
|
|
420
|
+
// version. npm and Swift binary versions can diverge (npm-only fixes bump npm
|
|
421
|
+
// without a Swift rebuild). Writing pkg.version here caused the tray to think
|
|
422
|
+
// a newer binary was on disk and restart-loop (LMC-TODO).
|
|
423
|
+
fs.writeFileSync(path.join(CACHE_DIR, '.server-version'), binaryVersion || '')
|
|
424
|
+
const settingsSrc = path.join(path.dirname(binPath), 'settings.html')
|
|
425
|
+
const settingsDst = path.join(CACHE_DIR, 'settings.html')
|
|
426
|
+
if (fs.existsSync(settingsSrc)) fs.copyFileSync(settingsSrc, settingsDst)
|
|
427
|
+
} catch {}
|
|
428
|
+
process.stderr.write('✓ Runtime ready\n\n')
|
|
429
|
+
}
|
|
407
430
|
// Write the launcher script now that we have CACHE_DIR.
|
|
408
431
|
// The launcher uses curl (no Node dependency) to send telemetry if Node is
|
|
409
432
|
// too old, then exec's npx. Claude Desktop runs this script instead of npx
|