@saulwade/swl-ses 1.4.1 → 1.5.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.
Files changed (136) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +561 -560
  3. package/agentes/nemesis-auditor-swl.md +161 -161
  4. package/bin/swl-mcp-server.js +49 -22
  5. package/bin/swl-ses.js +74 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/contribuir.md +233 -233
  8. package/comandos/swl/ejecutar-fase.md +33 -4
  9. package/comandos/swl/metricas.md +72 -0
  10. package/comandos/swl/nemesis.md +122 -122
  11. package/gateway/lib/event-channel.js +191 -191
  12. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  13. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  14. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  15. package/habilidades/discutir-fase/SKILL.md +50 -2
  16. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  17. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  18. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -0
  19. package/habilidades/eval-framework/SKILL.md +212 -212
  20. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  21. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  22. package/habilidades/harness-claude-code/SKILL.md +299 -299
  23. package/habilidades/infra-github-actions/SKILL.md +166 -166
  24. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  25. package/habilidades/manejo-errores/.evolved.json +8 -8
  26. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  27. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  28. package/habilidades/patrones-python/SKILL.md +229 -229
  29. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  30. package/habilidades/planear-fase/SKILL.md +319 -319
  31. package/habilidades/protocolo-revision-swl/SKILL.md +276 -0
  32. package/habilidades/release-semver/.evolved.json +8 -8
  33. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  34. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  35. package/habilidades/testing-python/SKILL.md +340 -340
  36. package/habilidades/verificar-trabajo/SKILL.md +49 -5
  37. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  38. package/hooks/claudemd-bloat-detector.js +161 -161
  39. package/hooks/lib/agent-routing.js +107 -107
  40. package/hooks/lib/auto-consolidator.js +335 -335
  41. package/hooks/lib/error-classifier.js +308 -308
  42. package/hooks/lib/merkle-audit.js +96 -96
  43. package/hooks/lib/provenance-tracker.js +191 -191
  44. package/hooks/lib/rate-limit-tracker.js +253 -253
  45. package/hooks/lib/resource-quota.js +122 -122
  46. package/hooks/lib/retry-jitter.js +165 -165
  47. package/hooks/lib/security-net.js +201 -201
  48. package/hooks/lib/skill-auditor.js +588 -588
  49. package/hooks/lib/sync-status.js +228 -228
  50. package/hooks/lib/taint-tracker.js +107 -107
  51. package/hooks/lib/text-similarity.js +241 -241
  52. package/hooks/lib/toon-compressor.js +245 -245
  53. package/hooks/registro-turnos.js +209 -209
  54. package/hooks/sugerir-regenerar-inventario.js +170 -170
  55. package/hooks/validar-formato-post-subagente.js +140 -140
  56. package/hooks/validar-memoria-hook.js +218 -218
  57. package/instintos/prompt-appendices.yaml +57 -57
  58. package/manifiestos/agent-output-schemas.json +57 -57
  59. package/manifiestos/modulos.json +1321 -1262
  60. package/manifiestos/perfiles.json +2 -1
  61. package/manifiestos/skills-lock.json +1114 -1114
  62. package/package.json +3 -3
  63. package/plantillas/auditor-veto-template.md +105 -105
  64. package/plantillas/github-workflows/README.md +47 -47
  65. package/plantillas/github-workflows/release-please.yml +44 -44
  66. package/plantillas/github-workflows/swl-ci.yml +107 -107
  67. package/plantillas/github-workflows/swl-security.yml +51 -51
  68. package/plugin.json +351 -343
  69. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  70. package/reglas/arreglar-al-detectar.md +147 -147
  71. package/reglas/fragmentos-compartidos.md +152 -152
  72. package/reglas/harness-claude-code.md +213 -213
  73. package/reglas/usar-context7.md +226 -226
  74. package/schemas/diary-entry.schema.json +80 -80
  75. package/scripts/audit-tools/audit-history.js +330 -330
  76. package/scripts/audit-tools/bundle-tracker.js +290 -290
  77. package/scripts/audit-tools/canary-monitor.js +352 -352
  78. package/scripts/audit-tools/code-profiler.js +605 -605
  79. package/scripts/audit-tools/dep-doctor.js +320 -320
  80. package/scripts/audit-tools/env-validator.js +206 -206
  81. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  82. package/scripts/audit-tools/lib/output.js +23 -23
  83. package/scripts/audit-tools/migration-checker.js +392 -392
  84. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  85. package/scripts/benchmark-memoria.js +167 -167
  86. package/scripts/configurar-branch-protection.js +418 -418
  87. package/scripts/derivar-feature-list.js +489 -0
  88. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  89. package/scripts/doctor.js +31 -4
  90. package/scripts/field-report.js +199 -199
  91. package/scripts/generar-checklists-consolidados.js +273 -273
  92. package/scripts/generar-inventario.js +420 -420
  93. package/scripts/generar-matriz-lenguajes.js +271 -271
  94. package/scripts/instalador.js +56 -5
  95. package/scripts/lib/artefactos-python.js +43 -43
  96. package/scripts/lib/benchmark-metrics.js +160 -160
  97. package/scripts/lib/budget-enforcer.js +252 -252
  98. package/scripts/lib/configurar-ci.js +380 -380
  99. package/scripts/lib/contadores-inventario.js +217 -217
  100. package/scripts/lib/detectar-runtime.js +75 -9
  101. package/scripts/lib/detectar-stack-detallado.js +307 -307
  102. package/scripts/lib/diary-entry.js +234 -234
  103. package/scripts/lib/estado.js +13 -1
  104. package/scripts/lib/eval-metrics-store.js +218 -218
  105. package/scripts/lib/eval-quality.js +171 -171
  106. package/scripts/lib/eval-schemas.js +144 -144
  107. package/scripts/lib/eval-self-correct.js +106 -106
  108. package/scripts/lib/eval-validator.js +185 -185
  109. package/scripts/lib/expandir-targets.js +71 -0
  110. package/scripts/lib/jaccard-similarity.js +98 -98
  111. package/scripts/lib/longmemeval-runner.js +125 -125
  112. package/scripts/lib/manifiestos.js +42 -1
  113. package/scripts/lib/npm-version.js +261 -261
  114. package/scripts/lib/paquetes-conocidos.js +50 -50
  115. package/scripts/lib/parsear-opciones.js +3 -0
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/lib/toml-merge.js +204 -0
  121. package/scripts/lib/transformadores/base.js +43 -9
  122. package/scripts/lib/transformadores/codex.js +375 -115
  123. package/scripts/lib/transformadores/cursor.js +359 -0
  124. package/scripts/lib/transformadores/index.js +2 -0
  125. package/scripts/limpiar-artefactos-python.js +131 -131
  126. package/scripts/mcp-server/README.md +122 -80
  127. package/scripts/mcp-server/auth.js +105 -0
  128. package/scripts/mcp-server/cache.js +106 -0
  129. package/scripts/mcp-server/handlers.js +386 -206
  130. package/scripts/mcp-server/telemetry.js +78 -0
  131. package/scripts/migrar-csv-a-array.js +168 -168
  132. package/scripts/migrar-fase-dominio.js +201 -201
  133. package/scripts/publicar.js +511 -511
  134. package/scripts/run-eval.js +141 -141
  135. package/scripts/validar-manifest.js +231 -195
  136. package/scripts/validar-userland-vacio.js +110 -110
