👻

MCP 2025/11/25 Update まとめ

に公開

MCP の仕様が 2025/11/25 にアップデートしました。

前回(2025/6/28)からのメジャーアップデートをまとめます。
https://modelcontextprotocol.io/specification/2025-11-25/changelog

自分の調査がてら生成 AI でいろいろ手を抜きながら…

OpenID Connect Discovery 1.0 対応

https://github.com/modelcontextprotocol/modelcontextprotocol/pull/797

※こちらだけ Issue の管理 ID が見当たらず

概要

OpenID Connect Discovery 1.0 のサポート追加により、認証サーバーの検出メカニズム
を強化しました。

1. 認証サーバーメタデータの検出方法の拡張

  • 従来: OAuth 2.0 Authorization Server Metadata (RFC8414) のみ
  • 変更後: 以下の両方をサポート
    • OAuth 2.0 Authorization Server Metadata (RFC8414)
    • OpenID Connect Discovery 1.0

2. 複数のエンドポイント試行の実装

MCP クライアントは、以下の優先順位でメタデータエンドポイントを試行する必要があり
ます:

パスコンポーネントありの場合 (例: https://auth.example.com/tenant1):

  1. /.well-known/oauth-authorization-server/tenant1
  2. /.well-known/openid-configuration/tenant1
  3. /tenant1/.well-known/openid-configuration

パスコンポーネントなしの場合 (例: https://auth.example.com):

  1. /.well-known/oauth-authorization-server
  2. /.well-known/openid-configuration

3. PKCE サポートの検証強化

  • code_challenge_methods_supported フィールドの存在確認を必須化
  • このフィールドがない場合、クライアントは認証を拒否する必要がある
  • S256 コードチャレンジメソッドの使用を推奨

互換性

  • 破壊的変更なし: 既存の OAuth 2.0 実装は引き続き動作
  • Keycloak、Auth0、Logto などの主要な ID プロバイダーとの互換性向上

改善シナリオ

Before

あなたの会社が Keycloak を使って認証管理しているとします。

会社の Keycloak: https://auth.company.com/realms/employees

Keycloak は OIDC をデフォルトで提供しており、以下のエンドポイントがあります:
https://auth.company.com/realms/employees/.well-known/openid-configuration

問題点:

  • MCP は OAuth 2.0 メタデータ (/.well-known/oauth-authorization-server) しか見な
  • Keycloak はこのエンドポイントを提供していない
  • 結果: MCP クライアントが認証サーバーを見つけられず、接続失敗

回避策が必要:

  1. Keycloak に OAuth 2.0 メタデータエンドポイントを追加実装する
  2. または、プロキシサーバーを立てて変換する
  3. または、MCP サーバー側で認証情報をハードコードする

After

MCP クライアントは自動的に以下を試行:

  1. https://auth.company.com/.well-known/oauth-authorization-server/realms/employees
    → 404 (存在しない)

  2. https://auth.company.com/.well-known/openid-configuration/realms/employees
    → 404 (存在しない)

  3. https://auth.company.com/realms/employees/.well-known/openid-configuration
    → 200 OK! ✓ メタデータ取得成功

結果: 追加設定なしで即座に動作します。

実際の設定例

Auth0 を使う場合
// MCP サーバーの設定
{
  "authorization": {
    "issuer": "https://your-tenant.auth0.com"
  }
}

この PR のおかげで:

  • Auth0 の .well-known/openid-configuration が自動検出される
  • 認証エンドポイント、トークンエンドポイントなどが自動取得される
  • PKCE サポートも自動確認される
Google Identity を使う場合
{
  "authorization": {
    "issuer": "https://accounts.google.com"
  }
}

Google も OIDC Discovery を提供しているため、同様に自動動作します。

コード例: クライアント側の動作
// この PR により、以下のような実装が可能に
async function discoverAuthServer(issuer: string) {
  const endpoints = [
    `${issuer}/.well-known/oauth-authorization-server`,
    `${issuer}/.well-known/openid-configuration`
  ];
  
  for (const endpoint of endpoints) {
    try {
      const response = await fetch(endpoint);
      if (response.ok) {
        return await response.json();
      }
    } catch (e) {
      continue;
    }
  }
  
  throw new Error('Authorization server not found');
}

// 使用例
const metadata = await discoverAuthServer('https://auth.company.com/realms/employees');
// → Keycloak の OIDC メタデータが取得できる

まとめ

この PR がないと:

  • 「Keycloak 使ってます」→「すみません、対応してません」
  • 「Auth0 使ってます」→「カスタム設定が必要です」

この PR があると:

  • 「Keycloak 使ってます」→「そのまま動きます」
  • 「Auth0 使ってます」→「そのまま動きます」
  • 「Google 使ってます」→「そのまま動きます」

つまり、エンタープライズ環境での実用性が大幅に向上したということです。

SEP-973 MCP の各リソースにアイコンと Web サイト URL を追加

https://github.com/modelcontextprotocol/modelcontextprotocol/issues/973

概要

MCP の Implementation、Tool、Resource、Prompt に アイコン と Web サイト URL を追加
する提案です。

何が変わるのか

Before

🔧 github_search_repositories
🔧 github_create_issue
🔧 slack_send_message

すべてのツールが同じように見え、どのサーバーが提供しているか分からない。

After

🐙 github_search_repositories [GitHub のアイコン + ドキュメントリンク]
🐙 github_create_issue [GitHub のアイコン + ドキュメントリンク]
💬 slack_send_message [Slack のアイコン + ドキュメントリンク]

視覚的に識別でき、ドキュメントにもすぐアクセスできるようになります。

追加される機能

1. アイコン (icons)

{
  "icons": [
    {
      "src": "https://github.com/favicon.ico",
      "mimeType": "image/png",
      "sizes": "48x48"
    },
    {
      "src": "data:image/svg+xml;base64,...",  // Data URI も可
      "mimeType": "image/svg+xml",
      "sizes": "any"
    }
  ]
}

サポート必須の形式:

  • PNG (image/png)
  • JPEG (image/jpeg)

推奨サポート:

  • SVG (image/svg+xml) - スケーラブル
  • WebP (image/webp) - 軽量

2. Web サイト URL (websiteUrl)

{
  "websiteUrl": "https://github.com/myorg/mcp-server-github"
}

ドキュメントやヘルプページへの直リンク。

具体的な使用例

サーバー側の実装

// MCP サーバーの初期化時
server.setRequestHandler(InitializeRequestSchema, async () => ({
  protocolVersion: "2024-11-05",
  capabilities: { tools: {} },
  serverInfo: {
    name: "github-mcp-server",
    version: "1.0.0",
    icons: [
      {
        src: "https://github.githubassets.com/favicons/favicon.png",
        mimeType: "image/png",
        sizes: "32x32"
      }
    ],
    websiteUrl: "https://github.com/myorg/mcp-server-github"
  }
}));

// ツールの定義
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "search_repositories",
      description: "Search GitHub repositories",
      icons: [
        {
          src: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png",
          mimeType: "image/png",
          sizes: "48x48"
        }
      ],
      inputSchema: { /* ... */ }
    }
  ]
}));

