В Часть 1Я представил идею «сначала открытие» — MCP, который можно подключить к любой SaaS CMS и самостоятельно изучить ее структуру.
В этом посте подробно рассматриваются: как MCP обнаруживает вашу схему, строит ее полезную карту и запоминает полученные знания, чтобы последующие запросы казались мгновенными.
Discovery – опросить CMS о себе
Когда MCP впервые подключается к CMS, он не угадывает, какие типы существуют. Он анализирует API GraphQL CMS, используя стандартный запрос самоанализа GraphQL.
В коде это выглядит так:
// src/clients/graph-client.ts
import { getIntrospectionQuery, IntrospectionQuery } from 'graphql';
async introspect(): Promise {
return await this.query(
getIntrospectionQuery(),
undefined,
{
cacheKey: 'graphql:introspection',
cacheTtl: 3600 // 1 hour cache
}
);
}
getIntrospectionQuery() — это полная спецификация интроспекции схемы из эталонной реализации GraphQL.
Он не просто выбирает типы и поля — он возвращает все: типы объектов, интерфейсы, перечисления, объекты ввода, директивы и даже ссылки на вложенные типы глубиной до девяти уровней.
Это богатство имеет значение, потому что MCP необходимо рассуждать о таких структурах, как [BlockData!]! или определить, какие типы реализуют _IContent или _IComponent.
Создание карты типов: превращение сырого самоанализа во что-то полезное
Реакция самоанализа может быть огромной. По сути, это вся схема CMS в форме JSON, описывающая сотни типов и отношений. Само по себе это громоздко. MCP нужен более быстрый способ навигации.
Вот где тип карты входит.
Карта типов — это простая, но мощная таблица поиска, которая индексирует каждый обнаруженный тип по имени.
Он позволяет MCP напрямую переходить к деталям любого типа без повторного анализа всей схемы, что обеспечивает такие функции, как:
- Определение того, какие типы представляют контент или компоненты.
- Обход отношений между вложенными объектами.
- Динамическое создание действительных запросов GraphQL без шаблонов жесткого кодирования.
Вот код, который его создает:
// src/logic/graph/schema-introspector.ts
async initialize(): Promise {
if (this.schema) return;
// Fetch and cache the full schema
this.schema = await withCache(
'graphql:schema:full',
() => this.client.introspect(),
3600
);
// Index all types for fast lookup
this.schema.__schema.types.forEach(type => {
this.typeMap.set(type.name, type);
});
// Identify root query type for future lookups
const queryType = this.typeMap.get(this.schema.__schema.queryType.name);
if (queryType && queryType.kind === 'OBJECT') {
this.queryTypeInfo = this.extractTypeInfo(queryType);
}
this.logger.info('Schema introspection completed', {
typeCount: this.schema.__schema.types.length,
queryFields: this.queryTypeInfo?.fields?.length || 0
});
}
К концу этого шага MCP знает, как формируется ваша CMS: какие типы существуют, как они соединяются и что предоставляет каждое поле.
Эти знания он использует для создания безопасных запросов на лету.
Кэширование — учимся один раз, запоминаем быстро
Как только MCP обнаружит вашу схему, ему не нужно будет делать это снова. Полный самоанализ GraphQL может занять секунду или больше, поэтому сервер накладывает свои кэши, чтобы все было мгновенно после первого вызова.
На высоком уровне существует три уровня кэша:
- Базовый кэш – простое хранилище ключей/значений в памяти с 5-минутным сроком жизни, используемое в инструментах и логике.
- Кэш обнаружения – проводит самоанализ схемы и карты типов (TTL 5–60 минут) и автоматически аннулирует ее при изменении версии схемы.
- Кэш фрагментов – сохраняет сгенерированные фрагменты GraphQL в памяти и на диске, выдерживая перезапуски и становясь недействительными при изменении схемы.
// Simplified Base Cache
const cache = new Map();
export function get(key) {
const e = cache.get(key);
return e && Date.now() - e.ts < e.ttl ? e.val : null;
}
export function set(key, val, ttl) {
cache.set(key, { val, ts: Date.now(), ttl });
}
Пример — Получение страницы статьи
В этом примере показано, как MCP извлекает стандартную страницу контента, например ArticlePage или StandardPage — это не страница Visual Builder, которую мы рассмотрим в следующей части серии.
// User perspective — Claude or an AI client calls:
await get({ identifier: "/" }); // homepage
await get({ identifier: "/articles/my-article/" }); // by path
await get({ identifier: "Getting Started Guide" }); // by search
Вот что на самом деле происходит за кулисами
// 1. Initialize schema (cached after first call)
const introspector = new SchemaIntrospector(graphClient);
await introspector.initialize(); // runs getIntrospectionQuery()
// 2. Detect identifier type
const strategy = this.detectIdentifierType(identifier); // e.g. "path"
// 3. Find the content
const foundContent = await this.findContent(identifier, strategy, locale);
const contentType = foundContent.contentType; // e.g. "ArticlePage"
// 4. Generate or reuse a fragment
let fragment = await fragmentCache.getCachedFragment(contentType);
if (!fragment) {
fragment = await fragmentGenerator.generateFragment(contentType, {
maxDepth: 2,
includeBlocks: true
});
await fragmentCache.setCachedFragment(contentType, fragment);
}
// 5. Build query and execute
const fullQuery = `
${fragment}
query GetFullContent($key: String!) {
_Content(where: { _metadata: { key: { eq: $key } } }) {
items {
_metadata {
key displayName types url { default hierarchical }
published lastModified status
}
...${contentType}Fragment
}
}
}
`;
const data = await graphClient.query(fullQuery, { key: foundContent.key });
Холодный старт:
[INFO] Schema introspection completed (203 types, 1247 ms)
[DEBUG] Cache miss: fragment:ArticlePage
[INFO] Generating fragment for ArticlePage (124 ms)
[INFO] Query executed successfully (287 ms)
Теплый кэш:
[DEBUG] Cache hit: graphql:schema:full
[DEBUG] Cache hit: fragment:ArticlePage
[INFO] Query executed successfully (78 ms)
Пример ответа:
{
"_metadata": {
"key": "f3e8ef7f63ac45758a1dca8fbbde8d82",
"displayName": "Getting Started with MCP",
"types": ["ArticlePage", "_Page", "_Content"],
"url": {
"default": "/articles/getting-started/",
"hierarchical": "/articles/getting-started/"
},
"published": "2024-01-15T10:30:00Z",
"lastModified": "2024-01-20T14:22:00Z",
"status": "Published"
},
"Title": "Getting Started with MCP",
"Heading": "Your Guide to Model Context Protocol",
"Body": { "html": "This guide will help you…
" },
"PromoImage": { "url": { "default": "https://cdn.example.com/mcp.jpg" } },
"PublishDate": "2024-01-15T00:00:00Z",
"SeoSettings": {
"MetaTitle": "Getting Started with MCP | Developer Guide",
"MetaDescription": "Learn how to integrate Model Context Protocol…"
}
}
Дальше
В Часть 3мы будем копаться Визуальный конструктор страницы — вложенный слой композиции, который делает поиск более сложным и интересным.
Именно здесь наша Optimizely MCP переходит от «понимания структуры» к «пониманию композиции».
По теме
