Skip to main content
Glama
nihar777

apollo-cache-copilot

by nihar777

apollo-cache-copilot

CI TypeScript Tested with Vitest License: ISC MCP

KI-Copilot und MCP-Server zur Diagnose von Normalisierungsfehlern des Apollo InMemoryCache – gebaut für React Native, wo es Apollo DevTools nicht gibt.


Das Problem

Apollo Client normalisiert jedes Ergebnis in eine flache Map von __typename:id-Entitäten und speichert Querverweise als { "__ref": "Type:id" }-Zeiger. Diese Normalisierung ist beim Schreiben unsichtbar und schlägt erst beim Lesen fehl – meist auf einem Bildschirm, der weit von der Mutation entfernt ist, die den Fehler verursacht hat. Drei Fehlerklassen dominieren, und alle drei sind still:

Defekt

Was Apollo tut

Symptom

Verwaister Zeiger{ __ref: "User:99" } ohne User:99 im Store

Gibt für das Feld undefined zurück

Leere Zeile, kein Fehler

Fehlendes __typename / id

Kann keinen Cache-Schlüssel berechnen, speichert das Objekt inline

Rendert korrekt, weicht dann beim zweiten Schreiben ab

Typ-/Schlüssel-DriftkeyFields stimmt nicht mit der Server-Payload überein

Dieselbe logische Entität unter zwei Schlüsseln

Doppelte Listeneinträge, veraltete Lesevorgänge

React Native verschlimmert jede einzelne davon:

  • Keine Apollo DevTools. Die Browser-Erweiterung ist das primäre Cache-Debugging-Werkzeug und existiert auf RN nicht. Der Notbehelf ist console.log(JSON.stringify(client.cache.extract())) und das manuelle Durchsehen eines mehrere Megabyte großen Datenblocks.

  • Persistierter Cache. apollo3-cache-persist + AsyncStorage bedeutet, dass ein beschädigter Cache einen Neustart der App überlebt – hartnäckig und nur auf dem Gerät des Benutzers reproduzierbar.

  • Offline-First-Mutationen. Optimistische Antworten schreiben von Natur aus partielle Entitäten – genau die Form, die Defekte 1 und 2 auslöst.

  • Lange Sitzungen. Mobile Apps bleiben tagelang aktiv, daher akkumuliert die Drift weit länger als in einem Browser-Tab.

Related MCP server: mcp-rn-devtools

Die Lösung

Die Erkennung ist deterministisch. Die Erklärung ist Aufgabe des Modells.

  1. Ein Cache-Analysator, der die Ausgabe von cache.extract() durchläuft und strukturelle Defekte mit exakten Pfaden meldet (User:1.avatar → Avatar:99). Reine Graphtraversierung – kein Modell beteiligt, kein Raten, läuft auf einem 10-MB-Snapshot.

  2. Ein MCP-Server, der diesen Analysator dem jeweiligen Agenten zugänglich macht, mit dem der Entwickler bereits spricht. Der Agent fragt nach Befunden plus dem relevanten Teilgraphen, sodass er nie den gesamten Cache im Kontext halten muss.

Die Diagnose wandelt sich von „10-MB-Blob einfügen und blinzeln" zu einem Gespräch.


Architektur

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, dasselbe:

  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 cache

Jeder Graphknoten besitzt genau einen Zustandskanal – der Inspector schreibt findings, der Reasoner schreibt proposedPatches, der Patcher schreibt messages. Nur messages akkumuliert; wenn ein Knoten erneut läuft, wird derselbe Cache erneut analysiert, sodass ein Anhängen an anderer Stelle jeden Befund beim zweiten Durchlauf duplizieren würde.

Warum kein LLM im Graph? Jeder Defekt, den dieser Copilot erkennt, hat eine mechanische Reparatur (Zeiger beschneiden, Waise entfernen). Ein Modell würde Latenz, Kosten und Nichtdeterminismus zu einer Entscheidung hinzufügen, die ein switch bereits korrekt trifft. Der Graph verdient seine Daseinsberechtigung als Orchestrierung; das Modell lebt im MCP-Client, wo es einen Befund mit der Mutation oder dem Fragment korreliert, die es geschrieben hat.


Installation

npm install @indianic/apollo-cache-copilot
# or, from a checkout
npm install && npm run build

Erfordert Node.js ≥ 20 (vitest 4 und @langchain/core setzen es voraus; CI deckt 20 und 22 ab). @apollo/client (v3.8+ oder v4), react und react-native sind Peer-Abhängigkeiten – das Paket verwendet die Kopien deiner App.


Verwendung der Bibliothek

Nur ESM. Das Paket enthält Typdefinitionen.

inspectDanglingRefs – eine Momentaufnahme prüfen

Rein und synchron. Nimmt die Ausgabe von cache.extract() entgegen und gibt Befunde + Statistiken zurück.

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
}

Arten von Befunden: ORPHANED_REF, UNREACHABLE_ENTITY, MISSING_TYPENAME, MISSING_ID. Jeder Befund trägt einen exakten Cache-Pfad.

patchCache – Reparaturen auf einen Live-Cache anwenden

Operationen sind deklarative Deskriptoren, damit sie einen JSON-Sprung überleben; das Werkzeug rehydriert sie in die Funktionen, die cache.modify erwartet. Geordnet, und Fehler werden aufgezeichnet statt geworfen, sodass ein ungültiger Schlüssel mitten im Stapel den Cache nicht halb gepatcht stranden lassen kann.

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() removed

Feldaktionen: DELETE, INVALIDATE, SET (mit value), PRUNE_DANGLING_REFS.

cacheAgentGraph – prüfen → ableiten → planen

