MCP サーバーでの outputSchema の注意点
MCP ではツールが outputSchema を定義している場合、レスポンスは structuredContent フィールドを使用して返す必要があります。しかし現状のクライアントではその値を見ないようなので、注意が必要です。
サンプル
Gemini CLI でテスト用の MCP サーバーを作成しました。元ファイルは TypeScript で書かれたコードですが、今回の記事に関係ある部分だけに簡略化して、ビルドの手間を省くためトランスパイル済のコードを示します。
適当な作業ディレクトリを作成して、以下の 2 つのファイルを作成します。
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const testServerToolDefinition = {
name: 'test_call',
description: 'Returns a test string.',
inputSchema: {
type: 'object',
properties: {},
required: []
},
outputSchema: {
type: 'object',
properties: {
content: {
type: 'array',
items: {
type: 'object',
properties: {
type: { type: 'string' },
text: { type: 'string' }
},
required: ['type', 'text']
}
}
},
required: ['content']
}
};
async function main() {
const serverTransport = new StdioServerTransport();
const serverInfo = { name: 'test_server', version: '0.1.0' };
const server = new Server(serverInfo, { capabilities: { tools: {} } });
server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
return { tools: [testServerToolDefinition] };
});
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
if (request.params.name == testServerToolDefinition.name) {
return {
content: [
{
type: 'text',
text: 'abc'
}
]
};
}
else {
throw new Error(`Tool "${request.params.name}" not found.`);
}
});
try {
await server.connect(serverTransport);
await new Promise(() => {});
}
catch (error) {
console.error('An error occurred:', error);
}
finally {
await server.close();
}
}
main().catch(console.error);
{
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.15.0"
}
}
npm install を実行すれば、依存関係として MCP TypeScript SDK がインストールされます。
動作確認
stdio サーバーのため、node で実行して JSON を入力すれば動作確認できます。(感覚的には httpd に telnet で接続するのに似ています)
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
{"result":{"tools":[{"name":"test_call","description":"Returns a test string.","inputSchema":{"type":"object","properties":{},"required":[]},"outputSchema":{"type":"object","properties":{"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type","text"]}}},"required":["content"]}}]},"jsonrpc":"2.0","id":1}
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "test_call"}}
{"result":{"content":[{"type":"text","text":"abc"}]},"jsonrpc":"2.0","id":2}
最初に tools/list でツールの一覧を取得し、次に tools/call で test_call ツールを実行して abc という結果を得ています。
tools/list を処理するハンドラが ListToolsRequestSchema、tools/call を処理するハンドラが CallToolRequestSchema として登録されています。
server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => ...);
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => ...);
Claude Code での動作確認
カレントディレクトリに設定ファイル (.mcp.json) を置けば、そのディレクトリで Claude Code を起動して MCP サーバーが実行できます。
{
"mcpServers": {
"test_server": {
"type": "stdio",
"command": "node",
"args": [
"/path/to/index.js"
]
}
}
}
> use test_call
● I'll call the test_call function for you.
⎿ abc
● The test_call function returned "abc".
一見問題ないように見えます。
Gemini CLI での動作確認
.mcp.json と同じ内容を .gemini/settings.json に保存すれば、そのディレクトリで起動した Gemini CLI でも MCP サーバーが実行できます。
しかし use test_call と指示して実行すると、エラーが発生します。
MCP error -32600: Tool test_call has an output schema but did not return structured content
エラーの原因
MCP の仕様では、outputSchema は result ではなく、structuredContent の内容を定義します。Claude Code ではスキーマとは無関係に content の内容を見ますが、Gemini CLI では MCP の仕様に従って structuredContent がスキーマ通りの構造を持つかチェックした結果、エラーが発生します。
structuredContent を返す
スキーマ通りに structuredContent を返せば以下のようになります。
return {
structuredContent: {
content: [
{
type: 'text',
text: 'abc'
}
]
}
};
しかしこの修正を適用して実行しても、Claude Code では結果が得られません。
> use test_call
● The test call completed successfully with no output.
Gemini CLI ではエラーは発生しなくなりますが、やはり結果が得られません。
✦ I have used that tool and it produced no output.
これは、MCP の仕様通りでエラーはなく、ツールの実行自体は成功しているものの、structuredContent の中身を認識・表示できないことを示しています。
content と structuredContent の両方が定義されていれば、content(この例では abc)が結果として認識されます。
return {
content: [
{
type: 'text',
text: 'abc'
}
],
structuredContent: {
content: [
{
type: 'text',
text: 'def'
}
]
}
};
MCP は汎用的な RPC プロトコルであるため、構造化された結果を直接使うようなクライアントを作ることは可能ですが、LLM では文字列を使うため content しか見ないということだと考えられます。
outputSchema を削除
単純に outputSchema を削除してしまうのが簡単です。
const testServerToolDefinition = {
name: 'test_call',
description: 'Returns a test string.',
inputSchema: {
type: 'object',
properties: {},
required: []
}
};
return {
content: [
{
type: 'text',
text: 'abc'
}
]
};
これは Claude Code でも Gemini CLI でも正常に動作します。
まとめ
-
outputSchema の有無: ツール定義に
outputSchemaがある場合、レスポンスはstructuredContentを使用する必要があります -
スキーマの一致:
structuredContentの内容は、定義されたoutputSchemaと一致している必要があります -
実際の動作:
structuredContentが返されても、contentフィールドしか使われません
参考
Discussion