@@ -1,15 +1,14 @@
1
- # swl-mcp-server — STUB EXPERIMENTAL
1
+ # swl-mcp-server v1.0.0
2
2
 
3
- > **NO USAR EN PRODUCCIÓN**. Este es un stub experimental que demuestra
4
- > el patrón de exponer la memoria de swl-ses a clientes MCP externos. La
5
- > implementación completa requiere trabajo adicional (auth, observabilidad,
6
- > tests de integración, schema migration). Ver sección "Limitaciones" más
7
- > abajo.
3
+ Servidor MCP de solo lectura para exponer la memoria de swl-ses
4
+ (aprendizajes, sesiones, instintos) a clientes MCP externos como Cursor,
5
+ Codex CLI y Gemini CLI.
6
+
7
+ Promovido de stub experimental (v0.1.x) a v1.0.0 en ADR-0019 Sub-fase 3.
8
8
 
9
9
  ## Qué hace
10
10
 
11
- `bin/swl-mcp-server.js` es un servidor MCP en modo stdio que expone 3
12
- endpoints de solo lectura:
11
+ Expone 5 endpoints sobre stdio JSON-RPC:
13
12
 
14
13
  1. **`swl_memory_search`** — búsqueda hybrid sobre memoria SWL
15
14
  (aprendizajes + sesiones + instintos) usando `hooks/lib/memory-search`
