local-mcp 3.0.376 → 3.0.378

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.
Files changed (3) hide show
  1. package/download.js +31 -7
  2. package/index.js +34 -3
  3. package/package.json +1 -1
package/download.js CHANGED
@@ -145,15 +145,22 @@ function _resetWinHardwareIdCacheForTests() {
145
145
  _winHardwareIdCache = undefined
146
146
  }
147
147
 
148
- function _getMachineId() {
148
+ // deps existe SOLO como costura de test (#1705): los 8 tests de orden ejercitaban
149
+ // _probeWinHardwareId y el cache por separado, nunca los tres juntos por acá, asi
150
+ // que invertir la prioridad hardware-antes-que-cache —la decision irreversible de
151
+ // machineid.go— los pasaba a todos en verde. Sin argumentos se comporta igual que
152
+ // antes; ningun caller de produccion pasa deps.
153
+ function _getMachineId(deps = {}) {
154
+ const platform = deps.platform || process.platform
155
+ const cacheDir = deps.cacheDir || CACHE_DIR
149
156
  // QA override, checked before any platform probe — same rank as go-server's
150
157
  // Get() — so a smoke-* run stays a smoke-* run on every code path that
151
158
  // reports machine_id, not just the ones that already remembered to check it.
152
159
  const envId = (process.env.LMCP_MACHINE_ID || '').trim()
153
160
  if (envId) return envId
154
161
 
155
- const { execSync } = require('child_process')
156
- if (process.platform === 'darwin') {
162
+ const execSync = deps.execImpl || require('child_process').execSync
163
+ if (platform === 'darwin') {
157
164
  try {
158
165
  const r = execSync('security find-generic-password -s com.local-mcp.machine-id -a machine-id -w 2>/dev/null', { stdio: 'pipe' })
159
166
  const id = r.toString().trim()
@@ -164,17 +171,17 @@ function _getMachineId() {
164
171
  const match = ioreg.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/)
165
172
  if (match) return match[1]
166
173
  } catch {}
167
- } else if (process.platform === 'win32') {
168
- const hw = _probeWinHardwareId()
174
+ } else if (platform === 'win32') {
175
+ const hw = _probeWinHardwareId(execSync)
169
176
  if (hw) {
170
- _writeWinMachineIdCache(CACHE_DIR, hw)
177
+ _writeWinMachineIdCache(cacheDir, hw)
171
178
  return hw
172
179
  }
173
180
  // Both probes failed or are blocked — fall back to whatever the go-server
174
181
  // already cached rather than diverging to the hostname hash (the poisoned-
175
182
  // cache case go-server's own Get() guards against does not apply here:
176
183
  // this file is only ever written with a hardware-derived id, by either side).
177
- const cached = _readWinMachineIdCache(CACHE_DIR)
184
+ const cached = _readWinMachineIdCache(cacheDir)
178
185
  if (cached) return cached
179
186
  } else {
180
187
  try {
@@ -208,6 +215,22 @@ function versionFromArtifactUrl(url, arch) {
208
215
  return m ? m[1] : null
209
216
  }
210
217
 
218
+ // La version contra la que ESTA plataforma tiene que compararse, sacada del
219
+ // mismo manifiesto. Existe porque `info.version` es la linea de Mac: usarla en
220
+ // Windows/Linux compara dos series distintas, que es lo que fastPathVersionOK ya
221
+ // se niega a hacer en el fast path (#1394 §C).
222
+ //
223
+ // Devuelve null cuando no se puede determinar — sin artefacto para la
224
+ // plataforma, o con un nombre que no parsea. El llamador NO debe caer a
225
+ // info.version: preferimos no decidir antes que decidir sobre un numero que
226
+ // describe otra plataforma.
227
+ function latestVersionForPlatform(info, arch) {
228
+ if (!info) return null
229
+ if (String(arch).startsWith('darwin')) return info.version || null
230
+ const url = info[String(arch).replace('-', '_')]
231
+ return url ? versionFromArtifactUrl(url, arch) : null
232
+ }
233
+
211
234
  /**
212
235
  * Obtiene la versión más reciente del binario desde el backend.
213
236
  * Si esta máquina está en beta_machines, el backend retorna la versión beta.
@@ -700,6 +723,7 @@ async function ensureSlackProxy(platform = process.platform) {
700
723
 
701
724
  module.exports = {
702
725
  ensureBinary, ensureRuntime, ensureTray, ensureTeamsProxy, ensureSlackProxy, versionFromArtifactUrl,
726
+ latestVersionForPlatform, getArch,
703
727
  CACHE_DIR, TRAY_DIR, _getMachineId,
704
728
  _probeWinHardwareId, _resetWinHardwareIdCacheForTests,
705
729
  _readWinMachineIdCache, _writeWinMachineIdCache, WIN_MACHINE_ID_CACHE_NAME,
package/index.js CHANGED
@@ -62,6 +62,32 @@ function fastPathVersionOK(platform, cachedVersion, pkgVersion) {
62
62
  return true
63
63
  }
64
64
 
65
+ /**
66
+ * Whether the 24h background check should download a newer binary.
67
+ *
68
+ * fastPathVersionOK (arriba) declara que en Windows/Linux la version cacheada y
69
+ * `info.version` son series distintas por construccion, se niega a compararlas,
70
+ * y delega en "the 24h background check below". Ese chequeo hacia exactamente la
71
+ * comparacion declarada invalida: `semverGte(cachedVersion, info.version)`, con
72
+ * info.version = la release de MAC.
73
+ *
74
+ * Medido el 2026-08-27: las 21 Windows de la flota corrian 3.0.401 con 3.0.402
75
+ * publicado y Mac en 3.0.377. semverGte('3.0.401','3.0.377') es true, asi que
76
+ * ninguna se enteraba del update — en silencio. Y cuando la linea de Mac pasa a
77
+ * la de Windows, la misma cuenta se da vuelta y actualiza lo que ya esta al dia.
78
+ *
79
+ * Sin version resoluble para la plataforma NO se actualiza: fail-closed, el
80
+ * mismo criterio que download.js ("we FAIL rather than fall back to
81
+ * info.version").
82
+ */
83
+ function backgroundUpdateNeeded(cachedVersion, info, arch) {
84
+ if (!cachedVersion) return false
85
+ const { latestVersionForPlatform } = require('./download')
86
+ const latest = latestVersionForPlatform(info, arch)
87
+ if (!latest) return false
88
+ return !semverGte(cachedVersion, latest)
89
+ }
90
+
65
91
  /**
66
92
  * PIDs of the lmcp/local-mcp servers running right now, or null when the answer
67
93
  * cannot be determined.
@@ -557,8 +583,13 @@ async function main() {
557
583
  res.on('data', c => buf += c)
558
584
  res.on('end', () => {
559
585
  try {
560
- const { version: latestVersion } = JSON.parse(buf)
561
- if (latestVersion && !semverGte(_cachedVersion, latestVersion)) {
586
+ const info = JSON.parse(buf)
587
+ const { latestVersionForPlatform, getArch } = require('./download')
588
+ const _arch = getArch()
589
+ // El nombre que se escribe en .server-version si hay que bajar:
590
+ // el de ESTA plataforma, no el de la release de Mac.
591
+ const latestVersion = latestVersionForPlatform(info, _arch)
592
+ if (backgroundUpdateNeeded(_cachedVersion, info, _arch)) {
562
593
  // New version available — download for next restart only while no server is running.
563
594
  // Replacing stableBin mid-session changes its mtime and used to cause sibling
564
595
  // stdio proxies to be SIGTERM'd → Cursor transport_closed (LMCA-1044).
@@ -644,4 +675,4 @@ if (require.main === module) {
644
675
  })
645
676
  }
646
677
 
647
- module.exports = { semverGte, fastPathVersionOK, listRunningServerPids, countRunningServers, safeToReplaceBinary, replaceCachedBinary }
678
+ module.exports = { semverGte, fastPathVersionOK, backgroundUpdateNeeded, listRunningServerPids, countRunningServers, safeToReplaceBinary, replaceCachedBinary }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.376",
3
+ "version": "3.0.378",
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": {