はじめに
Model Context Protocol (MCP) は、LLM(大規模言語モデル)が外部データソースに標準化された方法でアクセスできるようにするプロトコルです。
LLMの回答品質は、提供されるコンテキストの質に大きく依存します。本記事では、MCPを使って外部データを活用する際に、データの信頼性と追跡可能性を確保するための実装パターンを解説します。
重要な前提: MCPはデータアクセスの標準化を提供しますが、信頼性保証の仕組みを自動的に提供するわけではありません。開発者が適切に実装する必要があります。
1. なぜLLM推論における「信頼性」が重要なのか
1.1. LLM利用時のリスク
LLMが不正確または古いデータに基づいて回答すると、以下のリスクが発生します:
- ハルシネーション(幻覚): 事実と異なる情報を自信を持って出力する
- 古い情報に基づく判断: 最新の状況を反映しない回答
- トレーサビリティの欠如: 誤った回答の原因を特定できない
- 責任の所在の不明確さ: ビジネス上の意思決定に使う場合、問題が発生したときの責任追及が困難
1.2. MCPにおける信頼性の考え方
MCPは以下の点で信頼性向上に貢献できます:
- 明示的なデータソース: どこからデータが来たのかを明確にする
- 構造化されたメタデータ: データに付随する情報を標準化された形式で提供する
- 監査可能性: アクセスログによる事後追跡
ただし、これらは開発者が適切に実装して初めて機能します。
2. データソースの信頼性を実装する方法
2.1. リソースメタデータの活用
MCPのリソース定義では、mimeTypeやdescriptionに加えて、カスタムのannotationsを使って信頼性情報を付与できます。
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "company://policies/hr-handbook",
name: "人事ハンドブック",
description: "社内人事規定の最新版",
mimeType: "text/plain",
// カスタムメタデータで信頼性情報を付与
annotations: {
source: "internal-hr-system",
lastUpdated: "2025-09-15T10:00:00Z",
version: "3.2",
confidentiality: "internal",
verifiedBy: "hr-department"
}
},
{
uri: "web://cached/competitor-info",
name: "競合他社情報(キャッシュ)",
description: "Webスクレイピングで取得した競合情報",
mimeType: "text/plain",
annotations: {
source: "web-scraper",
lastUpdated: "2025-01-10T08:00:00Z",
version: "1.0",
confidentiality: "public",
reliability: "unverified", // 信頼性が低いことを明示
warning: "この情報は未検証です"
}
}
]
};
});
2.2. データの鮮度管理
データの鮮度を管理し、古いデータに明示的な警告を付与します。
function addFreshnessWarning(content: string, lastUpdated: string): string {
const updateDate = new Date(lastUpdated);
const now = new Date();
const daysSinceUpdate = Math.floor((now.getTime() - updateDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysSinceUpdate > 90) {
return `⚠️ 注意: この情報は${daysSinceUpdate}日前に更新されたものです。最新の情報を確認してください。\n\n${content}`;
} else if (daysSinceUpdate > 30) {
return `📅 この情報は${daysSinceUpdate}日前に更新されました。\n\n${content}`;
}
return content;
}
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resource = await getResource(request.params.uri);
const contentWithWarning = addFreshnessWarning(
resource.content,
resource.lastUpdated
);
return {
contents: [
{
uri: request.params.uri,
mimeType: "text/plain",
text: contentWithWarning
}
]
};
});
2.3. ソースの信頼性レベルの明示
データソースごとに信頼性レベルを定義し、コンテンツに反映させます。
enum TrustLevel {
HIGH = "high", // 社内公式システム、検証済みAPI
MEDIUM = "medium", // 信頼できる外部ソース
LOW = "low", // 未検証のデータ
UNVERIFIED = "unverified" // ユーザー入力など
}
function formatContentWithTrustLevel(content: string, trustLevel: TrustLevel, source: string): string {
const headers = {
[TrustLevel.HIGH]: `✅ 検証済み(出典: ${source})`,
[TrustLevel.MEDIUM]: `ℹ️ 信頼できるソース(出典: ${source})`,
[TrustLevel.LOW]: `⚠️ 未検証の情報(出典: ${source})`,
[TrustLevel.UNVERIFIED]: `❌ 未検証(出典: ${source}) - この情報は確認が必要です`
};
return `${headers[trustLevel]}\n\n${content}`;
}
3. プロンプトエンジニアリングによる信頼性向上
MCPで提供されるメタデータを、プロンプトを通じてLLMに明示的に伝えることが重要です。
3.1. システムプロンプトでの指示
const SYSTEM_PROMPT = `
あなたは企業の情報アシスタントです。以下のガイドラインに従ってください:
1. 情報の鮮度に注意する
- 90日以上古い情報は「古い可能性がある」と明示する
- 重要な判断には最新情報の確認を推奨する
2. ソースの信頼性を考慮する
- ✅マーク: 公式情報として信頼できる
- ⚠️マーク: 参考情報として扱い、確認を推奨する
- ❌マーク: 未検証情報として明示的に警告する
3. 情報源を明示する
- 回答には必ず「出典: [ソース名]」を含める
- 複数ソースがある場合は、すべて列挙する
4. 不確実性を認める
- 情報が不足している場合は推測せず、その旨を伝える
- 古い情報しかない場合は、その制限を明示する
`;
3.2. MCPプロンプトの活用
MCPのprompts機能を使って、再利用可能なプロンプトテンプレートを定義できます。
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "verify-information",
description: "情報の信頼性を検証しながら回答する",
arguments: [
{
name: "query",
description: "ユーザーの質問",
required: true
}
]
}
]
};
});
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (request.params.name === "verify-information") {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `以下の質問に答える際は、利用可能なリソースの信頼性レベルと鮮度を確認してください。
質問: ${request.params.arguments?.query}
回答には以下を含めてください:
1. 回答内容
2. 使用した情報源とその信頼性レベル
3. 情報の最終更新日
4. 情報が古い場合や未検証の場合の注意事項`
}
}
]
};
}
});
4. バージョン管理と追跡可能性
4.1. バージョン情報の記録
データの変更履歴を追跡できるようにします。
interface VersionedResource {
uri: string;
content: string;
version: string;
previousVersion?: string;
changeLog?: string;
lastModifiedBy: string;
lastModified: string;
}
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resource = await getVersionedResource(request.params.uri);
const versionInfo = `
--- ドキュメント情報 ---
バージョン: ${resource.version}
最終更新: ${resource.lastModified}
更新者: ${resource.lastModifiedBy}
${resource.changeLog ? `変更内容: ${resource.changeLog}` : ''}
-----------------------
${resource.content}
`;
return {
contents: [
{
uri: request.params.uri,
mimeType: "text/plain",
text: versionInfo
}
]
};
});
4.2. 利用ログの記録
どのリソースがいつ、誰によってアクセスされたかを記録します。
interface AccessLog {
timestamp: string;
resourceUri: string;
resourceVersion: string;
sessionId: string;
userId?: string;
action: "read" | "list";
}
async function logResourceAccess(log: AccessLog) {
// データベースやログファイルに記録
await db.accessLogs.insert(log);
// 構造化ログとして出力
console.log(JSON.stringify({
type: "resource_access",
...log
}));
}
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resource = await getResource(request.params.uri);
await logResourceAccess({
timestamp: new Date().toISOString(),
resourceUri: request.params.uri,
resourceVersion: resource.version,
sessionId: request.sessionId || "unknown",
action: "read"
});
// リソース内容を返す
return { contents: [{ uri: request.params.uri, mimeType: "text/plain", text: resource.content }] };
});
5. エラーハンドリングと品質保証
5.1. データ検証
取得したデータの整合性を検証します。
function validateResourceData(resource: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!resource.content || resource.content.trim().length === 0) {
errors.push("コンテンツが空です");
}
if (!resource.lastUpdated) {
errors.push("最終更新日が不明です");
}
if (resource.version && !isValidVersion(resource.version)) {
errors.push("バージョン形式が不正です");
}
return {
isValid: errors.length === 0,
errors
};
}
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resource = await getResource(request.params.uri);
const validation = validateResourceData(resource);
if (!validation.isValid) {
return {
contents: [
{
uri: request.params.uri,
mimeType: "text/plain",
text: `❌ データ品質の問題が検出されました:\n${validation.errors.join('\n')}\n\n${resource.content}`
}
]
};
}
return {
contents: [{ uri: request.params.uri, mimeType: "text/plain", text: resource.content }]
};
});
5.2. フォールバック戦略
データソースが利用できない場合の対応を実装します。
async function getResourceWithFallback(uri: string): Promise<string> {
try {
// プライマリソースを試す
return await primaryDataSource.get(uri);
} catch (primaryError) {
console.warn(`Primary source failed: ${primaryError.message}`);
try {
// キャッシュされたデータを試す
const cached = await cache.get(uri);
return `⚠️ プライマリソースが利用できません。キャッシュされたデータを使用しています(${cached.cachedAt}時点)\n\n${cached.content}`;
} catch (cacheError) {
// 両方失敗した場合
throw new Error("データソースとキャッシュの両方が利用できません");
}
}
}
6. ベストプラクティスまとめ
6.1. 実装チェックリスト
- すべてのリソースに鮮度情報(最終更新日)を付与する
- データソースの信頼性レベルを定義・表示する
- 古いデータには明示的な警告を付ける
- バージョン情報を記録・追跡する
- すべてのアクセスをログに記録する
- データ検証ロジックを実装する
- エラー時のフォールバック戦略を用意する
- システムプロンプトで信頼性ガイドラインを明示する
6.2. ユーザーへの透明性
信頼性の高いシステムは、透明性が高いシステムです:
- 情報源を常に明示する
- データの制限や不確実性を隠さない
- 古い情報や未検証情報には明確に警告する
- ユーザーが自分で判断できるよう、十分な情報を提供する
まとめ
MCPは強力なデータ統合プロトコルですが、信頼性は自動的には保証されません。開発者が以下を適切に実装する必要があります:
- メタデータの活用: 鮮度、ソース、バージョン情報の付与
- 明示的な警告: 古い情報や未検証情報への注意喚起
- プロンプト設計: LLMに信頼性を考慮させる指示
- ログと監査: すべてのアクセスの記録と追跡
- 品質保証: データ検証とエラーハンドリング
これらを実装することで、MCPを使った信頼性の高いLLMアプリケーションを構築できます。
参考リンク
注意: MCPはAnthropicが開発した比較的新しいプロトコルです。最新の情報については、公式ドキュメントを参照してください。