コンテンツにスキップ

出典: https://github.com/microsoft/mcp-for-beginners フォーク元リポジトリ: https://github.com/microsoft/mcp-for-beginners (Microsoft) ライセンス: MIT License


MCP ルートコンテキスト

ルートコンテキストは、Model Context Protocol の基本的な概念であり、複数のリクエストやセッションにわたって会話履歴や共有状態を持続的に保持するためのレイヤーを提供します。

はじめに

このレッスンでは、MCP におけるルートコンテキストの作成、管理、活用方法について学びます。

学習目標

このレッスンを終える頃には、以下ができるようになります:

  • ルートコンテキストの目的と構造を理解する
  • MCP クライアントライブラリを使ってルートコンテキストを作成・管理する
  • .NET、Java、JavaScript、Python アプリケーションでルートコンテキストを実装する
  • マルチターン会話や状態管理にルートコンテキストを活用する
  • ルートコンテキスト管理のベストプラクティスを実践する

ルートコンテキストの理解

ルートコンテキストは、一連の関連するやり取りの履歴や状態を保持するコンテナとして機能します。これにより以下が可能になります:

  • 会話の持続性:一貫したマルチターン会話の維持
  • メモリ管理:やり取りをまたいだ情報の保存と取得
  • 状態管理:複雑なワークフローの進行状況の追跡
  • コンテキスト共有:複数のクライアントが同じ会話状態にアクセス可能

MCP におけるルートコンテキストの主な特徴は以下の通りです:

  • 各ルートコンテキストは一意の識別子を持つ
  • 会話履歴、ユーザーの好み、その他のメタデータを含めることができる
  • 必要に応じて作成、アクセス、アーカイブが可能
  • 細かいアクセス制御や権限管理をサポート

ルートコンテキストのライフサイクル

flowchart TD
    A[Create Root Context] --> B[Initialize with Metadata]
    B --> C[Send Requests with Context ID]
    C --> D[Update Context with Results]
    D --> C
    D --> E[Archive Context When Complete]

ルートコンテキストの操作

以下はルートコンテキストの作成と管理の例です。

C# 実装例

// .NET Example: Root Context Management
using Microsoft.Mcp.Client;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;

public class RootContextExample
{
    private readonly IMcpClient _client;
    private readonly IRootContextManager _contextManager;

    public RootContextExample(IMcpClient client, IRootContextManager contextManager)
    {
        _client = client;
        _contextManager = contextManager;
    }

    public async Task DemonstrateRootContextAsync()
    {
        // 1. Create a new root context
        var contextResult = await _contextManager.CreateRootContextAsync(new RootContextCreateOptions
        {
            Name = "Customer Support Session",
            Metadata = new Dictionary<string, string>
            {
                ["CustomerName"] = "Acme Corporation",
                ["PriorityLevel"] = "High",
                ["Domain"] = "Cloud Services"
            }
        });

        string contextId = contextResult.ContextId;
        Console.WriteLine($"Created root context with ID: {contextId}");

        // 2. First interaction using the context
        var response1 = await _client.SendPromptAsync(
            "I'm having issues scaling my web service deployment in the cloud.", 
            new SendPromptOptions { RootContextId = contextId }
        );

        Console.WriteLine($"First response: {response1.GeneratedText}");

        // Second interaction - the model will have access to the previous conversation
        var response2 = await _client.SendPromptAsync(
            "Yes, we're using containerized deployments with Kubernetes.", 
            new SendPromptOptions { RootContextId = contextId }
        );

        Console.WriteLine($"Second response: {response2.GeneratedText}");

        // 3. Add metadata to the context based on conversation
        await _contextManager.UpdateContextMetadataAsync(contextId, new Dictionary<string, string>
        {
            ["TechnicalEnvironment"] = "Kubernetes",
            ["IssueType"] = "Scaling"
        });

        // 4. Get context information
        var contextInfo = await _contextManager.GetRootContextInfoAsync(contextId);

        Console.WriteLine("Context Information:");
        Console.WriteLine($"- Name: {contextInfo.Name}");
        Console.WriteLine($"- Created: {contextInfo.CreatedAt}");
        Console.WriteLine($"- Messages: {contextInfo.MessageCount}");

        // 5. When the conversation is complete, archive the context
        await _contextManager.ArchiveRootContextAsync(contextId);
        Console.WriteLine($"Archived context {contextId}");
    }
}

上記のコードでは以下を行っています:

  1. カスタマーサポートセッション用のルートコンテキストを作成しました。
  2. そのコンテキスト内で複数のメッセージを送信し、モデルが状態を維持できるようにしました。
  3. 会話に基づいて関連するメタデータでコンテキストを更新しました。
  4. 会話履歴を理解するためにコンテキスト情報を取得しました。
  5. 会話が完了したらコンテキストをアーカイブしました。

例:金融分析のためのルートコンテキスト実装

この例では、金融分析セッション用のルートコンテキストを作成し、複数のやり取りにわたって状態を維持する方法を示します。

Java 実装例

// Java Example: Root Context Implementation
package com.example.mcp.contexts;

import com.mcp.client.McpClient;
import com.mcp.client.ContextManager;
import com.mcp.models.RootContext;
import com.mcp.models.McpResponse;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class RootContextsDemo {
    private final McpClient client;
    private final ContextManager contextManager;

    public RootContextsDemo(String serverUrl) {
        this.client = new McpClient.Builder()
            .setServerUrl(serverUrl)
            .build();

        this.contextManager = new ContextManager(client);
    }

    public void demonstrateRootContext() throws Exception {
        // Create context metadata
        Map<String, String> metadata = new HashMap<>();
        metadata.put("projectName", "Financial Analysis");
        metadata.put("userRole", "Financial Analyst");
        metadata.put("dataSource", "Q1 2025 Financial Reports");

        // 1. Create a new root context
        RootContext context = contextManager.createRootContext("Financial Analysis Session", metadata);
        String contextId = context.getId();

        System.out.println("Created context: " + contextId);

        // 2. First interaction
        McpResponse response1 = client.sendPrompt(
            "Analyze the trends in Q1 financial data for our technology division",
            contextId
        );

        System.out.println("First response: " + response1.getGeneratedText());

        // 3. Update context with important information gained from response
        contextManager.addContextMetadata(contextId, 
            Map.of("identifiedTrend", "Increasing cloud infrastructure costs"));

        // Second interaction - using the same context
        McpResponse response2 = client.sendPrompt(
            "What's driving the increase in cloud infrastructure costs?",
            contextId
        );

        System.out.println("Second response: " + response2.getGeneratedText());

        // 4. Generate a summary of the analysis session
        McpResponse summaryResponse = client.sendPrompt(
            "Summarize our analysis of the technology division financials in 3-5 key points",
            contextId
        );

        // Store the summary in context metadata
        contextManager.addContextMetadata(contextId, 
            Map.of("analysisSummary", summaryResponse.getGeneratedText()));

        // Get updated context information
        RootContext updatedContext = contextManager.getRootContext(contextId);

        System.out.println("Context Information:");
        System.out.println("- Created: " + updatedContext.getCreatedAt());
        System.out.println("- Last Updated: " + updatedContext.getLastUpdatedAt());
        System.out.println("- Analysis Summary: " + 
            updatedContext.getMetadata().get("analysisSummary"));

        // 5. Archive context when done
        contextManager.archiveContext(contextId);
        System.out.println("Context archived");
    }
}

上記のコードでは以下を行っています:

  1. 金融分析セッション用のルートコンテキストを作成しました。
  2. そのコンテキスト内で複数のメッセージを送信し、モデルが状態を維持できるようにしました。
  3. 会話に基づいて関連するメタデータでコンテキストを更新しました。
  4. 分析セッションの要約を生成し、コンテキストのメタデータに保存しました。
  5. 会話が完了したらコンテキストをアーカイブしました。

例:ルートコンテキスト管理

ルートコンテキストを効果的に管理することは、会話履歴や状態を維持する上で重要です。以下はルートコンテキスト管理の実装例です。

JavaScript 実装例

// JavaScript Example: Managing MCP Root Contexts
const { McpClient, RootContextManager } = require('@mcp/client');

class ContextSession {
  constructor(serverUrl, apiKey = null) {
    // Initialize the MCP client
    this.client = new McpClient({
      serverUrl,
      apiKey
    });

    // Initialize context manager
    this.contextManager = new RootContextManager(this.client);
  }

