Claude Code カスタムMCPサーバー開発入門——自分だけのツールをエージェントに持たせる
Model Context Protocol(MCP)を使ってClaude Code用のカスタムツールを開発する方法をハンズオン形式で解説。TypeScriptでのサーバー実装からClaude Codeへの登録、チームでの共有、Tool Searchによるコンテキスト最適化まで実践的に紹介します。

はじめに
Claude Codeは標準で多くのツール(ファイル読み書き、Bash実行、Web検索など)を備えていますが、実務ではこんな場面に遭遇します。
| 状況 | 困りごと |
|---|---|
| 社内APIからデータを取得したい | Bashでcurlを書かせるのは非効率で危険 |
| Firestoreの特定コレクションを検索したい | 毎回接続コードを書かせるのは冗長 |
| チーム独自のデプロイスクリプトを実行したい | 権限管理なしに生のBashで走らせたくない |
| プロジェクト固有のLintルールを適用したい | 汎用ツールではカバーしきれない |
「Claude Codeに、プロジェクト専用のツールを持たせられたら——」そう思った方のために、MCP(Model Context Protocol) を使ったカスタムツール開発を解説します。
MCPはAnthropicが策定したオープン標準で、AIエージェントに外部ツールを接続するためのプロトコルです。これまでFigma MCPやSlack MCP、Linear MCPで既存のMCPサーバーを使う方法を紹介してきましたが、今回は自分でMCPサーバーを作る側に回ります。
この記事を読み終わると、以下ができるようになります:
- MCPの3つのプリミティブ(Tools, Resources, Prompts)を理解できる
- TypeScriptでカスタムMCPサーバーを実装できる
- Claude Codeにツールを登録して使えるようになる
.mcp.jsonでチーム全体にツールを共有できる
MCPの全体像
アーキテクチャ
3つのプリミティブ
| プリミティブ | 説明 | 主体 | 例 |
|---|---|---|---|
| Tools | Claudeが呼び出せる関数 | Claude側が判断して呼ぶ | API呼び出し、DB検索、デプロイ |
| Resources | 参照可能なデータソース | ユーザーまたはClaude | 設定ファイル、ドキュメント |
| Prompts | 再利用可能なプロンプトテンプレート | ユーザーが明示的に使う | コードレビュー定型文 |
最も頻繁に使うのはToolsです。本記事のハンズオンでもToolsを中心に解説します。
3つのトランスポート
| トランスポート | 用途 | 登録コマンド |
|---|---|---|
| stdio | ローカルプロセス(推奨) | claude mcp add my-server node server.js |
| HTTP | クラウドサービス | claude mcp add --transport http my-api https://... |
| SSE | レガシー(非推奨) | claude mcp add --transport sse my-sse https://... |
ローカル開発にはstdioが最もシンプルで、デバッグもしやすいです。
事前準備
必要なもの
| 項目 | 詳細 |
|---|---|
| Node.js | v20以上 |
| Claude Code | 最新版(claude updateで更新) |
| TypeScript | 基本的な知識 |
| yarn | パッケージマネージャー |
セットアップ
mkdir kanri-pro-mcp && cd kanri-pro-mcp
yarn init -y
yarn add @modelcontextprotocol/sdk zod
yarn add -D typescript @types/node
tsconfig.jsonを作成します。
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
package.jsonにtypeとscriptsを追加します。
{
"type": "module",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
ハンズオン 1: 最初のMCPサーバーを作る
KanriProの開発で使う3つのツールを持つMCPサーバーを作ります。
Step 1: サーバーの基本構造
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFileSync, existsSync } from "fs";
import { join } from "path";
const server = new McpServer({
name: "kanri-pro-tools",
version: "1.0.0",
});
Step 2: ツール1 — 依存パッケージの取得
server.tool(
"get_dependencies",
"プロジェクトの依存パッケージ一覧を返す",
{
devDeps: z
.boolean()
.optional()
.describe("devDependenciesも含めるか(デフォルト: false)"),
},
async ({ devDeps }) => {
const pkgPath = join(process.cwd(), "package.json");
if (!existsSync(pkgPath)) {
return {
content: [{ type: "text", text: "package.json が見つかりません" }],
};
}
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
const deps = {
dependencies: pkg.dependencies || {},
...(devDeps ? { devDependencies: pkg.devDependencies || {} } : {}),
};
return {
content: [
{
type: "text",
text: JSON.stringify(deps, null, 2),
},
],
};
}
);
server.tool()の引数は以下の4つです。
| 引数 | 説明 |
|---|---|
name | ツール名(Claude Codeが呼び出す際のID) |
description | ツールの説明(Claudeが使うかどうかを判断する材料) |
inputSchema | 入力パラメータのzodスキーマ |
handler | 実際の処理関数 |
Step 3: ツール2 — TODO/FIXMEコメントの検索
server.tool(
"find_todos",
"ソースコード内のTODO/FIXME/HACKコメントを検索する",
{
pattern: z
.enum(["TODO", "FIXME", "HACK"])
.optional()
.describe("検索パターン(デフォルト: TODO)"),
directory: z
.string()
.optional()
.describe("検索ディレクトリ(デフォルト: src)"),
},
async ({ pattern = "TODO", directory = "src" }) => {
const { execSync } = await import("child_process");
try {
const result = execSync(
`grep -rn "${pattern}" "${directory}" --include="*.ts" --include="*.tsx" 2>/dev/null || true`,
{ encoding: "utf-8", cwd: process.cwd() }
);
if (!result.trim()) {
return {
content: [
{
type: "text",
text: `${pattern}コメントは見つかりませんでした`,
},
],
};
}
const lines = result.trim().split("\n");
return {
content: [
{
type: "text",
text: `## ${pattern}コメント一覧(${lines.length}件)\n\n${result}`,
},
],
};
} catch {
return {
content: [{ type: "text", text: "検索中にエラーが発生しました" }],
};
}
}
);
Step 4: ツール3 — 環境情報の取得
server.tool(
"get_environment",
"現在のNode.js/yarn/gitバージョンとプロジェクト情報を返す",
{},
async () => {
const { execSync } = await import("child_process");
const run = (cmd: string): string => {
try {
return execSync(cmd, { encoding: "utf-8" }).trim();
} catch {
return "N/A";
}
};
const info = {
node: run("node --version"),
yarn: run("yarn --version"),
gitBranch: run("git branch --show-current"),
gitStatus: run("git status --short"),
cwd: process.cwd(),
platform: process.platform,
};
return {
content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
};
}
);
Step 5: サーバーを起動
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// 重要: stdioサーバーでは console.log() を絶対に使わない
console.error("KanriPro MCP Server started");
}
main().catch(console.error);
重要: stdioトランスポートではconsole.log()を絶対に使わないでください。標準出力はJSON-RPCの通信チャネルとして使われるため、console.log()の出力が混ざるとプロトコルが壊れます。デバッグログはconsole.error()で標準エラー出力に書き出します。
Step 6: ビルドしてClaude Codeに登録
# ビルド
yarn build
# Claude Codeに登録
claude mcp add kanri-pro-tools node dist/index.js
Step 7: 動作確認
Claude Codeを起動し、ツールが使えることを確認します。
KanriProの依存パッケージを教えて
ソースコード内のTODOコメントを一覧にして
現在の開発環境の情報を確認して
Claude Codeが自動的にMCPサーバーのツールを認識し、適切なタイミングで呼び出してくれます。
ハンズオン 2: チームで共有する
Step 1: .mcp.jsonを作成
プロジェクトルートに.mcp.jsonを配置し、Gitにコミットします。
{
"mcpServers": {
"kanri-pro-tools": {
"command": "node",
"args": ["./tools/mcp-server/dist/index.js"],
"env": {
"API_BASE_URL": "${KANRI_PRO_API_URL}"
}
}
}
}
| フィールド | 説明 |
|---|---|
command | 実行コマンド |
args | コマンドライン引数 |
env | 環境変数(${}で展開可能) |
${KANRI_PRO_API_URL}のように環境変数の展開が使えるため、シークレットをコードに含めずに済みます。
Step 2: ディレクトリ構成
kanri-pro/
├── .mcp.json # チーム共有(Git管理)
├── tools/
│ └── mcp-server/
│ ├── src/
│ │ └── index.ts
│ ├── dist/
│ │ └── index.js
│ ├── package.json
│ └── tsconfig.json
├── src/
│ └── ...
└── package.json
Step 3: 3つのスコープの使い分け
| スコープ | 登録方法 | 共有範囲 | 用途 |
|---|---|---|---|
| Local(デフォルト) | claude mcp add | 自分のみ、このプロジェクト | 個人的なツール |
| Project | .mcp.jsonをGit管理 | チーム全員 | プロジェクト標準ツール |
| User | claude mcp add --scope user | 自分の全プロジェクト | 汎用ツール |
ハンズオン 3: Tool Searchでコンテキスト最適化
MCPサーバーのツール定義が増えると、コンテキストウィンドウを圧迫します。Tool Searchを使えば、ツールをオンデマンドでロードできます。
仕組み
自動有効化の条件
MCPサーバーのツール定義がコンテキストウィンドウの10%を超えると、Tool Searchが自動的に有効化されます。
手動で有効化
# 環境変数で明示的に有効化
ENABLE_TOOL_SEARCH=true claude
多数のツールを持つMCPサーバーを使う場合、Tool Searchによりコンテキスト使用量を最大95%削減できます。
実践的なMCPサーバーのアイデア
| ユースケース | ツール例 | 難易度 |
|---|---|---|
| 社内Wiki検索 | Confluenceの記事を検索・取得 | 中 |
| DB読み取り | 読み取り専用のSQLクエリ実行 | 中 |
| デプロイ | ステージング環境へのデプロイ | 高 |
| モニタリング | Datadogのメトリクス取得 | 中 |
| テスト実行 | 特定のテストスイートを安全に実行 | 低 |
| コード品質 | プロジェクト固有のLintルール適用 | 低 |
| ドキュメント生成 | APIドキュメントの自動更新 | 中 |
Tips・注意点
セキュリティのベストプラクティス
| 注意点 | 対策 |
|---|---|
| ツールが意図しないデータを返す | 出力サイズを制限する |
| 機密情報へのアクセス | 環境変数 + 最小権限の原則 |
| 破壊的な操作 | 読み取り専用ツールと書き込みツールを明確に分離 |
| プロンプトインジェクション | ツールの入力を厳密にバリデーション(zodで制約) |
デバッグ方法
# サーバーの起動ログを確認(stderrに出力される)
node dist/index.js 2>&1 | head -20
# Claude Code内でMCPサーバーの状態を確認
/mcp
よくあるエラーと対処
| エラー | 原因 | 対処 |
|---|---|---|
MCP server failed to start | ビルドエラーまたはパスが間違い | node dist/index.jsを手動実行して確認 |
| ツールが表示されない | サーバーのtool登録に問題 | server.tool()の引数を確認 |
| レスポンスが空 | console.log()がstdoutを汚染 | console.error()に変更 |
| タイムアウト | ツールの処理が重い | 非同期処理を最適化、タイムアウト設定を追加 |
まとめ
| やりたいこと | 使う機能 | 難易度 |
|---|---|---|
| カスタムツールを作る | server.tool() + zodスキーマ | 低 |
| Claude Codeに登録 | claude mcp add (stdio) | 低 |
| チームで共有 | .mcp.json をGit管理 | 低 |
| クラウドサービスに接続 | HTTPトランスポート | 中 |
| コンテキスト最適化 | Tool Search | 自動 |
次のアクションとしておすすめ:
- まずは小さなツールを1つ作る — 依存パッケージ表示やTODO検索など
- Claude Codeに登録して動作確認 —
claude mcp addで即座に使える - チームで使うなら
.mcp.jsonに移行 — Git管理で全員に展開 - 徐々にツールを追加 — 社内API連携やデプロイ自動化へ拡張
MCPは「Claude Codeの手足を増やす」技術です。AIエージェントにプロジェクト固有の能力を与えることで、定型作業の自動化がさらに加速します。まずは1つのツールから始めてみてください。
参考リンク:
- Claude Code公式ドキュメント — MCP
- Model Context Protocol公式 — Build a Server
- Building MCP Servers: Custom Context for Claude Code(SitePoint)
関連記事: