Skip to main content
Glama
nihar777

apollo-cache-copilot

by nihar777

apollo-cache-copilot

CI TypeScript Tested with Vitest License: ISC MCP

ИИ-копilot и MCP-сервер для диагностики дефектов нормализации InMemoryCache в Apollo — создан для React Native, где Apollo DevTools не существует.


Проблема

Apollo Client нормализует каждый результат в плоскую карту сущностей __typename:id и хранит перекрёстные ссылки как указатели { "__ref": "Type:id" }. Эта нормализация невидима в момент записи и проявляется только в момент чтения — обычно на экране, далёком от мутации, которая её вызвала. Доминируют три класса дефектов, и все три молчаливы:

Дефект

Что делает Apollo

Симптом

Осиротевший указатель{ __ref: "User:99" } без User:99 в хранилище

Возвращает undefined для поля

Пустая строка, без исключения

Отсутствует __typename / id

Не может вычислить ключ кэша, хранит объект встроенно

Рендерится нормально, затем расходится при второй записи

Дрейф типа/ключаkeyFields расходится с данными сервера

Одна и та же логическая сущность под двумя ключами

Дублирующиеся элементы списка, устаревшие чтения

React Native усугубляет каждый из них:

  • Нет Apollo DevTools. Расширение для браузера — основной отладчик кэша, и в RN его не существует. Запасной вариант — console.log(JSON.stringify(client.cache.extract())) и чтение многомегабайтного блоба вручную.

  • Персистентный кэш. apollo3-cache-persist + AsyncStorage означает, что повреждённый кэш переживает перезапуск приложения — липкая проблема, воспроизводимая только на устройстве пользователя.

  • Офлайн-мутации. Оптимистичные ответы записывают частичные сущности по своей природе — это ровно та форма, которая провоцирует дефекты 1 и 2.

  • Долгие сессии. Мобильные приложения остаются в памяти днями, поэтому дрейф накапливается значительно дольше, чем в браузерной вкладке.

Related MCP server: mcp-rn-devtools

Решение

Детекция детерминирована. Объяснение — задача модели.

  1. Анализатор кэша, который обходит вывод cache.extract() и сообщает о структурных дефектах с точными путями (User:1.avatar → Avatar:99). Обычный обход графа — без модели, без догадок, работает на снимке в 10 МБ.

  2. MCP-сервер, предоставляющий этот анализатор любому агенту, с которым уже работает разработчик. Агент запрашивает находки плюс релевантный подграф, поэтому ему не нужно удерживать весь кэш в контексте.

Диагностика переходит от «вставь 10 МБ блоба и вглядывайся» к диалогу.


Архитектура

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, то же самое:

  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

Каждый узел графа владеет ровно одним каналом состояния — инспектор пишет findings, аналитик пишет proposedPatches, патчер пишет messages. Только messages накапливается; повторный запуск узла заново анализирует тот же кэш, поэтому добавление в другом месте продублировало бы каждую находку при втором проходе.

Почему в графе нет LLM? Каждый дефект, который обнаруживает этот копilot, имеет механическое исправление (обрезать указатель, удалить осиротевшую сущность). Модель добавила бы задержку, стоимость и недетерминизм в решение, которое switch уже принимает корректно. Граф оправдывает себя как оркестрация; модель живёт в MCP-клиенте, где она сопоставляет находку с мутацией или фрагментом, которые её породили.


Установка

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

Требуется Node.js ≥ 20 (vitest 4 и @langchain/core оба требуют этого; CI покрывает 20 и 22). @apollo/client (v3.8+ или v4), react и react-nativepeer-зависимости — пакет использует зависимости вашего приложения.


Использование библиотеки

Только ESM. Пакет поставляется с типами.

inspectDanglingRefs — аудит снимка

Чистая и синхронная. Принимает вывод cache.extract(), возвращает находки + статистику.

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
}

Виды находок: ORPHANED_REF, UNREACHABLE_ENTITY, MISSING_TYPENAME, MISSING_ID. Каждая находка несёт точный путь в кэше.

patchCache — применение исправлений к живому кэшу

