MCPサーバーの作り方: TypeScript SDKで自作ツールをClaude Codeにつなぐ
MCP(Model Context Protocol)サーバーをTypeScript SDKで自作する手順。tools/resources公開、stdio、claude mcp addでの登録まで動くコード付き。
「天気APIをClaudeから直接叩きたいだけなのに、なんでこんなに情報が散らばってるんだ」
最初に自分のMCPサーバーを書こうとした夜、僕はタブを20個くらい開いて固まっていました。仕様書、SDKのREADME、誰かのブログ、半年前のサンプル。書いてあるコードがバラバラで、どれが今でも動くのか分からない。結局、公式のサンプルをそのまま写経して、やっと「あ、これだけか」と力が抜けたのを覚えています。
正直に言うと、MCPサーバーの最小構成は驚くほど小さいです。50行も書けば、Claude Codeから呼べる自作ツールができあがる。難しいのは概念のほうで、コードのほうじゃない。だから今日は、まず概念を一気にほどいてから、コピペで動く最小サーバーを一緒に作ります。
この記事の要点
- MCP(Model Context Protocol)は、Claudeに外部ツールやデータを標準の形でつなぐ仕組み。サーバーが「道具」を出し、Claude Codeが「使う側(クライアント)」になる。
- サーバーが公開できるのは主に3種類。tools(関数)、resources(読めるデータ)、prompts(定型文)。まずはtoolsだけ覚えれば十分。
- つなぎ方(トランスポート)は2つ。ローカルは
stdio、リモートはHTTP(古いSSEは互換用)。自作の入門はstdio一択。 - 実装はTypeScript SDK(
@modelcontextprotocol/sdk)が一番楽。McpServerを作ってregisterToolしてconnectするだけ。 - Claude Codeへの登録は
claude mcp add 名前 -- node build/index.jsの1行。下のコードはそのまま動く。
そもそもMCPって何をつなぐもの?
MCPはModel Context Protocolの略で、ひとことで言うと「AIに外部の道具とデータをつなぐための共通プラグ」です。
USB-Cを思い出してください。昔は機器ごとに専用ケーブルが必要でしたよね。MCPはあれのAI版です。GitHub用、Notion用、社内DB用とバラバラに作り込むのではなく、同じ形の差し込み口を決めておく。そうすれば、一度サーバーを書けばClaude CodeでもClaude Desktopでも、対応した他のクライアントでも使い回せます。
登場人物は2人だけです。
- MCPサーバー: 道具を提供する側。あなたが今日作るのはこれ。
- MCPクライアント(ホスト): 道具を使う側。Claude CodeやClaude Desktopがこれにあたる。
Claude Codeは「賢いチャット」に見えて、その裏では何種類ものMCPサーバーから道具を借りて動いています。自作サーバーを足すというのは、Claudeの手札にあなた専用のカードを1枚追加することだと思ってください。
サーバーが出せる3つのもの
MCPサーバーがクライアントに公開できるものは、公式仕様では主に3種類に分かれます。最初に全部覚える必要はありませんが、地図として頭に入れておくと迷いません。
| 種類 | 役割 | 身近な例え | いつ使うか |
|---|---|---|---|
| tools | Claudeが呼べる関数。実行して結果を返す | 電卓のボタン | 計算、API呼び出し、検索、書き込み |
| resources | Claudeが読めるデータ。URIで指定する | 本棚のファイル | 設定値、ドキュメント、ログの中身 |
| prompts | 使い回せる定型プロンプト | メールの定型文 | 「この形でレビューして」を固定化 |
toolsとresourcesの違いでよくつまずくので、ここだけ補足します。**toolsは「動詞」、resourcesは「名詞」**です。「為替レートを取得する(動作)」はtool、「設定ファイルの中身(モノ)」はresource。迷ったらtoolにしておけば、たいてい動きます。今日のメインもtoolsです。
つなぎ方は2種類: stdioとHTTP
サーバーとクライアントは「トランスポート」という通信路でやり取りします。種類は実質2つです。
| トランスポート | 動く場所 | 向いている用途 | 注意点 |
|---|---|---|---|
stdio | 自分のPC内 | ローカルの自作ツール、npx起動 | 標準出力にログを出すと壊れる(後述) |
HTTP | ネット越し | リモートサーバー、SaaS連携 | OAuthや認証ヘッダーの設計が要る |
stdioは、サーバーをプロセスとして起動して、標準入力・標準出力でJSONをやり取りするだけのシンプルな方式です。設定もコードも一番少ない。だから自作入門はstdioで始めるのが鉄則です。リモート公開やチーム配布の話が出てきたらHTTPを考える、くらいでちょうどいいです。
なお、以前はSSE(Server-Sent Events)というトランスポートもよく使われていましたが、今はHTTP(Streamable HTTP)に置き換わりました。既存のSSEサーバーにつなぐとき以外は、新規でSSEを選ぶ理由はありません。
コピペで動く最小MCPサーバー
ここからが本番です。「為替レートっぽい数字を返す」だけの小さなサーバーを、TypeScriptで作ります。実際のAPIは叩かず固定値を返すので、APIキーもネット接続も不要。まず「Claude Codeから自作ツールが呼べた」という成功体験を取りに行きます。
Node.js(18以上)が入っていれば動きます。まず土台を用意します。
mkdir mcp-rate-demo && cd mcp-rate-demo
npm init -y
npm install @modelcontextprotocol/sdk zod@3
npm install -D typescript @types/node
mkdir src
package.json に2行足します。ESモジュールとして動かすためです。
{
"type": "module",
"scripts": {
"build": "tsc"
}
}
tsconfig.json を作ります。出力先を build にするだけの最小設定です。
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
そして本体、src/index.ts です。やることは3ステップだけ。(1)サーバーを作る → (2)道具を登録する → (3)stdioでつなぐ。コメントで対応させてあるので、上から目で追ってください。
// (1) 必要なものを読み込む
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// サーバー本体を作る。name はクライアント側に表示される識別名
const server = new McpServer({
name: "rate-demo",
version: "1.0.0",
});
// デモ用の固定レート(本来はここでAPIを叩く)
const RATES: Record<string, number> = {
USDJPY: 156.4,
EURJPY: 168.2,
GBPJPY: 198.7,
};
// (2) 道具(tool)を登録する
server.registerTool(
"get_rate",
{
description: "通貨ペアの為替レートを返す。例: USDJPY",
// inputSchema は zod で書く。Claudeはこの形に従って引数を渡してくる
inputSchema: {
pair: z
.string()
.describe("通貨ペア。USDJPY / EURJPY / GBPJPY のいずれか"),
},
},
// 実際に呼ばれたときの処理。content 配列で結果を返すのが約束事
async ({ pair }) => {
const key = pair.toUpperCase();
const rate = RATES[key];
if (rate === undefined) {
// エラーも「次の一手」が分かる文章で返すのがコツ
return {
content: [
{
type: "text",
text: `${key} は未対応です。USDJPY / EURJPY / GBPJPY から選んでください。`,
},
],
};
}
return {
content: [{ type: "text", text: `${key} の現在レートは ${rate} です。` }],
};
},
);
// (3) stdio でクライアントとつなぐ
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// 重要: stdio では console.log は厳禁。ログは必ず console.error(標準エラー)へ
console.error("rate-demo MCP server: stdio で起動しました");
}
main().catch((err) => {
console.error("起動に失敗:", err);
process.exit(1);
});
ビルドします。
npm run build
build/index.js ができていれば、サーバー側は完成です。たったこれだけ。McpServerを作り、registerToolで道具を1つ足し、StdioServerTransportでつないだ。MCPサーバーの骨格はずっとこの形です。あとはtoolを増やしたり、registerToolの中身を本物のAPI呼び出しに差し替えたりするだけで育っていきます。
Claude Codeに登録する(claude mcp add)
サーバーができたら、Claude Codeに「この道具を使えるよ」と教えます。コマンドは1行です。
claude mcp add rate-demo -- node /絶対パス/mcp-rate-demo/build/index.js
-- のうしろが「サーバーを起動するコマンド」です。ここは絶対パスにしてください。相対パスだと、別フォルダでClaude Codeを起動したときに見つからず、地味にハマります。
登録できたか確認します。
claude mcp list
claude mcp get rate-demo
Claude Codeのセッションの中なら、/mcp で接続状態と公開されているtoolsを見られます。
/mcp
ここで rate-demo が connected になっていて、get_rate が見えていれば成功です。あとはClaudeに普通に頼むだけ。「USDJPYのレートを教えて」と書けば、Claude Codeが裏で get_rate を呼んで、156.4と返してきます。自分の書いた関数がClaudeの手札に入った瞬間です。
Windowsで
npx起動のサーバーを登録する場合だけ、nodeを直接呼ばずにcmd /c npx ...の形が必要になることがあります。今回のようにnode build/index.jsを直接指定するなら、その回避策は要りません。スコープ(--scope localなど)の使い分けや、リモートMCPのOAuth確認は別記事にまとめています。権限まわりは Claude Code権限設定ガイド も合わせて読むと、事故が減ります。
resourcesも一応わかっておく
toolsが動いたら、resourcesも30秒で足せます。resourcesは「Claudeが読めるデータ」でしたね。たとえばサーバーの設定値を読ませたいなら、こう書きます。先ほどの registerTool の下に追記するイメージです。
// resource: Claude が「読む」データ。第2引数は URI
server.registerResource(
"supported-pairs",
"rate://config/pairs",
{
title: "対応している通貨ペア",
mimeType: "text/plain",
},
async (uri) => ({
contents: [
{
uri: uri.href,
text: "USDJPY, EURJPY, GBPJPY",
},
],
}),
);
これで rate://config/pairs というデータをClaudeが読めるようになります。toolsとの違いは、resourcesは基本「読むだけ」で副作用がないこと。設定や参照ドキュメントはresource、計算や実行はtool、と切り分けると設計が散らかりません。最初はtoolsだけでいいですが、「ただ参照させたいデータ」が出てきたら思い出してください。
僕がMCPサーバーでやらかした失敗3つ
最小構成は簡単でも、最初の数本は事故りました。同じ穴に落ちないでほしいので、正直に書きます。
ひとつ目は、console.log でデバッグして自滅したこと。stdioサーバーは標準出力をJSON-RPCの通信に使っています。そこに console.log("ここ通った") を1行入れた瞬間、通信が壊れて「サーバーが応答しない」とだけ言われる。原因が分からず1時間溶かしました。ログは必ず console.error(標準エラー)へ。上のコードでそうしているのはこのためです。
ふたつ目は、ビルドし忘れたまま登録したこと。TypeScriptは npm run build しないと build/index.js が更新されません。コードを直したのに挙動が変わらず、「SDKのバグか?」と疑った犯人は、ただの古いビルドでした。直したら必ずビルド。地味ですが一番やります。
みっつ目は、toolのdescriptionを手抜きしたこと。description: "get rate" みたいに雑に書くと、Claudeがいつそのtoolを使えばいいか分からず、呼んでくれなかったり変な引数を渡してきたりします。descriptionは「Claude向けの取扱説明書」です。「通貨ペア(例: USDJPY)を渡すと現在レートを返す」くらい具体的に書くと、ぐっと賢く使ってくれます。
安全に育てるための順番
最小サーバーが動いたら、つい「DBに書き込むtool」や「ファイルを消すtool」を足したくなります。でもそこは焦らない。MCPで外部に「書き込む」道具を渡すのは、Claudeに実行権限を渡すことと同じです。
おすすめの順番はいつも同じです。
- 読み取り専用のtoolから始める(取得・検索・集計だけ)。
- 書き込み系を足すときは、危険操作(削除・本番DB・課金)は確認待ちにする設計にする。
- 外部から取ってきたテキストを無条件に信じない。ドキュメントにプロンプトインジェクションが混ざっていることがある。
このあたりはMCP単体ではなく、Claude Code全体の安全設計の話になります。実行前に危険な操作を機械的に止める仕組みは Claude Code Hooks実践ガイド に、外部入力を信じすぎない考え方は Claude Codeセキュリティ実践ガイド にまとめています。「便利なtool」を1つ足すたびに、「これ、暴走したら何が壊れる?」を一度だけ自問する。それだけで、運用に乗ったあとの事故がかなり減ります。
よくある質問
Q. MCPサーバーって、PythonでもJavaScriptでも作れますか?
作れます。公式SDKはPython、TypeScript、Java、Kotlin、C#、Rubyなどで提供されています。ただ、Claude Codeの周辺ツールとの相性や情報量を考えると、入門はTypeScriptかPythonが楽です。この記事はTypeScript SDK(@modelcontextprotocol/sdk)を使っています。
Q. claude mcp add で登録したのに /mcp に出てきません。
まず claude mcp list で登録自体ができているか確認してください。次に、起動コマンドのパスが絶対パスか、npm run build 済みか、stdioサーバーで console.log を使っていないか、の3点を疑います。僕がハマった失敗はだいたいこのどれかでした。
Q. toolsとresourcesとprompts、結局どれを作ればいい? 最初はtoolsだけで十分です。「何かを実行して結果を返す」需要が一番多いからです。参照専用のデータを読ませたくなったらresources、決まった指示を使い回したくなったらpromptsを足す、という順で問題ありません。
Q. stdioとHTTP、自作ならどっち?
自分のPCで自分用に使うならstdio一択です。設定もコードも少なく、ハマりどころが少ない。チームに配ったりサーバーとして常駐させたくなったら、HTTP(Streamable HTTP)と認証の設計を考え始めるタイミングです。
Q. 既存の公開MCPサーバー(GitHubやNotion)を使うのと、自作はどう違う?
公開サーバーは「よくある連携」を即使えるのが利点です。自作が効くのは、社内APIや独自の業務ロジックなど、自分のところにしかないデータや処理をClaudeに渡したいときです。今日の get_rate を、自社の在庫APIや顧客ステータスAPIに差し替える、と考えると用途がイメージしやすいはずです。
実際に作ってみた結果
冒頭で20タブ開いて固まっていた僕ですが、いざ手を動かしたら、最初のサーバーは本当に50行ほどで動きました。拍子抜けするくらいです。
いま振り返ると、MCPサーバー作りで難しいのは「コードの量」ではなく「概念の整理」と「小さく確かめる順番」でした。toolsとresourcesの違いを腹落ちさせる。stdioで最小構成を1つ通す。console.errorでログを出す。ビルドを忘れない。この4つさえ押さえれば、あとは registerTool の中身を本物のAPIに差し替えていくだけで、自分の業務に合った道具が増えていきます。
最初の一歩としては、上の get_rate をそのまま動かして、Claude Codeから呼べた感触を確かめるのが一番です。動いたら、その関数の中身を、あなたが毎日叩いているAPIに置き換えてみてください。そこからが本当のスタートです。MCPやClaude Codeを業務にどう組み込むかをまとめて学びたい人は、教材一覧 ものぞいてみてください。
公式の最新仕様や他言語のサンプルは、Model Context Protocol 公式ドキュメント で確認できます。この記事のコードは、公式のTypeScriptクイックスタート(McpServer、registerTool、StdioServerTransport)の構成に合わせて整理しています。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeのチーム利用でコストが読めない時に作る予算ログ
チーム導入前に、誰が何に使い、どの成果が出たかを見える化する予算ログの作り方。
コミット前の3分チェック: Claude Codeが触った範囲を確認してから確定する
Claude Codeが勝手に広げた変更を、コミット前に3分で見抜く確認手順。差分の範囲、検証ログ、ステージするファイルの絞り込みを順番に解説します。
Claude Codeをチーム導入する前に作る「リスク台帳」の中身
Claude Codeを個人実験で終わらせずチーム導入するための、権限・CI・公開の事故を防ぐリスク台帳の作り方を実例とコードで解説します。