local-mcp 3.0.139 → 3.0.141
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/package.json +1 -1
- package/setup.js +99 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.141",
|
|
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
|
@@ -34,6 +34,19 @@ function _resolveNpxPath() {
|
|
|
34
34
|
// BEFORE Node loads our code (e.g. old nvm default in Claude Desktop's PATH).
|
|
35
35
|
// Returns the path to the launcher, or null if writing fails.
|
|
36
36
|
function _writeLaunchScript(npxAbsPath, cacheDir) {
|
|
37
|
+
// On Windows, don't write a bash launch script — use npx directly
|
|
38
|
+
if (_IS_WIN) {
|
|
39
|
+
try {
|
|
40
|
+
// Write a .cmd wrapper that Claude Desktop can execute
|
|
41
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
42
|
+
const cmdPath = path.join(cacheDir, 'lmcp-launch.cmd')
|
|
43
|
+
const script = `@echo off\r\n"${npxAbsPath}" -y local-mcp@latest %*\r\n`
|
|
44
|
+
fs.writeFileSync(cmdPath, script)
|
|
45
|
+
return cmdPath
|
|
46
|
+
} catch {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
37
50
|
try {
|
|
38
51
|
fs.mkdirSync(cacheDir, { recursive: true })
|
|
39
52
|
const launcherPath = path.join(cacheDir, 'lmcp-launch.sh')
|
|
@@ -71,13 +84,49 @@ const _IS_WIN = process.platform === 'win32'
|
|
|
71
84
|
const _IS_MAC = process.platform === 'darwin'
|
|
72
85
|
const _APPDATA = process.env.APPDATA || path.join(HOME, 'AppData', 'Roaming')
|
|
73
86
|
|
|
87
|
+
// Windows MSIX (Microsoft Store / WinGet) installs read config from a
|
|
88
|
+
// virtualized path inside %LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude.
|
|
89
|
+
// The non-MSIX (.exe) install reads from %APPDATA%\Claude.
|
|
90
|
+
// We detect which one exists and write to both if needed.
|
|
91
|
+
function _findMsixClaudePath() {
|
|
92
|
+
if (!_IS_WIN) return null
|
|
93
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(HOME, 'AppData', 'Local')
|
|
94
|
+
const packagesDir = path.join(localAppData, 'Packages')
|
|
95
|
+
try {
|
|
96
|
+
const entries = fs.readdirSync(packagesDir)
|
|
97
|
+
const claudePkg = entries.find(e => e.startsWith('Claude_'))
|
|
98
|
+
if (claudePkg) {
|
|
99
|
+
return path.join(packagesDir, claudePkg, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json')
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
74
105
|
function _claudeDesktopPath() {
|
|
75
|
-
if (_IS_WIN)
|
|
106
|
+
if (_IS_WIN) {
|
|
107
|
+
// Prefer MSIX path if the package exists
|
|
108
|
+
const msix = _findMsixClaudePath()
|
|
109
|
+
if (msix) return msix
|
|
110
|
+
return path.join(_APPDATA, 'Claude', 'claude_desktop_config.json')
|
|
111
|
+
}
|
|
76
112
|
if (_IS_MAC) return path.join(HOME, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
|
|
77
113
|
return path.join(HOME, '.config', 'Claude', 'claude_desktop_config.json')
|
|
78
114
|
}
|
|
79
115
|
|
|
116
|
+
// All paths where we should write the config (MSIX + non-MSIX on Windows)
|
|
117
|
+
function _claudeDesktopAllPaths() {
|
|
118
|
+
if (!_IS_WIN) return [_claudeDesktopPath()]
|
|
119
|
+
const paths = [path.join(_APPDATA, 'Claude', 'claude_desktop_config.json')]
|
|
120
|
+
const msix = _findMsixClaudePath()
|
|
121
|
+
if (msix && !paths.includes(msix)) paths.push(msix)
|
|
122
|
+
return paths
|
|
123
|
+
}
|
|
124
|
+
|
|
80
125
|
function _claudeDesktopDetect() {
|
|
126
|
+
if (_IS_WIN) {
|
|
127
|
+
// Check both MSIX and non-MSIX paths
|
|
128
|
+
return _claudeDesktopAllPaths().some(p => fs.existsSync(path.dirname(p)))
|
|
129
|
+
}
|
|
81
130
|
return fs.existsSync(path.dirname(_claudeDesktopPath()))
|
|
82
131
|
}
|
|
83
132
|
|
|
@@ -221,6 +270,11 @@ function _atomicWriteConfig(filePath, data) {
|
|
|
221
270
|
// Never overwrites a file whose JSON cannot be parsed.
|
|
222
271
|
|
|
223
272
|
function injectMcpConfig(client, command = NPX_COMMAND, args = NPX_ARGS) {
|
|
273
|
+
// On Windows, Claude Desktop needs cmd /c wrapper to find npx
|
|
274
|
+
if (_IS_WIN && client.id === 'claude-desktop') {
|
|
275
|
+
command = 'cmd'
|
|
276
|
+
args = ['/c', 'npx', '-y', 'local-mcp@latest']
|
|
277
|
+
}
|
|
224
278
|
// 1. Read existing config safely
|
|
225
279
|
const read = _safeReadConfig(client.cfgPath)
|
|
226
280
|
if (read.hadParseError) {
|
|
@@ -253,7 +307,14 @@ function injectMcpConfig(client, command = NPX_COMMAND, args = NPX_ARGS) {
|
|
|
253
307
|
}
|
|
254
308
|
|
|
255
309
|
// 3. Atomic write + verify
|
|
256
|
-
|
|
310
|
+
// On Windows, Claude Desktop MSIX reads from a different path than the .exe install.
|
|
311
|
+
// Write to all known paths so both install types work.
|
|
312
|
+
const allPaths = (client.id === 'claude-desktop' && _IS_WIN) ? _claudeDesktopAllPaths() : [client.cfgPath]
|
|
313
|
+
let write = { ok: false, error: 'no paths' }
|
|
314
|
+
for (const p of allPaths) {
|
|
315
|
+
fs.mkdirSync(path.dirname(p), { recursive: true })
|
|
316
|
+
write = _atomicWriteConfig(p, cfg)
|
|
317
|
+
}
|
|
257
318
|
return {
|
|
258
319
|
ok: write.ok,
|
|
259
320
|
stage: write.ok ? 'config_write' : 'config_write_failed',
|
|
@@ -453,35 +514,44 @@ async function runSetup(opts = {}) {
|
|
|
453
514
|
let emailPromptResult = 'not_shown_no_tty'
|
|
454
515
|
let emailPromptError = ''
|
|
455
516
|
try {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
517
|
+
let rl
|
|
518
|
+
let ttyFd = null
|
|
519
|
+
|
|
520
|
+
if (_IS_WIN) {
|
|
521
|
+
// Windows: use process.stdin directly (works in PowerShell and cmd)
|
|
522
|
+
rl = require('readline').createInterface({ input: process.stdin, output: process.stderr })
|
|
523
|
+
} else {
|
|
524
|
+
// macOS/Linux: open /dev/tty to read from terminal even when stdin is piped
|
|
525
|
+
ttyFd = fs.openSync('/dev/tty', 'r+')
|
|
526
|
+
const ttyIn = require('stream').Readable.from(
|
|
527
|
+
(function* () {
|
|
528
|
+
const buf = Buffer.alloc(256)
|
|
529
|
+
let line = ''
|
|
530
|
+
while (true) {
|
|
531
|
+
const n = fs.readSync(ttyFd, buf, 0, 1, null)
|
|
532
|
+
if (n === 0) break
|
|
533
|
+
const ch = buf.slice(0, n).toString()
|
|
534
|
+
if (ch === '\n' || ch === '\r') break
|
|
535
|
+
line += ch
|
|
536
|
+
}
|
|
537
|
+
yield line
|
|
538
|
+
})()
|
|
539
|
+
)
|
|
540
|
+
const ttyOut = new (require('stream').Writable)({
|
|
541
|
+
write(chunk, _enc, cb) { fs.writeSync(ttyFd, chunk); cb() },
|
|
542
|
+
})
|
|
543
|
+
rl = require('readline').createInterface({ input: ttyIn, output: ttyOut, terminal: true })
|
|
544
|
+
}
|
|
475
545
|
|
|
476
|
-
process.
|
|
546
|
+
process.stderr.write('\n Email for update notifications (optional, press Enter to skip): ')
|
|
477
547
|
emailPromptResult = 'shown'
|
|
478
548
|
const ans = await Promise.race([
|
|
479
549
|
new Promise(res => {
|
|
480
|
-
rl.once('line', a => { rl.close(); try { fs.closeSync(ttyFd) } catch {} ; res((a || '').trim()) })
|
|
550
|
+
rl.once('line', a => { rl.close(); if (ttyFd !== null) try { fs.closeSync(ttyFd) } catch {} ; res((a || '').trim()) })
|
|
481
551
|
}),
|
|
482
552
|
new Promise(res => setTimeout(() => {
|
|
483
553
|
try { rl.close() } catch {}
|
|
484
|
-
try { fs.closeSync(ttyFd) } catch {}
|
|
554
|
+
if (ttyFd !== null) try { fs.closeSync(ttyFd) } catch {}
|
|
485
555
|
res('')
|
|
486
556
|
}, 30000)),
|
|
487
557
|
])
|
|
@@ -530,7 +600,7 @@ async function runSetup(opts = {}) {
|
|
|
530
600
|
console.log('┌─────────────────────────────────────────────────────┐')
|
|
531
601
|
console.log('│ CURSOR — activate in 3 steps: │')
|
|
532
602
|
console.log('│ │')
|
|
533
|
-
console.log(
|
|
603
|
+
console.log(`│ 1. Quit Cursor completely (${_IS_WIN ? 'Alt+F4' : 'Cmd+Q'})${_IS_WIN ? ' ' : ' '}│`)
|
|
534
604
|
console.log('│ 2. Reopen Cursor │')
|
|
535
605
|
console.log('│ 3. Cursor Settings (⚙) → MCP → find "local-mcp" │')
|
|
536
606
|
console.log('│ → toggle it ON │')
|
|
@@ -547,7 +617,8 @@ async function runSetup(opts = {}) {
|
|
|
547
617
|
console.log('┌─────────────────────────────────────────────────────┐')
|
|
548
618
|
console.log('│ NEXT STEP — restart to activate: │')
|
|
549
619
|
console.log('│ │')
|
|
550
|
-
|
|
620
|
+
const quitKey = _IS_WIN ? 'Alt+F4' : 'Cmd+Q'
|
|
621
|
+
console.log(`│ 1. Quit ${primaryClient} completely (${quitKey})${''.padEnd(Math.max(0, 28 - primaryClient.length - quitKey.length))}│`)
|
|
551
622
|
console.log(`│ 2. Reopen ${primaryClient.padEnd(41)}│`)
|
|
552
623
|
console.log('│ │')
|
|
553
624
|
console.log('│ Then try: "Summarize my unread emails" │')
|
|
@@ -628,15 +699,14 @@ async function _installSlackProxy() {
|
|
|
628
699
|
}
|
|
629
700
|
|
|
630
701
|
async function _installTray() {
|
|
702
|
+
// Tray is macOS-only — skip on Windows/Linux
|
|
703
|
+
if (_IS_WIN || process.platform === 'linux') return
|
|
631
704
|
try {
|
|
632
705
|
const { ensureTray } = require('./download')
|
|
633
706
|
const trayApp = await ensureTray()
|
|
634
707
|
if (!trayApp) return // x64 — skip silencioso
|
|
635
708
|
console.log('\n✓ Tray installed — look for the LMCP icon in your menu bar\n')
|
|
636
|
-
// ensureTray() ya escribió el LaunchAgent y lo cargó con RunAtLoad=true
|
|
637
|
-
// No hace falta llamar open() por separado
|
|
638
709
|
} catch (err) {
|
|
639
|
-
// No fatal — el servidor MCP funciona igual sin el tray
|
|
640
710
|
process.stderr.write(` (Tray not available: ${err.message})\n\n`)
|
|
641
711
|
}
|
|
642
712
|
}
|