fix(gx-cli mcp): expone notes/append_notes en MCP y bloquea regresion

Bug encontrado por el agente Echo: el MCP server gx-cli (subcomando
`mcp-server`) llamaba a cmd_node_create / cmd_node_update con un
SimpleNamespace que NO incluia `notes`, asi que `args.notes` lanzaba
AttributeError. Causa raiz: MCP_DISPATCH no defaulteaba `notes` ni
`append_notes`, y el inputSchema de las tools tampoco los anunciaba.

Cambios:

* MCP_TOOLS["node_create"].inputSchema.properties anyade `notes`.
* MCP_TOOLS["node_update"].inputSchema.properties anyade `notes`
  + `append_notes` (boolean, default false).
* MCP_DISPATCH["node_create"] defaultea `notes: None`.
* MCP_DISPATCH["node_update"] defaultea `notes: None`,
  `append_notes: False`.

Tests nuevos en tests/test_gx_cli.py (30 tests):

* CLI: node create/update/delete con notes (replace + append),
  list/show/search, rel create/list/delete con cascada, query
  read-only que rechaza writes, autodetect de tipos.
* MCP dispatcher: cada cmd_* tolera args opcionales omitidos,
  notes y append_notes funcionan via dispatch, MCP_TOOLS y
  MCP_DISPATCH coinciden 1:1 (sanity contractual).
* Regresion 0035d: tests dedicados que congelan el contrato
  notes/append_notes en defaults e inputSchema — si alguien
  vuelve a quitarlos el test se queja inmediatamente.

WSL 74 / Windows 63 + 11 skipped.
This commit is contained in:
2026-05-03 16:09:47 +02:00
parent a0921d8a2c
commit e35c30cdf7
2 changed files with 475 additions and 4 deletions
+10 -4
View File
@@ -726,20 +726,24 @@ MCP_TOOLS = [
"limit": {"type": "integer", "default": 50, "minimum": 1, "maximum": 200}},
"required": ["query"]}},
{"name": "node_create",
"description": "Crea una entidad nueva. Si type se omite, se infiere heuristicamente del name (email/url/domain/ip/phone/text).",
"description": "Crea una entidad nueva. Si type se omite, se infiere heuristicamente del name (email/url/domain/ip/phone/text). Pasa `notes` para inicializar el panel Note del nodo (es lo que leen los enrichers split_sentences y extract_iocs_text como fuente de texto).",
"inputSchema": {"type": "object", "properties": {
"name": {"type": "string"},
"type": {"type": "string", "description": "Opcional. Auto-detectado si se omite."},
"description": {"type": "string"}},
"description": {"type": "string"},
"notes": {"type": "string", "description": "Texto largo libre del nodo (panel Note)."}},
"required": ["name"]}},
{"name": "node_update",
"description": "Modifica campos de una entidad existente. Al menos un campo aparte de id debe pasarse.",
"description": "Modifica campos de una entidad existente. Al menos un campo aparte de id debe pasarse. `notes` reemplaza el contenido completo del panel Note; combinalo con `append_notes=true` para anyadir al final preservando lo existente.",
"inputSchema": {"type": "object", "properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"type": {"type": "string"},
"status": {"type": "string", "enum": ["active", "stale", "corrupted", "archived"]},
"description": {"type": "string"},
"notes": {"type": "string", "description": "Texto del panel Note. Reemplaza el contenido salvo que append_notes=true."},
"append_notes": {"type": "boolean", "default": False,
"description": "Si true, anyade `notes` al final con doble newline en vez de reemplazar."},
"tags": {"type": "string", "description": "JSON array literal o CSV 'a,b,c'"}},
"required": ["id"]}},
{"name": "node_delete",
@@ -830,9 +834,11 @@ MCP_DISPATCH = {
"node_list": (cmd_node_list, {"type": None, "status": None, "limit": 100}),
"node_show": (cmd_node_show, {}),
"node_search": (cmd_node_search, {"limit": 50}),
"node_create": (cmd_node_create, {"type": None, "description": None}),
"node_create": (cmd_node_create, {"type": None, "description": None,
"notes": None}),
"node_update": (cmd_node_update, {"name": None, "type": None,
"status": None, "description": None,
"notes": None, "append_notes": False,
"tags": None}),
"node_delete": (cmd_node_delete, {}),
"rel_create": (cmd_rel_create, {"name": None}),