@saulwade/swl-ses 1.6.3 → 1.6.6

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 (46) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +2 -2
  3. package/agentes/gh-fix-ci-swl.md +275 -0
  4. package/agentes/nemesis-auditor-swl.md +90 -1
  5. package/comandos/swl/exportar-vault.md +106 -14
  6. package/comandos/swl/nemesis.md +70 -3
  7. package/comandos/swl/release.md +62 -2
  8. package/comandos/swl/salud.md +32 -0
  9. package/comandos/swl/verificar.md +116 -2
  10. package/habilidades/agent-browser/SKILL.md +111 -4
  11. package/habilidades/agent-deep-links/SKILL.md +148 -0
  12. package/habilidades/backend-async-postgres-testing/SKILL.md +215 -0
  13. package/habilidades/backend-error-design/SKILL.md +221 -0
  14. package/habilidades/browser-interaction-patterns/SKILL.md +514 -0
  15. package/habilidades/browser-research-domains/SKILL.md +635 -0
  16. package/habilidades/changelog-generator/SKILL.md +172 -0
  17. package/habilidades/changelog-generator/scripts/parse-commits.js +354 -0
  18. package/habilidades/devsecops-pipeline-security/SKILL.md +3 -0
  19. package/habilidades/fastapi-experto/SKILL.md +49 -4
  20. package/habilidades/harness-claude-code/SKILL.md +4 -1
  21. package/habilidades/postgresql-experto/SKILL.md +80 -4
  22. package/habilidades/proceso-discovery-machote/SKILL.md +157 -0
  23. package/habilidades/proceso-modular-split/SKILL.md +256 -0
  24. package/habilidades/tdd-workflow/SKILL.md +12 -5
  25. package/hooks/extraccion-aprendizajes.js +8 -0
  26. package/hooks/lib/deep-links.js +185 -0
  27. package/hooks/lib/evolution-tracker.js +148 -20
  28. package/hooks/lib/gateway-notify.js +70 -7
  29. package/manifiestos/modulos.json +13 -3
  30. package/manifiestos/skills-lock.json +1247 -1191
  31. package/package.json +92 -92
  32. package/plugin.json +371 -362
  33. package/reglas/arquitectura.md +38 -0
  34. package/reglas/arreglar-al-detectar.md +93 -0
  35. package/reglas/auditorias-documentales-estructurales.md +38 -0
  36. package/reglas/registro-componentes-nuevos.md +14 -0
  37. package/reglas/tests-cleanup.md +220 -0
  38. package/scripts/instalador.js +72 -4
  39. package/scripts/lib/mcp_config.py +29 -14
  40. package/scripts/lib/notificaciones-telegram.js +14 -0
  41. package/scripts/lib/transformadores/codex.js +4 -0
  42. package/scripts/lib/transformadores/cursor.js +5 -0
  43. package/scripts/mcp-orchestrator.py +153 -131
  44. package/scripts/mcp-pool-manager.py +132 -107
  45. package/scripts/mcp-telemetry.py +139 -120
  46. package/scripts/verificar-release.js +199 -1
@@ -343,6 +343,10 @@ class TransformadorCodex extends TransformadorBase {
343
343
  SWL_MCP_BASE_DIR: process.cwd(),
344
344
  },
345
345
  });
346
+ // FIX v1.6.6: confirmar al usuario que config.toml se actualizó. Antes
347
+ // el efecto de --with-mcp era invisible — sin lanzar Codex era imposible
348
+ // saber si el registro había ocurrido.
349
+ console.log(` + MCP server swl-memory registrado en ${configToml}`);
346
350
  } catch (err) {
347
351
  // No bloquear instalación si el registro del MCP falla — solo loggear
348
352
  // El usuario verá el error y puede registrar manualmente con `codex mcp add`.
@@ -353,6 +353,11 @@ class TransformadorCursor extends TransformadorBase {
353
353
  };
354
354
 
355
355
  atomicWriteSync(rutaMcp, JSON.stringify(existente, null, 2) + '\n', 'utf8', { mode: 0o600 });
356
+
357
+ // FIX v1.6.6: confirmar al usuario que mcp.json se escribió. Antes el
358
+ // efecto de --with-mcp era invisible — sin abrir Cursor era imposible
359
+ // saber si el registro había ocurrido.
360
+ console.log(` + MCP server swl-memory registrado en ${rutaMcp}`);
356
361
  }