クライアント側の表示

// Claude Desktop などのクライアントでの表示例
function renderTool(tool) {
  return (
    <div className="tool-item">
      {tool.icons?.[0] && (
        <img 
          src={tool.icons[0].src} 
          alt={tool.name}
          width="24"
          height="24"
        />
      )}
      <span>{tool.name}</span>
      {tool.websiteUrl && (
        <a href={tool.websiteUrl} target="_blank">
          📖 Docs
        </a>
      )}
    </div>
  );
}

実際の UI イメージ

スラッシュコマンドの例:

/search_repositories [🐙 GitHub] 📖
/create_issue [🐙 GitHub] 📖
/send_message [💬 Slack] 📖
/query_database [🐘 PostgreSQL] 📖

各ツールが視覚的に区別でき、ドキュメントアイコンをクリックすると詳細情報にアクセ
スできる。

メリット

  1. 視認性の向上: 複数のサーバーからツールを使う場合、どれがどのサービスのものか一
    目で分かる
  2. ユーザビリティ: ドキュメントへのアクセスが簡単になる
  3. ブランディング: サーバー提供者が自分のサービスを視覚的にアピールできる
  4. 後方互換性: オプショナルなので既存の実装に影響なし

セキュリティ考慮事項

  • URL は信頼できるドメインからのみ読み込むべき
  • SVG は JavaScript を含む可能性があるため注意が必要
  • クライアントは適切なサニタイゼーションを実装する必要がある

つまり、MCP のツールやリソースが視覚的に分かりやすくなり、ユーザー体験が大幅に
向上する
アップデートです。

SEP-835 OAuth スコープ管理の改善

https://github.com/modelcontextprotocol/modelcontextprotocol/pull/835

概要

OAuth 2.1 ベースの認証仕様を強化し、**スコープ(権限)の選択と動的アップグレード
**を改善する提案です。

解決する問題

現状の課題

  1. スコープ選択が不明確: クライアントがどの権限を最初に要求すべきか分からない
  2. 権限不足エラーの処理: 実行時に権限が足りない場合の標準フローがない
  3. クライアントタイプの違い: 対話型とサービス間で同じ扱い
  4. エラーレスポンスの不整合: 403 エラーに必要な情報が欠けている

結果: 過剰な権限要求、悪いユーザー体験、実装のばらつき

主な改善点

1. スコープ選択戦略(最小権限の原則)

優先順位:

  1. WWW-Authenticate ヘッダーの scope パラメータ(即座に必要な権限)
  2. Protected Resource Metadata の scopes_supported(フォールバック)

2. 動的スコープアップグレードフロー

権限不足で失敗した場合の処理:

リクエスト → 403 insufficient_scope エラー

クライアントタイプで分岐:

  • 対話型(ユーザー代理): アップグレード試行 SHOULD
  • サービス間(自己代理): アップグレード試行 MAY(または即座に中止)

3. エラーレスポンスの改善

403 レスポンスに resource_metadata を含めることで、401 と同じ情報を提供。

具体例

シナリオ: GitHub MCP サーバーの使用

初期接続(最小権限):

// クライアントは最小限のスコープで開始
const scopes = ["repo:read"];  // リポジトリの読み取りのみ

// トークン取得
const token = await getAccessToken(scopes);

リポジトリ検索(成功):

// 読み取り権限で実行可能
await mcpClient.callTool("search_repositories", {
  query: "mcp"
});
// → 200 OK

Issue 作成(権限不足):

// 書き込み権限が必要
await mcpClient.callTool("create_issue", {
  title: "Bug report",
  body: "Found a bug"
});

// → 403 Forbidden
// WWW-Authenticate: Bearer error="insufficient_scope",
//                   scope="repo:write",
//                   resource_metadata="https://github.com/.well-known/oauth-protected-resource"

自動アップグレード(対話型クライアント):

// クライアントが自動的に処理
async function handleInsufficientScope(error) {
  // 1. 現在のスコープと必要なスコープを結合
  const currentScopes = ["repo:read"];
  const requiredScopes = ["repo:write"];
  const newScopes = [...new Set([...currentScopes, ...requiredScopes])];
  // → ["repo:read", "repo:write"]
  
  // 2. ユーザーに追加権限を要求
  const newToken = await reauthorize(newScopes);
  
  // 3. リトライ
  await mcpClient.callTool("create_issue", {
    title: "Bug report",
    body: "Found a bug"
  });
  // → 200 OK
}

サービス間クライアントの場合:

// Client Credentials フロー(ユーザー不在)
try {
  await mcpClient.callTool("create_issue", { /* ... */ });
} catch (error) {
  if (error.code === "insufficient_scope") {
    // オプション1: 自動アップグレード試行(MAY)
    const newToken = await getClientCredentialsToken(["repo:write"]);
    // リトライ...
    
    // オプション2: 即座に中止(推奨)
    throw new Error("Insufficient permissions for this operation");
  }
}

フロー図の説明

初回認証(限定スコープ)

リクエスト成功 → 200 OK

後で追加スコープが必要なリクエスト

403 Forbidden + WWW-Authenticate

クライアントタイプで分岐:

【対話型】

ブラウザで再認証(拡張スコープ)