  /**
   * Create a new conversation context
   * @param {string} sessionName - Name of the conversation session
   * @param {Object} metadata - Additional metadata for the context
   * @returns {Promise<string>} - Context ID
   */
  async createConversationContext(sessionName, metadata = {}) {
    try {
      const contextResult = await this.contextManager.createRootContext({
        name: sessionName,
        metadata: {
          ...metadata,
          createdAt: new Date().toISOString(),
          status: 'active'
        }
      });

      console.log(`Created root context '${sessionName}' with ID: ${contextResult.id}`);
      return contextResult.id;
    } catch (error) {
      console.error('Error creating root context:', error);
      throw error;
    }
  }

  /**
   * Send a message in an existing context
   * @param {string} contextId - The root context ID
   * @param {string} message - The user's message
   * @param {Object} options - Additional options
   * @returns {Promise<Object>} - Response data
   */
  async sendMessage(contextId, message, options = {}) {
    try {
      // Send the message using the specified context
      const response = await this.client.sendPrompt(message, {
        rootContextId: contextId,
        temperature: options.temperature || 0.7,
        allowedTools: options.allowedTools || []
      });

      // Optionally store important insights from the conversation
      if (options.storeInsights) {
        await this.storeConversationInsights(contextId, message, response.generatedText);
      }

      return {
        message: response.generatedText,
        toolCalls: response.toolCalls || [],
        contextId
      };
    } catch (error) {
      console.error(`Error sending message in context ${contextId}:`, error);
      throw error;
    }
  }

  /**
   * Store important insights from a conversation
   * @param {string} contextId - The root context ID
   * @param {string} userMessage - User's message
   * @param {string} aiResponse - AI's response
   */
  async storeConversationInsights(contextId, userMessage, aiResponse) {
    try {
      // Extract potential insights (in a real app, this would be more sophisticated)
      const combinedText = userMessage + "
" + aiResponse;

      // Simple heuristic to identify potential insights
      const insightWords = ["important", "key point", "remember", "significant", "crucial"];

      const potentialInsights = combinedText
        .split(".")
        .filter(sentence => 
          insightWords.some(word => sentence.toLowerCase().includes(word))
        )
        .map(sentence => sentence.trim())
        .filter(sentence => sentence.length > 10);

      // Store insights in context metadata
      if (potentialInsights.length > 0) {
        const insights = {};
        potentialInsights.forEach((insight, index) => {
          insights[`insight_${Date.now()}_${index}`] = insight;
        });

        await this.contextManager.updateContextMetadata(contextId, insights);
        console.log(`Stored ${potentialInsights.length} insights in context ${contextId}`);
      }
    } catch (error) {
      console.warn('Error storing conversation insights:', error);
      // Non-critical error, so just log warning
    }
  }

  /**
   * Get summary information about a context
   * @param {string} contextId - The root context ID
   * @returns {Promise<Object>} - Context information
   */
  async getContextInfo(contextId) {
    try {
      const contextInfo = await this.contextManager.getContextInfo(contextId);

      return {
        id: contextInfo.id,
        name: contextInfo.name,
        created: new Date(contextInfo.createdAt).toLocaleString(),
        lastUpdated: new Date(contextInfo.lastUpdatedAt).toLocaleString(),
        messageCount: contextInfo.messageCount,
        metadata: contextInfo.metadata,
        status: contextInfo.status
      };
    } catch (error) {
      console.error(`Error getting context info for ${contextId}:`, error);
      throw error;
    }
  }

  /**
   * Generate a summary of the conversation in a context
   * @param {string} contextId - The root context ID
   * @returns {Promise<string>} - Generated summary
   */
  async generateContextSummary(contextId) {
    try {
      // Ask the model to generate a summary of the conversation so far
      const response = await this.client.sendPrompt(
        "Please summarize our conversation so far in 3-4 sentences, highlighting the main points discussed.",
        { rootContextId: contextId, temperature: 0.3 }
      );

      // Store the summary in context metadata
      await this.contextManager.updateContextMetadata(contextId, {
        conversationSummary: response.generatedText,
        summarizedAt: new Date().toISOString()
      });

      return response.generatedText;
    } catch (error) {
      console.error(`Error generating context summary for ${contextId}:`, error);
      throw error;
    }
  }

