fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
284 lines
14 KiB
Markdown
284 lines
14 KiB
Markdown
---
|
|
id: "0064"
|
|
title: "registry_mcp: servidor MCP que expone registry.db a Claude"
|
|
status: completado
|
|
type: feature
|
|
domain:
|
|
- meta
|
|
scope: multi-app
|
|
priority: alta
|
|
depends: []
|
|
blocks: []
|
|
related: []
|
|
created: 2026-05-17
|
|
updated: 2026-05-17
|
|
tags: []
|
|
---
|
|
# 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": <objeto del fn doctor --json> }
|
|
```
|
|
|
|
## 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 '%"<id>"%'`).
|
|
3.7 `tools/doctor.go`: subprocess `fn doctor <sub> --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.
|