MCPサーバーの自作|TypeScriptで作る5手順
MCPサーバーの自作|TypeScriptで作る5手順
MCPサーバーの自作は、2026年時点ではもはや重い仕様読解ではありません。公式のTypeScript SDK @modelcontextprotocol/sdk がJSON-RPCの面倒を吸収するので、書くのは実質的にNode.jsの関数とZodのスキーマだけで、ツール1本なら30分から1時間で動きます。
MCPサーバーの自作は、2026年時点ではもはや重い仕様読解ではありません。
公式のTypeScript SDK @modelcontextprotocol/sdk がJSON-RPCの面倒を吸収するので、書くのは実質的にNode.jsの関数とZodのスキーマだけで、ツール1本なら30分から1時間で動きます。
既存のMCPサーバーを5本ほどつないでも社内の在庫DBだけ届かず、結局30分で自作した、という拍子抜けするほどの軽さがそのまま入口になるでしょう。
この記事では、初期化から registerTool、トランスポート接続、Claude Codeへの登録、切り分けまでの5手順を通して、自作ツールがClaude Codeとの会話から呼ばれる状態を最短で作ります。
ただし、自作MCPサーバーで最も多い事故は実装ミスではなく console.log によるstdout汚染です。
stdioトランスポートは標準入出力そのものが通信路なので、ログを1行出しただけでJSON-RPCが壊れ、接続が即死する。
動かした後の分岐も先に決めておくと無駄がありません。
ローカル専用ならstdio、チーム共有ならStreamable HTTP、さらにlocal・project・userのスコープをどう使い分けるかまで最初に押さえておけば、作り直しを避けられます。
「MCPサーバーの自作」で何が変わるか
MCPサーバーの自作で変わるのは、Claude CodeのようなLLMアプリに、自分の業務データや処理を会話のまま接続できる点です。
ホスト、クライアント、サーバーの3構成で責務が分かれているため、実装者が向き合うのは外側のサーバーだけで済みます。
だからこそ、思ったより重い基盤づくりにはなりません。
既存サーバーで足りるケース/自作が必要になるケース
既存の公開サーバーで足りるなら、自作は不要です。
GitHubやSlackのように公式サーバーがある対象は、そのままつないだ方が速く、運用も軽いでしょう。
実際に公開サーバーを一通り試したうえで、社内の在庫管理DBに繋ぐものだけが見つからず、自作に踏み切ったことがあります。
探す時間より書く時間の方が短いと判断できた瞬間でした。
自作が必要になる境界線は、公開サーバーが存在しない対象に触りたいときに限られます。
社内API、社内DB、自作スクリプト、独自フォーマットのファイルは、その典型です。
ここでは「Claudeが自分のデータや処理を直接呼べるようになる」ことが本質になります。
コピペで文脈を渡していた作業が関数呼び出しに置き換わり、手作業の往復が消えるからです。
Tools・Resources・Promptsの3機能と使い分け
サーバーが提供できる機能はTools、Resources、Promptsの3種類です。
Toolsは関数実行、Resourcesはデータ読み取り、Promptsはテンプレート提供と考えると整理しやすいでしょう。
最初に作るべきはTools一択です。
ResourcesとPromptsは、必要になってから足せば十分です。
最初から3つ全部を実装しようとすると、完成が遠のきます。
実際、Tools・Resources・Promptsを全部入れようとして2日溶かしたあと、結局Toolsだけで十分だったと気づいたことがあります。
機能を絞るほど設計が単純になり、MCPサーバーの価値が早く見えるのです。
descriptionも機能説明ではなく発火条件を書くと、Claudeがどのツールを使うべきか判断しやすくなります。
表にすると、役割の違いはかなり明確です。
| 機能 | 役割 | 初回実装 | 向いている用途 |
|---|---|---|---|
| Tools | 関数を実行する | 必須 | 在庫照会、更新、集計 |
| Resources | ファイルやデータを読む | 後回し可 | ログ参照、文書読み取り |
| Prompts | テンプレートを渡す | 後回し可 | 定型指示、繰り返し依頼 |
ツール1本なら30分、実用サーバーで2〜3時間
2026年時点のMCPサーバー自作は、プロトコル仕様を読み込む重作業ではありません。
公式のTypeScript SDK @modelcontextprotocol/sdk がJSON-RPC通信層を隠蔽するため、書くものは実質的に関数本体とZodの入力スキーマです。
npmで週間15万ダウンロード以上の実績があり、サンプルや詰まりどころも十分に蓄積されています。
だから、ツール1つのシンプルなサーバーなら30分〜1時間で形になります。
実装の流れも素直です。
プロジェクトを初期化し、new McpServer({name, version}) でインスタンスを作り、registerTool にツール名、description、inputSchema、ハンドラを登録します。
そのあと StdioServerTransport をつなぎ、ビルドして起動確認すればよいだけです。
ハンドラの戻り値は {content: [{type: 'text', text: ...}]} 形式で包む必要があり、文字列をそのまま返すと通りません。
ここを押さえるだけで、最初の一歩は十分です。
実用段階でも、ファイル操作やDB連携を含むサーバーで2〜3時間が目安になります。
Claude Codeへの接続は .mcp.json に {"command": "node", "args": ["dist/index.js"]} を書くか、claude mcp add で済みますし、スコープもlocal、project、userの3種しかありません。
優先順位はlocal、project、userの順です。
もっとも、stdout汚染だけは注意が要ります。console.log 1行でJSON-RPCが壊れるため、ログはstderrへ逃がすのが基本です。
MCP Inspectorで単体検証し、次にパスやビルド鮮度、スコープを確認する流れにすると切り分けが速いでしょう。
自作に必要な環境と前提知識
MCPサーバーの自作は、Node.jsと公式SDK @modelcontextprotocol/sdk を入れるところから始まります。
TypeScriptで書くなら tsconfig とビルド環境も加わりますが、開発の中心はあくまで関数と入力スキーマです。
SDKがJSON-RPCの層を隠蔽するため、プロトコル仕様書を読み込む負担は小さく、実装の見通しも立てやすくなっています。
Node.js・TypeScript・公式SDKの3点セット
必要な環境はかなり絞れます。
Node.js、公式SDK @modelcontextprotocol/sdk、そしてTypeScriptで書く場合に限って tsconfig とビルド環境があれば足ります。
npmで週間15万ダウンロード以上の利用実績があるので、サンプルやトラブル事例が豊富で、詰まっても検索の手がかりを見つけやすいのが強みです。
前提知識も重くありません。
Node.jsで非同期関数が書けて、型定義に抵抗がなければ十分で、実際には「何を返す関数か」と「その入力をどう受けるか」を整える作業になるからです。
Claude Codeを使う開発者なら既にNode環境があることも多く、MCP用に新しい土台を作り直す必要はありません。
Pythonにも公式SDKがありますが、既存資産がPython側に寄っているなら、無理にTypeScriptへ寄せず接続先に合わせて選ぶのが自然でしょう。
入力スキーマはZodで定義する
Zodで { a: z.number(), b: z.number() } のように入力スキーマを書いておくと、SDKがそれをツール定義のスキーマへ変換し、実行時のバリデーションまで担ってくれます。
ここが手書きの引数処理と違う点で、型定義とバリデーションを別々に保守する二重管理が消えるのです。
小さな差に見えて、後で効いてきます。
Zodを後回しにして any で通そうとすると、Claude に引数の形が安定して伝わらず、ツール呼び出しがぶれます。
実装が動いているように見えても、呼び出し側が期待する名前や型が曖昧だと、同じツールでも成功したり失敗したりする。
スキーマは飾りではなく契約であり、そこを固めるとサーバー全体の挙動が一気に読みやすくなります。
手を抜くと後で手戻りになる、という話です。
v1系とv2系のサンプルが混在している問題
つまずきやすいのはSDKのバージョン差です。
v2系は2026-07-28版の仕様に対応していますが、Web上のサンプルはv1系とv2系が混在しており、同じ書き方をそのまま貼るとimportエラーになります。
実際にv1系の例をv2系の環境へコピペして小一時間ほど溶かしたことがあり、そこから最初に package.json のバージョンを見る癖がつきました。
見分け方は @modelcontextprotocol/sdk/server/mcp.js 系のimportパスを確認することです。
ここが合っていれば少なくとも入口は同じ系統に乗っていますし、逆に違っていれば別世代のサンプルを踏んでいる可能性が高い。
MCPは便利ですが、サンプルの新旧差をまたいでしまうと単純な構文エラーで時間を失います。
だからこそ、インストールしたバージョンの公式ドキュメントに合わせて実装するのが近道になるでしょう。
TypeScriptでMCPサーバーを実装する5つの手順
TypeScriptでMCPサーバーを組む流れは、まず土台を固めてからツール登録と接続に進むのが最短です。
npmプロジェクトを作り、@modelcontextprotocol/sdk と zod を入れ、ビルド先を dist に決めておけば、Claude Code 側の設定もぶれません。
そこで McpServer を立て、registerTool と StdioServerTransport をつなげるだけで、最小構成のサーバーが動きます。
Step1-2: プロジェクト初期化とサーバーインスタンス生成
最初に npm プロジェクトを初期化し、@modelcontextprotocol/sdk と zod を入れ、TypeScript の出力先を dist に設定します。
ここで出力パスを先に固定しておくのは、あとから Claude Code 側の設定に書き込むためです。
構成が固まっていない段階で進めると、ビルド結果の置き場所を変えるたびに設定を書き直すことになり、手戻りが増えます。
次に new McpServer({ name, version }) でサーバーインスタンスを生成します。name は Claude Code 側で表示される識別子になるので、途中で変えると設定全体に影響が及ぶのが厄介です。
だからこそ、この段階で名前を確定させておくのが筋でしょう。
Step3: registerToolでツールを登録する
本体は registerTool です。
ツール名、description、inputSchema(Zod)、非同期ハンドラの4つを渡し、入力の形と実行ロジックをひとまとめに定義します。
ハンドラの戻り値は必ず { content: [{ type: 'text', text: ... }] } にする必要があり、文字列をそのまま返しても通りません。
初回実装でそこを外し、Claude 側が結果を受け取れず沈黙したことがあったが、content 配列で包む形に直した途端に一発で通った。
ハマりどころはそこです。
inputSchema を Zod で書く利点も大きいです。
受け取る引数の形が曖昧だと、ツールが増えたときに判定ロジックが散らかりますが、スキーマがあれば失敗箇所を早い段階で切り分けられます。
ツール追加はこの registerTool を増やすだけでよく、構造そのものを変える必要はありません。
Step4-5: トランスポート接続とビルド・起動確認
ローカル実行では new StdioServerTransport() を生成し、await server.connect(transport) に渡します。
標準入出力がそのまま通信路になるので、ネットワーク設定もポート番号も要りません。
接続形態が単純なぶん、まずは最小構成を確実に動かしやすいのが利点です。
コードの骨格はここで完成します。
ビルドして dist/index.js を生成したら、単体で起動してクラッシュしないかを確認します。
この段階では stdio で待ち受けるだけなので、画面に何も出なくても失敗ではありません。
実際、無反応を見て止まったと思い込んだ場面があったが、あとで見ると正常に待ち受けていただけだった。
stdio サーバーは静かなほど正しいのです。
ここまで通れば、ツールを1つ持つ最小構成の MCP サーバーが完成します。
Claude Codeに接続してツールを呼び出す
Claude Codeからツールを呼び出すには、まずサーバーを認識させ、そのうえで呼び出し条件をClaudeが判断できるようにしておく必要があります。
登録方法は .mcp.json によるプロジェクト共有と claude mcp add による追加の2系統があり、どちらを使うかは共有範囲で決めるのが素直です。
接続そのものより、スコープの重なりと description の書き方でつまずく場面が多いので、そこを先に押さえておきましょう。
claude mcp addと.mcp.jsonの2つの登録方法
登録方法は2つあります。
プロジェクトルートに .mcp.json を置き、"my-server": {"command": "node", "args": ["dist/index.js"]} と書けば、Claude Codeがそのサーバーを自動認識します。
もう1つは claude mcp add コマンドで登録する方法で、こちらはスコープをフラグで明示できるため、どこに保存するかを操作しやすいのが利点です。
.mcp.json は「そのリポジトリに入れた設定をそのまま共有する」やり方で、チームの手元に同じ定義を残したいときに向いています。
対して claude mcp add は、まず自分の作業環境で試し、挙動が固まってから共有側へ寄せる運用に向きます。
登録の入口が2つあるのは、開発途中と運用定着後で必要な広がり方が違うからです。
local・project・userスコープの選び分け
スコープは local、project、user の3種類です。
local は既定で、追加したプロジェクトだけで読み込まれる自分専用の領域になります。
project は .mcp.json に保存され、gitにコミットしてチーム共有できる設定です。
user は ~/.claude.json 側に置かれ、全プロジェクトから使える汎用設定になります。
選び分けの基準は共有範囲で考えると迷いません。
そのリポジトリのメンバー全員に使わせたいなら project、自分の全プロジェクトで使う汎用ツールなら user、まだ検証中で他人に見せたくないなら local です。
自作サーバーはまず local で試し、安定したら project に昇格させる流れが自然でしょう。
優先順位は local > project > user なので、同じ名前のサーバーが複数スコープにあると local が勝ちます。
チームの .mcp.json を編集したのに反映されない場面では、自分の local に同名の残骸が残っていないかを最初に疑うべきです。
ℹ️ Note
同名サーバーが複数スコープにあると、見えている設定と実際に使われる設定がずれることがあります。共有設定を直したのに挙動が古いままなら、まず自分の local 側を見直す流れが安全です。
登録後の疎通確認と呼び出しテスト
登録したら、Claude Codeを再起動してサーバーが認識されているかを確かめます。
そのうえで会話の中で、実際にツールを使う指示を出して呼び出し可否を確認しましょう。
認識されているのに呼ばれない場合、接続不良ではなく description が曖昧で、Claudeがいつ使うべきか判断できていないケースが目立ちます。
たとえば登録直後にツールが呼ばれず接続を疑ったものの、実際には description が「データを取得する」とだけ書かれていて、用途の境界が見えなかった、という場面があります。
これでは Claude から見ると選択の手がかりが足りません。
何をするツールかだけでなく、どんな場面で使うのかまで短く具体化しておくと、会話の流れに乗りやすくなります。
動くかどうかだけで終わらせず、どう使われるかまで確認しておくと、あとで悩む時間が減ります。
動かないときの切り分け手順
まずは壊れやすい順に切り分けると、原因の見当が一気につきます。
MCP の失敗で最初に疑うべきなのは設定ではなく stdout 汚染で、次に Inspector での単体確認、最後に Claude Code 側の読み込み条件を見る流れです。
見た目は同じ「動かない」でも、壊れている層が違えば手当ても変わるので、順番を固定して潰していくのが早いでしょう。
最頻出はconsole.logによるstdout汚染
stdioトランスポートでは標準出力そのものが JSON-RPC の通路になります。
そこに console.log を 1 行混ぜただけで、JSON の先頭に文字列が割り込んで Unexpected token S in JSON at position 0 のようなパースエラーになり、接続は即座に壊れる。
しかも厄介なのは、エラー文面だけ見ても「何が混ざったのか」が読み取りにくい点です。
デバッグのつもりで足したログ1行に 30 分持っていかれた、というのは珍しい話ではありません。
対処は単純で、ログはすべて stderr に逃がします。
stdio サーバーが stderr に書いた内容はホスト側が拾う前提なので、診断情報を捨てる必要はありません。
出力先を変えるだけで直るため、まずここを疑うのが最短です。
ポイントは手順の軽さだ。
MCP Inspectorで単体検証してから繋ぐ
MCP Inspector はトランスポート非依存のテスト UI で、stdio と Streamable HTTP のどちらにもつなげます。
ツール、リソース、プロンプトを個別に呼び出せるうえ、通知ストリームまで見えるので、サーバーが仕様どおりに応答しているかを Claude Code とは切り離して確認できる。
ここで通るなら、少なくともプロトコルの基本は守れていると判断しやすいでしょう。
Claude Code に直結する前に Inspector を通しておくと、切り分け範囲が半分になります。
逆に、Inspector で落ちるならサーバー本体、Inspector で動くなら接続設定か読み込み条件、と判断の軸がはっきりする。
手を広げる前に、まず1本の線で確認してみてください。
Inspectorでは動くのにClaude Codeで落ちるとき
このパターンはトランスポート層より設定側を疑う場面です。
典型は3つで、パスが相対解決できていない、ビルド後の dist/index.js が古いまま、スコープの優先順位で別のサーバーを読んでいる、のどれかに収まることが多い。
Inspector では完璧なのに Claude Code だけ認識しないなら、実装より「どのファイルを読んでいるか」を見直す方が当たりやすいでしょう。
実際、Inspector で問題なく動いていたのに Claude Code では沈黙したケースで、原因は単にビルドし忘れた古い dist/index.js でした。
拍子抜けする結末ですが、こういうときほど表示されるエラーはプロトコル層の顔をしていて、本当の原因と噛み合いません。
だからこそ、stdout → Inspector → 設定 の順に上から潰す固定手順が効くのです。
実用サーバーへ育てる設計判断
stdioとStreamable HTTPの分岐点は、「誰が使うか」で決まります。
自分のマシンでClaude Codeから呼ぶだけならstdioが素直で、プロセス直結のため認証もネットワーク設定も要らず、レイテンシも約1msで済みます。
Streamable HTTPは公開や共有に向きますが、ネットワーク条件次第で10〜100msかかるので、ローカル用途では余計な待ち時間が増えるだけです。
stdioとStreamable HTTPの分岐点
チームで1本のサーバーを共有したい、あるいは複数マシンから同じ機能を使わせたい段階になったら、Streamable HTTPへ切り替える判断になります。
ただし、この移行はトランスポートを変えるだけでは終わりません。
誰がどの範囲まで使えるかをスコープ設計と一緒に決めないと、結局は各自がローカルにコピーを持つ運用へ戻りやすいからです。
育て方は、まずstdioで実用性を確かめるのが筋でしょう。
descriptionの精度がツール選択の精度になる
descriptionは飾りではなく、Claudeがツールを選ぶための判断材料です。
実際に「データ取得」という薄い説明を「商品コードから現在の在庫数を照会したいときに使う」に書き換えた途端、呼ばれ方が目に見えて変わりました。
機能名だけでは発火条件が読めないため、いつ使う道具なのかを文章で渡す必要があるのです。
関連処理を10本以上に割るより、1本に畳んで引数で分岐させた方が、選択精度も保守性も上がります。
ツールを増やしすぎると、定義そのものがコンテキストを圧迫します。
全ツールの定義は常時読み込まれるため、使うかもしれないツールを並べるだけでトークンが削られ、本来の作業に使える余地が減ってしまうのです。
だからこそ、似た処理はまとめて持ち、descriptionで発火条件を明示する設計が効きます。
ポイントは数ではない。
読みやすさである。
リモート公開なら認証は後付けにしない
Streamable HTTPで外部に公開するなら、認証は最初から組み込むべきです。
仕様上はPKCE付きOAuth 2.1が前提で、既存のIDプロバイダをそのまま使える設計になっています。
認証なしで公開したサーバーは短期間で見つかって悪用される前提で考えるのが安全で、後から足す発想だと公開前の実装で止まりやすいでしょう。
まずstdioで動かし、使われることを確認してからStreamable HTTPと認証を足しましょう。
AIビルダーの編集チームです。AI開発ツールの最新情報と使い方を発信しています。
関連記事
MCPサーバーおすすめ15選|Claude Code/Cursor
MCPサーバーおすすめ15選|Claude Code/Cursor
MCPサーバーは、2026年時点で公開数が1万件を超え、紹介記事ごとにおすすめが食い違うほど選択肢が膨らんだ仕組みである。だが実務で本当に迷うのは「どれが良いか」ではなく、「どれを常時有効にし、どれを切り替えるか」で、Cursorではツール総数が40個で頭打ちになり、6個前後でほぼ上限、
MCP運用設計|監視・ログ・障害対応の要点
MCP運用設計|監視・ログ・障害対応の要点
MCPはLLMを外部ツールやデータソースにつなぐ実装として注目されていますが、PoCのまま本番に持ち込むと、監視・ログ・セキュリティ・障害対応の穴が一気に表面化します。
MCP接続トラブル対処|5層で原因特定
MCP接続トラブル対処|5層で原因特定
MCPサーバーを追加したのに、接続できない、認証が通らない、ツールが出てこない。そんな詰まり方は、設定を総当たりで触るより、Host・Client・Server・Transport・Authorization のどこで止まっているかを順に切り分けたほうが早く抜けられます。
MCP自動化パターン10選|導入順と最小手順
MCP自動化パターン10選|導入順と最小手順
筆者の試用では、Jira と Notion を横断して要約する流れを組むと、毎朝の状況把握にかかる時間が短く感じられ、概ね2〜3分程度で済むことがありました。これはあくまで筆者の環境での体験値であり、環境や設定によって大きく変わります。一般化して示す場合は、社内PoCや計測ログなどの出典を併記してください。