  /**
   * Archive a context when it's no longer needed
   * @param {string} contextId - The root context ID
   * @returns {Promise<Object>} - Result of the archive operation
   */
  async archiveContext(contextId) {
    try {
      // Generate a final summary before archiving
      const summary = await this.generateContextSummary(contextId);

      // Archive the context
      await this.contextManager.archiveContext(contextId);

      return {
        status: "archived",
        contextId,
        summary
      };
    } catch (error) {
      console.error(`Error archiving context ${contextId}:`, error);
      throw error;
    }
  }
}

// Example usage
async function demonstrateContextSession() {
  const session = new ContextSession('https://mcp-server-example.com');

  try {
    // 1. Create a new context for a product support conversation
    const contextId = await session.createConversationContext(
      'Product Support - Database Performance',
      {
        customer: 'Globex Corporation',
        product: 'Enterprise Database',
        severity: 'Medium',
        supportAgent: 'AI Assistant'
      }
    );

    // 2. First message in the conversation
    const response1 = await session.sendMessage(
      contextId,
      "I'm experiencing slow query performance on our database cluster after the latest update.",
      { storeInsights: true }
    );
    console.log('Response 1:', response1.message);

    // Follow-up message in the same context
    const response2 = await session.sendMessage(
      contextId,
      "Yes, we've already checked the indexes and they seem to be properly configured.",
      { storeInsights: true }
    );
    console.log('Response 2:', response2.message);

    // 3. Get information about the context
    const contextInfo = await session.getContextInfo(contextId);
    console.log('Context Information:', contextInfo);

    // 4. Generate and display conversation summary
    const summary = await session.generateContextSummary(contextId);
    console.log('Conversation Summary:', summary);

    // 5. Archive the context when done
    const archiveResult = await session.archiveContext(contextId);
    console.log('Archive Result:', archiveResult);

    // 6. Handle any errors gracefully
  } catch (error) {
    console.error('Error in context session demonstration:', error);
  }
}

demonstrateContextSession();

上記のコードでは以下を行っています:

  1. createConversationContext 関数を使って、データベースのパフォーマンス問題に関する製品サポート会話用のルートコンテキストを作成しました。
  2. sendMessage 関数を使って、そのコンテキスト内で複数のメッセージを送信し、モデルが状態を維持できるようにしました。送信されたメッセージは遅いクエリのパフォーマンスやインデックス設定に関するものです。
  3. 会話に基づいて関連するメタデータでコンテキストを更新しました。
  4. generateContextSummary 関数を使って会話の要約を生成し、コンテキストのメタデータに保存しました。
  5. 会話が完了したら archiveContext 関数でコンテキストをアーカイブしました。
  6. エラー処理を適切に行い、堅牢性を確保しました。

マルチターン支援のためのルートコンテキスト

この例では、マルチターン支援セッション用のルートコンテキストを作成し、複数のやり取りにわたって状態を維持する方法を示します。

Python 実装例

# Python Example: Root Context for Multi-Turn Assistance
import asyncio
from datetime import datetime
from mcp_client import McpClient, RootContextManager

class AssistantSession:
    def __init__(self, server_url, api_key=None):
        self.client = McpClient(server_url=server_url, api_key=api_key)
        self.context_manager = RootContextManager(self.client)

    async def create_session(self, name, user_info=None):
        """Create a new root context for an assistant session"""
        metadata = {
            "session_type": "assistant",
            "created_at": datetime.now().isoformat(),
        }

        # Add user information if provided
        if user_info:
            metadata.update({f"user_{k}": v for k, v in user_info.items()})

        # Create the root context
        context = await self.context_manager.create_root_context(name, metadata)
        return context.id

    async def send_message(self, context_id, message, tools=None):
        """Send a message within a root context"""
        # Create options with context ID
        options = {
            "root_context_id": context_id
        }

        # Add tools if specified
        if tools:
            options["allowed_tools"] = tools

        # Send the prompt within the context
        response = await self.client.send_prompt(message, options)

        # Update context metadata with conversation progress
        await self.context_manager.update_context_metadata(
            context_id,
            {
                f"message_{datetime.now().timestamp()}": message[:50] + "...",
                "last_interaction": datetime.now().isoformat()
            }
        )

        return response

    async def get_conversation_history(self, context_id):
        """Retrieve conversation history from a context"""
        context_info = await self.context_manager.get_context_info(context_id)
        messages = await self.client.get_context_messages(context_id)

        return {
            "context_info": context_info,
            "messages": messages
        }

    async def end_session(self, context_id):
        """End an assistant session by archiving the context"""
        # Generate a summary prompt first
        summary_response = await self.client.send_prompt(
            "Please summarize our conversation and any key points or decisions made.",
            {"root_context_id": context_id}
        )

        # Store summary in metadata
        await self.context_manager.update_context_metadata(
            context_id,
            {
                "summary": summary_response.generated_text,
                "ended_at": datetime.now().isoformat(),
                "status": "completed"
            }
        )

        # Archive the context
        await self.context_manager.archive_context(context_id)

        return {
            "status": "completed",
            "summary": summary_response.generated_text
        }

