Claude Code Media
123,285中級

Claude Code MCP 自作完全ガイド|TypeScript でカスタムサーバーを作る手順【2026】

Claude Code 向けカスタム MCP サーバーの作り方を完全解説。TypeScript + @modelcontextprotocol/sdk で社内 API・独自ツールを Claude に接続する実装手順をステップバイステップで紹介。

By Claude Code Media 編集部Reviewed by Claude Code Media 編集部

MCP(Model Context Protocol)の既存サーバーには載っていない 社内 API・独自データベース・レガシーシステム を Claude Code と繋ぎたい場合、自作 MCP サーバーが解決策になります。本記事では MCP 公式 TypeScript SDK を使い、TypeScript で動く自作 MCP サーバーを 30 分で作り切る手順 をステップバイステップで解説します。

本手順は 2026 年 6 月時点の @modelcontextprotocol/sdk v1 系をベースにしています。SDK のバージョンアップで API が変わる場合があるため、実装前に 公式リポジトリの最新情報 を必ず確認してください。

1. MCP を自作するべき 3 つのケース

Claude Code MCP 完全ガイド で解説した Slack / Notion / GitHub 等の公式 MCP サーバーで対応できない場合、自作が必要になります。具体的には以下の 3 ケースです。

1-1. 社内専用システムへの接続

レガシー ERP・自社開発の基幹システム・社内 API など、外部には MCP サーバーが存在しないシステムとの連携。

1-2. 認証ロジックのカスタマイズ

OAuth 2.0 フローや社内 SSO、HMAC 署名など、既存サーバーでは扱えない独自認証が必要なケース。

1-3. 複数 API の集約

在庫 API・受注 API・会計 API の3つを1つの MCP Tool にまとめ、Claude が1回の呼び出しで横断データを取得できるようにする場合。

2. MCP サーバーの 3 要素

自作 MCP サーバーが提供できる機能は以下の 3 種類です。

要素役割典型的な用途
ToolsClaude が呼び出せる「操作」API への書き込み、検索、計算
ResourcesClaude が読み取れる「データ」ファイル、DB レコード、ドキュメント
Prompts再利用可能なプロンプトテンプレート定型業務のチェックリスト等

多くのユースケースでは Tools だけ 実装すれば十分です。Resources は Claude が自律的にデータを参照する場面(コンテキスト補充)で使います。

3. TypeScript で MCP サーバーを作る(ステップバイステップ)

ステップ 1: プロジェクト初期化

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init --target es2022 --module nodenext --moduleResolution nodenext

package.json に以下を追記します。

{
  "type": "module",
  "scripts": {
    "dev": "tsx src/index.ts",
    "build": "tsc"
  }
}

ステップ 2: サーバーの骨格を作る

src/index.ts を作成します。

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-company-mcp",
  version: "1.0.0",
});

// --- Tools をここに追加 ---

// サーバーを起動(stdio 通信)
const transport = new StdioServerTransport();
await server.connect(transport);

ステップ 3: Tools を実装する

社内の在庫 API を叩く Tool を追加してみます。// --- Tools をここに追加 --- の下に記述します。

// 在庫照会 Tool
server.tool(
  "get_inventory",
  "SKU コードで在庫数を照会する",
  {
    sku: z.string().describe("商品 SKU コード(例: ITEM-001)"),
  },
  async ({ sku }) => {
    const res = await fetch(`https://api.internal.example.com/inventory/${sku}`, {
      headers: { "Authorization": `Bearer ${process.env.INTERNAL_API_TOKEN}` },
    });

    if (!res.ok) {
      return {
        content: [{ type: "text", text: `エラー: ${res.status} ${res.statusText}` }],
        isError: true,
      };
    }

    const data = await res.json() as { sku: string; stock: number; updated_at: string };
    return {
      content: [
        {
          type: "text",
          text: `SKU: ${data.sku} の在庫数は ${data.stock} 個です(最終更新: ${data.updated_at})`,
        },
      ],
    };
  }
);

ポイント:

  • zod でパラメータのバリデーションを定義する(型安全 + SDK が JSON Schema に自動変換)
  • API 認証情報は process.env で管理し、コードに直書きしない
  • エラー時は isError: true を返すと Claude が「失敗した」と認識して対処する

ステップ 4: Resources を実装する(オプション)

Claude に「コンテキストとして読んでほしいデータ」がある場合に使います。

// 社内 FAQ ドキュメントを Resource として公開
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";

server.resource(
  "faq",
  new ResourceTemplate("faq://{category}", { list: undefined }),
  async (uri) => {
    const category = uri.pathname.replace("/", "");
    const faqs = await loadFaqByCategory(category); // 社内 DB からロード

    return {
      contents: [
        {
          uri: uri.toString(),
          text: faqs.map((f) => `Q: ${f.question}\nA: ${f.answer}`).join("\n\n"),
          mimeType: "text/plain",
        },
      ],
    };
  }
);

ステップ 5: ローカルで動作確認する

# stdio でインタラクティブに確認
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node --loader tsx src/index.ts

Tools の一覧が JSON で返ってくれば動作しています。

4. Claude Code に自作 MCP を接続する

プロジェクトルートの .mcp.json に追記します。

{
  "mcpServers": {
    "my-company-mcp": {
      "command": "node",
      "args": ["--loader", "tsx", "/path/to/my-mcp-server/src/index.ts"],
      "env": {
        "INTERNAL_API_TOKEN": "${env:INTERNAL_API_TOKEN}"
      }
    }
  }
}