Операции — декларативные дескрипторы, поэтому они переживают JSON-передачу; инструмент восстанавливает их в функции, которые ожидает cache.modify. Упорядочены, а сбои записываются, а не выбрасываются, чтобы плохой ключ в середине пакета не оставил кэш наполовину пропатченным.

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

Действия с полями: DELETE, INVALIDATE, SET (со value), PRUNE_DANGLING_REFS.

cacheAgentGraph — инспекция → анализ → план

Скомпилированный LangGraph. Возвращает находки, операции патча, которые он бы применил, и пошаговое описание. Он никогда не мутирует — передайте proposedPatches в patchCache, когда вы их проверите.

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

Также экспортируются: buildCacheAgentGraph() (нескомпилированный конструктор), отдельные узлы inspectorNode / reasonerNode / patcherNode, CacheAgentAnnotation, все Zod-схемы (InspectDanglingRefsInputSchema, PatchCacheInputSchema, …) и их выведенные типы, а также MCP-поверхность (createServer, startStdioServer, runInspectDanglingRefs, runPatchCache, runDiagnoseCacheGraph).


Использование CLI

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>

Сделайте дамп кэша из вашего приложения, затем прочитайте его:

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

Чистый кэш выводит ✓ Cache is clean: no findings.

Коды выхода: 0 — успех, 1 — непредвиденный сбой, 2 — некорректный ввод (отсутствующий файл, нечитаемый файл, невалидный JSON, неизвестная команда).

apollo-copilot mcp

Запускает MCP-сервер на stdio и блокируется. Полезен только когда MCP-клиент владеет процессом — см. ниже. apollo-copilot-mcp — легаси-алиас для того же самого.

stdout — это канал протокола. Сервер пишет в stdout только JSON-RPC; вся диагностика идёт в stderr. Никогда не добавляйте console.log в этот путь.


Настройка MCP

Доступные инструменты

Инструмент

Входные данные

Поведение

inspect_dangling_refs

cache, опционально rootIds / includeUnreachable / includeNormalizationGaps

Только чтение. Возвращает findings + stats.

diagnose_cache_graph

cache

Только чтение. Запускает полный граф. Возвращает findings, proposedPatches, narration. Только планирование.

patch_cache

cache, operations, gc, dryRun

Восстанавливает снимок в одноразовом InMemoryCache, применяет патчи, возвращает results + повторно извлечённый cache.

patch_cache переносит снимок, потому что у stdio-сервера нет живого кэша, который можно передать патчеру — только JSON. Сравните возвращённый cache со своим, или вызовите client.cache.restore().

Каждый инструмент возвращает и человекочитаемую сводную строку, и машиночитаемый structuredContent, чтобы клиенты, не понимающие структурированный вывод, всё равно получили JSON.

Claude Desktop

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

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

Из локального клона — сначала соберите (npm run build), затем укажите на бинарник абсолютным путём:

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

Перезапустите Claude Desktop. Три инструмента появятся в меню инструментов.

Cursor

.cursor/mcp.json в проекте (или ~/.cursor/mcp.json для всех проектов):

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

Локальный клон:

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

Затем Cursor → Settings → MCP → убедитесь, что сервер зелёный.

Затем просто спросите

«Вот мой снимок кэша — почему аватар пустой на экране профиля?»

Агент вызывает diagnose_cache_graph, получает User:1.avatar → Avatar:99 плюс предлагаемый PRUNE_DANGLING_REFS и сопоставляет это с мутацией, которая записала ссылку без тела сущности.


Разработка

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 — это сборка, и он исключает __tests__ / __mocks__, чтобы опубликованный пакет содержал только инструменты. tsconfig.test.json проверяет типы всего и ничего не генерирует.

Метрики успеха

#

Метрика

Цель

1

Полнота детекции на наборе фикстур

100% — каждый посеянный дефект найден

2

Ложные срабатывания на здоровом снимке

0

3

Время работы анализатора на extract() в 10 МБ

< 1с

4

Находки с точным путём в кэше

100%

5

Время разработчика от симптома до названной первопричины

< 5 мин (вместо часов)

6

Токенов, отправляемых модели на одну диагностику

< 10k — находки + подграф, никогда не весь кэш

Лицензия

ISC — см. 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