# Example usage
async def demo_assistant_session():
    assistant = AssistantSession("https://mcp-server-example.com")

    # 1. Create session
    context_id = await assistant.create_session(
        "Technical Support Session",
        {"name": "Alex", "technical_level": "advanced", "product": "Cloud Services"}
    )
    print(f"Created session with context ID: {context_id}")

    # 2. First interaction
    response1 = await assistant.send_message(
        context_id, 
        "I'm having trouble with the auto-scaling feature in your cloud platform.",
        ["documentation_search", "diagnostic_tool"]
    )
    print(f"Response 1: {response1.generated_text}")

    # Second interaction in the same context
    response2 = await assistant.send_message(
        context_id,
        "Yes, I've already checked the configuration settings you mentioned, but it's still not working."
    )
    print(f"Response 2: {response2.generated_text}")

    # 3. Get history
    history = await assistant.get_conversation_history(context_id)
    print(f"Session has {len(history['messages'])} messages")

    # 4. End session
    end_result = await assistant.end_session(context_id)
    print(f"Session ended with summary: {end_result['summary']}")

if __name__ == "__main__":
    asyncio.run(demo_assistant_session())

上記のコードでは以下を行っています:

  1. create_session 関数を使って、名前や技術レベルなどのユーザー情報を含む技術サポートセッション用のルートコンテキストを作成しました。
  2. send_message 関数を使って、そのコンテキスト内で複数のメッセージを送信し、モデルが状態を維持できるようにしました。送信されたメッセージはオートスケーリング機能の問題に関するものです。
  3. get_conversation_history 関数を使って会話履歴を取得し、コンテキスト情報やメッセージを確認しました。
  4. end_session 関数を使ってセッションを終了し、コンテキストをアーカイブするとともに会話の要点をまとめた要約を生成しました。

ルートコンテキストのベストプラクティス

ルートコンテキストを効果的に管理するためのベストプラクティスを紹介します:

  • 目的に応じたコンテキスト作成:異なる会話目的やドメインごとに別々のルートコンテキストを作成し、明確さを保つ。
  • 有効期限ポリシーの設定:古いコンテキストをアーカイブまたは削除するポリシーを実装し、ストレージ管理やデータ保持ポリシーに準拠する。
  • 関連メタデータの保存:会話に関する重要な情報を後で活用できるようにコンテキストのメタデータに保存する。
  • コンテキストIDの一貫使用:コンテキスト作成後は、そのIDを関連するすべてのリクエストで一貫して使用し、連続性を保つ。
  • 要約の生成:コンテキストが大きくなった場合は、重要な情報をまとめた要約を作成し、コンテキストサイズを管理する。
  • アクセス制御の実装:マルチユーザーシステムでは、会話コンテキストのプライバシーとセキュリティを確保するために適切なアクセス制御を実装する。
  • コンテキスト制限への対応:コンテキストサイズの制限を理解し、非常に長い会話に対処するための戦略を講じる。
  • 完了時のアーカイブ:会話が完了したらコンテキストをアーカイブし、リソースを解放しつつ会話履歴を保持する。

次に進むには

免責事項
本書類はAI翻訳サービス「Co-op Translator」を使用して翻訳されました。正確性の向上に努めておりますが、自動翻訳には誤りや不正確な部分が含まれる可能性があります。原文の言語によるオリジナル文書が正式な情報源とみなされるべきです。重要な情報については、専門の人間による翻訳を推奨します。本翻訳の利用により生じたいかなる誤解や誤訳についても、当方は責任を負いかねます。