kakigoori 21151

MCP の Structured tool output を試してみる

MCP の 2025-06-18 バージョンでは Structured tool output がサポートされました。ツールの定義で `outputSchema` を出力のスキーマを定義し、`structuredContent` フィールドに構造化された出力を返すことができます。この記事では MCP の TypeScript SDK を使用して Structured tool output を試してみます。

Model Context Protocol (MCP) ではサーバーが LLM が呼び出すことができるツールを公開できます。ツールは LLM が外部のシステムと対話をすることを可能にします。例えばユーザーが LLM に対して旅行の計画を依頼した場合には、予約可能なホテルのリストを取得して正確な情報を提供したり、ユーザーのカレンダーに予定を追加したりできます。

MCP ツールを定義する際には inputSchema フィールドにより JSON Schema を使用してツールの入力を定義することで、LLM が適切にツールを呼び出すことができます。以下の例は天気予報を取得するツールの定義です。inputSchema フィールドを参照すると location を指定する必要があることがわかります。

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Get current weather information for a location",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "City name or zip code"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

しかし、ツールの出力は通常、単純な文字列で返されます。典型的なツールの出力は以下のようになります。

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
      }
    ],
    "isError": false
  }
}

ツールの出力は常にテキストとして返されるため、このツールがどのようなデータを返すのか予測できません。このことはツールを呼び出す LLM に適切なコンテキストを提供することを妨げるのみならず、ツールの利用を検討している開発者にとっても不都合でした。

また、複雑なデータ構造を返す必要があるツールでは文字列化した JSON を返すといったハックも行われており、構造化された出力を扱うことは多くの MCP ツールの開発者にとっても望まれている機能でした。

MCP の 2025-06-18 バージョンの仕様 では Structured tool output がサポートされました。これにより、ツールの出力を構造化された形式で返すことができるようになります。この記事では MCP の TypeScript SDK を使用して Structured tool output を試してみます。

ツールを定義する

サーバー側でツールを定義します。まず Node.js のプロジェクトを作成し、必要なパッケージをインストールします。

mkdir mcp-weather-tool
cd mcp-weather-tool
npm init -y
 
npm install @modelcontextprotocol/sdk zod
npm install --save-dev typescript tsx @types/node

@modelcontextprotocol/sdk パッケージが MCP の TypeScript SDK です。zod ツールの入力と出力のスキーマを定義するために使用します。

まずは new McpServer() を使用して MCP サーバーを作成します。ツールの定義は server.registerTool() メソッドを使用して行います。以下のコードは天気予報を取得するツールを定義しています。

src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import z from "zod";
 
const server = new McpServer({
  name: "WeatherToolServer",
  version: "0.0.0",
});
 
const weatherInputSchema = {
  location: z.string().describe("City name or zip code"),
};
 
const weatherOutputSchema = {
  location: z.string().describe("Location name"),
  temperature: z.string().describe("Current temperature"),
  conditions: z.string().describe("Current weather conditions"),
};
 
server.registerTool(
  // ツール名
  "getWeather",
  {
    title: "Get Weather",
    description: "Get the current weather for a specified location.",
    inputSchema: weatherInputSchema,
    outputSchema: weatherOutputSchema,
  },
  // ツールが呼び出されたときの処理
  async ({ location }) => {
    // TODO...
  }
);
 
// サーバーを stdio で起動
const run = async () => {
  const transport = new StdioServerTransport();
  console.error("Starting server...");
  await server.connect(transport);
};
 
run().catch(console.error);

registerTool() メソッドの第 2 引数でツールのメタデータを定義します。outputSchema が 2025-06-18 バージョンの MCP で追加されたフィールドです。zod を使用してツールの出力のスキーマを定義しています。

outputSchema が定義されている場合、サーバーは必ずこのスキーマに従い構造化された結果を返す必要があります。クライアントはこのスキーマを使用してツールの出力を検証すべきです。

それではツールが呼び出されたときの処理を実装します。以下のコードはダミーの天気予報データを返すようにしています。

