MCP サーバーの自作手順。最小30行で動かす天気サーバー入門
既製の MCP サーバーが見つからないとき、自作するという選択肢があります。意外なことに、最小構成のサーバーは **30〜50 行程度** で書けます。本記事では「天気を答えるサーバー」を例に、TypeScript SDK のセットアップから、Claude Code に繋いで動かすまでを通しで歩いてみます。
はじめに
MCP シリーズの最終章、自作編 です。前2本で、
- MCP とは何か:AI と外部ツールをつなぐ共通プロトコル
- MCP サーバーを使う:既製サーバーを Claude Code に繋ぐ手順
を見てきました。今回は、自分で MCP サーバーを書いて Claude Code に繋ぐ ところまで進みます。
「サーバーを自作」と聞くと身構える方が多いと思います。私も最初は同じ感覚でした。実際にやってみると、最小構成は驚くほど短い——tools を1個だけ持つサーバーなら 30〜50 行のコードで動きます。本記事では、「都市名を渡すと天気を答えるサーバー」を例に、いちから組み立てます。
本記事の対象は、
- TypeScript(または JavaScript)の 基本的な書き方は知っている
- ターミナルで
npmコマンドが使える - Node.js が手元の PC に入っている
くらいの方を想定しています。ガッツリ系の解説は避けて、「動くものを最短で1つ作る」 ことに集中します。
自作の前に:何を作りたいかを決める
最初に、「自分が MCP サーバーを書きたい理由」をはっきりさせておきます。これが曖昧だと、サンプルを作って終わりになりがちです。
よくある自作の動機としては、
| 動機 | 例 |
|---|---|
| 既製サーバーがない外部 API を使いたい | 自社の社内システム、独自 SaaS |
| 既製サーバーが大きすぎて、最小限だけ欲しい | GitHub サーバーから Issue 作成だけ抜き出す |
| 既製サーバーの認証方式が合わない | 自社の SSO 経由でアクセスしたい |
| 自分の作業フローを Claude に任せたい | 「LP の見出しを CSV に出力して」を1コマンド化 |
本記事では入門用に、外部 API を叩くサーバー の代表例として「天気を答えるサーバー」を作ります。Tool は1個(get_weather)だけのシンプルな構成です。
使う SDK を決める
MCP には、サーバーを楽に書くための 公式 SDK が用意されています。記事公開時点で主要な選択肢は、
| SDK | 言語 | おすすめ度 |
|---|---|---|
@modelcontextprotocol/sdk | TypeScript / JavaScript | ◎ Web 制作経験者にいちばん馴染む |
mcp | Python | ○ Python に慣れていれば |
本記事では TypeScript SDK を使います。Web 制作経験のある方なら、JavaScript の延長で書けるので入りやすいはずです。
最小構成プロジェクトを作る
ターミナルで作業していきます。プロジェクトフォルダを作って、必要なものをセットアップします。
Step 1. フォルダを作る
mkdir ~/mcp-weather
cd ~/mcp-weather
npm init -y
npm init -y で、デフォルト設定の package.json がいきなり作られます。
Step 2. SDK と TypeScript を入れる
npm install @modelcontextprotocol/sdk
npm install -D typescript tsx @types/node
| パッケージ | 役割 |
|---|---|
@modelcontextprotocol/sdk | MCP サーバーの本体機能 |
typescript | TypeScript コンパイラ |
tsx | TypeScript を直接実行できるツール |
@types/node | Node.js の型定義 |
Step 3. tsconfig.json を作る
最小の TypeScript 設定。
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "dist"
},
"include": ["src/**/*"]
}
src/ フォルダの下に TypeScript ファイルを置く、という前提です。
Step 4. package.json に "type": "module" を足す
ESM(モジュール形式)で動かしたいので、package.json に1行加えます。
{
"name": "mcp-weather",
"version": "1.0.0",
"type": "module",
...
}
これでセットアップ完了。次から実コードに入ります。
サーバー本体を書く
src/index.ts を新規作成して、以下を書きます。長く見えますが、半分はコメントです。
// MCP の SDK から、サーバー本体と「コマンドラインで起動する型」を取り込む
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// サーバーを作る
const server = new Server(
{ name: 'mcp-weather', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// 「どんな Tool が使えるか」を Claude に伝える
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_weather',
description: '都市名を受け取って、その都市の現在の天気を返す',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: '都市名(例:「東京」「大阪」)',
},
},
required: ['city'],
},
},
],
}));
// Claude が「Tool を呼んでくれ」と言ってきたときの実処理
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_weather') {
const city = request.params.arguments?.city as string;
// ※本来はここで天気 API を叩く。今回はダミーの返答。
return {
content: [
{
type: 'text',
text: `${city} の天気は晴れ、気温は 22°C です(ダミー応答)。`,
},
],
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
// サーバーを「標準入出力経由」で起動する
const transport = new StdioServerTransport();
await server.connect(transport);
50 行ちょっとです。本当に書いている処理は、
Serverを作る- 「使える Tool の一覧」 を返すハンドラを登録
- 「Tool を呼んだときの実処理」 を登録
- 標準入出力で起動
の4ステップだけです。
コードの読みどころ
nameとversion:Serverのコンストラクタで自分のサーバーの名前を決めますListToolsRequestSchemaのハンドラ:Claude が「君は何ができるの?」と聞いてきたときの返答。ここのdescriptionとinputSchemaが、Claude にとっての判断材料になりますCallToolRequestSchemaのハンドラ:Claude が実際に Tool を呼んだときの本処理。今回はダミー応答ですが、実用ではfetchで外部 API を叩いて結果を返しますStdioServerTransport:「標準入出力でやり取りする」型。これが ローカル STDIO 系サーバー の正体です
Claude Code に繋ぐ
サーバーを書いたら、Claude Code から呼べるように登録します。ターミナルで、
claude mcp add weather tsx -- ~/mcp-weather/src/index.ts
| 部分 | 意味 |
|---|---|
weather | 自分でつけるニックネーム |
tsx | TypeScript を直接実行できるコマンド |
~/mcp-weather/src/index.ts | 自分のサーバーファイル |
実行すると、
✓ Added MCP server 'weather'
Tools: get_weather
——となれば成功です。
動作確認
Claude Code を起動して、
> 東京の天気を教えて
と頼んでみてください。Claude が mcp__weather__get_weather を呼んで、
東京の天気は晴れ、気温は 22°C です(ダミー応答)。
を返してきたら、自作 MCP サーバーが動いています。
「さっき書いた50行のファイル が、いまの返答の正体です」と気づくと、ちょっと感動的な瞬間があります。
スキーマ定義のコツ
get_weather を Claude が 正しく呼んでくれるかどうか は、inputSchema と description の質に大きく左右されます。Skills のときと同じ、「どういう場面で呼ぶべきか」を具体的に書く のが大事です。
| 部分 | 良くない例 | 良い例 |
|---|---|---|
description | 天気を返す | 都市名を受け取って、その都市の現在の天気を返す。日本国内の主要都市が対象 |
properties.city.description | 文字列 | 都市名(例:「東京」「大阪」)。日本語と英語のどちらでも可 |
「Claude が 何の場面で どう呼ぶべきか が伝わるか」を判断軸に、自分の言葉で書き直してみてください。
入力が複雑になるとき
入力が単純な文字列1個ではなく、複数のフィールドが必要なときは、properties を増やします。
inputSchema: {
type: 'object',
properties: {
city: { type: 'string', description: '都市名' },
days: {
type: 'integer',
description: '何日先までの天気を返すか(1〜7)',
minimum: 1,
maximum: 7,
},
units: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: '気温の単位',
},
},
required: ['city'],
},
enum で選択肢を絞る、minimum/maximum で範囲を絞る、といった指定もできます。ここを丁寧に書くと、Claude の呼び出しの精度が目に見えて上がります。
エラーハンドリング
実用的なサーバーには、エラーハンドリングを足す必要があります。最小限の方針は、
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
if (request.params.name === 'get_weather') {
const city = request.params.arguments?.city as string;
// 入力チェック
if (!city || city.trim() === '') {
return {
content: [{ type: 'text', text: 'エラー:city は必須です' }],
isError: true,
};
}
// 本処理
const result = await fetchWeather(city);
return {
content: [{ type: 'text', text: result }],
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
} catch (e) {
// 例外は `isError: true` で返す
return {
content: [{ type: 'text', text: `エラー:${(e as Error).message}` }],
isError: true,
};
}
});
ポイントは、
- 入力検証を先頭で(
cityが空文字なら早期リターン) - try / catch で例外を吸い上げる(外部 API の障害で落ちないように)
isError: trueを付けて返す(Claude にエラーだと伝える)
の3点。これだけで、サーバーが落ちずに「困ったときに困った理由を返す」状態にできます。
デバッグの方法
サーバーが思ったように動かないとき、よく使う確認手段は3つです。
1. サーバー本体をターミナルで直接起動して見る
tsx ~/mcp-weather/src/index.ts
エラーが起きていれば、ここでスタックトレースが見えます。「サーバーが起動すらしていない」ケースは、ここでだいたい原因がわかります。
2. Claude Code の /mcp で接続状態を見る
/mcp
weather が Connected になっているか、Tool が見えているか。エラーがあれば /mcp の表示に出ることがあります。
3. ログ出力を仕込む
サーバーのコードに console.error を仕込むと、Claude Code の MCP ログに出力されます。console.log(標準出力)は MCP の通信に使われるので 使ってはいけない 点だけ注意してください。
console.error(`[debug] get_weather called with city=${city}`);
このログは、Claude Code のログフォルダ(~/.claude/logs/ 配下)で確認できます。
公開・配布の選択肢
自作サーバーが手元で動くようになったら、公開する という選択肢があります。
| 公開先 | 向いている場面 |
|---|---|
| GitHub の自分のリポジトリ | 個人プロジェクトとして配布 |
| npm レジストリ | 「npx でインストールできる」状態にしたい |
| マーケットプレイス(プラグイン経由) | プラグインの中身として MCP サーバーを同梱して配布 |
| 社内 Git のみ | 社外には出さず、社内で配布 |
最初のうちは GitHub に置くだけ で十分です。npm 公開やマーケットプレイス配布は、サーバーが安定して使える状態になり、他の人にも配ろうと思った段階で検討すれば足ります。
自作の心構え
最後にひとつだけ、自作 MCP サーバーへの心構えを。
「使う側」と「作る側」では、求められる責任が変わります。自分のサーバーが間違った返答をしたら、Claude はそれを信じて動きます。 認証情報を扱うサーバーなら、その情報の保管・破棄まで自分の責任です。
ただ、最初の1個目は 「動かしてみる」 が圧倒的に大事です。実用に耐えるサーバーは、最初の1個を動かして、3個目くらいで形が見えてきます。本記事のダミー応答からスタートして、徐々に 「自分の作業に直結する Tool」 に育てていく順序がおすすめです。
まとめ
MCP サーバー自作の要点を整理します。
- 最小構成は 30〜50 行:プログラミング経験があれば1日で書ける
- TypeScript SDK が Web 制作経験者には親しみやすい:JavaScript の延長で書ける
- 必要なのは
Server+ 2つのハンドラ:Tool 一覧 + Tool 呼び出し - Claude Code への接続は
claude mcp add <name> tsx -- <パス> descriptionとinputSchemaの質が呼び出し精度を左右する- エラーハンドリングは
isError: trueで返す:try / catch で例外を吸う - デバッグは「直接起動」「
/mcp」「console.errorログ」の3点 console.logは MCP の通信を壊すので禁止- 公開は GitHub に置くだけから:npm・マーケットプレイスは段階的に
- 作る側の責任が増える:返答が間違えば Claude も間違える
これで MCP シリーズの3本(とは/使う/自作する)を読み終わりました。MCP は概念が広く、最初は身構えがちですが、実際にやってみると素朴な仕組みの組み合わせ であることが体感できたと思います。
次の記事「Claude Code の Hooks とは」では、Step 3 の残るテーマ Hooks(フック) に進みます。Hooks は「特定のイベントで自動的にシェルコマンドを走らせるしくみ」で、Skills や MCP とはまた違った角度で Claude Code の挙動を拡張できる機能です。続けて読むと、「Claude を賢くする」 の Skills、「外の世界に繋ぐ」 の MCP、「タイミングで動かす」 の Hooks という3つの拡張軸が出揃います。
WordPressを実際に動かしてきたサーバー:ロリポップ
Claude Code でWordPressサイトを組み立てるとき、最初に置く先として無理のないレンタルサーバー。月数百円から始められ、WordPressの自動インストールにも対応しています。設定で詰まりがちな初期段階の時間をかなり減らせます。