@@ -18,111 +17,154 @@ endpoints de solo lectura:
18
17
  `.planning/APRENDIZAJES.md`.
19
18
  3. **`swl_instintos_activos`** — instintos con `effective_confidence ≥
20
19
  umbral`.
20
+ 4. **`swl_list_skills`** (Sub-fase 9 v1.5.0) — lista skills SWL
21
+ disponibles con nombre + descripción del frontmatter. Útil para
22
+ descubrir conocimiento operacional antes de invocar uno.
23
+ 5. **`swl_invoke_skill`** (Sub-fase 9 v1.5.0) — devuelve el SKILL.md
24
+ completo de un skill por nombre. Para clientes MCP que no cargan
25
+ skills filesystem nativamente (Codex `--local`, Gemini CLI, otros) —
26
+ el cliente recibe el cuerpo del SKILL.md como texto y lo usa como
27
+ contexto en su próxima llamada.
21
28
 
22
29
  El server lee el estado file-based de swl-ses tal como existe en `cwd`
23
30
  (o el directorio especificado por `SWL_MCP_BASE_DIR`). NO escribe — solo
24
- lectura.
31
+ lectura. NO ejecuta scripts referenciados desde un skill.
32
+
33
+ ### Resolución del directorio de skills
34
+
35
+ Para `swl_list_skills` y `swl_invoke_skill`, el server busca skills en
36
+ el primer directorio que exista, en este orden:
25
37
 
26
- ## Cómo arrancar (para testing)
38
+ 1. `<baseDir>/habilidades/` repo SWL como project root.
39
+ 2. `<baseDir>/.claude/skills/` — proyecto consumidor con SWL instalado
40
+ en Claude Code.
41
+ 3. `<baseDir>/.cursor/skills/` — proyecto con SWL instalado en Cursor.
42
+
43
+ ## Features v1.0.0
44
+
45
+ | Feature | Cómo activar | Default |
46
+ |---|---|---|
47
+ | Auth opt-in (Bearer en `params._auth`) | `SWL_MCP_API_KEY=<token>` en el env del server | sin auth (backward-compat) |
48
+ | Caching mtime-based con TTL | Siempre activo | 60 segundos |
49
+ | Override TTL del cache | `SWL_MCP_CACHE_TTL_MS=<ms>` | 60000 |
50
+ | Telemetría JSONL por call | `SWL_MCP_METRICS=1` | sin telemetría |
51
+ | Schema versioning por handler | Siempre presente en `tools/list` | `1.0.0` |
52
+
53
+ ## Cómo arrancar (testing)
27
54
 
28
55
  ```bash
29
- # Modo standalone (smoke test)
56
+ # Smoke standalone
30
57
  echo '{"jsonrpc":"2.0","id":1,"method":"initialize"}' | node bin/swl-mcp-server.js
31
58
 
32
- # Output esperado en stdout:
33
- # {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"swl-mcp-server","version":"0.1.0-experimental"}}}
59
+ # Output esperado (sin auth):
60
+ # {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{...},"serverInfo":{"name":"swl-mcp-server","version":"1.0.0","authRequired":false,"telemetryEnabled":false}}}
61
+
62
+ # Con auth opt-in
63
+ SWL_MCP_API_KEY="secret-123" node bin/swl-mcp-server.js
64
+
65
+ # Cliente debe enviar params._auth:
66
+ echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"_auth":"secret-123","name":"swl_aprendizajes_recientes","arguments":{"limit":5}}}' | SWL_MCP_API_KEY=secret-123 node bin/swl-mcp-server.js
67
+ ```
68
+
69
+ ## Configuración en clientes MCP
70
+
71
+ ### Cursor (autoconfig disponible)
34
72
 
