apollo-cache-copilot
apollo-cache-copilot
ИИ-копilot и MCP-сервер для диагностики дефектов нормализации InMemoryCache в Apollo — создан для React Native, где Apollo DevTools не существует.
Проблема
Apollo Client нормализует каждый результат в плоскую карту сущностей __typename:id и хранит перекрёстные ссылки как указатели { "__ref": "Type:id" }. Эта нормализация невидима в момент записи и проявляется только в момент чтения — обычно на экране, далёком от мутации, которая её вызвала. Доминируют три класса дефектов, и все три молчаливы:
Дефект | Что делает Apollo | Симптом |
Осиротевший указатель — | Возвращает | Пустая строка, без исключения |
Отсутствует | Не может вычислить ключ кэша, хранит объект встроенно | Рендерится нормально, затем расходится при второй записи |
Дрейф типа/ключа — | Одна и та же логическая сущность под двумя ключами | Дублирующиеся элементы списка, устаревшие чтения |
React Native усугубляет каждый из них:
Нет Apollo DevTools. Расширение для браузера — основной отладчик кэша, и в RN его не существует. Запасной вариант —
console.log(JSON.stringify(client.cache.extract()))и чтение многомегабайтного блоба вручную.Персистентный кэш.
apollo3-cache-persist+ AsyncStorage означает, что повреждённый кэш переживает перезапуск приложения — липкая проблема, воспроизводимая только на устройстве пользователя.Офлайн-мутации. Оптимистичные ответы записывают частичные сущности по своей природе — это ровно та форма, которая провоцирует дефекты 1 и 2.
Долгие сессии. Мобильные приложения остаются в памяти днями, поэтому дрейф накапливается значительно дольше, чем в браузерной вкладке.
Related MCP server: mcp-rn-devtools
Решение
Детекция детерминирована. Объяснение — задача модели.
Анализатор кэша, который обходит вывод
cache.extract()и сообщает о структурных дефектах с точными путями (User:1.avatar → Avatar:99). Обычный обход графа — без модели, без догадок, работает на снимке в 10 МБ.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-native — peer-зависимости — пакет использует зависимости вашего приложения.
Использование библиотеки
Только 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 findingsapollo-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
Доступные инструменты
Инструмент | Входные данные | Поведение |
|
| Только чтение. Возвращает |
|
| Только чтение. Запускает полный граф. Возвращает |
|
| Восстанавливает снимок в одноразовом |
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 runtsconfig.json — это сборка, и он исключает __tests__ / __mocks__, чтобы
опубликованный пакет содержал только инструменты. tsconfig.test.json проверяет типы
всего и ничего не генерирует.
Метрики успеха
# | Метрика | Цель |
1 | Полнота детекции на наборе фикстур | 100% — каждый посеянный дефект найден |
2 | Ложные срабатывания на здоровом снимке | 0 |
3 | Время работы анализатора на | < 1с |
4 | Находки с точным путём в кэше | 100% |
5 | Время разработчика от симптома до названной первопричины | < 5 мин (вместо часов) |
6 | Токенов, отправляемых модели на одну диагностику | < 10k — находки + подграф, никогда не весь кэш |
Лицензия
ISC — см. 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