発展LESSON 06

この教材で学ぶこと Sampling・通知・認証を含むMCP運用を学びます。

MCP 応用 — サンプリング・通知・本番運用

MCP 入門の続き。本番サーバー実装・Sampling・Notifications の高度な機能。


1. Sampling — モデルへの問い合わせをサーバーが要求する

通常の MCP フロー(Tool Use):
  ホスト(Claude) → [call_tool] → MCP サーバー → ツール実行 → 結果を返す

Sampling:
  MCP サーバー → [create_message リクエスト] → ホスト → Claude に問い合わせ → 結果をサーバーに返す
  ↑ サーバー側が「Claude に判断させたい」ときに使う

用途:
  - 取得したデータの要約を Claude に作らせる
  - 複数の選択肢から Claude に最適なものを選ばせる
  - エラーの原因を Claude に分析させる
from mcp.server import Server
from mcp.types import (
    CreateMessageRequest,
    CreateMessageResult,
    TextContent,
    SamplingMessage,
)

server = Server("intelligent-analyzer")

@server.call_tool()
async def analyze_logs(arguments: dict) -> list[TextContent]:
    """ログを取得し、Claude に分析させる"""
    # 1. ログデータを取得
    raw_logs = fetch_cloudwatch_logs(
        log_group=arguments["log_group"],
        hours=arguments.get("hours", 1)
    )
    
    # 2. Sampling で Claude に分析を依頼
    result: CreateMessageResult = await server.request_context.session.create_message(
        CreateMessageRequest(
            messages=[
                SamplingMessage(
                    role="user",
                    content=TextContent(
                        type="text",
                        text=f"""以下のログを分析してエラーパターンと原因を特定してください:

{raw_logs[:5000]}  # 長すぎる場合は切り詰め

出力形式:
1. 発見されたエラー(件数付き)
2. 推定される根本原因
3. 対処の優先順位"""
                    )
                )
            ],
            maxTokens=1024,
            system="あなたはSREエンジニアです。ログを分析し、簡潔かつ実用的な回答をしてください。"
        )
    )
    
    # 3. Claude の分析結果を返す
    return [TextContent(type="text", text=result.content.text)]

2. Notifications — 長時間処理の進捗通知

from mcp.types import ProgressNotification

@server.call_tool()
async def run_deployment(arguments: dict) -> list[TextContent]:
    """デプロイ処理(長時間)の進捗を通知しながら実行"""
    steps = [
        ("ビルド中", build_image),
        ("テスト中", run_tests),
        ("ECR プッシュ中", push_to_ecr),
        ("ECS デプロイ中", deploy_to_ecs),
        ("ヘルスチェック", verify_health),
    ]
    
    for i, (description, func) in enumerate(steps):
        # クライアント(Claude)に進捗を通知
        await server.request_context.session.send_notification(
            ProgressNotification(
                method="notifications/progress",
                params={
                    "progressToken": arguments.get("progressToken"),
                    "progress": i / len(steps),
                    "total": 1.0,
                    "message": description
                }
            )
        )
        
        await func(arguments)
    
    return [TextContent(type="text", text="デプロイ完了")]

3. Resources — 動的コンテンツの提供

from mcp.types import Resource, ReadResourceResult

@server.list_resources()
async def list_resources() -> list[Resource]:
    """利用可能なリソースの一覧を返す"""
    # 動的に生成(例: S3 バケット内のファイル一覧)
    files = list_s3_files("my-config-bucket")
    return [
        Resource(
            uri=f"s3://my-config-bucket/{f}",
            name=f,
            mimeType="application/json" if f.endswith(".json") else "text/plain"
        )
        for f in files
    ]

@server.read_resource()
async def read_resource(uri: str) -> ReadResourceResult:
    """リソースの内容を返す"""
    # URI からバケット・キーを解析
    parts = uri.replace("s3://", "").split("/", 1)
    bucket, key = parts[0], parts[1]
    
    content = get_s3_object(bucket, key)
    
    return ReadResourceResult(
        contents=[
            TextContent(type="text", text=content)
        ]
    )

4. 本番 MCP サーバーの構成

開発:
  claude <--(stdio)--> mcp-server.py

本番(ネットワーク越し):
  Claude Desktop ──(HTTP/SSE)──> MCP サーバー (FastAPI) ──> AWS

セキュリティ考慮:
  - 認証: API キー / mTLS
  - 権限: 最小権限の IAM ロール
  - ログ: 全 tool call を CloudWatch に記録
  - レート制限: 1ユーザーあたりの呼び出し数制限
# HTTP/SSE トランスポートを使った本番 MCP サーバー
from mcp.server.sse import SseServerTransport
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import uvicorn

app = FastAPI()

# CORS 設定(Claude Desktop からのアクセスを許可)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://claude.ai"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# API キー認証
async def verify_api_key(request: Request):
    api_key = request.headers.get("X-API-Key")
    if api_key != os.environ["MCP_API_KEY"]:
        raise HTTPException(status_code=401, detail="Invalid API key")

@app.get("/sse", dependencies=[Depends(verify_api_key)])
async def sse_endpoint(request: Request):
    transport = SseServerTransport("/messages")
    await server.run(
        transport.connect_sse(request),
        InitializationOptions(server_name="infra-tools", server_version="1.0.0")
    )

@app.post("/messages", dependencies=[Depends(verify_api_key)])
async def message_endpoint(request: Request):
    return await transport.handle_post_message(request)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8080)

5. claude_desktop_config.json への登録

{
  "mcpServers": {
    "infra-tools-local": {
      "command": "python",
      "args": ["/path/to/mcp_server.py"],
      "env": {
        "AWS_PROFILE": "default"
      }
    },
    "infra-tools-remote": {
      "url": "https://mcp.example.com/sse",
      "headers": {
        "X-API-Key": "your-secret-key"
      }
    }
  }
}

6. デバッグとテスト

# MCP Inspector でサーバーをテスト
npx @modelcontextprotocol/inspector python mcp_server.py

# ログ出力(stderr に書く)
import sys
print("デバッグメッセージ", file=sys.stderr)

# ユニットテスト
from mcp.client.session import ClientSession
from mcp.client.stdio import stdio_client

async def test_tool():
    async with stdio_client(
        server_params=StdioServerParameters(
            command="python", args=["mcp_server.py"]
        )
    ) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "analyze_logs",
                {"log_group": "/aws/lambda/test"}
            )
            assert result.content[0].text  # レスポンスがあることを確認

🔗 関連リソース