ユーザーが承認

新しいトークン取得

リトライ → 200 OK

【サービス間】

アップグレード試行 OR 中止

(試行する場合)
新しいトークン要求

成功 → リトライ
失敗 → エラー報告

メリット

  1. セキュリティ向上: 最小権限の原則に従い、必要な時だけ権限を要求
  2. UX 改善: 初回は最小限の権限で、必要に応じて段階的に追加
  3. 実装の明確化: クライアント開発者に明確なガイダンス
  4. 無限ループ防止: リトライ制限とキャッシングで保護

実際の体験

Before:
アプリ起動時:
「このアプリに以下の権限を許可しますか?」
✓ リポジトリの読み取り
✓ リポジトリの書き込み
✓ Issue の管理
✓ Pull Request の管理
✓ Webhook の管理
... (使わない権限も全部要求)

After:
アプリ起動時:
「このアプリに以下の権限を許可しますか?」
✓ リポジトリの読み取り

(後で Issue 作成を試みた時)
「追加の権限が必要です」
✓ Issue の管理

つまり、より安全で、ユーザーフレンドリーな権限管理が可能になるアップデートで
す。

SEP-986: ツール名の形式を標準化

https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1603

解決する問題

現状

❌ 統一されていないツール名の例:

  • getUser
  • get-user
  • get_user
  • GET_USER
  • user/get
  • user.get
  • "get user" (スペース含む)
  • getUserProfileInformation... (長すぎる)

各実装がバラバラの命名規則を使っており、混乱とエラーの原因に。

新しい標準

ルール

✅ 長さ: 1〜64文字
✅ 大文字小文字: 区別する(case-sensitive)
✅ 使用可能文字:

  • 英字: A-Z, a-z
  • 数字: 0-9
  • 記号: _ (アンダースコア)
    - (ハイフン)
    . (ドット)
    / (スラッシュ)

❌ 使用不可:

  • スペース
  • カンマ
  • その他の特殊文字

有効な例

// ✅ すべて有効
"getUser"                    // キャメルケース
"get_user"                   // スネークケース
"get-user"                   // ケバブケース
"user-profile/update"        // 階層的(スラッシュ)
"admin.tools.list"           // 名前空間(ドット)
"DATA_EXPORT_v2"             // 大文字 + バージョン
"github/issues/create"       // 深い階層

無効な例

// ❌ すべて無効
"get user"                   // スペース
"get,user"                   // カンマ
"get@user"                   // 特殊文字
"a".repeat(65)               // 長すぎる(65文字)
""                           // 空文字

実装例

サーバー側

// ツール定義時にバリデーション
const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_\-./]{1,64}$/;

function validateToolName(name: string): boolean {
  return TOOL_NAME_PATTERN.test(name);
}

// ツール登録
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "github/issues/create",  // ✅ 有効
      description: "Create a GitHub issue",
      inputSchema: { /* ... */ }
    },
    {
      name: "slack/send message",    // ❌ 無効(スペース)
      // エラーになる
    }
  ]
}));

クライアント側

// ツール呼び出し時のバリデーション
async function callTool(name: string, args: any) {
  if (!validateToolName(name)) {
    throw new Error(`Invalid tool name: ${name}`);
  }
  
  return await mcpClient.callTool({ name, arguments: args });
}

// 使用例
await callTool("github/issues/create", { /* ... */ });  // ✅
await callTool("create issue", { /* ... */ });          // ❌ エラー

命名規則の推奨パターン

1. フラットな命名

"getUser"
"createIssue"
"sendMessage"

2. 階層的命名(スラッシュ)

"github/repos/list"
"github/issues/create"
"slack/channels/list"
"slack/messages/send"

3. 名前空間(ドット)

"admin.users.list"
"admin.users.create"
"api.v2.search"

4. ハイブリッド

"github/issues.create"
"slack/channels.archive"

実際の使用例

Before(標準化前)

// 各サーバーがバラバラ
const githubTools = [
  "CreateIssue",           // パスカルケース
  "list-repositories",     // ケバブケース
  "get_user_profile",      // スネークケース
  "search repos"           // スペース(無効)
];

// クライアント側で混乱
await client.callTool("CreateIssue", { /* ... */ });
await client.callTool("create-issue", { /* ... */ });  // 同じツール?

After(標準化後)

// 統一された命名
const githubTools = [
  "github/issues/create",
  "github/repos/list",
  "github/users/get"
];

// 明確で予測可能
await client.callTool("github/issues/create", { /* ... */ });

後方互換性

非互換の変更ですが、移行期間を設けます:

// 移行期間中はエイリアスをサポート
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "github/issues/create",  // 新しい名前
      description: "Create a GitHub issue",
      // 非推奨の古い名前もサポート(警告付き)
      aliases: ["CreateIssue", "create-issue"]
    }
  ]
}));

メリット

  1. 予測可能性: ツール名の形式が統一され、推測しやすい
  2. 相互運用性: すべてのクライアントで同じツール名が動作
  3. 階層化: スラッシュやドットで論理的にグループ化可能
  4. パース容易性: スペースやカンマがないため解析が簡単
  5. ドキュメント化: 一貫した命名規則でドキュメントが明確に

SEP-1330: Enum スキーマの改善と JSON Schema 標準準拠

https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330

概要

MCP の列挙型(enum)スキーマを JSON Schema 標準に準拠させ、複数選択をサポートする提案です。

解決する問題

現状の課題

// 非標準の enumNames プロパティを使用
{
  "type": "string",
  "enum": ["#FF0000", "#00FF00", "#0000FF"],
  "enumNames": ["Red", "Green", "Blue"]  // ❌ JSON Schema 標準外
}
  1. 非標準: enumNames は JSON Schema の公式仕様にない
  2. 単一選択のみ: 複数選択(チェックボックス)がサポートされていない
  3. バリデーション不可: 標準の JSON Schema バリデーターで検証できない

新しいスキーマ体系

1. Legacy(既存の非標準スキーマ)

// 後方互換性のため残す(将来的に非推奨)
interface LegacyEnumSchema {
  type: "string";
  enum: string[];
  enumNames?: string[];  // 非標準
}

2. 単一選択(Single Select)