Der kompilierte LangGraph. Gibt Befunde, die Patch-Operationen, die er anwenden würde, und eine Schritt-für-Schritt-Erzählung zurück. Er mutiert nie – gib proposedPatches an patchCache weiter, sobald du sie überprüft hast.

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 });

Außerdem exportiert: buildCacheAgentGraph() (unkompilierter Builder), die einzelnen Knoten inspectorNode / reasonerNode / patcherNode, CacheAgentAnnotation, jedes Zod-Schema (InspectDanglingRefsInputSchema, PatchCacheInputSchema, …) und seine abgeleiteten Typen, sowie die MCP-Oberfläche (createServer, startStdioServer, runInspectDanglingRefs, runPatchCache, runDiagnoseCacheGraph).


CLI-Verwendung

apollo-copilot [mcp]          Start the stdio MCP server (default when no args)
apollo-copilot inspect FILE   Diagnose a JSON cache snapshot and print findings

apollo-copilot inspect <file>

Exportiere den Cache aus deiner App und lies ihn dann:

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

Ein sauberer Cache gibt ✓ Cache is clean: no findings. aus.

Exit-Codes: 0 Erfolg, 1 unerwarteter Fehler, 2 ungültige Eingabe (fehlende Datei, nicht lesbare Datei, ungültiges JSON, unbekannter Befehl).

apollo-copilot mcp

Startet den MCP-Server auf stdio und blockiert. Nur nützlich, wenn ein MCP-Client den Prozess besitzt – siehe unten. apollo-copilot-mcp ist ein veralteter Alias für dasselbe.

stdout ist der Protokollkanal. Der Server schreibt nichts außer JSON-RPC nach stdout; alle Diagnosen gehen nach stderr. Füge auf diesem Weg niemals ein console.log hinzu.


MCP-Setup

Verfügbare Tools

Werkzeug

Eingabe

Verhalten

inspect_dangling_refs

cache, optional rootIds / includeUnreachable / includeNormalizationGaps

Nur lesen. Gibt findings + stats zurück.

diagnose_cache_graph

cache

Nur lesen. Führt den vollständigen Graphen aus. Gibt findings, proposedPatches, narration zurück. Nur Planung.

patch_cache

cache, operations, gc, dryRun

Stellt die Momentaufnahme in einem Wegwerf-InMemoryCache wieder her, patcht ihn, gibt results + den erneut extrahierten cache zurück.

patch_cache führt die Momentaufnahme mit sich, weil ein stdio-Server dem Patcher keinen Live-Cache übergeben kann – nur JSON. Vergleiche den zurückgegebenen cache mit deinem oder stelle ihn mit client.cache.restore() wieder her.

Jedes Werkzeug gibt sowohl eine menschenlesbare Zusammenfassungszeile als auch maschinenlesbares structuredContent zurück, sodass Clients, die strukturierte Ausgaben nicht verstehen, trotzdem das JSON erhalten.

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) oder %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "apollo-cache-copilot": {
      "command": "npx",
      "args": ["-y", "-p", "@indianic/apollo-cache-copilot", "apollo-copilot", "mcp"]
    }
  }
}

Bei einem lokalen Checkout – zuerst bauen (npm run build), dann mit einem absoluten Pfad auf das bin-Skript zeigen:

{
  "mcpServers": {
    "apollo-cache-copilot": {
      "command": "node",
      "args": ["/absolute/path/to/apollo-cache-copilot/bin/apollo-copilot.js", "mcp"]
    }
  }
}

Starte Claude Desktop neu. Die drei Werkzeuge erscheinen im Tools-Menü.

Cursor

.cursor/mcp.json im Projekt (oder ~/.cursor/mcp.json für jedes Projekt):

{
  "mcpServers": {
    "apollo-cache-copilot": {
      "command": "npx",
      "args": ["-y", "-p", "@indianic/apollo-cache-copilot", "apollo-copilot", "mcp"]
    }
  }
}

Lokaler Checkout:

{
  "mcpServers": {
    "apollo-cache-copilot": {
      "command": "node",
      "args": ["${workspaceFolder}/bin/apollo-copilot.js", "mcp"]
    }
  }
}

Dann Cursor → Einstellungen → MCP → bestätige, dass der Server grün ist.

Dann einfach fragen

„Hier ist meine Cache-Momentaufnahme – warum ist der Avatar auf dem Profilbildschirm leer?"

Der Agent ruft diagnose_cache_graph auf, erhält User:1.avatar → Avatar:99 plus den vorgeschlagenen PRUNE_DANGLING_REFS, und bringt es mit der Mutation in Verbindung, die eine Referenz ohne den Entitätskörper geschrieben hat.


Entwicklung

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 run

tsconfig.json ist die Build-Konfiguration und schließt __tests__ / __mocks__ aus, sodass das veröffentlichte Paket nur die Werkzeuge umfasst. tsconfig.test.json führt eine Typprüfung für alles durch und erzeugt keine Ausgabe.

Erfolgskennzahlen

#

Kennzahl

Ziel

1

Erkennungs-Recall in der Fixture-Suite

100 % – jeder gezielt eingepflanzte Defekt gefunden

2

Falschpositive bei einer gesunden Momentaufnahme

0

3

Laufzeit des Analysators bei einem 10-MB-extract()

< 1s

4

Befunde mit exaktem Cache-Pfad

100 %

5

Entwicklerzeit vom Symptom zur benannten Grundursache

< 5 Min. (statt Stunden)

6

Pro Diagnose an das Modell gesendete Tokens

< 10k – Befunde + Teilgraph, niemals der gesamte Cache

Lizenz

ISC – siehe LICENSE.

Install Server
A
license - permissive license
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

View all related MCP servers

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

View all MCP Connectors

Latest Blog Posts

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