local-mcp 1.87.0 → 1.89.0

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 CHANGED
@@ -14,6 +14,7 @@ const { execFileSync } = require('child_process')
14
14
 
15
15
  const BACKEND_URL = 'https://office-mcp-production.up.railway.app'
16
16
  const CACHE_DIR = path.join(os.homedir(), '.local', 'share', 'local-mcp', 'bin')
17
+ const TRAY_DIR = path.join(os.homedir(), '.local', 'share', 'local-mcp', 'tray')
17
18
 
18
19
  function getArch() {
19
20
  const arch = process.arch
@@ -142,4 +143,85 @@ async function ensureRuntime() {
142
143
  return { pythonBin: binPath, serverPath: binPath, runtimeDir: versionDir, binPath }
143
144
  }
144
145
 
145
- module.exports = { ensureBinary, ensureRuntime, CACHE_DIR }
146
+ /**
147
+ * Descarga e instala el tray SwiftUI (menu bar app) — universal binary (arm64 + Intel).
148
+ * @returns {Promise<string|null>} Ruta al .app instalado, o null si falla
149
+ */
150
+ async function ensureTray() {
151
+
152
+ const info = await getLatestBinary()
153
+ const version = info.version
154
+ const trayApp = path.join(TRAY_DIR, 'LocalMCPTray.app')
155
+ const verFile = path.join(TRAY_DIR, '.version')
156
+
157
+ // Ya instalado y actualizado
158
+ if (fs.existsSync(trayApp) && fs.existsSync(verFile)) {
159
+ if (fs.readFileSync(verFile, 'utf8').trim() === version) return trayApp
160
+ }
161
+
162
+ const url = `https://download.local-mcp.com/local-mcp-tray-${version}-darwin-universal.tar.gz`
163
+ process.stderr.write(`\nDescargando tray v${version}...\n`)
164
+
165
+ fs.mkdirSync(TRAY_DIR, { recursive: true })
166
+ // Limpiar versión anterior si existe
167
+ if (fs.existsSync(trayApp)) {
168
+ try { execFileSync('rm', ['-rf', trayApp], { stdio: 'pipe' }) } catch { /* ignorar */ }
169
+ }
170
+ const tarPath = path.join(TRAY_DIR, `tray-${version}.tar.gz`)
171
+
172
+ await downloadFile(url, tarPath)
173
+
174
+ process.stderr.write(` Instalando tray...\n`)
175
+ execFileSync('tar', ['-xzf', tarPath, '-C', TRAY_DIR], { stdio: 'pipe' })
176
+ fs.unlinkSync(tarPath)
177
+
178
+ if (!fs.existsSync(trayApp)) {
179
+ throw new Error(`Tray no encontrado tras extraer: ${trayApp}`)
180
+ }
181
+
182
+ // Quitar quarantine + firmar ad-hoc
183
+ try { execFileSync('xattr', ['-rd', 'com.apple.quarantine', trayApp], { stdio: 'pipe' }) } catch { /* no crítico */ }
184
+ const trayBin = path.join(trayApp, 'Contents', 'MacOS', 'LocalMCPTray')
185
+ try { execFileSync('codesign', ['--force', '--sign', '-', trayBin], { stdio: 'pipe' }) } catch { /* no crítico */ }
186
+
187
+ fs.writeFileSync(verFile, version, 'utf8')
188
+
189
+ // LaunchAgent — auto-start en cada reboot de sesión
190
+ _writeTrayLaunchAgent(trayApp)
191
+
192
+ process.stderr.write(` Tray instalado en ${trayApp}\n`)
193
+ return trayApp
194
+ }
195
+
196
+ function _writeTrayLaunchAgent(trayApp) {
197
+ const trayBin = path.join(trayApp, 'Contents', 'MacOS', 'LocalMCPTray')
198
+ const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents')
199
+ const plistPath = path.join(plistDir, 'com.local-mcp.tray.plist')
200
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
201
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
202
+ <plist version="1.0">
203
+ <dict>
204
+ <key>Label</key>
205
+ <string>com.local-mcp.tray</string>
206
+ <key>ProgramArguments</key>
207
+ <array>
208
+ <string>${trayBin}</string>
209
+ </array>
210
+ <key>RunAtLoad</key>
211
+ <true/>
212
+ <key>KeepAlive</key>
213
+ <false/>
214
+ </dict>
215
+ </plist>
216
+ `
217
+ try {
218
+ fs.mkdirSync(plistDir, { recursive: true })
219
+ fs.writeFileSync(plistPath, plist, 'utf8')
220
+ // Recargar (unload silencioso si no estaba cargado)
221
+ try { execFileSync('launchctl', ['unload', plistPath], { stdio: 'pipe' }) } catch {}
222
+ execFileSync('launchctl', ['load', plistPath], { stdio: 'pipe' })
223
+ // launchctl load con RunAtLoad=true arranca el proceso — no necesitamos open()
224
+ } catch { /* no crítico */ }
225
+ }
226
+
227
+ module.exports = { ensureBinary, ensureRuntime, ensureTray, CACHE_DIR, TRAY_DIR }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "1.87.0",
3
+ "version": "1.89.0",
4
4
  "description": "MCP server for macOS \u2014 connect Claude Desktop, Cursor, Windsurf to Mail, Calendar, Contacts, Teams, OneDrive. Privacy-first: all data stays on your Mac.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/postinstall.js CHANGED
@@ -17,8 +17,7 @@ if (process.platform !== 'darwin') {
17
17
  const { runSetup } = require('./setup')
18
18
 
19
19
  runSetup({ all: true }).catch(err => {
20
- // No fallar la instalación si el setup falla
21
- process.stderr.write(`\nAviso: setup automático falló: ${err.message}\n`)
22
- process.stderr.write('Ejecutá "local-mcp setup" manualmente para configurar tus clientes.\n\n')
20
+ process.stderr.write(`\nSetup failed: ${err.message}\n`)
21
+ process.stderr.write('Run manually: npx -y local-mcp@latest setup\n\n')
23
22
  process.exit(0)
24
23
  })
package/setup.js CHANGED
@@ -172,13 +172,28 @@ async function runSetup(opts = {}) {
172
172
 
173
173
  console.log('MCP config written:')
174
174
  console.log(JSON.stringify({ mcpServers: { 'local-mcp': { command: NPX_COMMAND, args: NPX_ARGS } } }, null, 2))
175
- console.log('\nOptional — full app with tray menu bar:')
176
- console.log(' curl -fsSL https://local-mcp.com/install | bash\n')
175
+
176
+ // Instalar y lanzar el tray (menu bar app) arm64 only, non-fatal
177
+ await _installTray()
177
178
 
178
179
  // Ping de tracking (fire-and-forget, no bloquea)
179
180
  _pingInstall(configured, opts.method || 'setup')
180
181
  }
181
182
 
183
+ async function _installTray() {
184
+ try {
185
+ const { ensureTray } = require('./download')
186
+ const trayApp = await ensureTray()
187
+ if (!trayApp) return // x64 — skip silencioso
188
+ console.log('\n✓ Tray installed — look for the Local MCP icon in your menu bar\n')
189
+ // ensureTray() ya escribió el LaunchAgent y lo cargó con RunAtLoad=true
190
+ // No hace falta llamar open() por separado
191
+ } catch (err) {
192
+ // No fatal — el servidor MCP funciona igual sin el tray
193
+ process.stderr.write(` (Tray not available: ${err.message})\n\n`)
194
+ }
195
+ }
196
+
182
197
  function _getMachineId() {
183
198
  try {
184
199
  // Try reading from macOS Keychain (same as server.py)