357
362
  }
358
363
 
@@ -31,8 +31,8 @@ from pathlib import Path
31
31
  from typing import Any
32
32
 
33
33
  # Importar helper compartido sin convertir scripts/ en paquete.
34
- sys.path.insert(0, str(Path(__file__).resolve().parent / 'lib'))
35
- from mcp_config import cargar_config_mcp # noqa: E402
34
+ sys.path.insert(0, str(Path(__file__).resolve().parent / "lib"))
35
+ from mcp_config import build_stdio_env, cargar_config_mcp # noqa: E402
36
36
 
37
37
  # ---------------------------------------------------------------------------
38
38
  # Dependencias — raw mcp SDK
@@ -41,6 +41,7 @@ try:
41
41
  from mcp import ClientSession
42
42
  from mcp.client.stdio import stdio_client, StdioServerParameters
43
43
  from mcp.client.sse import sse_client
44
+
44
45
  HAS_MCP = True
45
46
  except ImportError:
46
47
  HAS_MCP = False
@@ -49,8 +50,8 @@ except ImportError:
49
50
  # Constantes
50
51
  # ---------------------------------------------------------------------------
51
52
 
52
- TIMEOUT_S = 12
53
- SNAPSHOT_FILE = Path('.planning') / 'mcp-snapshot.json'
53
+ TIMEOUT_S = 12
54
+ SNAPSHOT_FILE = Path(".planning") / "mcp-snapshot.json"
54
55
 
55
56
  # ---------------------------------------------------------------------------
56
57
  # Carga de config — delega a scripts/lib/mcp_config.py para deep merge
@@ -63,13 +64,9 @@ def _cargar_config(cwd: Path, config_path: str | None = None) -> dict:
63
64
  return cargar_config_mcp(cwd, config_path)
64
65
 
65
66
 
66
- def _build_env(cfg: dict) -> dict | None:
67
- extra = cfg.get('env') or {}
68
- if not extra:
69
- return None
70
- merged = dict(os.environ)
71
- merged.update(extra)
72
- return merged
67
+ def _build_env(cfg: dict) -> dict:
68
+ return build_stdio_env(cfg)
69
+
73
70
 
74
71
  # ---------------------------------------------------------------------------
75
72
  # Conexion async (igual que pool-manager, duplicada para zero-imports externos)
@@ -79,24 +76,24 @@ def _build_env(cfg: dict) -> dict | None:
79
76
  async def _probe_server(nombre: str, cfg: dict) -> dict:
80
77
  """Conecta a un servidor, devuelve su inventario completo de herramientas."""
81
78
  resultado: dict = {
82
- 'server': nombre,
83
- 'transport': 'http' if 'url' in cfg else 'stdio',
84
- 'tools': [],
85
- 'error': None,
86
- 'estado': 'OK',
87
- 'duration_ms': 0,
79
+ "server": nombre,
80
+ "transport": "http" if "url" in cfg else "stdio",
81
+ "tools": [],
82
+ "error": None,
83
+ "estado": "OK",
84
+ "duration_ms": 0,
88
85
  }
89
86
  t0 = time.time()
90
87
  try:
91
- if 'url' in cfg:
92
- transport = sse_client(cfg['url'])
88
+ if "url" in cfg:
89
+ transport = sse_client(cfg["url"])
93
90
  else:
94
91
  env = _build_env(cfg)
95
92
  params = StdioServerParameters(
96
- command=cfg['command'],
97
- args=cfg.get('args', []),
93
+ command=cfg["command"],
94
+ args=cfg.get("args", []),
98
95
  env=env,
99
- cwd=cfg.get('cwd'),
96
+ cwd=cfg.get("cwd"),
100
97
  )