src/server.ts
server.registerTool(
  // ツール名
  "getWeather",
  {
    title: "Get Weather",
    description: "Get the current weather for a specified location.",
    inputSchema: weatherInputSchema,
    outputSchema: weatherOutputSchema,
  },
  // ツールが呼び出されたときの処理
  async ({ location }) => {
    // ダミーの天気予報データ
    const weatherData = {
      location: location,
      temperature: "72°F",
      conditions: "Partly cloudy",
    } satisfies z.infer<z.ZodObject<typeof weatherOutputSchema>>;
 
    // API の呼び出しが失敗した場合をシミュレート
    if (location === "error-location") {
      return {
        content: [
          {
            type: "text",
            text: "An error occurred while fetching the weather data.",
          },
        ],
        isError: true,
      };
    }
 
    return {
      structuredContent: weatherData,
      content: [
        {
          type: "text",
          // 後方互換性のため、文字列化した JSON も返す
          text: JSON.stringify(weatherData),
        },
      ],
      isError: false,
    };
  }
);

構造化された出力は structuredContent フィールドに JSON 形式で格納されます。後方互換性のため、構造化された出力と機能的に同等なコンテンツを構造化されていない形式で返すべきだと規定されています。文字列化した JSON を TextContent として返すのが一般的な方法です。

ツールをテストする

MCP サーバーのテストには @modelcontextprotocol/inspector を使用するのが便利です。Web ブラウザ上でツールの定義を確認したり、実際にツールを呼び出してデバッグできます。

以下のコマンドで MCP Inspector を MCP サーバーと連携させて起動します。

npx @modelcontextprotocol/inspector npx tsx src/server.ts

http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=xxx という形式で URL が表示されるので、ブラウザで開きます。左側のフォームに以下の内容が入力されているはずです。

  • Transport Type: stdio
  • Command: npx
  • Arguments: tsx src/server.ts

フォームに正しい内容が入力されていることを確認したら、Connect ボタンをクリックします。接続が成功した後に「Tools」→「List Tools」を選択すると、先ほど定義した getWeather ツールが表示されます。

ツールの詳細を確認するには、ツール名をクリックします。先ほど定義した inputSchemaoutputSchema が表示されます。Zod のスキーマが JSON Schema に変換されて表示されていることがわかりますね。

ツールを呼び出すをテストするには、inputSchema の入力フォームに値を入力して「Run Tool」ボタンをクリックします。Structured Content がスキーマ通りに返されていること、構造化されていない形式でも同等の内容が返されていることが検証されています。

試しに構造化された出力をスキーマに従わない形式で返してみましょう。サーバーのコードを以下のように変更します。

src/server.ts
server.registerTool(
  // ...
  async ({ location }) => {
    // ダミーの天気予報データ
    const weatherData = {
      location: location,
      temperature: "72°F",
      conditions: "Partly cloudy",
    } satisfies z.infer<z.ZodObject<typeof weatherOutputSchema>>;
 
    // スキーマに従わない形式で返す
    return {
      structuredContent: {
        ...weatherData,
        extraField: "This field is not in the output schema",
      },
      content: [
        {
          type: "text",
          text: JSON.stringify(weatherData),
        },
      ],
      isError: false,
    };
  }
);

サーバーを再起動して、再度ツールを呼び出してみます。MCP_PROXY_AUTH_TOKEN の値は毎回異なるので、Inspector の URL を再度開く必要があります。

余分なフィールドが含まれている場合には、MCP Inspector 上で Validation Error が表示されているものの、ツールの呼び出し自体は成功しています。

一方で必須のフィールドが欠けている場合には、ツールの呼び出し自体が失敗しエラーコード -32602 が返されます。

まとめ

  • MCP の 2025-06-18 バージョンで Structured tool output がサポートされた。SDK を使用してツールの出力を構造化された形式で返すことができる
  • ツールを定義する際には outputSchema フィールドを使用してツールの出力のスキーマを定義する
  • ツールの出力は structuredContent フィールドに JSON 形式で格納され、後方互換性のために文字列化した JSON を TextContent として返すことが推奨されている
  • MCP Inspector を使用してツールの定義や呼び出しをテストすることができる

参考

記事の理解度チェック

以下の問題に答えて、記事の理解を深めましょう。

Structured tool output を使用する際に、ツールの出力スキーマを定義するために使用するフィールドは何ですか?

  • outputSchema

    正解!

    outputSchema フィールドを使用してツールの出力のスキーマを定義します。

  • responseSchema

    もう一度考えてみましょう

  • resultSchema

    もう一度考えてみましょう

  • structureSchema

    もう一度考えてみましょう

構造化された出力はどのフィールドに格納されますか?

  • structuredContent

    正解!

    構造化された出力は structuredContent フィールドに JSON 形式で格納されます。

  • structuredData

    もう一度考えてみましょう

  • outputData

    もう一度考えてみましょう

  • content

    もう一度考えてみましょう

    content フィールドは構造化されていない形式の出力を返すために使用されます。