MCPサーバーの作り方入門 — Claude Codeを自分好みに拡張しよう
公開日
📢 PR表記・AI利用開示
現時点で本記事にアフィリエイトリンクは掲載していません。今後掲載する場合は、リンクの近くに広告である旨を明記します。 本記事はAIツールを活用して執筆・編集しています。公式情報は執筆・更新時点(2026年10月)のものであり、最新仕様は公式サイトでご確認ください。🔎 検証状況: 記事内のサーバーコードは、筆者が検証環境で実行して確認しています(結果は「動作確認ログ」の節)。確認していない範囲もその節に明記しています。
Claude Codeを使っていて「この外部サービスと連携できたらいいのに」と思ったことはありませんか? それを実現するのがMCP(Model Context Protocol)サーバーです。
この記事では、MCPサーバーをゼロから作る方法を、コード付きで解説します。TypeScriptとNode.jsの環境があれば、短時間で最初のMCPサーバーを動かせる場合があります(所要時間は環境や経験で大きく変わります)。
関連記事: Claude Code入門ガイド、AI副業の始め方
MCPとは
MCP(Model Context Protocol)は、AIアシスタント(Claude等)と外部ツール・データソースを接続するためのプロトコルです。
Claude Code と外部サービス(DB・API・ファイル等)の間にMCPサーバーが入り、双方向にやり取りを仲介します。
MCPサーバーを作ることで、Claude Codeに新しい能力を追加できます。
⚠️ SDKのバージョンについて(先にお読みください) 公式の MCP TypeScript SDK は、執筆時点(2026年10月)でv2系(
@modelcontextprotocol/server/@modelcontextprotocol/client、Node.js 20以上、zod 4以上)が現行の安定版で、v1系(@modelcontextprotocol/sdk)は保守系列として位置づけられています(リポジトリREADMEの記載)。本記事のコードはv1系(@modelcontextprotocol/sdk)の書き方です。v2ではstdioトランスポートの import 先などが変わるため、新規に作る場合は 公式のv2移行ガイド を確認してください。本記事のv2対応コードの動作は確認していません。
MCPサーバーでできること
- ツール: Claudeが呼び出せる関数(API呼び出し、計算、ファイル操作等)
- リソース: Claudeが参照できるデータ(DB、設定ファイル等)
- プロンプト: 再利用可能なプロンプトテンプレート
セットアップ
前提条件
- Node.js 18以上(v1系。v2系はNode.js 20以上)
- TypeScript
- Claude Code がインストール済み
プロジェクトの作成
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
# 注: 本記事は v1 系(@modelcontextprotocol/sdk)の書き方(冒頭の注意書き参照)
npm install -D typescript @types/node
npx tsc --init
tsconfig.json の設定
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*"]
}
"types": ["node"]は、筆者の検証環境(TypeScript 7.0.2)で、これを入れないとtscがCannot find name 'node:stream'/Cannot find name 'Buffer'(@types/nodeを入れているのに型が見つからない)というエラーで止まったため追記しています。
package.json に追加
{
"type": "module",
"bin": {
"my-mcp-server": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Hello World: 最初のMCPサーバー
src/index.ts を作成します。
#!/usr/bin/env node
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: "hello-mcp",
version: "1.0.0",
});
server.registerTool(
"greet",
{
description: "Greet someone by name",
inputSchema: { name: z.string().describe("Name of the person to greet") },
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}! 👋` }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
ビルドと登録
# ビルド
npx tsc
# Claude Code に登録
claude mcp add --transport stdio hello-mcp -- node /absolute/path/to/dist/index.js
これでClaude Codeから greet ツールが使えるようになります。claude mcp list で登録状況を確認でき、Claude Codeに「greet ツールで World に挨拶して」のように依頼すると、ツールが呼び出されます(実際の表示や承認の求められ方はClaude Codeのバージョンや権限設定で異なります)。
実践: 外部API連携
外部のWeb APIを呼ぶツールの例として、天気情報を取得するMCPサーバーを作ってみましょう。ここでは、APIキー不要で利用できる Open-Meteo の予報エンドポイント(https://api.open-meteo.com/v1/forecast)を使います。緯度・経度と、取得したい現在値の変数(current)を指定します。
注意: Open-Meteoのキー不要の利用は非商用向けで、商用利用にはAPIキーが必要と公式に記載されています。利用前に最新の利用条件を確認してください。以下のコードは公式ドキュメントのパラメータ仕様に沿って書いたもので、実行結果は後述の「動作確認ログ」に載せています(Node.js標準の
fetchが環境のプロキシを自動では使わない点など、環境依存の注意も併記しています)。
#!/usr/bin/env node
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: "weather-mcp",
version: "1.0.0",
});
server.registerTool(
"get_weather",
{
description: "Get current weather for a location (latitude/longitude)",
inputSchema: {
latitude: z.number().min(-90).max(90).describe("Latitude (e.g., 35.68)"),
longitude: z.number().min(-180).max(180).describe("Longitude (e.g., 139.77)"),
},
},
async ({ latitude, longitude }) => {
const url =
"https://api.open-meteo.com/v1/forecast" +
`?latitude=${latitude}&longitude=${longitude}` +
"¤t=temperature_2m,relative_humidity_2m,wind_speed_10m";
const response = await fetch(url);
if (!response.ok) {
return {
content: [{ type: "text", text: `Failed to fetch weather (HTTP ${response.status})` }],
isError: true,
};
}
const data = await response.json();
return {
content: [{ type: "text", text: JSON.stringify(data.current, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
外部APIは仕様変更や停止があり得ます。実際に使うAPIの最新ドキュメントとレート制限・利用規約を確認し、失敗時は上のように isError: true で返すようにしておくと安全です。
ツール設計のベストプラクティス
1. 引数のバリデーション
zodスキーマで引数を定義すると、Claude が正しい引数を渡してくれます。
server.registerTool(
"search_users",
{
description: "Search users by name or email",
inputSchema: {
query: z.string().min(2).describe("Search query (min 2 characters)"),
limit: z.number().int().min(1).max(100).default(10)
.describe("Number of results to return"),
},
},
async ({ query, limit }) => {
// ...
}
);
2. エラーハンドリング
server.registerTool("risky_operation", { description: "...", inputSchema: { /* ... */ } }, async (args) => {
try {
const result = await doSomething(args);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
});
3. ツールの説明を丁寧に書く
Claudeはツールの description を読んで、いつどう使うかを判断します。具体的に書きましょう。
// ❌ 悪い例
"Get data"
// ✅ 良い例
"Get the current exchange rate between two currencies. Returns the rate and last updated timestamp."
リソースの実装
ファイルやDBのデータをClaudeに公開する場合はリソースを使います。ツールと同じく register 系のAPI(registerResource)を使い、引数は「リソース名、URI、メタデータ、読み取りコールバック」の順です(v1系の公式ドキュメントの例に基づく)。
server.registerResource(
"config",
"config://app",
{
title: "Application Config",
description: "Application configuration data",
mimeType: "application/json",
},
async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify({
version: "1.0.0",
environment: "development",
}),
}],
})
);
動作確認ログ(筆者がこの環境で実行した結果)
本記事のコード(Hello World、天気ツール、search_users、risky_operation、リソース)を、記事のとおりにコピーして新規プロジェクトに置き、実際にビルド・実行して確認しました。
- 実行環境: Linux、Node.js v22.22.0、npm 10.9.4
- インストールされたパッケージ:
@modelcontextprotocol/sdk1.32.1、zod4.6.7、typescript7.0.2、@types/node26.6.5、実行用にtsx4.23.15 - 確認日: 2026年10月13日
- 方法:
npx tscでビルド後、SDKのClientとStdioClientTransportから各サーバー(node dist/*.js)を子プロセスとして起動し、tools/list・ツール呼び出し・リソース読み取りを実行 - 結果:
npx tscはエラーなしで通り、下記の呼び出しはすべて期待どおりに動きました。記事のコードの修正が必要だったのは、前述の tsconfig のtypes追記のみです。Claude Code本体への登録(claude mcp add)は、この検証では実行していません。 search_usersとrisky_operationは、記事のスニペットの// ...やdoSomethingなどを、検証用に最小の実装(引数を返す/fail: trueで例外を投げる)で補ったものです。
以下は出力の抜粋です(JSONは整形し、tools/list の inputSchema 内の $schema と execution フィールドは省略しています)。
Hello World(greet)
tools/list -> name: "greet", description: "Greet someone by name"
inputSchema.properties.name = { type: "string", description: "Name of the person to greet" }, required: ["name"]
callTool greet {name: "World"} ->
{ "content": [ { "type": "text", "text": "Hello, World! 👋" } ] }
天気ツール(get_weather)
tools/list では、latitude に minimum: -90 / maximum: 90、longitude に minimum: -180 / maximum: 180 が反映されていました。
範囲外の引数(latitude: 999)はzodの検証で弾かれ、ツール本体は呼ばれませんでした。
callTool get_weather {latitude: 999, longitude: 0} ->
{ "content": [ { "type": "text", "text": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: Too big: expected number to be <=90 at latitude" } ], "isError": true }
Open-Meteoへの実リクエスト(latitude: 35.68, longitude: 139.77)は、次の2通りの結果でした。
- 記事のコードをそのまま実行した場合: この検証環境はHTTPSの外部通信がプロキシ経由に限られ、Node.js 22の標準
fetchはプロキシを自動では使わないため、fetch failedとなり、記事のコードどおりisError: trueで返りました(curlではHTTP 200で到達できたので、API側の停止ではなく環境の制約です)。
callTool get_weather {latitude: 35.68, longitude: 139.77} ->
{ "content": [ { "type": "text", "text": "fetch failed" } ], "isError": true }
- サーバーのコードは変えず、起動時にプロキシ設定(
undiciのProxyAgent)をnode --importで差し込んだ場合: 取得できました。
callTool get_weather {latitude: 35.68, longitude: 139.77} ->
{ "content": [ { "type": "text", "text": "{\n \"time\": \"2026-10-13T10:15\",\n \"interval\": 900,\n \"temperature_2m\": 21.4,\n \"relative_humidity_2m\": 84,\n \"wind_speed_10m\": 2\n}" } ] }
通常のインターネット環境(プロキシなし)で、同じコードがそのまま動くかどうかは、この検証では確認していません。
引数バリデーションとエラーハンドリング
callTool search_users {query: "ab"} ->
{ "content": [ { "type": "text", "text": "{\"query\":\"ab\",\"limit\":10}" } ] } # limit の default(10) が適用された
callTool search_users {query: "a"} ->
{ "content": [ { "type": "text", "text": "MCP error -32602: Input validation error: Invalid arguments for tool search_users: Too small: expected string to have >=2 characters at query" } ], "isError": true }
callTool risky_operation {fail: true} ->
{ "content": [ { "type": "text", "text": "Error: boom" } ], "isError": true }
callTool risky_operation {} ->
{ "content": [ { "type": "text", "text": "{\"ok\":true}" } ] }
リソース(registerResource)
resources/list ->
{ "resources": [ { "name": "config", "title": "Application Config", "uri": "config://app", "description": "Application configuration data", "mimeType": "application/json" } ] }
resources/read config://app ->
{ "contents": [ { "uri": "config://app", "text": "{\"version\":\"1.0.0\",\"environment\":\"development\"}" } ] }
Claude Code への登録方法
ローカル開発時
claude mcp add --transport stdio my-server -- node /absolute/path/to/dist/index.js
-- より後ろがサーバーを起動するコマンドです。--env や --scope などのオプションは名前より前(-- の前)に書きます(公式: MCP)。
npm公開後
claude mcp add --transport stdio my-server -- npx -y my-mcp-server
設定ファイルで管理
チームで共有するなら、プロジェクト直下の .mcp.json に書く方法(--scope project)が公式に用意されています:
{
"mcpServers": {
"my-server": {
"type": "stdio",
"command": "node",
"args": ["/path/to/dist/index.js"],
"env": {
"API_KEY": "${API_KEY}"
}
}
}
}
APIキーなどの秘密情報は .mcp.json に直書きせず、環境変数展開(${VAR})を使ってください。
スコープの違い
| スコープ | フラグ | 保存先 | チーム共有 |
|---|---|---|---|
| local(既定) | --scope local | ~/.claude.json(該当プロジェクト配下) | しない |
| project | --scope project | プロジェクト直下の .mcp.json | する(バージョン管理) |
| user | --scope user | ~/.claude.json | しない(全プロジェクトで有効) |
プロジェクトスコープのサーバーは、初回利用時に承認が求められます。
npm公開の手順
# package.json のnameを公開用に
# @your-name/my-mcp-server
# ビルド
npm run build
# npm ログイン
npm login
# 公開
npm publish --access public
公開後は誰でも npx @your-name/my-mcp-server で使えます。
MCPサーバーのアイデア
- データベースクエリ: SQLiteやPostgreSQLに接続して自然言語でクエリ
- 為替レート: リアルタイムの為替情報を取得
- GitHub連携: Issue/PRの操作を拡張
- Slack連携: チャンネルの投稿・検索
- 翻訳: DeepL APIで高精度翻訳
- 画像処理: 画像のリサイズ、変換、メタデータ読み取り
まとめ
MCPサーバーの開発は、Claude Codeの可能性を大きく広げます。TypeScriptが書ければ短時間で最初のサーバーを動かせる可能性があり、npmに公開すれば他のユーザーも使えるようになります。
まずはHello Worldから始めて、自分の日常業務で「Claude Codeにやってほしいこと」をMCPサーバーとして実装してみてください。
参考情報
免責事項
- 本記事の情報は更新時点(2026年10月)のものです。サービス仕様・料金は変更される可能性があります。
- 現時点でアフィリエイトリンクは掲載していません(掲載する場合はリンクの近くに明記します)。
- 本記事は一般的な情報提供を目的としており、投資・法務・税務等の専門的助言ではありません。個別の判断は専門家にご相談ください。