本番環境(ビルド後)の場合:

{
  "mcpServers": {
    "my-company-mcp": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "INTERNAL_API_TOKEN": "${env:INTERNAL_API_TOKEN}"
      }
    }
  }
}

.mcp.json を保存して claude を起動すると、自作サーバーが自動検出されます。claude "get_inventory を使って ITEM-001 の在庫を確認して" と入力すれば Tool が呼ばれます。

5. 実践例:複数 API を集約した受注確認 Tool

5-1. 要件

「注文番号を渡すと、受注システム・在庫システム・配送システムの3つから情報をまとめて返す」Tool。

5-2. 実装コード

server.tool(
  "get_order_summary",
  "注文番号から受注・在庫・配送の統合サマリーを取得する",
  {
    order_id: z.string().describe("注文番号(例: ORD-20260612-001)"),
  },
  async ({ order_id }) => {
    // 3 API を並列で叩く(モデルケース)
    const [order, stock, shipping] = await Promise.all([
      fetch(`https://api.example.com/orders/${order_id}`).then((r) => r.json()),
      fetch(`https://api.example.com/stock/${order_id}`).then((r) => r.json()),
      fetch(`https://api.example.com/shipping/${order_id}`).then((r) => r.json()),
    ]);

    const summary = [
      `## 注文 ${order_id} サマリー`,
      `- 受注日時: ${order.created_at}`,
      `- 商品: ${order.items.map((i: { name: string; qty: number }) => `${i.name} × ${i.qty}`).join(", ")}`,
      `- 在庫ステータス: ${stock.status}(残 ${stock.available} 個)`,
      `- 配送ステータス: ${shipping.status}(追跡番号: ${shipping.tracking_id ?? "未割当"})`,
    ].join("\n");

    return { content: [{ type: "text", text: summary }] };
  }
);

Claude Code にこの Tool が追加されると、「ORD-20260612-001 の状況を教えて」の一言で3システムの情報を横断取得できます。

※上記コードは実装パターンを示すモデルケースです。実際の API レスポンス形状は各社の仕様に合わせて修正してください。効果や処理速度は環境・API の応答時間によって異なります。

6. Python で作る場合

TypeScript の代わりに Python でも実装できます。公式の mcp ライブラリを使います。

pip install mcp
import mcp.server.stdio
from mcp.server import Server
from mcp.types import Tool, TextContent

app = Server("my-company-mcp")

@app.list_tools()
async def list_tools():
    return [
        Tool(
            name="get_inventory",
            description="SKU コードで在庫数を照会する",
            inputSchema={
                "type": "object",
                "properties": {"sku": {"type": "string"}},
                "required": ["sku"],
            },
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "get_inventory":
        sku = arguments["sku"]
        # ここに API 呼び出しを実装
        return [TextContent(type="text", text=f"SKU: {sku} の在庫数は 42 個です")]

if __name__ == "__main__":
    import asyncio
    asyncio.run(mcp.server.stdio.run(app))

Python の場合は .mcp.jsoncommandpython3args.py ファイルのパスを指定します。

7. よくある失敗と対処法

7-1. Claude が Tool を認識しない

.mcp.json のパスが間違っているか、起動時エラーで MCP サーバーが落ちているケースがほとんどです。

# MCP サーバーが正常起動するか単体確認
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}' \
  | node --loader tsx src/index.ts

7-2. ERR_UNKNOWN_FILE_EXTENSION エラー

package.json"type": "module" が抜けているか、tsconfig.jsonmoduleResolutionnodenext になっていない場合に発生します。「ステップ 1」の設定を再確認してください。

7-3. 環境変数が渡らない

.mcp.jsonenv フィールドで ${env:VAR} 記法を使っているのに、シェルに変数がセットされていないパターン。export INTERNAL_API_TOKEN=xxx.zshrc.env.local に追加し、シェルを再起動してから claude を起動してください。

7-4. Tool の応答が空になる

return する content 配列が空、または text フィールドが空文字になっているケースです。デバッグ時は console.error(JSON.stringify(result)) で stderr に出力し、Claude Code の MCP ログ(~/.claude/logs/)で確認できます。

8. まとめ

MCP 自作は「5 ステップ + 50 行のコード」で始められます。

  1. @modelcontextprotocol/sdk をインストール
  2. McpServer に Tools / Resources を定義
  3. .mcp.json で Claude Code に接続
  4. process.env で認証情報を安全に管理
  5. 必要に応じて Tools を追加・組み合わせ

既存の公式 MCP では対応できない社内システムや独自 API を Claude Code と繋ぐことで、「自然言語で社内全システムを横断操作する」ワークフローが実現できます。

次のステップ:

自作 MCP の実装でお困りのことがあれば、週1ニュースレター(無料)でも実装サンプル・Q&A を毎週木曜に配信しています。

関連記事

この記事の著者

claude-code-media 編集部Claude Code 専門編集チーム

Claude Code の非エンジニア向け業務効率化メディア『claude-code-lab.jp』を運営。フリーランス・中小企業・個人開発者向けに、実装テンプレ・業務自動化テクニック・Vibe Coding 入門を配信。

Claude Code 完全ガイドVibe CodingAI 業務効率化非エンジニア向け AI 教育MCP(Model Context Protocol)
ByClaude Code Media 編集部

AI支援で執筆 — 本記事は Claude Code エージェントによる執筆支援を受け、編集部が事実確認・編集を行っています。 数値・引用元は記事更新日時点で確認済みですが、最新情報は各公式サイトでご確認ください。