diff --git a/dev/issues/README.md b/dev/issues/README.md index 7e915974..20cfbfe4 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -79,3 +79,4 @@ | [0061](0061-notify-telegram-integration.md) | Integrar `notify_telegram` en deploy_server + bucle reactivo | pendiente | media | integration | 0054 | | [0062](0062-deprecate-unused-core-functions.md) | Politica de deprecacion para funciones del registry sin consumidores | pendiente | baja | research | — | | [0063](0063-kanban-stickers.md) | kanban: sistema de stickers (emojis) sobre cards | pendiente | media | feature | — | +| [0064](completed/0064-registry-mcp-server.md) | registry_mcp: servidor MCP que expone registry.db a Claude | completado | alta | feature | — | diff --git a/dev/issues/completed/0064-registry-mcp-server.md b/dev/issues/completed/0064-registry-mcp-server.md new file mode 100644 index 00000000..13f89041 --- /dev/null +++ b/dev/issues/completed/0064-registry-mcp-server.md @@ -0,0 +1,267 @@ +# 0064 — registry_mcp: servidor MCP que expone registry.db a Claude + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0064 | +| **Estado** | pendiente | +| **Prioridad** | alta | +| **Tipo** | feature — `apps/registry_mcp/` | + +## Dependencias + +- Ninguna directa. Aplica TBD obligatorio (`.claude/rules/apps_tbd.md`): trabajar en `issue/0064-registry-mcp-server`, merge `--no-ff` a master. +- Reutiliza `registry/` package (ya carga registry.db con FTS5) y `cmd/fn/run.go` (dispatcher por lenguaje). + +## Objetivo + +Convertir `registry.db` en herramienta de primera clase para Claude (Code, Desktop, otros clientes MCP) via servidor MCP. Tras este issue, cualquier sesion Claude con MCP registrado puede llamar `fn_search`, `fn_show`, `fn_code`, `fn_run`, etc., sin abrir bash ni `sqlite3`. + +## Contexto + +Estado actual: + +- registry.db tiene 1090 funciones, 166 tipos, FTS5 sobre `code/description/params_schema/notes/documentation`. 23 MB. Local en raiz del repo + replica en `registry_api` del VPS. +- Cada agente reinventa: `sqlite3 registry.db "SELECT..."`, parseo manual, ejecucion via `fn run` shellando. Caro en tokens, error-prone, no compone. +- `fn` CLI ya implementa todo (search/show/run/doctor) — falta exponerlo como protocolo MCP estable. + +MCP (Model Context Protocol): + +- Anthropic spec, transport stdio (default Claude Code/Desktop) o HTTP+SSE. +- SDKs: Python (`mcp` paquete oficial), TypeScript, Go (`mark3labs/mcp-go`). +- Tools = funciones JSON-schema-tipadas que el LLM ve y puede invocar. + +Decision por defecto (confirmar en Fase 1): + +- Lenguaje: **Go**. Reutiliza `registry/` (mismo paquete que `cmd/fn`), un solo binario, despliegue trivial. SDK: `github.com/mark3labs/mcp-go`. +- Transport: **stdio** primero (caso 90% Claude Code/Desktop). HTTP+SSE como flag opcional (`--http :7733`) para clientes remotos. +- **Read-only en v1.** `fn_run` y mutacion de proposals quedan para fase 2 (issue separado), tras decidir politica de confirmacion. + +## Decisiones de diseno (a confirmar antes de implementar) + +| Decision | Default recomendado | Alternativas | +|----------|--------------------|--------------| +| Lenguaje | Go (`mark3labs/mcp-go`) | Python (`mcp` oficial, mas comun) | +| Transport v1 | stdio | HTTP+SSE, ambos | +| Tools v1 | search, show, code, list_domains, uses, doctor (read-only) | Incluir `fn_run` desde el dia 1 con confirmation prompt | +| Auth HTTP | `REGISTRY_API_TOKEN` (basicAuth) | Sin auth (solo loopback) | +| Origen datos | registry.db local (FN_REGISTRY_ROOT) | Fallback a `registry_api` HTTP si no hay clone | +| Logging | stdout estructurado (slog) en stderr para no romper stdio | operations.db con executions de cada tool call | +| Salida `fn_show` | Markdown render-ready (frontmatter + code fenced) | JSON crudo | +| Limites | search: 50 resultados default, paginable | Sin limite | + +Confirmar con usuario antes de Fase 2. Si elige defaults, proceder. + +## Arquitectura + +### Archivos afectados + +**App nueva (`apps/registry_mcp/`):** + +- `main.go` (NEW) — flag parsing (`--stdio` default, `--http :PORT`), arranque server, wiring tools. +- `server.go` (NEW) — registro de tools en `mcp-go`, dispatcher. +- `tools/search.go` (NEW) — `fn_search`, query a `functions_fts` + `types_fts`. +- `tools/show.go` (NEW) — `fn_show`, lee fila completa + formatea markdown. +- `tools/code.go` (NEW) — `fn_code`, devuelve `code` columna como string. +- `tools/list_domains.go` (NEW) — `fn_list_domains`, agregados por `(domain, kind, purity, lang)`. +- `tools/uses.go` (NEW) — `fn_uses`, parsea `uses_functions/uses_types` JSON + busca consumidores reverso. +- `tools/doctor.go` (NEW) — `fn_doctor`, llama a `fn doctor --json` como subprocess (mas barato que reimplementar). +- `tools/run.go` (NEW pero **detras de flag** `--enable-run` por seguridad) — wrap de `fn run` via subprocess. +- `db.go` (NEW) — abre registry.db en read-only WAL, ping, helpers de query. +- `format.go` (NEW) — formateo markdown comun (frontmatter + code blocks). +- `app.md` (NEW) — frontmatter de la app, `framework: "mcp"`, `tags: [service, mcp]`. +- `README.md` (NEW) — instalacion, ejemplos de queries. +- `CMakeLists.txt` — N/A (es Go). +- `Makefile` (NEW, opcional) — `make build`, `make install-claude-code`, `make install-claude-desktop`. +- `migrations/001_init.sql` (NEW, opcional) — operations.db propio para logs de queries (fase 2). + +**fn_registry root:** + +- `cmd/fn/` — sin cambios. La app es independiente y consume `registry/` package directamente. +- `.gitignore` ya excluye `apps/*/` excepto `app.md`. La app vive en su sub-repo Gitea (regla `apps_tbd.md` + `apps_own_repo`). + +### Pure / impure split + +- **Pure (testeable sin BD):** + - `format.RenderFunctionMarkdown(fn registry.Function) string` + - `format.RenderTypeMarkdown(t registry.Type) string` + - `tools.parseSearchFilters(args map[string]any) SearchFilters` +- **Impure (bordes):** + - `db.Open(path)`, `db.Search`, `db.Show` — SQLite I/O. + - `tools/run.go`, `tools/doctor.go` — subprocess. + - server stdio/HTTP — I/O. + +### Funciones del registry a reutilizar (registry-first) + +Auditar antes de escribir: + +```bash +sqlite3 registry.db "SELECT id FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:mcp OR description:mcp OR description:\"model context protocol\"');" +sqlite3 registry.db "SELECT id FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:sqlite OR name:fts5');" +``` + +Reutilizables casi seguro: + +- `sqlite_open_go_infra` (WAL + foreign keys + ping). +- `error_go_core` para tipos de error. +- `http_json_response_go_infra`, `http_parse_body_go_infra` si transport HTTP. +- `random_hex_id_go_core` para correlation IDs en logs. + +Si falta: + +- `mcp_tool_register_go_infra` (registra tool con schema en mcp-go) — si surge patron repetido en N tools, delegar a `fn-constructor`. +- `markdown_function_card_go_infra` (formatea funcion del registry como markdown card) — candidato si encaja con regla "dos artefactos lo usaran". + +### Schema MCP de cada tool (resumen) + +```jsonc +// fn_search +{ + "query": "string (FTS5 expression o texto libre)", + "kind": "function|pipeline|component|type? (optional)", + "lang": "go|py|bash|ts|cpp? (optional)", + "domain": "string? (optional)", + "purity": "pure|impure? (optional)", + "limit": "int? (default 50)" +} +// returns: { "results": [{ "id","name","kind","lang","domain","purity","signature","description" }] } + +// fn_show +{ "id": "string" } +// returns: { "id","markdown" } + +// fn_code +{ "id": "string" } +// returns: { "id","lang","code" } + +// fn_list_domains +{} +// returns: { "domains": [{ "domain","functions","types","pure","impure","by_lang":{...} }] } + +// fn_uses +{ "id": "string" } +// returns: { "uses_functions":[], "uses_types":[], "consumed_by":[] } + +// fn_doctor +{ "subcommand": "artefacts|services|sync|uses-functions|unused? (optional, default all)" } +// returns: { "report": } +``` + +## Tareas + +### Fase 1 — confirmar diseno + +1.1 Confirmar las 8 decisiones de la tabla con el usuario. Si elige defaults, seguir. +1.2 Auditar registry: ejecutar las dos queries FTS5 de "Funciones del registry a reutilizar" y documentar reutilizables aqui. +1.3 Decidir si v1 incluye `fn_run` (con confirmation) o solo read-only. + +### Fase 2 — bootstrap app + +2.1 Crear `apps/registry_mcp/` con `app.md` (frontmatter completo: `framework: "mcp"`, `tags: [service, mcp]`, `dir_path`, `repo_url` placeholder), `README.md` esqueleto. +2.2 Inicializar sub-repo Gitea (`dataforge/registry_mcp`) via `gitea_create_repo_bash_infra` + `ensure_repo_synced_bash_infra`. Branch `master`. +2.3 `go mod init` y añadir `github.com/mark3labs/mcp-go`. +2.4 Wire minimo: `main.go` con stdio server vacio + 1 tool `ping` que devuelve `pong`. Probar con `claude` MCP. + +### Fase 3 — tools read-only + +3.1 `db.go`: abrir registry.db read-only, helpers de query. Reusar `sqlite_open_go_infra` si compatible. +3.2 `tools/list_domains.go` (mas simple, valida wiring con datos reales). +3.3 `tools/search.go`: FTS5 con escapado seguro de tokens (gotcha de la regla de quoting). Filtros opcionales. +3.4 `tools/show.go` + `format.go`: markdown card. +3.5 `tools/code.go`: solo `code` column. +3.6 `tools/uses.go`: parse JSON `uses_functions/uses_types` + reverse lookup (`SELECT id FROM functions WHERE uses_functions LIKE '%""%'`). +3.7 `tools/doctor.go`: subprocess `fn doctor --json`. Devolver el JSON parseado tal cual. + +### Fase 4 — transport HTTP opcional + +4.1 Flag `--http :7733` en `main.go`. +4.2 Auth basicAuth via `REGISTRY_API_TOKEN` (header `Authorization: Bearer ...` o user:pass). +4.3 Bind por defecto a `127.0.0.1` salvo flag `--bind 0.0.0.0` explicito. + +### Fase 5 — tests + +5.1 `db_test.go`: abrir registry.db de fixture (mini, ~5 funciones), verificar search/show. +5.2 `tools/search_test.go`: filtros, escapado FTS5, paginacion. +5.3 `tools/show_test.go`: markdown bien formado (snapshot). +5.4 `tools/uses_test.go`: dependencias directas + reverse. +5.5 Integration test: arrancar server stdio en goroutine, mandar `tools/list` JSON-RPC, validar schema de cada tool. +5.6 `fn doctor uses-functions` no debe regresionar. + +### Fase 6 — instalacion + docs + +6.1 `Makefile`: `build` (CGO_ENABLED=1 -tags fts5), `install-claude-code` (escribe `.mcp.json` en cwd), `install-claude-desktop` (linea en `~/.config/claude/mcp.json` o equivalente macOS). +6.2 `README.md`: como registrar el server, ejemplos de queries Claude haria ("busca funciones puras de finance", "muestra el codigo de filter_slice_go_core", "que funciones usan sqlite_open_go_infra"). +6.3 Probar end-to-end desde una sesion Claude Code real: `fn_search("slice")`, `fn_show("filter_slice_go_core")`. + +### Fase 7 — cleanup + index + +7.1 Si se reutilizo o creo funcion del registry → declararla en `apps/registry_mcp/app.md` (`uses_functions`). +7.2 Una linea en CHANGELOG.md. +7.3 `fn index` y verificar `fn show registry_mcp_app`. +7.4 Crear sub-issue 0064b si se decide implementar `fn_run` y mutacion de proposals (fuera de scope de este issue). + +## Ejemplo de uso + +Sesion Claude Code, MCP `registry_mcp` registrado: + +``` +Usuario: "necesito una funcion Go pura que filtre slices" + +Claude (internamente): + → fn_search({ query: "filter slice", lang: "go", purity: "pure" }) + ← { results: [ + { id: "filter_slice_go_core", signature: "func FilterSlice[T any](xs []T, pred func(T) bool) []T", description: "..." }, + ... + ] } + → fn_show({ id: "filter_slice_go_core" }) + ← { markdown: "# filter_slice_go_core\n\n```go\nfunc FilterSlice...\n```\n..." } + +Claude (al usuario): "existe `filter_slice_go_core`. Aqui el codigo: ..." +``` + +``` +Usuario: "diagnostico del sistema" + +Claude: + → fn_doctor({}) + ← { report: { artefacts: [...], services: [...], sync_drift: [], unused: [...] } } + +Claude: "5 services activos, 2 con puerto sin escuchar (...), 12 funciones huerfanas en `core`." +``` + +## Decisiones de diseno (rationale) + +- **Go vs Python**: Go reusa el codigo existente sin duplicar (mismo `registry/` package, mismos tipos). Python tendria SDK MCP mas maduro pero obligaria a reimplementar parsers. Coste-beneficio favorece Go. +- **stdio default**: Claude Code lo usa nativamente. Cero config de red/firewall/puertos. HTTP es para casos remoto. +- **Read-only v1**: `fn_run` ejecuta codigo arbitrario en host del usuario. Confirmation flow (prompt antes de exec) merece su propio issue, no urge en v1. +- **Subprocess para `fn doctor`**: reimplementar todos los checks duplicaria logica. Subprocess es ~50ms y ya devuelve JSON. KISS. +- **markdown en `fn_show`**: el LLM consume markdown nativamente. JSON crudo le obligaria a re-renderizar. Una sola conversion en el server. + +## Riesgos + +- **CGO + sqlite-fts5 en build**: el binario debe compilar con `-tags fts5`. Documentar en Makefile + README. Si el VPS hostea version HTTP, debe tener `gcc`. +- **Tamaño de respuesta**: `fn_show` puede devolver funciones de varios KB de codigo. Limitar a ~50 KB y truncar con aviso. +- **FTS5 quoting**: la regla `CLAUDE.md` documenta gotchas (`description:single-page` rompe). El server debe sanitizar input del LLM antes de pasar a FTS5. Test explicito de tokens con guiones, puntos, comillas. +- **Race con `fn index`**: si registry.db se regenera mientras el server tiene WAL abierto, podria ver schema viejo. Mitigacion: el server reabre conexion si detecta `SQLITE_CORRUPT` o si mtime de la BD cambia. +- **Auth HTTP**: si se expone a 0.0.0.0 sin token, cualquier red local lee el registry. Default `127.0.0.1` + token obligatorio en `0.0.0.0`. +- **Drift entre PCs**: registry.db local puede estar desactualizado vs `registry_api`. Documentar que el server consume el local y sugerir `fn sync` al usuario antes. +- **Logs ensucian stdio**: Go SDK usa stdout para JSON-RPC. Cualquier `fmt.Println` rompe el protocolo. Forzar `slog` a stderr. +- **`fn_run` en fase 2**: ejecutar codigo arbitrario sin confirmation expone al usuario. Diseñar prompt-before-exec antes de habilitarlo. + +## Prerequisitos + +- TBD: rama `issue/0064-registry-mcp-server` desde `master` actualizado. +- Confirmar las 8 decisiones de Fase 1 con el usuario. +- Sub-repo Gitea `dataforge/registry_mcp` creado (Fase 2.2). +- `mark3labs/mcp-go` validado (revisar version/maturity en Fase 1). + +## Criterios de aceptacion + +- [ ] `claude` desde cualquier directorio con MCP `registry_mcp` registrado puede llamar `fn_search("slice")` y obtener resultados sin shellar `sqlite3`. +- [ ] `fn_show(id)` devuelve markdown renderizable que Claude muestra inline. +- [ ] Latencia <100ms para search en BD local (FTS5 ya es rapido; verificar). +- [ ] Tests pasan: unit (parsers, format) + integration (stdio JSON-RPC end-to-end). +- [ ] `app.md` con `uses_functions` actualizado. +- [ ] README documenta instalacion para Claude Code y Claude Desktop. +- [ ] `fn doctor uses-functions` no regresiona.