35
- # Listar herramientas
36
- echo '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node bin/swl-mcp-server.js
73
+ Desde v1.5.0 el instalador puede registrar el server automáticamente:
37
74
 
38
- # Buscar memoria
39
- echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"swl_memory_search","arguments":{"query":"RRF fusion","limit":3}}}' | node bin/swl-mcp-server.js
75
+ ```bash
76
+ npx @saulwade/swl-ses@latest install --target cursor --with-mcp
40
77
  ```
41
78
 
42
- ## Cómo configurar en clientes MCP (NO recomendado en producción)
79
+ Esto genera `.cursor/mcp.json` (per-proyecto) o `~/.cursor/mcp.json`
80
+ (con `--global`) preservando otros mcpServers configurados.
43
81
 
44
- ### Cursor (~/.cursor/mcp.json)
82
+ Configuración manual:
45
83
 
46
84
  ```json
47
85
  {
48
86
  "mcpServers": {
49
87
  "swl-memory": {
50
88
  "command": "node",
51
- "args": ["/ruta/absoluta/a/swl-ses/bin/swl-mcp-server.js"],
89
+ "args": ["/path/to/swl-ses/bin/swl-mcp-server.js"],
52
90
  "env": {
53
- "SWL_MCP_BASE_DIR": "/ruta/al/proyecto/que/quiero/recuperar"
91
+ "SWL_MCP_BASE_DIR": "/path/to/proyecto",
92
+ "SWL_MCP_API_KEY": "opcional-token-secreto"
54
93
  }
55
94
  }
56
95
  }
57
96
  }
58
97
  ```
59
98
 
99
+ ### Codex CLI (autoconfig disponible)
100
+
101
+ ```bash
102
+ npx @saulwade/swl-ses@latest install --target codex --global --with-mcp
103
+ ```
104
+
105
+ Esto inserta `[mcp_servers.swl-memory]` en `~/.codex/config.toml`
106
+ preservando otros servers configurados. Verificar con `codex mcp list`.
107
+
60
108
  ### Gemini CLI
61
109
 
62
- Similar, agregando el server a la config del cliente que soporte MCP stdio.
110
+ Manualmente similar a Cursor el server expone stdio JSON-RPC estándar.
63
111
 
64
112
  ### Claude Code (NO necesario)
65
113
 
66
114
  Claude Code ya tiene acceso directo a los archivos de swl-ses dentro de
67
115
  su propio runtime. NO usar el MCP server desde Claude Code en el mismo
68
- proyecto — sería redundante y agregaría latencia.
116
+ proyecto — sería redundante.
69
117
 
70
- ## Limitaciones (lo que NO se hace en este stub)
118
+ ## Cambios v0.1.x v1.0.0
71
119
 
72
- | Limitación | Impacto | Cuándo se debe arreglar |
120
+ | Aspecto | v0.1.x (stub) | v1.0.0 |
73
121
  |---|---|---|
