作る
| したいこと | やり方 | 詳しく |
| SDK を入れる | npm install @modelcontextprotocol/sdk | 03 |
| サーバーを作る | new McpServer({ name, version }) | 03 |
| stdio に繋ぐ | await server.connect(new StdioServerTransport()) | 03 |
| ツールを足す | server.registerTool(name, config, handler) | 03 |
| 読ませるデータを足す | server.registerResource(name, uri, config, handler) | 05 |
| URI に変数を入れる | new ResourceTemplate('note://{date}', { list: undefined }) | 05 |
| 定型の指示を足す | server.registerPrompt(name, config, handler) | 05 |
| SDK を使わずに書く | readline で 1 行ずつ読み、process.stdout.write で返す | 02 |
| C# で書く | csc.exe + System.Web.Extensions.dll | A3 |
ひな形
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
server.registerTool(
'my_tool',
{
title: '表示名',
description: 'いつ使うかを書く。モデルはこれを読んで呼ぶ',
inputSchema: { arg: z.string().describe('引数の説明') },
},
async ({ arg }) => ({ content: [{ type: 'text', text: `受け取った: ${arg}` }] }),
);
await server.connect(new StdioServerTransport());
登録する・確かめる
| したいこと | やり方 |
| Claude Code に登録 | claude mcp add --scope project 名前 -- node server.mjs |
| Codex に登録 | codex mcp add 名前 -- node server.mjs |
| Antigravity に登録 | antigravity --add-mcp '{"name":"名前","command":"node","args":["server.mjs"]}' |
| HTTP のサーバーを登録 | claude mcp add --transport http 名前 http://localhost:3333/mcp |
| 環境変数を渡す | claude mcp add 名前 -e KEY=値 -- node server.mjs |
| 一覧を見る | claude mcp list / codex mcp list |
| 詳細を見る | claude mcp get 名前 |
| 消す | claude mcp remove 名前 / codex mcp remove 名前 |
| 設定を汚さずに試す | codex exec -c 'mcp_servers.名前={command="node", args=["server.mjs"]}' "..." |
手で叩く
@(
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"手","version":"1.0"}}}'
'{"jsonrpc":"2.0","method":"notifications/initialized"}'
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
) -join "`n" | node server.mjs
版を確かめる
| 対象 | コマンド |
| Node.js | node --version |
| SDK | npm view @modelcontextprotocol/sdk version |
| Claude Code | claude --version |
| Codex | codex --version |
| Antigravity | antigravity --version |
| ホストが喋る MCP の版 | 観測用サーバーのログを見る |
入力と出力
入力の形(zod)
| したいこと | 書き方 |
| 文字列 | z.string() |
| 空を禁じる | z.string().min(1) |
| 整数 | z.number().int() |
| 範囲 | z.number().min(1).max(50) |
| 真偽 | z.boolean() |
| 省略できる | z.string().optional() |
| 既定値 | z.number().default(10) |
| 選択肢 | z.enum(['a', 'b']) |
| 配列 | z.array(z.string()) |
| 説明を足す | .describe('説明') |
| 引数なし | inputSchema: {} |
返し方
| したいこと | 書き方 |
| 文字列を返す | { content: [{ type: 'text', text: '...' }] } |
| 失敗を返す | { content: [...], isError: true } |
| 複数行 | text の中で \n で繋ぐ |
| Resource を返す | { contents: [{ uri: uri.href, text: '...' }] } |
| Prompt を返す | { messages: [{ role: 'user', content: { type: 'text', text: '...' } }] } |
失敗は isError で返します。例外を投げると JSON-RPC のエラーになり、モデルは理由を読めません。詳しくは 05 章。
伝える
| したいこと | やり方 | 詳しく |
| 進捗を送る | extra.sendNotification({ method: 'notifications/progress', params: {...} }) | 08 |
| 進捗の宛先を得る | extra._meta?.progressToken | 08 |
| 利用者に尋ねる | extra.sendRequest({ method: 'elicitation/create', params: {...} }, schema) | 08 |
| 画面に出す | PowerShell 5.1 経由でトースト | 08 |
| ログを出す | console.error(...)(stdio のとき console.log は禁止) | 02 |
| 長い処理を投げる | server.experimental.tasks.registerToolTask(...) | 09 |
| ツールの一覧が変わったと知らせる | server.sendToolListChanged() | — |
進捗を送る(そのまま使える形)
async ({ seconds }, extra) => {
const token = extra._meta?.progressToken;
for (let i = 1; i <= seconds; i++) {
await sleep(1000);
if (token === undefined) continue;
await extra.sendNotification({
method: 'notifications/progress',
params: { progressToken: token, progress: i, total: seconds, message: `${i}/${seconds}` },
});
}
return { content: [{ type: 'text', text: '終わった' }] };
}
できないこと。何も呼ばれていないときに、サーバーから会話へ割り込むことはできません。人に知らせたいなら OS の通知など MCP の外の手を使います。08 章に線が引いてあります。
運ぶ
| したいこと | やり方 | 詳しく |
| HTTP にする | new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }) | 07 |
| 同じ PC に限る | http.listen(PORT, '127.0.0.1') | 10 |
| 1 本を共有する | HTTP で常駐 + stdio ブリッジ | 10 |
| HTTP を手で叩く | Accept: application/json, text/event-stream を忘れない | 07 |
| SSE の本文を解く | data: で始まる行を取り出す | 07 |
テストから呼ぶ
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
const transport = new StdioClientTransport({
command: process.execPath,
args: ['server.mjs'],
env: { ...process.env, MEMO_FILE: '...' },
stderr: 'ignore',
});
const client = new Client({ name: 'tests', version: '1.0.0' }, { capabilities: {} });
await client.connect(transport);
const { tools } = await client.listTools();
const result = await client.callTool({ name: 'add', arguments: { a: 2, b: 3 } });
await client.close();
| クライアント側の操作 | 書き方 |
| ツールの一覧 | client.listTools() |
| ツールを呼ぶ | client.callTool({ name, arguments }) |
| 進捗を受ける | client.callTool(params, undefined, { onprogress }) |
| Resource を読む | client.readResource({ uri }) |
| Prompt を取る | client.getPrompt({ name, arguments }) |
| 尋ねられたら答える | client.setRequestHandler(ElicitRequestSchema, handler) |
| 相手の名乗りを見る | client.getServerVersion() |