タイトルなし:

{
  "type": "string",
  "enum": ["Red", "Green", "Blue"]
}

タイトルあり(標準準拠):

{
  "type": "string",
  "oneOf": [
    { "const": "#FF0000", "title": "Red" },
    { "const": "#00FF00", "title": "Green" },
    { "const": "#0000FF", "title": "Blue" }
  ]
}

3. 複数選択(Multi Select)- 新機能

タイトルなし:

{
  "type": "array",
  "minItems": 1,
  "maxItems": 3,
  "items": {
    "type": "string",
    "enum": ["Red", "Green", "Blue"]
  }
}

タイトルあり:

{
  "type": "array",
  "minItems": 1,
  "maxItems": 3,
  "items": {
    "anyOf": [
      { "const": "#FF0000", "title": "Red" },
      { "const": "#00FF00", "title": "Green" },
      { "const": "#0000FF", "title": "Blue" }
    ]
  }
}

実装例

サーバー側: Elicitation(ユーザー入力要求)

単一選択(ラジオボタン):

// 色を1つ選択
const response = await client.request({
  method: "elicit",
  params: {
    prompt: "Choose your favorite color",
    schema: {
      type: "string",
      oneOf: [
        { const: "red", title: "Red" },
        { const: "green", title: "Green" },
        { const: "blue", title: "Blue" }
      ]
    }
  }
});

// レスポンス: { action: "accept", content: { value: "red" } }

複数選択(チェックボックス):

// 色を複数選択
const response = await client.request({
  method: "elicit",
  params: {
    prompt: "Choose your favorite colors (1-3)",
    schema: {
      type: "array",
      minItems: 1,
      maxItems: 3,
      items: {
        anyOf: [
          { const: "red", title: "Red" },
          { const: "green", title: "Green" },
          { const: "blue", title: "Blue" },
          { const: "yellow", title: "Yellow" }
        ]
      }
    }
  }
});

// レスポンス: { action: "accept", content: { value: ["red", "blue"] } }

UI での表示

Before(単一選択のみ)

Choose your favorite color:
○ Red
○ Green
○ Blue

After(単一 + 複数選択)

単一選択:
Choose your favorite color:
○ Red
○ Green
○ Blue

複数選択:
Choose your favorite colors (1-3):
☐ Red
☐ Green
☐ Blue
☐ Yellow

実際のユースケース

1. 設定の選択

// 通知設定(複数選択)
{
  type: "array",
  title: "Notification Preferences",
  items: {
    anyOf: [
      { const: "email", title: "Email" },
      { const: "slack", title: "Slack" },
      { const: "sms", title: "SMS" }
    ]
  }
}

2. タグの選択

// 記事のタグ(複数選択、最大5個)
{
  type: "array",
  title: "Article Tags",
  maxItems: 5,
  items: {
    anyOf: [
      { const: "tech", title: "Technology" },
      { const: "ai", title: "AI/ML" },
      { const: "web", title: "Web Development" },
      { const: "mobile", title: "Mobile" }
    ]
  }
}

3. 優先度の選択

// 優先度(単一選択)
{
  type: "string",
  title: "Priority",
  oneOf: [
    { const: "low", title: "Low" },
    { const: "medium", title: "Medium" },
    { const: "high", title: "High" },
    { const: "urgent", title: "Urgent" }
  ]
}

ElicitResult の拡張

// Before
interface ElicitResult {
  action: "accept" | "decline" | "cancel";
  content?: { [key: string]: string | number | boolean };
}

// After(配列をサポート)
interface ElicitResult {
  action: "accept" | "decline" | "cancel";
  content?: { [key: string]: string | number | boolean | string[] };  // ← 追加
}

メリット

  1. 標準準拠: JSON Schema の公式仕様に従う
  2. バリデーション可能: 標準のバリデーターで検証できる
  3. 柔軟性: 単一/複数選択、タイトルあり/なしを選択可能
  4. UX 向上: チェックボックスによる複数選択が可能に
  5. 後方互換性: Legacy スキーマは引き続きサポート

検証

  • JSON Schema Validator で全パターンを検証済み
  • TypeScript SDK、Python SDK で実装済み
  • クライアント実装とデモも公開済み

SEP-1036: URL モード Elicitation(アウトオブバンド認証)

https://github.com/modelcontextprotocol/modelcontextprotocol/pull/887

概要

機密情報を安全に扱うための Out-of-Band(帯域外)Elicitation を追加する提案です。

解決する問題

現状の課題

既存の Elicitation(ユーザー入力要求)は、すべてのデータが MCP クライアント経由で送信されます:

サーバー → クライアント → ユーザー
         ← クライアント ← ユーザー(入力)

問題点:

  • API キーやパスワードなどの機密情報がクライアントを経由
  • サードパーティ API の認証フローが実装できない
  • 支払い情報などの PCI DSS 対象データを扱えない

新しいソリューション: Out-of-Band (OOB) モード

ブラウザで直接サーバーとやり取りし、クライアントを経由しない:

サーバー → クライアント → ユーザー(URL を開く)
         ← ブラウザ ←→ サーバー(直接通信)
         ← クライアント ← 完了通知

2つの Elicitation モード

1. Form モード(既存)

// クライアント経由で非機密情報を収集
{
  mode: "form",
  prompt: "Enter your name",
  schema: { type: "string" }
}

2. OOB モード(新規)

``typescript
// ブラウザで直接機密情報を収集
{
mode: "oob",
prompt: "Connect your GitHub account",
url: "https://myserver.com/auth/github?session=abc123"
}


### 実装例

#### ユースケース 1: API キーの収集

