apollo-cache-copilot
apollo-cache-copilot
Copiloto de IA y servidor MCP para diagnosticar defectos de normalización de InMemoryCache de Apollo, diseñado para React Native, donde Apollo DevTools no existe.
El problema
Apollo Client normaliza cada resultado en un mapa plano de entidades __typename:id y almacena referencias cruzadas como punteros { "__ref": "Type:id" }. Esa normalización es invisible en el momento de la escritura y solo falla en el momento de la lectura, normalmente en una pantalla lejos de la mutación que la causó. Tres clases de fallos dominan, y los tres son silenciosos:
Defecto | Lo que hace Apollo | Síntoma |
Puntero huérfano — | Devuelve | Fila en blanco, sin excepción |
Falta | No puede calcular una clave de caché, almacena el objeto en línea | Se renderiza bien, luego diverge en la segunda escritura |
Deriva de tipo/clave — | Misma entidad lógica bajo dos claves | Elementos de lista duplicados, lecturas obsoletas |
React Native empeora cada uno de ellos:
Sin Apollo DevTools. La extensión del navegador es el depurador principal de caché y no existe en RN. El recurso es
console.log(JSON.stringify(client.cache.extract()))y leer un blob de varios megabytes a simple vista.Caché persistente.
apollo3-cache-persist+ AsyncStorage significa que una caché corrupta sobrevive al reinicio de la aplicación — persistente, y reproducible solo en el dispositivo del usuario.Mutaciones offline-first. Las respuestas optimistas escriben entidades parciales por diseño, que es exactamente la forma que provoca los defectos 1 y 2.
Sesiones largas. Las aplicaciones móviles permanecen residentes durante días, por lo que la deriva se acumula mucho más que en una pestaña del navegador.
Related MCP server: mcp-rn-devtools
La solución
La detección es determinista. La explicación es trabajo del modelo.
Un analizador de caché que recorre la salida de
cache.extract()e informa defectos estructurales con rutas exactas (User:1.avatar → Avatar:99). Recorrido de grafo simple — sin modelo involucrado, sin adivinanzas, funciona en una instantánea de 10MB.Un servidor MCP que expone ese analizador al agente con el que el desarrollador ya está hablando. El agente solicita hallazgos más el subgrafo relevante, por lo que nunca tiene que mantener toda la caché en contexto.
El diagnóstico pasa de "pegar un blob de 10MB y entrecerrar los ojos" a una conversación.
Arquitectura
flowchart TD
subgraph client["MCP client — Claude Desktop / Cursor"]
A["Agent (the LLM)"]
end
subgraph server["apollo-cache-copilot (stdio process)"]
T["StdioServerTransport<br/>apollo-copilot mcp"]
R["Tool registry<br/>inspect_dangling_refs<br/>patch_cache<br/>diagnose_cache_graph"]
Z["Zod schemas<br/>parse in, shape out"]
subgraph g["cacheAgentGraph (LangGraph, LLM-free)"]
I["inspectorNode<br/>writes findings"]
RE["reasonerNode<br/>writes proposedPatches"]
P["patcherNode<br/>writes narration"]
I --> RE --> P
end
TOOL1["inspectDanglingRefs()<br/>pure, on a snapshot"]
TOOL2["patchCache()<br/>modify / evict / gc"]
T --> R --> Z --> I
I -.->|calls| TOOL1
P -.->|plans for| TOOL2
end
A <-->|"JSON-RPC 2.0 over stdio"| T
TOOL1 --- CACHE["cache.extract() snapshot"]
TOOL2 --- LIVE["live ApolloCache"]ASCII, lo mismo:
MCP client (Claude Desktop, Cursor, any stdio client)
│ JSON-RPC 2.0 ▲
▼ over stdio │ stdout IS the protocol channel —
┌─────────────────────────────────┐ all logs go to stderr
│ StdioServerTransport │
├─────────────────────────────────┤
│ tools: inspect_dangling_refs │ read-only
│ patch_cache │ mutating (dryRun available)
│ diagnose_cache_graph │ read-only, plans only
├─────────────────────────────────┤
│ Zod schemas — parse at the edge │
└───────────────┬─────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ cacheAgentGraph (LangGraph, deliberately LLM-free)│
│ │
│ INSPECTOR ──────► REASONER ──────► PATCHER │
│ walks the store maps findings narrates the │
│ → findings[] → patch ops plan │
│ │ │ │
│ │ owns `findings` │ owns `proposedPatches` │
└──────┼──────────────────┼────────────────────────────┘
▼ ▼
inspectDanglingRefs() patchCache()
pure, on a snapshot cache.modify / evict / gc on a live cacheCada nodo del grafo posee exactamente un canal de estado — el inspector escribe findings, el razonador escribe proposedPatches, el parcheador escribe messages. Solo messages se acumula; volver a ejecutar un nodo re-analiza la misma caché, por lo que añadir en otro lugar duplicaría cada hallazgo en la segunda pasada.
¿Por qué no hay LLM en el grafo? Cada defecto que detecta este copiloto tiene una reparación mecánica (podar el puntero, expulsar el huérfano). Un modelo añadiría latencia, costo y no determinismo a una decisión que un switch ya toma correctamente. El grafo se gana su lugar como orquestación; el modelo vive en el cliente MCP, donde correlaciona un hallazgo con la mutación o fragmento que lo escribió.
Instalación
npm install @indianic/apollo-cache-copilot
# or, from a checkout
npm install && npm run buildRequiere Node.js ≥ 20 (vitest 4 y @langchain/core lo requieren; CI cubre 20 y 22). @apollo/client (v3.8+ o v4), react y react-native son dependencias peer — el paquete usa las copias de tu aplicación.
Uso de la biblioteca
Solo ESM. El paquete incluye tipos.
inspectDanglingRefs — auditar una instantánea
Pura y síncrona. Toma la salida de cache.extract(), devuelve hallazgos y estadísticas.
import { inspectDanglingRefs } from 'apollo-cache-copilot';
const { findings, stats } = inspectDanglingRefs({
cache: client.cache.extract(),
// all optional:
rootIds: ['ROOT_QUERY', 'ROOT_MUTATION'], // reachability roots
includeUnreachable: true, // report gc candidates
includeNormalizationGaps: true, // report un-keyable inline objects
});
console.log(stats);
// { entityCount: 4, refCount: 3, danglingCount: 1, unreachableCount: 1 }
for (const f of findings) {
console.log(f.kind, f.path, f.danglingRef ?? '');
// ORPHANED_REF User:1.avatar Avatar:99
// UNREACHABLE_ENTITY Post:7
}Tipos de hallazgo: ORPHANED_REF, UNREACHABLE_ENTITY, MISSING_TYPENAME, MISSING_ID. Cada hallazgo lleva una ruta de caché exacta.
patchCache — aplicar reparaciones a una caché en vivo
Las operaciones son descriptores declarativos para que sobrevivan un salto JSON; la herramienta los rehidrata en las funciones que cache.modify quiere. Ordenadas, y los fallos se registran en lugar de lanzarse, para que una clave incorrecta en medio del lote no pueda dejar la caché a medio parchear.
import { patchCache } from 'apollo-cache-copilot';
const { dryRun, results, collected } = patchCache(client.cache, {
operations: [
// drop dangling refs from a list field
{ type: 'modify', id: 'User:1', fields: { posts: { action: 'PRUNE_DANGLING_REFS' } } },
// delete / invalidate / overwrite a field
{ type: 'modify', id: 'User:1', fields: { avatar: { action: 'DELETE' } } },
{ type: 'modify', id: 'User:1', fields: { bio: { action: 'SET', value: 'unset' } } },
// evict an entity, or one field of it
{ type: 'evict', id: 'Post:7' },
{ type: 'evict', id: 'ROOT_QUERY', fieldName: 'user', args: { id: '1' } },
],
gc: true, // run cache.gc() once, after everything lands
dryRun: false, // true = validate only, cache untouched
});
results.forEach((r) => console.log(r.changed, r.error ?? ''));
console.log('collected:', collected); // keys gc() removedAcciones de campo: DELETE, INVALIDATE, SET (con value), PRUNE_DANGLING_REFS.
cacheAgentGraph — inspeccionar → razonar → planificar
El LangGraph compilado. Devuelve hallazgos, las operaciones de parche que aplicaría y narración paso a paso. Nunca muta — alimenta proposedPatches a patchCache cuando los hayas revisado.
import { cacheAgentGraph } from 'apollo-cache-copilot';
const state = await cacheAgentGraph.invoke({ cacheState: client.cache.extract() });
state.messages.forEach((m) => console.log(String(m.content)));
// 2 findings: 1 orphaned ref, 1 unreachable entity.
// ...
// Review, then apply:
patchCache(client.cache, { operations: state.proposedPatches });También se exportan: buildCacheAgentGraph() (constructor sin compilar), los nodos individuales inspectorNode / reasonerNode / patcherNode, CacheAgentAnnotation, cada esquema Zod (InspectDanglingRefsInputSchema, PatchCacheInputSchema, …) y su tipo inferido, además de la superficie MCP (createServer, startStdioServer, runInspectDanglingRefs, runPatchCache, runDiagnoseCacheGraph).
Uso de CLI
apollo-copilot [mcp] Start the stdio MCP server (default when no args)
apollo-copilot inspect FILE Diagnose a JSON cache snapshot and print findingsapollo-copilot inspect <archivo>
Vuelca la caché de tu aplicación y luego léela:
// in the RN app
console.log(JSON.stringify(client.cache.extract()));npx -y -p @indianic/apollo-cache-copilot apollo-copilot inspect ./cache-snapshot.json━━ Cache Diagnostic ━━
Entities: 4 | Refs: 3 | Dangling: 1 | Unreachable: 1
⚠ ORPHANED_REF (1)
• User:1.avatar → Avatar:99
Points at "Avatar:99", which is not in the cache. Reads here return undefined.
🗑 UNREACHABLE_ENTITY (1)
• Post:7
No root reaches this entity; cache.gc() would collect it.Una caché limpia imprime ✓ Cache is clean: no findings.
Códigos de salida: 0 éxito, 1 fallo inesperado, 2 entrada incorrecta (archivo faltante, archivo ilegible, JSON inválido, comando desconocido).
apollo-copilot mcp
Inicia el servidor MCP en stdio y se bloquea. Solo es útil cuando un cliente MCP posee el proceso — ver más abajo. apollo-copilot-mcp es un alias heredado para lo mismo.
stdout es el canal de protocolo. El servidor no escribe nada más que JSON-RPC a stdout; todos los diagnósticos van a stderr. Nunca añadas un
console.loga esta ruta.
Configuración de MCP
Herramientas expuestas
Herramienta | Entrada | Comportamiento |
|
| Solo lectura. Devuelve |
|
| Solo lectura. Ejecuta el grafo completo. Devuelve |
|
| Restaura la instantánea en un |
patch_cache lleva la instantánea porque un servidor stdio no tiene una caché en vivo para pasar al parcheador — solo JSON. Compara la cache devuelta con la tuya, o usa client.cache.restore().
Cada herramienta devuelve tanto un resumen legible por humanos como structuredContent legible por máquina, para que los clientes que no entienden la salida estructurada aún obtengan el JSON.
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) o %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"apollo-cache-copilot": {
"command": "npx",
"args": ["-y", "-p", "@indianic/apollo-cache-copilot", "apollo-copilot", "mcp"]
}
}
}Desde un checkout local — compila primero (npm run build), luego apunta al bin con una ruta absoluta:
{
"mcpServers": {
"apollo-cache-copilot": {
"command": "node",
"args": ["/absolute/path/to/apollo-cache-copilot/bin/apollo-copilot.js", "mcp"]
}
}
}Reinicia Claude Desktop. Las tres herramientas aparecen en el menú de herramientas.
Cursor
.cursor/mcp.json en el proyecto (o ~/.cursor/mcp.json para todos los proyectos):
{
"mcpServers": {
"apollo-cache-copilot": {
"command": "npx",
"args": ["-y", "-p", "@indianic/apollo-cache-copilot", "apollo-copilot", "mcp"]
}
}
}Checkout local:
{
"mcpServers": {
"apollo-cache-copilot": {
"command": "node",
"args": ["${workspaceFolder}/bin/apollo-copilot.js", "mcp"]
}
}
}Luego Cursor → Configuración → MCP → confirma que el servidor está en verde.
Y luego solo pregunta
"Aquí está mi instantánea de caché — ¿por qué el avatar está en blanco en la pantalla de perfil?"
El agente llama a diagnose_cache_graph, obtiene User:1.avatar → Avatar:99 más el PRUNE_DANGLING_REFS propuesto, y lo correlaciona con la mutación que escribió una referencia sin el cuerpo de la entidad.
Desarrollo
npm install
npm run build # tsc -> dist/ (run first: typecheck and tests import dist)
npm run typecheck # tsc --noEmit -p tsconfig.test.json (includes tests)
npm test # vitest runtsconfig.json es la compilación y excluye __tests__ / __mocks__ para que el paquete publicado sea solo las herramientas. tsconfig.test.json verifica tipos de todo y no emite nada.
Métricas de éxito
# | Métrica | Objetivo |
1 | Recuerdo de detección en el conjunto de fixtures | 100% — cada defecto sembrado encontrado |
2 | Falsos positivos en una instantánea saludable | 0 |
3 | Tiempo de ejecución del analizador en un | < 1s |
4 | Hallazgos con una ruta de caché exacta | 100% |
5 | Tiempo del desarrollador desde el síntoma hasta la causa raíz nombrada | < 5 min (vs. horas) |
6 | Tokens enviados al modelo por diagnóstico | < 10k — hallazgos y subgrafo, nunca toda la caché |
Licencia
ISC — ver LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityFmaintenanceAn MCP server that connects to your React Native application debugger22032MIT
- AlicenseNot gradedqualityBmaintenanceThis MCP server enables real-time debugging and inspection of running React Native apps, providing access to console logs, errors, network requests, navigation state, storage, and performance profiling.1MIT
- AlicenseAqualityAmaintenanceMCP server that gives AI coding agents hands, eyes and a mechanic's ear for React Native development.9202MIT
- AlicenseNot gradedqualityAmaintenanceA plugin-based MCP server for React Native runtime debugging, inspection, and automation via Chrome DevTools Protocol. Works with Expo, bare React Native, and any Metro + Hermes project without app code changes.1,04475MIT
Related MCP Connectors
MCP server for Appcircle mobile CI/CD platform.
MCP server for managing Prisma Postgres.
MCP server for interacting with the Supabase platform
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nihar777/apollo-cache-copilot'
If you have feedback or need assistance with the MCP directory API, please join our Discord server