---
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.