101
98
  transport = stdio_client(params)
102
99
 
@@ -104,21 +101,21 @@ async def _probe_server(nombre: str, cfg: dict) -> dict:
104
101
  async with ClientSession(read, write) as session:
105
102
  await asyncio.wait_for(session.initialize(), timeout=TIMEOUT_S)
106
103
  resp = await asyncio.wait_for(session.list_tools(), timeout=TIMEOUT_S)
107
- resultado['tools'] = [
104
+ resultado["tools"] = [
108
105
  {
109
- 'name': t.name,
110
- 'description': t.description or '',
106
+ "name": t.name,
107
+ "description": t.description or "",
111
108
  }
112
109
  for t in (resp.tools or [])
113
110
  ]
114
111
  except asyncio.TimeoutError:
115
- resultado['error'] = f'Timeout ({TIMEOUT_S}s)'
116
- resultado['estado'] = 'ERROR'
112
+ resultado["error"] = f"Timeout ({TIMEOUT_S}s)"
113
+ resultado["estado"] = "ERROR"
117
114
  except Exception as exc:
118
- resultado['error'] = str(exc)
119
- resultado['estado'] = 'ERROR'
115
+ resultado["error"] = str(exc)
116
+ resultado["estado"] = "ERROR"
120
117
  finally:
121
- resultado['duration_ms'] = int((time.time() - t0) * 1000)
118
+ resultado["duration_ms"] = int((time.time() - t0) * 1000)
122
119
  return resultado
123
120
 
124
121
 
@@ -127,6 +124,7 @@ async def _probe_all(servers: dict) -> list:
127
124
  tareas = [_probe_server(n, c) for n, c in servers.items()]
128
125
  return list(await asyncio.gather(*tareas))
129
126
 
127
+
130
128
  # ---------------------------------------------------------------------------
131
129
  # Snapshot — persiste el ultimo estado conocido
132
130
  # ---------------------------------------------------------------------------
@@ -137,21 +135,23 @@ def _guardar_snapshot(cwd: Path, resultados: list) -> None:
137
135
  snap_file = cwd / SNAPSHOT_FILE
138
136
  snap_file.parent.mkdir(parents=True, exist_ok=True)
139
137
  snapshot = {
140
- 'generado': datetime.now(timezone.utc).isoformat(),
141
- 'servidores': resultados,
142
- 'resumen': {
143
- 'total': len(resultados),
144
- 'activos': sum(1 for r in resultados if r['estado'] == 'OK'),
145
- 'errores': sum(1 for r in resultados if r['estado'] == 'ERROR'),
146
- 'tools': sum(len(r['tools']) for r in resultados),
138
+ "generado": datetime.now(timezone.utc).isoformat(),
139
+ "servidores": resultados,
140
+ "resumen": {
141
+ "total": len(resultados),
142
+ "activos": sum(1 for r in resultados if r["estado"] == "OK"),
143
+ "errores": sum(1 for r in resultados if r["estado"] == "ERROR"),
144
+ "tools": sum(len(r["tools"]) for r in resultados),
147
145
  },
148
146
  }
149
147
  try:
150
- tmp = snap_file.with_suffix('.tmp')
151
- tmp.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2), encoding='utf-8')
148
+ tmp = snap_file.with_suffix(".tmp")
149
+ tmp.write_text(
150
+ json.dumps(snapshot, ensure_ascii=False, indent=2), encoding="utf-8"
151
+ )
152
152
  os.replace(tmp, snap_file)
153
153
  except Exception as exc:
154
- sys.stderr.write(f'[mcp-orchestrator] No se pudo guardar snapshot: {exc}\n')
154
+ sys.stderr.write(f"[mcp-orchestrator] No se pudo guardar snapshot: {exc}\n")
155
155
 
156
156
 
157
157
  def _cargar_snapshot(cwd: Path) -> dict | None:
@@ -159,10 +159,11 @@ def _cargar_snapshot(cwd: Path) -> dict | None:
159
159
  if not snap_file.exists():
160
160
  return None
161
161
  try:
162
- return json.loads(snap_file.read_text(encoding='utf-8'))
162
+ return json.loads(snap_file.read_text(encoding="utf-8"))
163
163
  except Exception:
164
164
  return None
165
165
 
166
+
166
167
  # ---------------------------------------------------------------------------
167
168
  # Telemetria opcional (importa mcp-telemetry si esta disponible)
168
169
  # ---------------------------------------------------------------------------
@@ -171,16 +172,18 @@ def _cargar_snapshot(cwd: Path) -> dict | None:
171
172
  def _registrar_traza_discovery(cwd: Path, resumen: dict) -> None:
172
173
  """Registra una traza del discovery si mcp-telemetry esta disponible."""
173
174
  try:
174
- sys.path.insert(0, str(cwd / 'scripts'))
175
+ sys.path.insert(0, str(cwd / "scripts"))
175
176
  from mcp_telemetry import registrar_traza # type: ignore
177
+
176
178
  registrar_traza(
177
- cwd = cwd,
178
- nombre = 'mcp:discovery',
179
- atributos = resumen,
180
- estado = 'OK' if resumen.get('errores', 0) == 0 else 'ERROR',
179
+ cwd=cwd,
180
+ nombre="mcp:discovery",
181
+ atributos=resumen,
182
+ estado="OK" if resumen.get("errores", 0) == 0 else "ERROR",
181
183
  )
182
184
  except ImportError:
183
- pass # mcp-telemetry no disponible — silencioso
185
+ pass # mcp-telemetry no disponible — silencioso
186
+
184
187
 
185
188
  # ---------------------------------------------------------------------------
186
189
  # Subcomandos
@@ -188,87 +191,100 @@ def _registrar_traza_discovery(cwd: Path, resumen: dict) -> None:
188
191
 
189
192
 
190
193
  async def cmd_status(servers: dict, cwd: Path, as_json: bool, con_traza: bool) -> None:
191
- out = sys.stdout.write
194
+ out = sys.stdout.write
192
195
  resultados = await _probe_all(servers)
193
196
  _guardar_snapshot(cwd, resultados)
194
197
 
195
198
  resumen = {
196
- 'total': len(resultados),
197
- 'activos': sum(1 for r in resultados if r['estado'] == 'OK'),
198
- 'errores': sum(1 for r in resultados if r['estado'] == 'ERROR'),
199
- 'tools': sum(len(r['tools']) for r in resultados),
199
+ "total": len(resultados),
200
+ "activos": sum(1 for r in resultados if r["estado"] == "OK"),
201
+ "errores": sum(1 for r in resultados if r["estado"] == "ERROR"),
202
+ "tools": sum(len(r["tools"]) for r in resultados),
200
203
  }
201
204
 
202
205
  if con_traza:
203
206
  _registrar_traza_discovery(cwd, resumen)
204
207
 
205
208
  if as_json:
206
- out(json.dumps({'resumen': resumen, 'servidores': resultados},
207
- ensure_ascii=False, indent=2) + '\n')
209
+ out(
210
+ json.dumps(
211
+ {"resumen": resumen, "servidores": resultados},
212
+ ensure_ascii=False,
213
+ indent=2,
214
+ )
215
+ + "\n"
216
+ )
208
217
  return
209
218
 
210
- out('\nEstado de servidores MCP:\n')
211
- out('-' * 68 + '\n')
219
+ out("\nEstado de servidores MCP:\n")
220
+ out("-" * 68 + "\n")
212
221
  for r in resultados:
213
- estado = r['estado']
214
- tools = len(r['tools'])
215
- ms = r['duration_ms']
216
- trans = r['transport']
217
- err = f' -- {r["error"]}' if r['error'] else ''
218
- out(f' {r["server"]:<28} {estado:<5} {tools:>3} tools {ms:>5}ms [{trans}]{err}\n')
219
- out('\n')
220
- out(f' Total: {resumen["total"]} servidores, {resumen["activos"]} activos, '
221
- f'{resumen["tools"]} herramientas disponibles\n')
222
-
223
-
224
- async def cmd_discover(servers: dict, cwd: Path, as_json: bool, con_traza: bool) -> None:
225
- out = sys.stdout.write
222
+ estado = r["estado"]
223
+ tools = len(r["tools"])
224
+ ms = r["duration_ms"]
225
+ trans = r["transport"]
226
+ err = f' -- {r["error"]}' if r["error"] else ""
227
+ out(
228
+ f' {r["server"]:<28} {estado:<5} {tools:>3} tools {ms:>5}ms [{trans}]{err}\n'
229
+ )
230
+ out("\n")
231
+ out(
232
+ f' Total: {resumen["total"]} servidores, {resumen["activos"]} activos, '
233
+ f'{resumen["tools"]} herramientas disponibles\n'
234
+ )
235
+
236
+
237
+ async def cmd_discover(
238
+ servers: dict, cwd: Path, as_json: bool, con_traza: bool
239
+ ) -> None:
240
+ out = sys.stdout.write
226
241
  resultados = await _probe_all(servers)
227
242
  _guardar_snapshot(cwd, resultados)
228
243
 
229
244
  if con_traza:
230
245
  resumen = {
231
- 'total': len(resultados),
232
- 'activos': sum(1 for r in resultados if r['estado'] == 'OK'),
233
- 'errores': sum(1 for r in resultados if r['estado'] == 'ERROR'),
234
- 'tools': sum(len(r['tools']) for r in resultados),
246
+ "total": len(resultados),
247
+ "activos": sum(1 for r in resultados if r["estado"] == "OK"),
248
+ "errores": sum(1 for r in resultados if r["estado"] == "ERROR"),
249
+ "tools": sum(len(r["tools"]) for r in resultados),
235
250
  }
236
251
  _registrar_traza_discovery(cwd, resumen)
237
252
 
238
253
  if as_json:
239
- out(json.dumps(resultados, ensure_ascii=False, indent=2) + '\n')
254
+ out(json.dumps(resultados, ensure_ascii=False, indent=2) + "\n")
240
255
  return
241
256
 
242
257
  for r in resultados:
243
- if r['error']:
258
+ if r["error"]:
244
259
  out(f'\n[{r["server"]}] ERROR: {r["error"]}\n')
245
260
  continue
246
261
  out(f'\n[{r["server"]}] {len(r["tools"])} herramientas:\n')
247
- for t in r['tools']:
248
- desc = t['description']
262
+ for t in r["tools"]:
263
+ desc = t["description"]
249
264
  if len(desc) > 72:
250
- desc = desc[:69] + '...'
265
+ desc = desc[:69] + "..."
251
266
  out(f' - {t["name"]:<32} {desc}\n')
252
267
 
253
268
 
254
269
  async def cmd_find_tool(servers: dict, keyword: str, as_json: bool) -> None:
255
- out = sys.stdout.write
270
+ out = sys.stdout.write
256
271
  resultados = await _probe_all(servers)
257
- kw_lower = keyword.lower()
272
+ kw_lower = keyword.lower()
258
273
 
259
274
  encontradas: list = []
260
275
  for r in resultados:
261
- for t in r.get('tools', []):
262
- if (kw_lower in t['name'].lower() or
263
- kw_lower in t['description'].lower()):
264
- encontradas.append({
265
- 'server': r['server'],
266
- 'name': t['name'],
267
- 'description': t['description'],
268
- })
276
+ for t in r.get("tools", []):
277
+ if kw_lower in t["name"].lower() or kw_lower in t["description"].lower():
278
+ encontradas.append(
279
+ {
280
+ "server": r["server"],
281
+ "name": t["name"],
282
+ "description": t["description"],
283
+ }
284
+ )
269
285
 
270
286
  if as_json:
271
- out(json.dumps(encontradas, ensure_ascii=False, indent=2) + '\n')
287
+ out(json.dumps(encontradas, ensure_ascii=False, indent=2) + "\n")
272
288
  return
273
289
 
274
290
  if not encontradas:
@@ -276,101 +292,107 @@ async def cmd_find_tool(servers: dict, keyword: str, as_json: bool) -> None:
276
292
  return
277
293
 
278
294
  out(f'\nHerramientas que coinciden con "{keyword}" ({len(encontradas)}):\n')
279
- out('-' * 68 + '\n')
295
+ out("-" * 68 + "\n")
280
296
  for e in encontradas:
281
- desc = e['description']
297
+ desc = e["description"]
282
298
  if len(desc) > 60:
283
- desc = desc[:57] + '...'
299
+ desc = desc[:57] + "..."
284
300
  out(f' [{e["server"]}] {e["name"]:<28} {desc}\n')
285
301
 
286
302
 
287
303
  def cmd_summary(cwd: Path, as_json: bool) -> None:
288
304
  """Muestra el ultimo snapshot guardado sin re-conectar a los servidores."""
289
- out = sys.stdout.write
305
+ out = sys.stdout.write
290
306
  snapshot = _cargar_snapshot(cwd)
291
307
 
292
308
  if not snapshot:
293
- out('Sin snapshot disponible. Ejecuta primero: mcp-orchestrator.py status\n')
309
+ out("Sin snapshot disponible. Ejecuta primero: mcp-orchestrator.py status\n")
294
310
  return
295
311
 
296
312
  if as_json:
297
- out(json.dumps(snapshot, ensure_ascii=False, indent=2) + '\n')
313
+ out(json.dumps(snapshot, ensure_ascii=False, indent=2) + "\n")
298
314
  return
299
315
 
300
- generado = snapshot.get('generado', '?')[:19]
301
- resumen = snapshot.get('resumen', {})
302
- out(f'\nResumen MCP (snapshot del {generado} UTC):\n')
303
- out(f' Servidores: {resumen.get("total", 0)} '
304
- f'({resumen.get("activos", 0)} activos, {resumen.get("errores", 0)} con error)\n')
316
+ generado = snapshot.get("generado", "?")[:19]
317
+ resumen = snapshot.get("resumen", {})
318
+ out(f"\nResumen MCP (snapshot del {generado} UTC):\n")
319
+ out(
320
+ f' Servidores: {resumen.get("total", 0)} '
321
+ f'({resumen.get("activos", 0)} activos, {resumen.get("errores", 0)} con error)\n'
322
+ )
305
323
  out(f' Herramientas disponibles: {resumen.get("tools", 0)}\n')
306
- out('\nDetalle por servidor:\n')
307
- for r in snapshot.get('servidores', []):
308
- estado = r.get('estado', '?')
309
- tools = len(r.get('tools', []))
310
- ms = r.get('duration_ms', 0)
324
+ out("\nDetalle por servidor:\n")
325
+ for r in snapshot.get("servidores", []):
326
+ estado = r.get("estado", "?")
327
+ tools = len(r.get("tools", []))
328
+ ms = r.get("duration_ms", 0)
311
329
  out(f' {r["server"]:<28} {estado:<5} {tools:>3} tools {ms:>5}ms\n')
312
330
 
331
+
313
332
  # ---------------------------------------------------------------------------
314
333
  # main()
315
334
  # ---------------------------------------------------------------------------
316
335
 
317
336
 
318
337
  def main() -> None:
319
- if hasattr(sys.stdout, 'reconfigure'):
320
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
338
+ if hasattr(sys.stdout, "reconfigure"):
339
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
321
340
 
322
341
  if not HAS_MCP:
323
342
  sys.stderr.write(
324
- '[mcp-orchestrator] ERROR: la libreria mcp no esta instalada.\n'
325
- '[mcp-orchestrator] Ejecuta: pip install mcp\n'
343
+ "[mcp-orchestrator] ERROR: la libreria mcp no esta instalada.\n"
344
+ "[mcp-orchestrator] Ejecuta: pip install mcp\n"
326
345
  )
