1761899873
2025-10-20 12:09:00
で パート 1、「ディスカバリーファースト」のアイデア、つまり任意の SaaS CMS にプラグインして、その構造がどのように構成されているかを独自に学習できる MCP を紹介しました。
この投稿では、MCP がどのようにスキーマを検出し、そのスキーマの使用可能なマップを構築し、後続のリクエストが即座に感じられるように学習内容を記憶するのかについて詳しく説明します。
ディスカバリー – CMS にそれ自体について尋ねる
MCP が CMS に初めて接続するとき、どのタイプが存在するかを推測しません。標準の GraphQL イントロスペクション クエリを使用して、CMS の GraphQL API をイントロスペクトします。
コードでは次のようになります。
// 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 リファレンス実装からの完全なスキーマ イントロスペクション仕様です。
型とフィールドをフェッチするだけではなく、次の値を返します。 すべて: オブジェクト型、インターフェイス、列挙型、入力オブジェクト、ディレクティブ、さらには最大 9 レベルの深さのネストされた型参照。
MCP は次のような構造について推論する必要があるため、その豊かさが重要です。 [BlockData!]!または、どの型が _IContent または _IComponent を実装しているかを検出します。
型マップの構築 — 生のイントロスペクションを使用可能なものに変える
内省的な反応は非常に大きくなる可能性があります。これは事実上、JSON 形式の CMS スキーマ全体であり、数百のタイプと関係を記述します。それ自体は扱いにくいです。 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 の完全なイントロスペクションには 2 秒以上かかる場合があるため、サーバーはキャッシュを階層化して、最初の呼び出し後すぐに処理できるようにします。
大まかに言うと、次の 3 つのキャッシュ層があります。
- ベースキャッシュ – ツールとロジック全体で使用される 5 分間の TTL を備えたシンプルなメモリ内キー/値ストア。
- ディスカバリーキャッシュ – スキーマのイントロスペクションと型マップ (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
例 — 記事ページの取得
この例では、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 は「構造の理解」から「構成の理解」に移行します。
#Optimizely #MCP #が #CMS #を学習 #そして記憶 #する方法 #Johnny #Mullaney