Build MCP Server:TypeScript SDK v2 で MCP サーバーを作る手順
MCP サーバーをゼロから構築(build mcp server):良いツール設計、stdio と Streamable HTTP に対応した TypeScript サーバーの実装、テスト、Claude Code への接続、npm と MCP Registry への公開まで解説します。
MCP サーバーを構築するには、公式 SDK を選び、型付きの入力と分かりやすい説明を持つツールをいくつか登録して、stdio(ローカルクライアント向け)または Streamable HTTP(リモートクライアント向け)で提供します。TypeScript SDK v2 なら約 40 行のコードで済みます。MCP Inspector でテストし、Claude Code や Cursor などのクライアントに追加し、npm と MCP Registry に公開すれば、ほかの人もインストールできるようになります。
このガイドでは、小さいながら実用的なサーバーを最初から最後まで作ります。すべてのコードは @modelcontextprotocol/server 2.3.1(2026 年 10 月時点の安定版)で実際に動作確認しています。このバージョンは 2026-07-28 版 MCP 仕様 を実装しつつ、古いクライアントにも応答します。概念に馴染みがない場合は、先に MCP サーバーとは を読んでください。
最初に知っておくこと
MCP サーバーは AI アプリケーションに 3 種類のものを公開します。
| プリミティブ | 使うかどうかを決めるのは | 典型例 |
|---|---|---|
| Tools(ツール) | モデル | API 呼び出し、データベース検索、ファイル書き込み |
| Resources(リソース) | アプリケーション | ファイルの内容、スキーマ、ドキュメント |
| Prompts(プロンプト) | ユーザー | ユーザーがメニューから選ぶ再利用可能なテンプレート |
ほとんどのサーバーは主にツールで構成されます。ツールとリソースの違いは MCP リソースとツールの違い で解説しています。同じサーバーコードを 2 つのトランスポートで動かせます。
- stdio:クライアントがサーバーを子プロセスとして起動し、stdin と stdout で通信します。ローカルツールのデフォルトです。
- Streamable HTTP:サーバーは複数のクライアントが接続する Web エンドポイント(通常は
/mcp)になります。リモートサーバーはこの方式で提供します。古い HTTP+SSE トランスポートは非推奨です。詳しくは MCP の SSE は非推奨か を参照してください。
ステップ 1:コードを書く前にツールを設計する
モデルに見えるのは、各ツールの名前・説明・入力スキーマだけです。明確なツールは正しく呼ばれ、曖昧なツールは無視されるか、誤った引数で呼ばれます。どの言語でも通用するルールをいくつか挙げます。
- エンドポイントではなくタスクをモデル化する。 必要なフィールドを返す
get-package1 つのほうが、生の API ルートを薄くラップした 3 つのツールより優れています。ツールが少なければトークン消費も減ります。MCP ツールディスカバリー も参照してください。 - 説明はモデルに向けて書く。 ツールが何をするのか、いつ使うのかを書きます。例:「依存関係を推奨またはインストールする前に使ってください。」
- すべての入力に型と制約を付ける。 列挙型、最小値・最大値、説明内の例があれば、ハンドラーが動く前にほとんどの誤った呼び出しを防げます。
- モデルが次の行動に移れるエラーを返す。 「パッケージが見つかりません。スペルを確認してください」ならモデルは再試行できますが、スタックトレースでは無理です。
- 読み取り専用のツールを明示する。
readOnlyHintなどのヒントは、先にユーザーに確認すべきかどうかをクライアントが判断する助けになります。ただしヒントであって、セキュリティ機構ではありません。 - シークレットを引数に含めない。 API キーは環境変数か OAuth から取得し、決してモデルから受け取らないでください。
サンプルサーバーのツールは get-package の 1 つだけです。npm パッケージの最新バージョンと週間ダウンロード数を調べるもので、API キーは不要です。
ステップ 2:プロジェクトをセットアップする
Node.js 20 以降が必要です。
mkdir npm-info && cd npm-info
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod
npm install -D typescript tsx @types/node
mkdir src
v2 では SDK が複数のパッケージに分かれています。サーバー用の @modelcontextprotocol/server、クライアント用の @modelcontextprotocol/client、そして @modelcontextprotocol/node や @modelcontextprotocol/express などのオプションのアダプターです。以前の単一パッケージ @modelcontextprotocol/sdk は v1 系です。アップグレードする場合は 公式の移行ガイド に従ってください。
tsconfig.json を追加します。SDK の README によると、TypeScript 6 以降では "types": ["node"] を明示的に設定する必要があります。
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "build",
"rootDir": "src",
"strict": true,
"types": ["node"],
"skipLibCheck": true
},
"include": ["src"]
}
ステップ 3:サーバーを書く
サーバーは、新しい McpServer を生成するファクトリー関数にまとめます。両方のトランスポートがこのファクトリーを共有します。src/server.ts を作成します。
import { McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
export function createServer(): McpServer {
const server = new McpServer({ name: 'npm-info', version: '1.0.0' });
server.registerTool(
'get-package',
{
title: 'Look up an npm package',
description:
'Get the latest version, description and weekly downloads of an npm package. Use this before recommending or installing a dependency.',
inputSchema: z.object({
name: z
.string()
.min(1)
.describe(
'Exact npm package name, e.g. "zod" or "@modelcontextprotocol/server"'
),
}),
annotations: { readOnlyHint: true, openWorldHint: true },
},
async ({ name }) => {
const meta = await fetch(
`https://registry.npmjs.org/${name.replace('/', '%2F')}/latest`
);
if (!meta.ok) {
return {
content: [
{
type: 'text',
text: `Package "${name}" not found on npm (HTTP ${meta.status}). Check the spelling.`,
},
],
isError: true,
};
}
const pkg = (await meta.json()) as {
version: string;
description?: string;
};
const dl = await fetch(
`https://api.npmjs.org/downloads/point/last-week/${name}`
);
const downloads = dl.ok
? ((await dl.json()) as { downloads: number }).downloads
: undefined;
return {
content: [
{
type: 'text',
text: `${name}@${pkg.version}: ${pkg.description ?? 'no description'}. Weekly downloads: ${downloads ?? 'unknown'}.`,
},
],
};
}
);
return server;
}
書くスキーマは Zod スキーマだけです。SDK はこれをモデルに見せる JSON Schema に変換し、各呼び出しの引数を検証し、ハンドラーの引数の型も推論します。検証に失敗した呼び出しには isError の結果が返り、モデルはそれを読んで修正できます。
ステップ 4:stdio で提供する
src/stdio.ts を作成します。
#!/usr/bin/env node
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import { createServer } from './server.js';
void serveStdio(createServer);
console.error('npm-info MCP server running on stdio');
stdio では stdout がプロトコルそのものを運びます。console.log が 1 つあるだけで JSON-RPC のストリームが壊れるので、ログは console.error で出力してください。
ステップ 5:Streamable HTTP で提供する
リモートエンドポイントにするには、同じファクトリーを createMcpHandler でラップします。これは Web 標準の fetch ハンドラーを返し、リクエストごとに新しいサーバーインスタンスを生成します。Node ではアダプターをインストールし(npm install @modelcontextprotocol/node)、src/http.ts を作成します。
import { createServer as createHttpServer } from 'node:http';
import {
localhostHostValidation,
localhostOriginValidation,
toNodeHandler,
} from '@modelcontextprotocol/node';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { createServer } from './server.js';
const handler = createMcpHandler(createServer);
const nodeHandler = toNodeHandler(handler);
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();
createHttpServer((req, res) => {
if (!validateHost(req, res) || !validateOrigin(req, res)) return;
void nodeHandler(req, res);
}).listen(3000, '127.0.0.1', () =>
console.error('npm-info MCP server on http://127.0.0.1:3000/mcp')
);
ハンドラー自体は Host ヘッダーも Origin ヘッダーもトークンもチェックしないため、前段の 2 つの localhost ガードで DNS リバインディング攻撃を防ぎます。npx tsx src/http.ts を実行し、curl で確認します。
curl -s -X POST http://127.0.0.1:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
get-package の定義を含む SSE の message イベントが 1 つ返ってきます。ハンドラーはステートレスなので、どんなロードバランサーの背後でも水平にスケールします。OAuth を前段に置いて公開ホスティングする方法は MCP サーバーのデプロイ方法 で解説しています。
ステップ 6:MCP Inspector でテストする
MCP Inspector は公式のテストクライアントです。stdio のエントリーポイントに対して Web UI を開きます。
npx @modelcontextprotocol/inspector npx tsx src/stdio.ts
Connect をクリックし、Tools を開いて zod を指定して get-package を実行します。続いて無効な名前を試し、エラー時の挙動を確認します。CI では CLI モードを使います。
npx @modelcontextprotocol/inspector --cli npx tsx src/stdio.ts \
--method tools/call --tool-name get-package --tool-arg name=@modelcontextprotocol/server
ステップ 7:Claude Code と Cursor に接続する
ローカルクライアントに必要なのは起動コマンド 1 つだけです。Claude Code では、起動コマンドをすべて -- の後ろに置きます。
claude mcp add npm-info -- npx tsx /absolute/path/to/npm-info/src/stdio.ts
セッション内で /mcp を実行し、接続されていることを確認します。Cursor では .cursor/mcp.json に追加します。
{
"mcpServers": {
"npm-info": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/npm-info/src/stdio.ts"]
}
}
}
HTTP 版を使う場合は、クライアントに URL を指定します。例:claude mcp add --transport http npm-info http://127.0.0.1:3000/mcp。クライアントにツールが表示されない場合は、まず起動コマンドを手で実行してください。stderr に 1 行出力して待機するのが正常です。クラッシュする場合や、stdout に何かが出力される場合はそれが原因です。
ステップ 8:npm と MCP Registry に公開する
誰でも npx でサーバーを実行できるようにするには、コンパイルして bin エントリーを追加します。package.json は次のとおりです。
{
"name": "@your-scope/npm-info-mcp",
"version": "1.0.0",
"type": "module",
"bin": { "npm-info-mcp": "build/stdio.js" },
"files": ["build"],
"mcpName": "io.github.your-username/npm-info",
"scripts": { "build": "tsc" }
}
npm run build と npm publish --access public を実行します。これでユーザーは npx -y @your-scope/npm-info-mcp で起動できます。
公式 MCP Registry はメタデータだけを保存し、mcpName フィールドで npm パッケージの所有者であることを検証します。Registry のクイックスタート に沿って進めます。
brew install mcp-publisher # or download a release binary
mcp-publisher init # generates server.json
mcp-publisher login github # namespace io.github.your-username/*
mcp-publisher publish
server.json の name は mcpName と一致させる必要があり、description は 100 文字までです。リモート専用のサーバーは npm を完全に省略し、代わりにタイプが streamable-http の remotes エントリーを記載できます。
ステップ 9:MCP.so に掲載する
最後に、サーバーを MCP.so に登録 して、MCP サーバーディレクトリ を見ている人が見つけられるようにします。インストールコマンド、必要な環境変数、ツール一覧を書いた分かりやすい README を用意してください。ユーザーが サーバーが信頼できるか確認する ときに読むのがこの README です。
ほかの言語
同じ設計ルールとトランスポートは、すべての公式 SDK に当てはまります。最も広く使われているのは TypeScript と Python です。C#、Go、Rust、Ruby も Tier 1 で、Java、Swift、PHP、Kotlin も利用できます(SDK 一覧)。Python については、公式 SDK の MCPServer クラスと uv を使う Python MCP サーバーガイド を参照してください。
次のステップ
- 設計のヒントを得るために、MCP サーバーディレクトリ で実際のサーバーを見てみましょう。GitHub MCP サーバー と Context7 は、目的が絞られ説明も明確なツールの良い例です。
- 公開前に MCP セキュリティのベストプラクティス でサーバーを堅牢にしましょう。
- 共有する準備ができたら、MCP.so に登録 してください。
よくある質問
自分で MCP サーバーを作るにはどうすればいいですか?
- TypeScript や Python などの公式 SDK を選び、明確な説明と型付きの入力を持つツールをいくつか登録して、ローカル向けには stdio、リモート向けには Streamable HTTP で提供します。その後 MCP Inspector でテストし、Claude Code や Cursor などのクライアントに追加します。
MCP サーバーを作るのに最適な言語は何ですか?
- 最も広く使われ、サンプルも多いのは TypeScript と Python の公式 SDK です。C#、Go、Rust、Ruby も Tier 1 の公式 SDK なので、通常は既存のコードやチームが使っている言語を選ぶのが最善です。
MCP サーバーは stdio と Streamable HTTP のどちらを使うべきですか?
- サーバーがユーザーのマシンで動き、クライアントが子プロセスとして起動する場合は stdio を使います。多くのユーザーが接続するエンドポイントをホストする場合は Streamable HTTP を使い、新しいサーバーでは非推奨の HTTP+SSE トランスポートは避けてください。
MCP サーバーをほかの人に使ってもらうにはどう公開しますか?
- パッケージを npm または PyPI に公開してユーザーが npx や uvx で実行できるようにし、mcp-publisher CLI で公式 MCP Registry にメタデータを登録します。MCP.so などのディレクトリに登録すれば、さらに見つけてもらいやすくなります。