327
346
  sys.exit(1)
328
347
 
329
348
  parser = argparse.ArgumentParser(
330
- description='Orquestador multi-servidor MCP para swl-ses'
349
+ description="Orquestador multi-servidor MCP para swl-ses"
331
350
  )
332
- parser.add_argument('--json', action='store_true', help='Salida en formato JSON')
333
- parser.add_argument('--cwd', default='.', help='Directorio raiz del proyecto')
334
- parser.add_argument('--trace', action='store_true',
335
- help='Registrar resultado en .planning/traces/')
336
- parser.add_argument('--config', default=None, help='Ruta al archivo de config MCP')
351
+ parser.add_argument("--json", action="store_true", help="Salida en formato JSON")
352
+ parser.add_argument("--cwd", default=".", help="Directorio raiz del proyecto")
353
+ parser.add_argument(
354
+ "--trace", action="store_true", help="Registrar resultado en .planning/traces/"
355
+ )
356
+ parser.add_argument("--config", default=None, help="Ruta al archivo de config MCP")
337
357
 
338
- sub = parser.add_subparsers(dest='cmd')
358
+ sub = parser.add_subparsers(dest="cmd")
339
359
 
340
- sub.add_parser('status', help='Health check de todos los servidores MCP')
341
- sub.add_parser('discover', help='Lista completa de herramientas por servidor')
360
+ sub.add_parser("status", help="Health check de todos los servidores MCP")
361
+ sub.add_parser("discover", help="Lista completa de herramientas por servidor")
342
362
 
343
- p_find = sub.add_parser('find-tool', help='Busca herramienta por nombre o descripcion')
344
- p_find.add_argument('keyword', help='Texto a buscar en nombre o descripcion')
363
+ p_find = sub.add_parser(
364
+ "find-tool", help="Busca herramienta por nombre o descripcion"
365
+ )
366
+ p_find.add_argument("keyword", help="Texto a buscar en nombre o descripcion")
345
367
 
346
- sub.add_parser('summary', help='Muestra el ultimo snapshot sin reconectar')
368
+ sub.add_parser("summary", help="Muestra el ultimo snapshot sin reconectar")
347
369
 
348
- args = parser.parse_args()
349
- cwd = Path(args.cwd).resolve()
350
- as_json = bool(getattr(args, 'json', False))
351
- traza = bool(getattr(args, 'trace', False))
370
+ args = parser.parse_args()
371
+ cwd = Path(args.cwd).resolve()
372
+ as_json = bool(getattr(args, "json", False))
373
+ traza = bool(getattr(args, "trace", False))
352
374
 
353
- if args.cmd == 'summary':
375
+ if args.cmd == "summary":
354
376
  cmd_summary(cwd, as_json)
355
377
  return
356
378
 
357
379
  servers = _cargar_config(cwd, args.config)
358
380
  if not servers:
359
381
  sys.stderr.write(
360
- '[mcp-orchestrator] No se encontraron servidores MCP.\n'
382
+ "[mcp-orchestrator] No se encontraron servidores MCP.\n"
361
383
  '[mcp-orchestrator] Verifica la clave "mcpServers" en .claude/settings.json\n'
362
384
  )
363
385
  sys.exit(1)
364
386
 
365
- if args.cmd == 'status':
387
+ if args.cmd == "status":
366
388
  asyncio.run(cmd_status(servers, cwd, as_json, traza))
367
- elif args.cmd == 'discover':
389
+ elif args.cmd == "discover":
368
390
  asyncio.run(cmd_discover(servers, cwd, as_json, traza))
369
- elif args.cmd == 'find-tool':
391
+ elif args.cmd == "find-tool":
370
392
  asyncio.run(cmd_find_tool(servers, args.keyword, as_json))
371
393
  else:
372
394
  parser.print_help()
373
395
 
374
396
 
375
- if __name__ == '__main__':
397
+ if __name__ == "__main__":
376
398
  main()