74
- | **Sin auth** | Cualquier proceso con acceso al stdio puede leer toda la memoria | Antes de exponer en redes públicas o multi-usuario |
75
- | **Sin rate limiting** | Cliente malicioso/buggy puede saturar lectura de archivos | Cuando se observen ≥1 incidentes de saturación |
76
- | **Sin HTTP transport** | Solo stdio; no se puede conectar remotamente | Cuando el caso de uso requiera servidor de red |
77
- | **Sin tests de integración** | Solo smoke tests manuales | Antes de v1.0 del MCP server |
78
- | **Sin observabilidad / métricas** | Logs JSON a stderr son lo único que hay | Cuando se use en >1 cliente simultáneo |
79
- | **Sin hot-reload** | Cambios en swl-ses no se reflejan hasta restart del server | Ya — el server lee files en cada call, así que SÍ se reflejan; documentado por completitud |
80
- | **Sin caching** | Cada call lee files de disco | Cuando latencia sea problema (~10ms hoy) |
81
- | **Sin schema versioning** | Si cambia formato de APRENDIZAJES.md, los handlers pueden romper | Cuando se introduzca breaking change en el formato |
82
- | **Sin support de resources/prompts** | Solo tools | Cuando el caso de uso lo demande |
83
- | **Sin paginación** | Resultados grandes se truncan a `limit` | Cuando se requiera browse de >50 entries |
84
- | **Single-tenant** | Asume un solo proyecto por instancia | Multi-tenancy necesita rediseño |
85
-
86
- ## Trigger para implementación completa
87
-
88
- **Hoy**: 0 instalaciones reportadas. Mantener como stub.
89
-
90
- **Trigger para invertir esfuerzo en implementación robusta**: el usuario
91
- reporta uso real consistente de ≥2 runtimes distintos (Cursor + Claude
92
- Code, o Gemini + Claude Code, etc.) sobre el mismo proyecto SWL durante
93
- ≥1 mes. Sin esto, la inversión de ~25 horas en hardening del server
94
- no se justifica.
95
-
96
- ## Diseño futuro (cuando se implemente completo)
97
-
98
- 1. **Auth**: API key estática + bearer token con scopes:
99
- - `swl:memory:read` (búsqueda y lectura)
100
- - `swl:memory:write` (crear aprendizajes desde MCP — requiere validación)
101
- - `swl:instintos:write` (modificar confidence alto riesgo)
102
- 2. **HTTP transport opcional**: además de stdio, ofrecer servidor HTTP/SSE
103
- con TLS y CORS configurable.
104
- 3. **Telemetría**: requests por handler, latencia p50/p95, errores por
105
- tipo. Persistir en `.planning/evolucion/mcp-metrics.jsonl`.
106
- 4. **Caching invalidable**: caché en memoria de las lecturas de
107
- APRENDIZAJES.md / instintos con `mtime`-based invalidation.
108
- 5. **Schema versioning**: cada handler declara `schema_version`. El
109
- cliente puede pedir un version range. Breaking changes bumpan major.
110
- 6. **Tests de integración**: arrancar el server contra una fixture y
111
- ejecutar 50+ scenarios. Smoke en CI.
112
-
113
- ## Estado de seguridad (auditoría rápida del stub)
114
-
115
- - NO expone credenciales ni archivos fuera de `baseDir`.
116
- - NO ejecuta código (solo lee files y devuelve JSON).
117
- - ✓ NO modifica archivos.
118
- - ✗ NO valida que `baseDir` sea un proyecto SWL válido — un cliente
119
- podría apuntarlo a un directorio arbitrario y leer cualquier
120
- archivo `*.md` que llamemos `APRENDIZAJES.md`.
121
- - NO sanitiza queries de búsqueda (los regex en `instintos.yaml` parser
122
- son seguros, pero falta hardening).
123
- - ✗ NO hay timeout — un proyecto enorme con miles de sesiones podría
124
- hacer colgar el server.
125
-
126
- Estos puntos son ACEPTABLES para un stub experimental usado por el
127
- mantenedor en un proyecto propio. NO ACEPTABLES para uso multi-usuario
128
- o expuesto a la red.
122
+ | Auth | sin auth | opt-in con `SWL_MCP_API_KEY` + Bearer en `params._auth` |
123
+ | Caching | sin caching | mtime-based con TTL configurable |
124
+ | Telemetría | logs stderr | JSONL en `.planning/evolucion/mcp-metrics.jsonl` (opt-in) |
125
+ | Schema versioning | implícito | `_schemaVersion` en cada tool de `tools/list` |
126
+ | Tests | smoke manual | 38 tests unitarios (`tests/mcp-server/*.test.js`) |
127
+ | Estado | "NO usar en producción" | apto para los proyectos del usuario |
128
+
129
+ ## Backward compatibility
130
+
131
+ v1.0.0 es 100% backward-compatible con clientes que conectan sin auth.
132
+ Si `SWL_MCP_API_KEY` no está set en el env del server, el comportamiento
133
+ es idéntico al stub v0.1.x — no se requiere ningún cambio en clientes
134
+ configurados antes.
135
+
136
+ ## Diseño futuro (cuando aparezca demanda real)
137
+
138
+ - **HTTP transport opcional**: además de stdio, ofrecer servidor HTTP/SSE
139
+ con TLS y CORS configurable.
140
+ - **Handlers de escritura** (`swl:memory:write`, `swl:instintos:write`)
141
+ fuera de scope v1.0 requerirían scopes en el token y validación
142
+ semántica de cada mutación.
143
+ - **Rate limiting** por API key cuando aparezca un caso multi-cliente.
144
+ - **OpenTelemetry** para latencias p95 distribuidas si el server se
145
+ consume desde múltiples editores en paralelo.
146
+
147
+ ## Estado de seguridad
148
+
149
+ - Read-onlyno ejecuta código ni modifica archivos.
150
+ - Auth opt-in con comparación constante para defender contra timing
151
+ attacks.
152
+ - Caching con invalidación correcta (mtime + TTL).
153
+ - Telemetría con error silencioso — nunca rompe el server.
154
+ - `baseDir` no se valida contra patrón "proyecto SWL válido". Un
155
+ cliente con acceso al stdio puede apuntarlo a cualquier directorio
156
+ con `APRENDIZAJES.md`. Mitigación: el operador configura el server
157
+ con `SWL_MCP_BASE_DIR` explícito.
158
+ - Sin límite de tamaño de query un cliente malicioso podría enviar
159
+ una query gigante. Mitigación parcial: `limit` está clampeado a 50/100.
160
+
161
+ Para casos de uso single-user en máquinas confiables del operador, los
162
+ puntos restantes son aceptables. Para uso multi-usuario o exposición a
163
+ red, evaluar primero el alcance del riesgo y considerar las features
164
+ de "diseño futuro" arriba.
165
+
166
+ ## Referencias
167
+
168
+ - ADR-0019 Sub-fase 3: `.planning/adrs/0019-integracion-codex-y-cursor-completa.md`.
169
+ - Tests: `tests/mcp-server/*.test.js` (auth, cache, telemetry, rutear).
170
+ - Configuración Cursor: `docs/MCP-SERVER-CURSOR-NOTAS.md`.
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Auth opt-in para swl-mcp-server v1.0.0 (ADR-0019 Sub-fase 3).
5
+ *
6
+ * El stub experimental v0.1.x corría sin auth. Cualquier proceso con acceso al
7
+ * stdio podía leer toda la memoria SWL. Esa decisión era aceptable solo en
8
+ * single-user local — no en multi-usuario, no expuesto a red, no compartido.
9
+ *
10
+ * v1.0.0 introduce auth **opt-in**:
11
+ * - Si `SWL_MCP_API_KEY` NO está definida en el entorno del server, el comportamiento
12
+ * es idéntico al stub (sin auth). Compatibilidad total con clientes existentes.
13
+ * - Si `SWL_MCP_API_KEY` ESTÁ definida, cada `tools/call` debe incluir
14
+ * `params._auth = "<api-key>"`. Sin el token o con token incorrecto, se devuelve
15
+ * `-32001 Unauthorized`.
16
+ *
17
+ * Decisión deliberada de NO usar header `Authorization: Bearer ...`:
18
+ * - El protocolo MCP sobre stdio NO transporta headers HTTP.
19
+ * - Inventar un campo en `initialize.capabilities` específico para swl-ses
20
+ * rompería la spec del protocolo.
21
+ * - `_auth` como campo en `params` es el patrón de menor fricción.
22
+ *
23
+ * Limitaciones aceptadas:
24
+ * - Token estático sin rotación automática — el operador rota manualmente
25
+ * cambiando la env var del proceso.
26
+ * - Sin scopes (read/write) porque v1 sigue siendo read-only.
27
+ *
28
+ * @module scripts/mcp-server/auth
29
+ */
30
+
31
+ const ENV_VAR_NAME = 'SWL_MCP_API_KEY';
32
+ const ERROR_CODE_UNAUTHORIZED = -32001;
33
+ const ERROR_CODE_FORBIDDEN = -32002;
34
+
35
+ /**
36
+ * Construye un validador de auth desde el entorno actual.
37
+ *
38
+ * Pattern de "construct once, use many": leer la env var una sola vez al arranque
39
+ * y devolver una función pura que valida cada request. Evita race conditions con
40
+ * tests que mutan process.env.
41
+ *
42
+ * @param {object} [opciones] - { env: NodeJS.ProcessEnv } sustituible para tests.
43
+ * @returns {{ requerida: boolean, validar: (request: object) => { ok: boolean, code?: number, message?: string } }}
44
+ */
45
+ function construirValidador(opciones = {}) {
46
+ const env = opciones.env || process.env;
47
+ const apiKey = env[ENV_VAR_NAME];
48
+ const requerida = typeof apiKey === 'string' && apiKey.length > 0;
49
+
50
+ return {
51
+ requerida,
52
+ validar: (request) => {
53
+ if (!requerida) return { ok: true };
54
+ // Solo validar tools/call — initialize, ping, tools/list son metadata pública.
55
+ const metodo = request && request.method;
56
+ if (metodo !== 'tools/call') return { ok: true };
57
+
58
+ const params = request.params || {};
59
+ const tokenCliente = params._auth;
60
+
61
+ if (typeof tokenCliente !== 'string' || tokenCliente.length === 0) {
62
+ return {
63
+ ok: false,
64
+ code: ERROR_CODE_UNAUTHORIZED,
65
+ message: 'swl-mcp-server requiere autenticación: SWL_MCP_API_KEY está configurada en el server. El cliente debe enviar params._auth con el API key.',
66
+ };
67
+ }
68
+ if (!comparacionConstante(tokenCliente, apiKey)) {
69
+ return {
70
+ ok: false,
71
+ code: ERROR_CODE_FORBIDDEN,
72
+ message: 'Token inválido.',
73
+ };
74
+ }
75
+ return { ok: true };
76
+ },
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Comparación de strings en tiempo constante para evitar timing attacks
82
+ * sobre el API key. Implementación zero-deps simple.
83
+ *
84
+ * Si las longitudes difieren, igual se itera la longitud máxima para no filtrar
85
+ * información sobre el largo del secreto vía duración de la comparación.
86
+ */
87
+ function comparacionConstante(a, b) {
88
+ if (typeof a !== 'string' || typeof b !== 'string') return false;
89
+ const len = Math.max(a.length, b.length);
90
+ let diff = a.length ^ b.length;
91
+ for (let i = 0; i < len; i++) {
92
+ const ca = i < a.length ? a.charCodeAt(i) : 0;
93
+ const cb = i < b.length ? b.charCodeAt(i) : 0;
94
+ diff |= ca ^ cb;
95
+ }
96
+ return diff === 0;
97
+ }
98
+
99
+ module.exports = {
100
+ construirValidador,
101
+ comparacionConstante,
102
+ ENV_VAR_NAME,
103
+ ERROR_CODE_UNAUTHORIZED,
104
+ ERROR_CODE_FORBIDDEN,
105
+ };
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Cache mtime-based para handlers del swl-mcp-server v1.0.0 (ADR-0019 Sub-fase 3).
5
+ *
6
+ * El stub experimental leía disco en cada call (latencia medida ~10ms con
7
+ * APRENDIZAJES.md de 1172 líneas). Para datasets más grandes (>10k líneas) o
8
+ * múltiples clientes concurrentes, el caching reduce sustancialmente la carga.
9
+ *
10
+ * Patrón:
11
+ * - Key = path absoluto del archivo.
12
+ * - Value = { mtime, contenido, parsed }.
13
+ * - Invalidación: si fs.stat().mtime > value.mtime → recargar y reparsear.
14
+ * - TTL opcional vía `SWL_MCP_CACHE_TTL_MS` (default 60000 ms): aunque mtime no
15
+ * haya cambiado, recargar tras TTL para defensa contra clock skew o ediciones
16
+ * que no actualizan mtime (raras pero posibles en filesystems exóticos).
17
+ *
18
+ * Zero-deps. NO usar para escritura — sigue read-only.
19
+ *
20
+ * @module scripts/mcp-server/cache
21
+ */
22
+
23
+ const fs = require('fs');
24
+
25
+ const DEFAULT_TTL_MS = 60 * 1000;
26
+
27
+ /**
28
+ * Crea una instancia de cache mtime-based.
29
+ *
30
+ * @param {object} [opciones]
31
+ * @param {number} [opciones.ttlMs] - TTL en milisegundos. Default 60000.
32
+ * @param {object} [opciones.env] - Para leer SWL_MCP_CACHE_TTL_MS en tests.
33
+ * @returns {{ get: Function, invalidate: Function, stats: Function, _store: Map }}
34
+ */
35
+ function crearCache(opciones = {}) {
36
+ const env = opciones.env || process.env;
37
+ const ttlMs = opciones.ttlMs !== undefined
38
+ ? opciones.ttlMs
39
+ : (parseInt(env.SWL_MCP_CACHE_TTL_MS, 10) || DEFAULT_TTL_MS);
40
+
41
+ const store = new Map();
42
+ const stats = { hits: 0, misses: 0, invalidations: 0 };
43
+
44
+ /**
45
+ * Obtiene el valor cacheado para `path`. Si el archivo cambió (mtime) o el TTL
46
+ * expiró, vuelve a leer y parsear con `parser(contenido, path)`.
47
+ *
48
+ * @param {string} ruta - Path absoluto del archivo a cachear.
49
+ * @param {(contenido: string, ruta: string) => any} parser - Función que transforma
50
+ * el contenido del archivo en el objeto a cachear. DEBE ser pura.
51
+ * @returns {{ data: any, hit: boolean } | null} null si el archivo no existe.
52
+ */
53
+ function get(ruta, parser) {
54
+ let stat;
55
+ try {
56
+ stat = fs.statSync(ruta);
57
+ } catch (err) {
58
+ if (err.code === 'ENOENT') return null;
59
+ throw err;
60
+ }
61
+
62
+ const mtimeMs = stat.mtimeMs;
63
+ const ahora = Date.now();
64
+ const cached = store.get(ruta);
65
+
66
+ if (cached && cached.mtimeMs === mtimeMs && (ahora - cached.cargadoEn) < ttlMs) {
67
+ stats.hits++;
68
+ return { data: cached.data, hit: true };
69
+ }
70
+
71
+ if (cached) stats.invalidations++;
72
+ else stats.misses++;
73
+
74
+ const contenido = fs.readFileSync(ruta, 'utf-8');
75
+ const data = parser(contenido, ruta);
76
+ store.set(ruta, { mtimeMs, cargadoEn: ahora, data });
77
+ return { data, hit: false };
78
+ }
79
+
80
+ /**
81
+ * Invalida una entrada o todo el cache.
82
+ * @param {string} [ruta] - Si se omite, vacía todo.
83
+ */
84
+ function invalidate(ruta) {
85
+ if (ruta === undefined) {
86
+ const n = store.size;
87
+ store.clear();
88
+ stats.invalidations += n;
89
+ return n;
90
+ }
91
+ if (store.delete(ruta)) stats.invalidations++;
92
+ return 0;
93
+ }
94
+
95
+ return {
96
+ get,
97
+ invalidate,
98
+ stats: () => ({ ...stats, size: store.size, ttlMs }),
99
+ _store: store, // exposed para tests
100
+ };
101
+ }
102
+
103
+ module.exports = {
104
+ crearCache,
105
+ DEFAULT_TTL_MS,
106
+ };