サーバー側:
```typescript
// API キーを安全に収集
const result = await server.request({
  method: "elicitation/create",
  params: {
    mode: "oob",
    prompt: "Please enter your OpenAI API key",
    url: "https://myserver.com/setup/api-key?session=" + sessionId
  }
});

// ユーザーがブラウザで入力完了後
if (result.action === "accept") {
  // サーバー側で直接 API キーを受け取り済み
  const apiKey = await getApiKeyFromSession(sessionId);
}

ブラウザページ(myserver.com):

<form action="/setup/api-key" method="POST">
  <label>OpenAI API Key:</label>
  <input type="password" name="api_key" />
  <button type="submit">Submit</button>
</form>

ユースケース 2: OAuth 認証

サーバー側:

// GitHub OAuth フロー
const result = await server.request({
  method: "elicitation/create",
  params: {
    mode: "oob",
    prompt: "Authorize access to your GitHub repositories",
    url: "https://github.com/login/oauth/authorize?" +
         "client_id=xxx&redirect_uri=https://myserver.com/callback"
  }
});

// ユーザーが GitHub で認証完了後
if (result.action === "accept") {
  // サーバーは OAuth コールバックで既にトークンを受け取り済み
  const token = await getGitHubToken(sessionId);
}

ユースケース 3: 支払い処理

サーバー側:

// Stripe Checkout
const result = await server.request({
  method: "elicitation/create",
  params: {
    mode: "oob",
    prompt: "Complete payment for premium features",
    url: "https://checkout.stripe.com/pay/cs_test_xxx"
  }
});

if (result.action === "accept") {
  // Stripe webhook で支払い完了を確認済み
  await activatePremiumFeatures(userId);
}

クライアント側の実装

// 初期化時に OOB サポートを宣言
client.connect({
  capabilities: {
    elicitation: {
      form: {},   // Form モードをサポート
      oob: {}     // OOB モードをサポート
    }
  }
});

// OOB リクエストの処理
client.setRequestHandler(ElicitationCreateRequestSchema, async (request) => {
  if (request.params.mode === "oob") {
    // 1. ユーザーに確認
    const consent = await askUserConsent(
      `${request.params.prompt}\n\nOpen: ${request.params.url}`
    );
    
    if (!consent) {
      return { action: "decline" };
    }
    
    // 2. ブラウザで URL を開く
    await openBrowser(request.params.url);
    
    // 3. サーバーからの完了通知を待つ
    // (サーバーが elicitation/complete を送信)
    return { action: "accept" };
  }
});

フロー図

  1. サーバー: OOB Elicitation リクエスト送信
  2. クライアント: ユーザーに確認ダイアログ表示
    「GitHub に接続しますか?」
    [はい] [いいえ]
  3. ユーザー: [はい] をクリック
  4. クライアント: ブラウザで URL を開く
  5. ブラウザ ←→ サーバー: 直接通信
    (GitHub OAuth フロー)
  6. サーバー: 認証完了、トークン取得
  7. サーバー → クライアント: elicitation/complete 通知
  8. クライアント: { action: "accept" } を返す

セキュリティ上のメリット

  1. 機密情報の保護: API キーやパスワードがクライアントを経由しない
  2. 標準プロトコル: OAuth などの既存の認証フローをそのまま使用可能
  3. PCI DSS 準拠: 支払い情報を安全に処理
  4. ユーザー同意: URL を開く前に必ずユーザーの同意が必要
  5. フィッシング対策: ユーザーは URL を確認できる

実際の体験

Form モード(既存):
Claude Desktop:

┌─────────────────────────────┐
│ Enter your name:            │
│ [________________]          │
│ [Submit] [Cancel]           │
└─────────────────────────────┘

OOB モード(新規):
Claude Desktop:

┌─────────────────────────────────────────┐
│ Connect your GitHub account             │
│                                         │
│ This will open:                         │
│ https://github.com/login/oauth/...      │
│                                         │
│ [Open Browser] [Cancel]                 │
└─────────────────────────────────────────┘

↓ [Open Browser] クリック

ブラウザが開く:

┌─────────────────────────────────────────┐
│ GitHub                                  │
│ Authorize MyApp?                        │
│ ✓ Read repositories                     │
│ ✓ Create issues                         │
│ [Authorize] [Cancel]                    │
└─────────────────────────────────────────┘

破壊的変更

  1. クライアント初期化: サポートするモードを宣言する必要がある
  2. リクエスト形式: mode フィールドが必須に

ステータス

  • マージ済み (2025年11月13日)
  • accepted ラベル
  • TypeScript SDK に実装済み

つまり、OAuth、API キー、支払いなどの機密操作を安全に処理できるようになるアップデートです。Web アプリが長年使ってきた「外部サイトにリダイレクト」パターンを MCP に導入したもの
です。

SEP-1577: Sampling With Tools(ツール呼び出し対応)

https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577

概要

MCP の Sampling(LLM 推論要求)にツール呼び出し機能を追加する提案です。これにより、サーバー側で独自のエージェントループを実行できるようになります。

解決する問題

現状の Sampling

// ツール呼び出しなし、単純なテキスト生成のみ
const result = await client.request({
  method: "sampling/createMessage",
  params: {
    messages: [{ role: "user", content: "What's the weather?" }]
  }
});
// → "I don't have access to weather data."

制限:

  • ツール呼び出しができない
  • エージェント的な動作が不可能
  • 複雑なプロンプトや出力パースで回避するしかない

新機能: Tools パラメータ

// ツールを定義して Sampling に渡す
const result = await client.request({
  method: "sampling/createMessage",
  params: {
    messages: [{ role: "user", content: "What's the weather in Tokyo?" }],
    tools: [
      {
        name: "get_weather",
        description: "Get current weather",
        inputSchema: {
          type: "object",
          properties: {
            location: { type: "string" }
          }
        }
      }
    ],
    toolChoice: { mode: "auto" }  // 自動判断
  }
});

// LLM がツール呼び出しを返す
// result.content = {
//   type: "tool_use",
//   id: "call_1",
//   name: "get_weather",
//   input: { location: "Tokyo" }
// }

サーバー側のツールループ実装

// MCP サーバーが独自のエージェントループを実行
async function agenticSampling(userMessage: string) {
  const messages = [{ role: "user", content: userMessage }];
  
  while (true) {
    // LLM に問い合わせ
    const result = await client.request({
      method: "sampling/createMessage",
      params: {
        messages,
        tools: myTools,
        toolChoice: { mode: "auto" }
      }
    });
    
    // ツール呼び出しがあるか確認
    const toolUse = result.content.find(c => c.type === "tool_use");
    
    if (!toolUse) {
      // ツール呼び出しなし、最終回答
      return result.content.find(c => c.type === "text").text;
    }
    
    // ツールを実行
    const toolResult = await executeMyTool(toolUse.name, toolUse.input);
    
    // 結果を会話履歴に追加
    messages.push({
      role: "assistant",
      content: [toolUse]
    });
    messages.push({
      role: "user",
      content: [{
        type: "tool_result",
        toolUseId: toolUse.id,
        content: [{ type: "text", text: JSON.stringify(toolResult) }]
      }]
    });
    
    // ループ継続
  }
}

実際の使用例

例1: 天気情報の取得

// ユーザー: "What's the weather in Tokyo?"

// ラウンド1: LLM がツール呼び出しを決定
{
  role: "assistant",
  content: [{
    type: "tool_use",
    id: "call_1",
    name: "get_weather",
    input: { location: "Tokyo" }
  }]
}

// サーバーがツールを実行
const weather = await getWeather("Tokyo");
// → { temperature: 20, condition: "sunny" }

// ラウンド2: ツール結果を LLM に渡す
{
  role: "user",
  content: [{
    type: "tool_result",
    toolUseId: "call_1",
    content: [{ type: "text", text: '{"temperature": 20, "condition": "sunny"}' }]
  }]
}

// LLM が最終回答を生成
{
  role: "assistant",
  content: [{
    type: "text",
    text: "The weather in Tokyo is currently sunny with a temperature of 20°C."
  }]
}

例2: 複数ツールの連鎖

// ユーザー: "Book a flight to Paris and check the weather there"

// ラウンド1: 天気確認LLM: tool_use(get_weather, {location: "Paris"})
→ Server: executes tool
→ Result: {temperature: 15, condition: "rainy"}

// ラウンド2: フライト検索LLM: tool_use(search_flights, {destination: "Paris"})
→ Server: executes tool
→ Result: [{flight: "AF123", price: 500}]

// ラウンド3: 予約LLM: tool_use(book_flight, {flight: "AF123"})
→ Server: executes tool
→ Result: {confirmation: "ABC123"}

// ラウンド4: 最終回答LLM: "I've booked flight AF123 to Paris (confirmation: ABC123). 
       Note that it's currently rainy there with 15°C."

ToolChoice オプション

interface ToolChoice {
  mode?: "auto" | "required" | "none";
}

使い分け:

  • "auto": LLM が自動判断(デフォルト)
  • "required": 必ずツールを呼び出す(構造化出力に便利)
  • "none": ツール呼び出しを禁止

構造化出力の例:

// JSON スキーマに従った出力を強制
const result = await client.request({
  method: "sampling/createMessage",
  params: {
    messages: [{ role: "user", content: "List 5 colors" }],
    tools: [{
      name: "return_colors",
      inputSchema: {
        type: "object",
        properties: {
          colors: {
            type: "array",
            items: { type: "string" },
            minItems: 5,
            maxItems: 5
          }
        }
      }
    }],
    toolChoice: { mode: "required" }  // 必ず呼び出す
  }
});

// 確実に構造化された出力を取得
// result.content[0].input = { colors: ["red", "blue", "green", "yellow", "purple"] }

主な変更点

1. ClientCapabilities の拡張

{
  capabilities: {
    sampling: {
      tools: {}  // ツールサポートを宣言
    }
  }
}

2. CreateMessageRequest の拡張

{
  messages: [...],
  tools: [...],        // 新規
  toolChoice: {...}    // 新規
}

3. メッセージ型の更新

// ツール呼び出し
{
  type: "tool_use",
  id: "call_1",
  name: "get_weather",
  input: { location: "Tokyo" }
}

// ツール結果
{
  type: "tool_result",
  toolUseId: "call_1",
  content: [{ type: "text", text: "..." }]
}

メリット

  1. エージェント機能: サーバー側で複雑なタスクを自動実行
  2. 構造化出力: JSON スキーマに従った確実な出力
  3. 標準準拠: OpenAI、Anthropic、Gemini API と互換性のある設計
  4. 柔軟性: ツールループをサーバー側で完全制御

将来の拡張可能性

  • ストリーミング: リアルタイムでツール呼び出し結果を返す
  • キャッシング: プロンプトキャッシュでコスト削減
  • クライアント側ツール: クライアントがツールを実行する選択肢

つまり、MCP サーバーが LLM を使った自律的なエージェントとして動作できるようになる重要なアップデートです。

SEP-991: OAuth Client ID Metadata Documents(URL ベースのクライアント登録)

https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1296

概要

HTTPS URL をクライアント ID として使用し、その URL でクライアントのメタデータを公開する新しい OAuth クライアント登録方式です。

解決する問題

現状の登録方式の課題

  1. 事前登録(Pre-registration):
    開発者 → 各サーバーに手動登録 → クライアント ID 取得
  • サーバーが存在しない段階では登録不可能
  • ユーザーが手動で認証情報を管理する必要
  1. 動的クライアント登録(DCR):
    クライアント → サーバーに登録リクエスト → 一時的な ID 取得
  • サーバーが無制限のデータベースを管理する必要
  • 自己申告のメタデータを信頼する必要
  • 有効期限管理が複雑

問題の核心:
MCP では「クライアントとサーバーが初めて出会う」シナリオが一般的だが、既存の方式では対応が困難。

新しいソリューション: Client ID Metadata Documents

基本コンセプト

client_id = 'https://app.example.com/oauth/metadata.json'

この URL で以下の JSON を公開:
json

{
  "client_id": "https://app.example.com/oauth/metadata.json",
  "client_name": "Example MCP Client",
  "redirect_uris": [
    "http://localhost:3000/callback"
  ],
  "token_endpoint_auth_method": "none"
}

動作フロー

  1. ユーザー: MCP サーバーに接続開始
  2. クライアント → 認証サーバー:
    client_id=https://app.example.com/oauth/metadata.json
  3. 認証サーバー: URL 形式の client_id を検出
  4. 認証サーバー → メタデータ URL:
    GET https://app.example.com/oauth/metadata.json
  5. メタデータ取得 & 検証:
    ✓ client_id が URL と一致
    ✓ redirect_uri が許可リストに含まれる
    ✓ ドメインが信頼ポリシーに適合
  6. ユーザーに同意画面を表示
  7. OAuth フロー継続

実装例

クライアント側: メタデータの公開

// https://app.example.com/oauth/metadata.json でホスト
{
  "client_id": "https://app.example.com/oauth/metadata.json",
  "client_name": "My MCP Client",
  "client_uri": "https://app.example.com",
  "logo_uri": "https://app.example.com/logo.png",
  "redirect_uris": [
    "http://127.0.0.1:3000/callback",
    "http://localhost:3000/callback"
  ],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

サーバー側: メタデータの取得と検証

async function validateClient(clientId: string, redirectUri: string) {
  // URL 形式の client_id を検出
  if (!clientId.startsWith("https://")) {
    throw new Error("Invalid client_id format");
  }
  
  // メタデータを取得(キャッシュ可能)
  const metadata = await fetchWithCache(clientId);
  
  // 検証
  if (metadata.client_id !== clientId) {
    throw new Error("client_id mismatch");
  }
  
  if (!metadata.redirect_uris.includes(redirectUri)) {
    throw new Error("redirect_uri not allowed");
  }
  
  // ドメイン信頼ポリシーを適用
  if (!isTrustedDomain(new URL(clientId).hostname)) {
    throw new Error("Untrusted domain");
  }
  
  return metadata;
}

信頼モデル

オープンサーバー(誰でも使用可能)

// すべての HTTPS client_id を受け入れ
function isTrustedDomain(hostname: string) {
  return true;  // すべて許可
}

保護されたサーバー(制限あり)

// 信頼できるドメインのみ許可
function isTrustedDomain(hostname: string) {
  const trustedDomains = [
    "claude.ai",
    "cursor.com",
    "mycompany.com"
  ];
  return trustedDomains.some(d => hostname.endsWith(d));
}

メリット

1. 事前調整不要

  • クライアント開発者はサーバーを知らなくてよい
  • サーバー運営者はクライアントを知らなくてよい
  • メタデータを公開するだけで準備完了

2. サーバー側の負担軽減

  • 登録データベース不要
  • 有効期限管理不要
  • メタデータをキャッシュ可能(最大24時間推奨)

3. セキュリティ向上

  • Redirect URI の証明: HTTPS で暗号的に保証
  • 安定した識別子: URL は永続的で監査可能
  • 柔軟な信頼ポリシー: サーバーが独自の基準を設定可能

4. 後方互換性

  • 既存の事前登録は引き続き動作
  • 既存の DCR も引き続き動作
  • 段階的な導入が可能

実際の使用例

Before(DCR)

// 1. クライアントが登録リクエスト
POST /register
{
  "client_name": "My Client",
  "redirect_uris": ["http://localhost:3000/callback"]
}

// 2. サーバーが一時的な ID を発行
{
  "client_id": "abc123xyz",  // ランダムな ID
  "client_secret": "..."
}

// 3. サーバーはこの情報を永続化する必要がある

After(Client ID Metadata Documents)

// 1. クライアントはメタデータを公開するだけ
// https://app.example.com/oauth/metadata.json

// 2. 認証時に URL を使用
client_id = "https://app.example.com/oauth/metadata.json"

// 3. サーバーは必要に応じてメタデータを取得
// データベース不要、キャッシュのみ

セキュリティ上の考慮事項

リスク1: Localhost URL なりすまし

攻撃者が正規クライアントの metadata URL を使用し、同じ localhost ポートをバインドして認証コードを横取り可能。

軽減策:

  • プラットフォーム固有の証明(iOS DeviceCheck、Android Play Integrity)
  • JWKS + 短命 JWT による認証
  • Localhost クライアントに追加の警告を表示

リスク2: SSRF(Server-Side Request Forgery)

悪意のあるクライアントが内部エンドポイントへのリクエストを誘発可能。

軽減策:

  • URL とその解決先 IP を検証
  • プライベート IP 範囲をブロック

リスク3: DDoS

複数の認証サーバーを使った DDoS 攻撃の可能性。

軽減策:

  • 積極的なキャッシング
  • レート制限

発見方法

サーバーは OAuth メタデータで対応を宣言:

{
  "client_id_metadata_document_supported": true
}

SEP-1686: Tasks(長時間実行タスクのサポート)

https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686

概要

長時間実行される操作を非同期で実行し、後で結果を取得できる Task プリミティブを導入する提案です。

解決する問題

現状の課題

// 通常のツール呼び出し: 結果が返るまでブロック
const result = await client.callTool("analyze_genome", { data: "..." });
// → 数時間かかる場合、タイムアウトやエラーが発生

問題点:

  • 長時間実行される操作(数分〜数時間)に対応できない
  • 進行状況を確認する標準的な方法がない
  • 結果が失われた場合、最初からやり直す必要がある
  • エージェントによるポーリングは不安定で高コスト

新しいソリューション: Task プリミティブ

基本コンセプト

  1. タスクを作成 → すぐに task ID を取得
  2. 他の作業を継続
  3. 定期的にステータスをポーリング
  4. 完了したら結果を取得

動作フロー

1. タスクの作成

// ツール呼び出しに task パラメータを追加
const response = await client.request({
  method: "tools/call",
  params: {
    name: "analyze_genome",
    arguments: { data: "..." },
    task: {
      ttl: 3600000  // 1時間保持
    }
  }
});

// すぐに task ID を取得(結果ではない)
// {
//   task: {
//     taskId: "786512e2-9e0d-44bd-8f29-789f320fe840",
//     status: "working",
//     createdAt: "2025-11-25T10:30:00Z",
//     ttl: 3600000,
//     pollInterval: 5000  // 5秒ごとにポーリング推奨
//   }
// }

2. ステータスのポーリング

// 定期的にステータスを確認
const status = await client.request({
  method: "tasks/get",
  params: {
    taskId: "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
});

// {
//   taskId: "...",
//   status: "working",  // まだ実行中
//   statusMessage: "Processing chromosome 5 of 23",
//   lastUpdatedAt: "2025-11-25T10:35:00Z"
// }

3. 結果の取得

// 完了したら結果を取得
const result = await client.request({
  method: "tasks/result",
  params: {
    taskId: "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
});

// 元のツール呼び出しが返すはずだった結果を取得

タスクのステータス

type TaskStatus = 
  | "working"          // 実行中(初期状態)
  | "input_required"   // ユーザー入力が必要
  | "completed"        // 完了(終端状態)
  | "failed"           // 失敗(終端状態)
  | "cancelled"        // キャンセル(終端状態)

実装例

サーバー側: タスク対応ツールの実装

// 初期化時にタスクサポートを宣言
server.connect({
  capabilities: {
    tasks: {
      requests: {
        "tools.call": {}  // ツール呼び出しでタスクをサポート
      },
      list: true,
      cancel: true
    }
  }
});

// ツール定義でタスクサポートを指定
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "analyze_genome",
      description: "Analyze genome data",
      execution: {
        taskSupport: "required"  // タスク必須
      },
      inputSchema: { /* ... */ }
    }
  ]
}));

// タスク付きツール呼び出しの処理
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.task) {
    // タスクとして実行
    const taskId = generateTaskId();
    
    // バックグラウンドで実行開始
    startBackgroundJob(taskId, request.params.name, request.params.arguments);
    
    // すぐに task ID を返す
    return {
      task: {
        taskId,
        status: "working",
        createdAt: new Date().toISOString(),
        ttl: request.params.task.ttl || 3600000,
        pollInterval: 5000
      }
    };
  }
  
  // 通常の同期実行
  return executeToolSync(request.params.name, request.params.arguments);
});

// ステータス取得
server.setRequestHandler(GetTaskRequestSchema, async (request) => {
  const task = getTaskStatus(request.params.taskId);
  return task;
});

// 結果取得
server.setRequestHandler(GetTaskResultRequestSchema, async (request) => {
  const result = getTaskResult(request.params.taskId);
  return result;
});

クライアント側: タスクの管理

// タスクを作成して管理
async function runLongTask(toolName: string, args: any) {
  // 1. タスクを作成
  const createResponse = await client.request({
    method: "tools/call",
    params: {
      name: toolName,
      arguments: args,
      task: { ttl: 3600000 }
    }
  });
  
  const { taskId, pollInterval } = createResponse.task;
  
  // 2. ポーリングループ
  while (true) {
    await sleep(pollInterval || 5000);
    
    const status = await client.request({
      method: "tasks/get",
      params: { taskId }
    });
    
    console.log(`Status: ${status.status} - ${status.statusMessage}`);
    
    // 終端状態に到達
    if (["completed", "failed", "cancelled"].includes(status.status)) {
      break;
    }
  }
  
  // 3. 結果を取得
  const result = await client.request({
    method: "tasks/result",
    params: { taskId }
  });
  
  return result;
}

実際のユースケース

1. ゲノム解析(数時間)

// タスクとして実行
const task = await client.callTool("analyze_genome", data, { task: true });

// ユーザーは他の作業を継続可能
// 定期的にステータスを確認
// "Processing chromosome 5 of 23..."
// "Processing chromosome 6 of 23..."

// 完了後に結果を取得

2. CI/CD パイプライン(数分〜数十分)

// ビルドを開始
const buildTask = await client.callTool("run_build", { repo: "..." }, { task: true });

// テストを並行実行
const testTask = await client.callTool("run_tests", { suite: "..." }, { task: true });

// 両方の完了を待つ
await Promise.all([
  waitForTask(buildTask.taskId),
  waitForTask(testTask.taskId)
]);

3. 深層リサーチ(数分)

// リサーチタスクを開始
const task = await client.callTool("deep_research", { topic: "..." }, { task: true });

// エージェントは他のタスクを実行可能
// タスクが完了したら結果を取得

その他の操作

タスク一覧の取得

const tasks = await client.request({
  method: "tasks/list",
  params: {
    cursor: null  // ページネーション
  }
});

タスクのキャンセル

await client.request({
  method: "tasks/cancel",
  params: {
    taskId: "..."
  }
});

セキュリティとリソース管理

  • TTL(Time To Live): タスクは指定時間後に自動削除
  • セッション分離: タスクは作成したセッションにバインド
  • レート制限: DoS 攻撃を防ぐ
  • ランダム ID: 推測不可能な task ID

メリット

  1. 非ブロッキング: 長時間実行中も他の作業が可能
  2. 進行状況の可視性: ステータスを能動的に確認可能
  3. 結果の永続化: 一時的な接続断でも結果を取得可能
  4. 並行実行: 複数のタスクを同時実行可能
  5. 既存システムとの統合: AWS Step Functions、CI/CD パイプラインなどを簡単にラップ

将来の拡張

  • プッシュ通知: 完了時にサーバーから通知
  • 中間結果: 実行中の部分的な結果を取得
  • ネストされたタスク: タスクがサブタスクを生成

まとめ

本記事では、2025年11月25日に公開されたMCP仕様のメジャーアップデートについてまとめました。

認証・認可の強化

  • OpenID Connect Discovery 1.0対応: Keycloak、Auth0、Googleなど主要なIDプロバイダーとの互換性が向上し、追加設定なしで認証サーバーを自動検出可能に
  • OAuthスコープ管理の改善: 最小権限の原則に基づき、必要な時だけ権限を要求する動的スコープアップグレードが可能に
  • OAuth Client ID Metadata Documents: URLベースのクライアント登録により、事前調整なしでOAuth認証が可能に

ユーザー体験の向上

  • アイコン・WebサイトURL追加: ツール、リソース、プロンプトに視覚的な識別情報を付与し、複数サーバーからのツールを一目で区別可能に
  • Enumスキーマの改善: JSON Schema標準準拠と複数選択(チェックボックス)のサポート追加
  • ツール名の標準化: 1〜64文字、英数字と_-./のみ使用可能なルールで統一

機能拡張

  • URL モード Elicitation: OAuthやAPIキー入力など、機密情報をクライアント経由せずブラウザで直接処理可能に
  • Sampling With Tools: サーバー側でLLMを使った自律的なエージェントループを実行可能に
  • Tasks: 長時間実行される操作(数分〜数時間)を非同期で実行し、進行状況の確認や後からの結果取得が可能に
アマゾン ウェブ サービス ジャパン (有志)

Discussion