MCPサーバーの作り方は、2026年7月28日の仕様改訂とPython SDK v2で大きく変わった。ネット上に残る2025年のチュートリアルの多くは、いまそのまま実行しても動かない。この記事はTypeScript・Pythonの両公式SDKで実際に手を動かしながら、いま動く書き方でMCPサーバーをゼロから構築する手順をまとめたものだ。まずは完成形を30秒で見てほしい。

MCPサーバー構築デモ — セットアップからClaude Code接続まで

30秒でわかる「MCPサーバーの作り方」所要時間:ツール1個の最小サーバーなら30分〜1時間。DB連携など実用サーバーでも2〜3時間
言語:公式SDKは10言語。迷ったらTypeScript(本番・配布向き)かPython(プロトタイプ向き)
作る流れ:①SDKを入れる → ②ツールを関数として定義 → ③stdioで起動 → ④MCP Inspectorで検証 → ⑤claude mcp add で接続、の5ステップ
2026年8月の最重要注意点pip install mcpv2系を入れる。2025年の記事にある from mcp.server.fastmcp import FastMCPv2では ModuleNotFoundError になる(実測。後述の対照表を参照)
TypeScriptは事情が違う:npmの @modelcontextprotocol/sdkまだ1.x系(最新1.30.0)。Pythonだけが先にv2へ移った

MCPサーバーとは何か:AIツール拡張の新標準プロトコル

2024年11月にAnthropicが公開したModel Context Protocol(MCP)は、AIアシスタントと外部ツールを接続するオープンな標準プロトコルだ。2026年8月時点で、Claude Code、Cursor、Cline、Windsurf、VS Code Copilotなど主要なAI開発ツールがMCPクライアントとして対応し、公式リポジトリ modelcontextprotocol/servers にはGitHub Star 89,000超が集まっている。

MCPが解決する問題はシンプルだ。従来、AIツールに「ファイルを読む」「データベースを検索する」「APIを叩く」といった機能を追加するには、ツールごとに独自のプラグイン仕様を学ぶ必要があった。MCPは1つのプロトコルで複数のAIクライアントに対応できるため、一度サーバーを作れば Claude Code でも Cursor でも使い回せる。

MCPの通信構造: MCPクライアント(Claude Code, Cursor等)がJSON-RPC over stdio / Streamable HTTPでMCPサーバー(自作ツール)と通信し、MCPサーバーが任意のプロトコルで外部リソース(DB, API, ファイル等)にアクセスする。

MCPサーバーが提供できる機能は3種類ある。

機能 説明 具体例
Tools(ツール) AIが呼び出せる関数 ファイル書き込み、DB検索、API呼び出し
Resources(リソース) AIが読み取れるデータ 設定ファイル、ログ、ドキュメント
Prompts(プロンプト) 再利用可能なプロンプトテンプレート コードレビュー用プロンプト、SQL生成テンプレート

この記事では、TypeScriptとPythonの両方の公式SDKを使って、実用的なMCPサーバーをゼロからステップバイステップで構築する方法を解説する。

graph LR A["AIクライアント
Claude Code / Cursor"] -->|"JSON-RPC"| B["MCPサーバー
自作ツール"] B -->|"読み取り"| C["ファイルシステム"] B -->|"クエリ"| D["データベース"] B -->|"HTTP"| E["外部API"] B -->|"ツール定義"| A style A fill:#4A90D9,color:#fff style B fill:#7B68EE,color:#fff style C fill:#50C878,color:#fff style D fill:#50C878,color:#fff style E fill:#50C878,color:#fff

MCPサーバーの作り方:全体像を5ステップで把握する

個別のコードに入る前に、完成までの道筋を一度俯瞰しておく。どの言語を選んでも、MCPサーバー作りは次の5ステップに分解できる。この記事の構成もこの順番に沿っている。

ステップ やること 使うもの つまずきやすい点
① 環境準備 SDKをインストールし、プロジェクトを初期化 npm / uvpip Pythonはv1とv2でimport文が違う(後述)
② ツール定義 AIに呼ばせたい処理を関数として書く server.tool() / @mcp.tool() 説明文(description)が雑だとAIが呼んでくれない
③ 起動 stdioトランスポートでサーバーを走らせる StdioServerTransport console.log を使うとJSON-RPCが壊れる
④ 動作確認 ツールが登録され、実際に動くかを単体で検証 MCP Inspector クライアントに繋ぐ前にここで潰す
⑤ 接続 AIクライアントに登録して実際に使う claude mcp add パスは絶対パスで指定する
先に④を用意すると開発が一気に速くなる
初心者がいちばん時間を溶かすのは「Claude Codeに繋いだが動かない。サーバーが悪いのか設定が悪いのか分からない」という状態だ。MCP Inspector(npx @modelcontextprotocol/inspector)はクライアント抜きでサーバー単体を叩けるので、③が終わった直後にInspectorで確認する癖をつけると、切り分けの手間がほぼ消える。

そもそも自作すべきか:既存のMCPサーバーで足りないかを先に確認する

作り方の解説に入る前に、身も蓋もない前提を1つ置いておきたい。あなたがやりたいことは、すでに誰かがMCPサーバーにしている可能性が高いmodelcontextprotocol/servers のStarが89,000を超えている理由の大半は、公式・コミュニティ製サーバーの厚みにある。

自作が正解になるのは、次のいずれかに当てはまるときだ。

社内固有の対象を触りたい——社内API・独自DB・オンプレの業務システムなど、外部に存在しようがないもの
既存サーバーの粒度が合わない——汎用のDBサーバーだと権限が広すぎる、逆に細かすぎてAIが手順を間違える
複数の操作を1つの「意図」にまとめたい——「デプロイ前チェック」のように、内部で3〜4個のAPIを叩く処理を1ツールに畳む
認証・監査を自分で握りたい——どのツールが何を実行したかを自社のログ基盤に流す必要がある

逆に「GitHubを操作したい」「Slackを読みたい」「PostgreSQLを検索したい」といった一般的な対象は、まず既存サーバーを試す方が早い。既存サーバーを1つ動かしてMCPの挙動を体感してから自作に入るのが、遠回りに見えて最短ルートだ。既存サーバーの探し方は本記事後半の「公式MCPサーバーの活用と既存エコシステム」で扱う。

【2026年8月版】2025年の記事どおりに作ると動かない:Python SDK v2の破壊的変更

ここが本記事でもっとも重要な節だ。MCPは2026年7月28日に大きな仕様改訂を迎え、Python SDKはそれに合わせてv2.0.0(2026年7月28日リリース)へメジャーバージョンアップした。日本語で読めるMCPサーバー構築記事の多くは2025年に書かれており、そのままなぞると最初のimport文で止まる。

実測:v1とv2で何が通り、何が落ちるか

同じスクリプトを、pip install "mcp>=1.28,<2"(結果1.29.0)と pip install mcp(結果2.0.0)の2つのvenvで実行して比較した。検証環境: macOS / Python 3.14.4 / 2026-08-11実施

書き方 mcp 1.29.0(v1) mcp 2.0.0(v2)
from mcp.server.fastmcp import FastMCP ✅ 通る ModuleNotFoundError: No module named ‘mcp.server.fastmcp’
FastMCP("demo")@srv.tool() ✅ 通る ❌ 上記で停止
from mcp.server import MCPServer ❌ AttributeError ✅ 通る
MCPServer("demo")@s.tool() ❌ 上記で停止 ✅ 通る
from mcp.types import Tool ✅ 通る ✅ 通る(mcp_types への永続エイリアス)
import mcp_types ❌ ModuleNotFoundError ✅ 通る(型が独立パッケージ化)
from mcp.server import Server(低レベルAPI) ✅ 通る ✅ 通る

読み取れることは3つある。FastMCP というクラス名はv2で消え、MCPServer になった②デコレータ(@tool())の書き味は変わっていないので、移行で書き直すのは主にimport文とクラス名だ。mcp.types は残るが、型定義本体は mcp-types という別パッケージに切り出された

いま2025年のチュートリアルを開いている人へ
記事どおりに pip install mcp して from mcp.server.fastmcp import FastMCP を書くと、v2が入るため確実に ModuleNotFoundError になる。対処は2択:
記事をそのまま完走したいpip install "mcp>=1.28,<2" でv1に固定する(v1系はメンテナンスモードで、以後はセキュリティ修正のみ)
これから長く使う → v2で書く。FastMCPMCPServer に置き換えるところから始める

TypeScriptとPythonでバージョン事情が食い違っている

ここを混同すると混乱するので、明示しておく。v2化したのはPython SDKだけで、TypeScript SDKはまだ1.x系だ。

SDK 最新版(2026-08-11時点) メジャー移行
Python(mcp 2.0.0(2026-07-28) 済み。v1はメンテナンスモード
TypeScript(@modelcontextprotocol/sdk 1.30.0(2026-07-27) まだ1.x系

つまり本記事のTypeScriptのコードは現行のまま有効で、Pythonのコードだけがv1向けの書き方という状態にある(該当箇所には注記を入れた)。「両方いっぺんに壊れた」わけではない点は押さえておきたい。

プロトコル仕様側(2026-07-28改訂)の変更点

SDKの下にあるプロトコル自体も変わっている。サーバーを書くうえで影響が大きいものを挙げる。

ステートレス化——initialize / notifications/initialized のハンドシェイクが廃止された。各リクエストが _meta でプロトコルバージョンとクライアント能力を自己申告する
server/discover の必須化——サーバーは対応バージョン・能力・自身の識別情報を返すこのRPCをMUSTで実装する
セッションの廃止——Streamable HTTPから Mcp-Session-Id ヘッダーが消えた。状態を持ちたい場合はサーバーが発行したハンドルを通常のツール引数として渡す
Roots / Sampling / Logging が非推奨に——新規実装では使わない。ログは stderr かOpenTelemetryへ、Samplingは LLM プロバイダのAPIを直接叩く方式へ移行する
HTTP+SSEトランスポートが正式にDeprecated——2025-03-26から非推奨だったものが、feature lifecycleポリシー上の「Deprecated」に正式に再分類された。新規サーバーはStreamable HTTPで書く
MRTR(Multi Round-Trip Requests)——サーバーからクライアントを呼ぶ方式が廃止され、サーバーは「追加で聞きたいこと」を InputRequiredResult として返し、クライアントが再送で答える形になった

なお公式は12か月の非推奨期間を定めており、Deprecated入りした機能がすぐ消えるわけではない。ただし新規に作るサーバーでわざわざ採用する理由はない。

ステップ①:MCPサーバー開発環境のセットアップ手順(TypeScript / Python)

TypeScript SDK(推奨)

TypeScript SDKは最も成熟しており、公式サーバーの大半がTypeScriptで書かれている。

# プロジェクト作成
mkdir my-mcp-server && cd my-mcp-server
npm init -y

# MCP SDK と 型定義をインストール
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

# TypeScript設定
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --outDir build --rootDir src --strict true
mkdir src

package.json に以下を追加する。

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "bin": {
    "my-mcp-server": "./build/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  }
}

Python SDK

Python SDKは mcp パッケージとして提供されている。高レベルAPIが用意されており、デコレータベースで直感的にサーバーを定義できる。

# uvを使う場合(推奨)
uv init my-mcp-server && cd my-mcp-server
uv add "mcp[cli]"

# pipを使う場合
pip install "mcp[cli]"
バージョンに注意:以降のPythonコードはv1系(mcp>=1.28,<2)の書き方
上の uv add "mcp[cli]" / pip install "mcp[cli]" は、いま実行すると v2.0.0 が入る。v2ではクラス名が FastMCPMCPServer に変わっており、以降のサンプルにある from mcp.server.fastmcp import FastMCPModuleNotFoundError になる(前掲の実測表を参照)。
サンプルをそのまま動かすならuv add "mcp[cli]>=1.28,<2"pip install "mcp[cli]>=1.28,<2" でv1に固定する
v2で書くならfrom mcp.server import MCPServer に読み替え、FastMCP("my-server")MCPServer("my-server") にする。デコレータ(@mcp.tool())以下の書き方は変わらないので、置き換えは機械的に済む

Python SDKの FastMCP(v2では MCPServer)は、Flaskのようなデコレータスタイルでツールを定義できるのが特徴だ。

from mcp.server.fastmcp import FastMCP

# サーバーインスタンス作成
mcp = FastMCP("my-server")

# ツール定義(デコレータで宣言)
@mcp.tool()
def hello(name: str) -> str:
    """挨拶を返すツール"""
    return f"こんにちは、{name}さん!"

# サーバー起動
if __name__ == "__main__":
    mcp.run()

公式SDKの全体像(10言語対応)

MCPは2026年8月時点で10言語の公式SDKが提供されている(Star数は2026-08-11にGitHub APIで取得した実測値)。

SDK Tier GitHub Stars 推奨用途
Python Tier 1 23,962 デコレータで高速開発、データ分析連携
TypeScript Tier 1 13,119 公式サーバーの主要言語、本番運用・配布
Go Tier 1 4,962 高速・軽量サーバー
C# Tier 1 4,462 .NETエンタープライズ環境
Rust Tier 2 3,778 高パフォーマンス要件
Java Tier 2 3,648 Spring Boot連携
PHP Tier 3 1,576 WordPress/Laravel連携
Swift Tier 3 1,462 macOS/iOSネイティブ
Kotlin Tier 3 1,433 Android・JVM連携
Ruby Tier 3 882 Railsアプリ連携

Tier 1は公式が完全サポート、Tier 2はコミュニティ主導+公式レビュー、Tier 3はコミュニティ主導。Star数はそのまま成熟度を意味しない——Pythonが突出しているのはデータ分析・AI領域の母数が大きいためで、本番サーバーの配布しやすさではTypeScriptに分がある。

TypeScript vs Python SDKの選択基準

基準 TypeScript SDK Python SDK (FastMCP)
成熟度 ★★★★★ 公式サーバーの主要言語 ★★★★★ Star 22,500+で最も人気
学習コスト 中(Zodスキーマ定義が必要) 低(デコレータのみ)
型安全性 完全な型推論 Pydantic連携で型検証
デプロイ npx で即実行可能 uv run または python -m
向いている用途 大規模・本番運用 プロトタイプ・データ分析連携

実践1:ファイル操作MCPサーバーをTypeScriptで構築する

最初のサーバーとして、指定ディレクトリ内のファイルを検索・読み取り・要約できるツールを作る。AIアシスタントが「プロジェクト内のREADMEを読んで」と言われたときに、このサーバーを通じてファイルにアクセスする。

サーバー本体の実装

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";
import * as fs from "fs/promises";
import * as path from "path";

// サーバーインスタンス作成
const server = new McpServer({
  name: "file-explorer",
  version: "1.0.0",
});

// ルートディレクトリ(環境変数 or デフォルト)
const ROOT_DIR = process.env.MCP_ROOT_DIR || process.cwd();

// ツール1: ファイル一覧取得
server.tool(
  "list_files",
  "指定パターンにマッチするファイルの一覧を返す",
  {
    directory: z.string().describe("検索するディレクトリの相対パス"),
    extension: z.string().optional().describe("フィルタする拡張子(例: .md, .ts)"),
  },
  async ({ directory, extension }) => {
    const targetDir = path.resolve(ROOT_DIR, directory);
    
    // ディレクトリトラバーサル防止
    if (!targetDir.startsWith(ROOT_DIR)) {
      return { content: [{ type: "text", text: "エラー: ルートディレクトリ外へのアクセスは禁止" }] };
    }
    
    const entries = await fs.readdir(targetDir, { withFileTypes: true, recursive: true });
    let files = entries
      .filter(e => e.isFile())
      .map(e => path.relative(ROOT_DIR, path.join(e.parentPath || targetDir, e.name)));
    
    if (extension) {
      files = files.filter(f => f.endsWith(extension));
    }
    
    return {
      content: [{ type: "text", text: files.join("\n") || "ファイルが見つかりません" }],
    };
  }
);

// ツール2: ファイル読み取り
server.tool(
  "read_file",
  "指定パスのファイル内容を返す",
  {
    file_path: z.string().describe("読み取るファイルの相対パス"),
    max_lines: z.number().optional().default(200).describe("最大行数(デフォルト200)"),
  },
  async ({ file_path, max_lines }) => {
    const fullPath = path.resolve(ROOT_DIR, file_path);
    
    if (!fullPath.startsWith(ROOT_DIR)) {
      return { content: [{ type: "text", text: "エラー: ルートディレクトリ外へのアクセスは禁止" }] };
    }
    
    const content = await fs.readFile(fullPath, "utf-8");
    const lines = content.split("\n").slice(0, max_lines);
    const truncated = content.split("\n").length > max_lines
      ? `\n\n--- (${content.split("\n").length - max_lines}行省略) ---`
      : "";
    
    return {
      content: [{ type: "text", text: lines.join("\n") + truncated }],
    };
  }
);

// ツール3: ファイル内テキスト検索
server.tool(
  "search_files",
  "ファイル内容をキーワード検索し、マッチした行を返す",
  {
    query: z.string().describe("検索キーワード"),
    directory: z.string().optional().default(".").describe("検索ディレクトリ"),
    extension: z.string().optional().describe("対象拡張子"),
  },
  async ({ query, directory, extension }) => {
    const targetDir = path.resolve(ROOT_DIR, directory);
    if (!targetDir.startsWith(ROOT_DIR)) {
      return { content: [{ type: "text", text: "エラー: アクセス禁止" }] };
    }
    
    const entries = await fs.readdir(targetDir, { withFileTypes: true, recursive: true });
    const results: string[] = [];
    
    for (const entry of entries) {
      if (!entry.isFile()) continue;
      const filePath = path.join(entry.parentPath || targetDir, entry.name);
      if (extension && !filePath.endsWith(extension)) continue;
      
      try {
        const content = await fs.readFile(filePath, "utf-8");
        const lines = content.split("\n");
        lines.forEach((line, i) => {
          if (line.toLowerCase().includes(query.toLowerCase())) {
            const relPath = path.relative(ROOT_DIR, filePath);
            results.push(`${relPath}:${i + 1}: ${line.trim()}`);
          }
        });
      } catch { /* バイナリファイル等はスキップ */ }
    }
    
    return {
      content: [{ type: "text", text: results.slice(0, 50).join("\n") || "マッチなし" }],
    };
  }
);

// リソース: プロジェクト情報
server.resource(
  "project-info",
  "project://info",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: "text/plain",
      text: `ルートディレクトリ: ${ROOT_DIR}\nサーバー: file-explorer v1.0.0`,
    }],
  })
);

// サーバー起動
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("file-explorer MCPサーバーが起動しました");
}

main().catch(console.error);

ビルドと動作確認

# ビルド
npm run build

# MCP Inspectorで動作確認(公式デバッグツール)
npx @modelcontextprotocol/inspector node ./build/index.js

MCP Inspectorはブラウザベースのデバッグツールで、ツールの一覧確認・テスト実行・レスポンス検証ができる。http://localhost:6274 でUIが開く。

sequenceDiagram participant C as Claude Code participant S as MCPサーバー participant F as ファイルシステム C->>S: initialize(接続開始) S-->>C: capabilities(ツール一覧) C->>S: tools/call "list_files" S->>F: readdir(ディレクトリ読み取り) F-->>S: ファイル一覧 S-->>C: 結果(JSON) C->>S: tools/call "read_file" S->>F: readFile(ファイル読み取り) F-->>S: ファイル内容 S-->>C: 結果(JSON)

実践2:データベース連携MCPサーバーをPythonで構築する

次に、SQLiteデータベースに自然言語でクエリできるMCPサーバーをPythonで作る。「売上トップ10の商品を教えて」とAIに聞くと、このサーバーがSQLクエリを実行して結果を返す。

サーバー実装

import sqlite3
from pathlib import Path
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("db-explorer")

# データベースパス(環境変数 or デフォルト)
DB_PATH = Path(__file__).parent / "data.db"

def get_connection() -> sqlite3.Connection:
    conn = sqlite3.connect(str(DB_PATH))
    conn.row_factory = sqlite3.Row
    return conn

@mcp.tool()
def list_tables() -> str:
    """データベース内の全テーブルとカラム情報を返す"""
    conn = get_connection()
    cursor = conn.execute(
        "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
    )
    tables = [row["name"] for row in cursor]
    
    result = []
    for table in tables:
        cols = conn.execute(f"PRAGMA table_info({table})").fetchall()
        col_info = ", ".join(f"{c['name']} ({c['type']})" for c in cols)
        count = conn.execute(f"SELECT COUNT(*) as cnt FROM {table}").fetchone()["cnt"]
        result.append(f"📊 {table} ({count}行): {col_info}")
    
    conn.close()
    return "\n".join(result) if result else "テーブルが見つかりません"

@mcp.tool()
def run_query(sql: str) -> str:
    """SQLクエリを実行して結果を返す(SELECT文のみ)
    
    Args:
        sql: 実行するSELECTクエリ
    """
    # SELECT文のみ許可(安全対策)
    normalized = sql.strip().upper()
    if not normalized.startswith("SELECT"):
        return "エラー: SELECT文のみ実行可能です(安全対策)"
    
    conn = get_connection()
    try:
        cursor = conn.execute(sql)
        rows = cursor.fetchmany(100)  # 最大100行
        
        if not rows:
            return "結果: 0行"
        
        # ヘッダー
        headers = [desc[0] for desc in cursor.description]
        lines = [" | ".join(headers)]
        lines.append("-" * len(lines[0]))
        
        # データ行
        for row in rows:
            lines.append(" | ".join(str(v) for v in row))
        
        total = conn.execute(f"SELECT COUNT(*) FROM ({sql})").fetchone()[0]
        if total > 100:
            lines.append(f"\n(100/{total}行を表示)")
        
        return "\n".join(lines)
    except sqlite3.Error as e:
        return f"SQLエラー: {e}"
    finally:
        conn.close()

@mcp.tool()
def get_sample_data(table_name: str, limit: int = 5) -> str:
    """テーブルのサンプルデータを返す
    
    Args:
        table_name: テーブル名
        limit: 取得行数(デフォルト5)
    """
    conn = get_connection()
    try:
        # テーブル名のバリデーション(SQLインジェクション防止)
        tables = [r["name"] for r in conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table'"
        )]
        if table_name not in tables:
            return f"エラー: テーブル '{table_name}' は存在しません"
        
        cursor = conn.execute(f"SELECT * FROM [{table_name}] LIMIT ?", (limit,))
        rows = cursor.fetchall()
        
        if not rows:
            return "データなし"
        
        headers = [desc[0] for desc in cursor.description]
        lines = [" | ".join(headers)]
        for row in rows:
            lines.append(" | ".join(str(v) for v in row))
        
        return "\n".join(lines)
    finally:
        conn.close()

# リソース: スキーマ情報
@mcp.resource("schema://all")
def get_schema() -> str:
    """全テーブルのCREATE TABLE文を返す"""
    conn = get_connection()
    schemas = conn.execute(
        "SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL"
    ).fetchall()
    conn.close()
    return "\n\n".join(row["sql"] for row in schemas)

if __name__ == "__main__":
    mcp.run()

テスト用データベース作成

# テスト用DBを作成
python3 -c "
import sqlite3
conn = sqlite3.connect('data.db')
conn.execute('''CREATE TABLE products (
    id INTEGER PRIMARY KEY, name TEXT, price INTEGER, category TEXT, stock INTEGER
)''')
conn.executemany('INSERT INTO products VALUES (?,?,?,?,?)', [
    (1, 'AIカメラ', 29800, 'ガジェット', 150),
    (2, 'スマートスピーカー', 12800, 'ガジェット', 300),
    (3, 'コーディングキーボード', 19800, 'デバイス', 80),
    (4, 'LLMサーバーGPU', 498000, 'サーバー', 12),
    (5, 'USBマイク', 5980, 'デバイス', 500),
])
conn.commit()
conn.close()
print('テストDB作成完了')
"

# MCP Inspectorで動作確認
mcp dev server.py

実践3:外部API連携MCPサーバーの構築パターン

3つ目のパターンとして、外部REST APIをラップするMCPサーバーを作る。ここでは GitHub APIを叩いて、リポジトリ情報を取得するサーバーを TypeScript で実装する。

#!/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: "github-explorer",
  version: "1.0.0",
});

const GITHUB_TOKEN = process.env.GITHUB_TOKEN || "";

async function githubFetch(endpoint: string) {
  const headers: Record<string, string> = {
    "Accept": "application/vnd.github.v3+json",
    "User-Agent": "mcp-github-explorer",
  };
  if (GITHUB_TOKEN) {
    headers["Authorization"] = `Bearer ${GITHUB_TOKEN}`;
  }
  
  const res = await fetch(`https://api.github.com${endpoint}`, { headers });
  if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
  return res.json();
}

// ツール: リポジトリ情報取得
server.tool(
  "get_repo",
  "GitHubリポジトリの詳細情報を取得する",
  {
    owner: z.string().describe("リポジトリオーナー"),
    repo: z.string().describe("リポジトリ名"),
  },
  async ({ owner, repo }) => {
    const data = await githubFetch(`/repos/${owner}/${repo}`);
    const info = [
      `📦 ${data.full_name}`,
      `⭐ ${data.stargazers_count.toLocaleString()} stars`,
      `🍴 ${data.forks_count.toLocaleString()} forks`,
      `📝 ${data.description || "説明なし"}`,
      `🔧 言語: ${data.language || "不明"}`,
      `📅 最終更新: ${new Date(data.updated_at).toLocaleDateString("ja-JP")}`,
      `📄 ライセンス: ${data.license?.spdx_id || "不明"}`,
    ].join("\n");
    
    return { content: [{ type: "text", text: info }] };
  }
);

// ツール: リポジトリ検索
server.tool(
  "search_repos",
  "キーワードでGitHubリポジトリを検索する",
  {
    query: z.string().describe("検索キーワード"),
    sort: z.enum(["stars", "forks", "updated"]).optional().default("stars"),
    limit: z.number().optional().default(10),
  },
  async ({ query, sort, limit }) => {
    const data = await githubFetch(
      `/search/repositories?q=${encodeURIComponent(query)}&sort=${sort}&per_page=${limit}`
    );
    
    const results = data.items.map((item: any, i: number) =>
      `${i + 1}. ${item.full_name} (⭐${item.stargazers_count}) - ${item.description || ""}`
    );
    
    return {
      content: [{ type: "text", text: `検索結果 (${data.total_count}件中${limit}件):\n\n${results.join("\n")}` }],
    };
  }
);

// ツール: Issue一覧取得
server.tool(
  "list_issues",
  "リポジトリのオープンIssue一覧を取得する",
  {
    owner: z.string(),
    repo: z.string(),
    limit: z.number().optional().default(10),
  },
  async ({ owner, repo, limit }) => {
    const data = await githubFetch(`/repos/${owner}/${repo}/issues?state=open&per_page=${limit}`);
    
    const issues = data.map((issue: any) =>
      `#${issue.number} ${issue.title} (${issue.labels.map((l: any) => l.name).join(", ") || "ラベルなし"})`
    );
    
    return {
      content: [{ type: "text", text: issues.join("\n") || "オープンIssueなし" }],
    };
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);

3パターンの使い分け

パターン 通信先 セキュリティ 代表的な用途
ファイル操作 ローカルFS ディレクトリトラバーサル防止 プロジェクト探索、ドキュメント管理
DB連携 SQLite/PostgreSQL SELECT文のみ許可 データ分析、レポート生成
外部API REST/GraphQL APIキー管理、レート制限 GitHub連携、Slack通知

MCPサーバーをAIクライアントに接続する方法

Claude Codeとの接続

Claude Codeは claude mcp add コマンドで簡単にMCPサーバーを登録できる。

# TypeScriptサーバーを登録
claude mcp add file-explorer node /path/to/build/index.js

# 環境変数付きで登録
claude mcp add github-explorer \
  -e GITHUB_TOKEN=ghp_xxxxxxxxxxxx \
  node /path/to/build/index.js

# Pythonサーバーを登録
claude mcp add db-explorer python /path/to/server.py

# 登録済みサーバー一覧
claude mcp list

# サーバー削除
claude mcp remove file-explorer

MCPサーバーの設定ファイルは ~/.claude/claude_desktop_config.json に保存される。手動で編集することも可能だ。

{
  "mcpServers": {
    "file-explorer": {
      "command": "node",
      "args": ["/path/to/build/index.js"],
      "env": {
        "MCP_ROOT_DIR": "/home/user/projects"
      }
    },
    "db-explorer": {
      "command": "python",
      "args": ["/path/to/server.py"]
    }
  }
}

Cursorとの接続

Cursorは .cursor/mcp.json をプロジェクトルートに配置する。AIコーディングツールの選び方については、Claude CodeとCursorの違い比較記事も参照してほしい。

{
  "mcpServers": {
    "file-explorer": {
      "command": "node",
      "args": ["./build/index.js"]
    }
  }
}

VS Code + Copilotとの接続

VS Codeの設定(settings.json)に追加する。

{
  "mcp": {
    "servers": {
      "file-explorer": {
        "command": "node",
        "args": ["./build/index.js"]
      }
    }
  }
}
graph TB subgraph Clients["AIクライアント"] CC["Claude Code
claude mcp add"] CU["Cursor
.cursor/mcp.json"] VS["VS Code Copilot
settings.json"] end subgraph Servers["自作MCPサーバー"] S1["file-explorer
ファイル操作"] S2["db-explorer
DB連携"] S3["github-explorer
API連携"] end CC --> S1 CC --> S2 CC --> S3 CU --> S1 CU --> S3 VS --> S1 style CC fill:#D4A574,color:#000 style CU fill:#4A90D9,color:#fff style VS fill:#007ACC,color:#fff style S1 fill:#50C878,color:#fff style S2 fill:#50C878,color:#fff style S3 fill:#50C878,color:#fff

MCPサーバー開発のセキュリティベストプラクティス

MCPサーバーはAIにシステムリソースへのアクセス権を与えるため、セキュリティ設計が極めて重要だ。AgentSealの調査によると、1,808のMCPサーバーのうち66%に脆弱性が発見され、そのうち43%がシェルインジェクションだった。OWASPもGen AIセキュリティプロジェクトでMCPサーバー開発ガイドを公開している。

入力バリデーション

// ❌ 悪い例:パストラバーサル脆弱性
server.tool("read", { path: z.string() }, async ({ path }) => {
  const content = await fs.readFile(path, "utf-8"); // ../../etc/passwd が読める
  return { content: [{ type: "text", text: content }] };
});

// ✅ 良い例:ルートディレクトリ制限
server.tool("read", { path: z.string() }, async ({ path }) => {
  const fullPath = path.resolve(ROOT_DIR, path);
  if (!fullPath.startsWith(ROOT_DIR)) {
    throw new Error("Access denied");
  }
  const content = await fs.readFile(fullPath, "utf-8");
  return { content: [{ type: "text", text: content }] };
});

セキュリティチェックリスト

チェック項目 対策
パストラバーサル path.resolve() + プレフィックスチェック
SQLインジェクション パラメータバインディング、SELECT文のみ許可
APIキー漏洩 環境変数で管理、レスポンスに含めない
過剰なアクセス 最小権限の原則(読み取り専用等)
レート制限 外部API呼び出しにはスロットリングを実装
大量データ 結果の最大行数を制限

認証とアクセス制御

import os
from functools import wraps

def require_auth(func):
    """環境変数でAPIキーを検証するデコレータ"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        api_key = os.environ.get("MCP_API_KEY")
        if not api_key:
            return "エラー: MCP_API_KEY 環境変数が未設定です"
        return func(*args, **kwargs)
    return wrapper

@mcp.tool()
@require_auth
def sensitive_operation(query: str) -> str:
    """認証が必要な操作"""
    # ...

MCPサーバーをリモート公開する:トランスポートの選び方

ローカルのstdio接続だけでなく、リモートからHTTP経由でアクセスできる構成も可能だ。チーム全体で1つのMCPサーバーを共有する場合に使う。ただしここは仕様変更の影響を最も強く受けた領域なので、選択肢を先に整理する。

トランスポート 接続方式 2026年8月時点の扱い 用途
stdio 標準入出力 ✅ 現役 ローカル開発、CLI統合。個人利用ならこれ一択
Streamable HTTP HTTP POST/レスポンス 推奨 本番デプロイ、チーム共有
HTTP+SSE HTTP(Server-Sent Events) ⚠️ Deprecated 新規採用しない。既存資産の互換維持のみ

HTTP+SSEは2025-03-26で非推奨化され、2026-07-28改訂で正式に「Deprecated」に再分類された。ネット上の記事には SSEServerTransport を使う例が今も多く残っているが、これから作るサーバーで採用する理由はない。公式は12か月の非推奨期間を設けているため即座に壊れはしないものの、移行先はStreamable HTTP一本と考えてよい。

Streamable HTTPで書く

Streamable HTTPは単一のHTTPエンドポイントでリクエスト・レスポンスを処理する方式で、ステートレスなデプロイが容易だ。TypeScript SDKでは @modelcontextprotocol/sdk/server/streamableHttp.jsStreamableHTTPServerTransport を使い、Expressなら app.post("/mcp", ...) の中で transport.handleRequest(req, res) を呼ぶだけで繋がる。

セッションIDの扱いが変わった
2026-07-28改訂で プロトコルレベルのセッションと Mcp-Session-Id ヘッダーが廃止された。古い記事にある sessionIdGenerator: () => crypto.randomUUID() のような指定は、旧プロトコル互換のためにSDK 1.x側でまだ受け付けられているが、新しい仕様に合わせるならセッション前提の設計をしないのが正しい。呼び出しをまたいで状態を保持したい場合は、サーバーが発行したハンドル(ID文字列)を通常のツール引数として明示的に受け渡す設計にする。

加えて、Streamable HTTPで公開する場合はPython SDK v2がリクエストボディ4MiB超をHTTP 413で拒否する点も頭に入れておきたい。大きなファイルを直接ツール引数で受け取る設計にせず、パスやURLを渡してサーバー側で読む形にするのが無難だ。

作ったMCPサーバーを配布する:npx / uvx で他人が使える形にする

自分の手元で動くところまで来たら、次は「他の人が1コマンドで使える形」にする段階だ。ここを飛ばすと、せっかく作ったサーバーがREADMEに git clone からの手順を延々並べる代物になってしまう。実際、公式・コミュニティの主要サーバーはほぼ例外なく npxuvx で直接起動できる形で配布されている。

配布形態 利用者側のコマンド 準備すること
npm(TypeScript) npx -y your-mcp-server package.jsonbin を定義し、エントリに #!/usr/bin/env node を付けて npm publish
PyPI(Python) uvx your-mcp-server pyproject.toml[project.scripts] にエントリポイントを定義して公開
ローカル配布 claude mcp add name node /abs/path/build/index.js 社内限定ならこれで十分。パスは必ず絶対パス

配布時に効いてくる細かい作法を3つ挙げておく。

実行ビットとshebang——TypeScriptでビルドした build/index.js は、chmod +x と先頭の #!/usr/bin/env node が無いと npx 経由で起動しない。ビルドスクリプトに含めておく
stdoutを絶対に汚さない——配布後に最も多い不具合報告がこれだ。依存ライブラリが起動時にバナーをstdoutへ出すだけでJSON-RPCが壊れる。公開前に一度、素の状態で起動して余計な出力が無いか確認する
READMEに接続設定をそのまま貼れる形で載せる——利用者がコピペできる claude mcp add の1行、あるいはクライアント設定JSONの断片を置く。ここが無いだけで導入率は大きく落ちる

社内配布なら公開レジストリに出さなくてよい
npmのプライベートレジストリやGitHub Packages、あるいは単に社内Gitリポジトリの絶対パスを claude mcp add で登録する形でも運用は成立する。「配布=公開」ではないので、社内APIを叩くサーバーを無理にパブリックへ出す必要はない。

公式MCPサーバーの活用と既存エコシステム

自前で作る前に、公式リポジトリ modelcontextprotocol/servers83,000+ Star)に収録されている既存サーバーを確認するのが効率的だ。2026年4月時点で200以上の公式・コミュニティサーバーが登録されている。

主要な公式サーバー

# ファイルシステム操作
npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/dir

# GitHub連携
npx -y @modelcontextprotocol/server-github

# PostgreSQL
npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb

# Google Drive
npx -y @modelcontextprotocol/server-gdrive

# Slack
npx -y @modelcontextprotocol/server-slack

サイト上では、Unity開発環境をMCPで拡張する方法や、LINE BotをMCPサーバーとして構築する事例セキュリティ診断MCPサーバーなど、実践的なMCPサーバーの解説記事も公開している。

コミュニティサーバーの探し方

MCP公式のサーバーディレクトリは以下で確認できる。

  • 公式サーバーリポジトリ: github.com/modelcontextprotocol/servers
  • Awesome MCP Servers: github.com/punkpeye/awesome-mcp-servers(コミュニティキュレーション、Star 40,000超)
  • Smithery.ai: MCPサーバーのレジストリサービス(2,000以上登録)
  • mcp.so: MCPサーバーの検索・比較サイト
graph LR subgraph Official["公式サーバー"] FS["filesystem
ファイル操作"] GH["github
GitHub連携"] PG["postgres
DB連携"] GD["gdrive
Google Drive"] end subgraph Community["コミュニティ"] UN["unity-mcp
Unity開発"] LN["line-bot-mcp
LINE Bot"] SC["mcp-for-security
セキュリティ"] TR["tradingview-mcp
トレード"] end subgraph Registry["レジストリ"] SM["Smithery.ai
2,000+サーバー"] AW["awesome-mcp-servers
Star 40K+"] end Official --> Registry Community --> Registry style FS fill:#4A90D9,color:#fff style GH fill:#4A90D9,color:#fff style PG fill:#4A90D9,color:#fff style GD fill:#4A90D9,color:#fff

MCPサーバーの作り方でつまずく定番エラーと解決策

開発中によく遭遇するエラーと解決策をまとめる。

よくあるエラーと解決策

エラー 原因 解決策
Server disconnected サーバープロセスがクラッシュ console.error でログ出力。stdoutにデバッグログを出すとJSON-RPCが壊れる
Tool not found ツール名の不一致 MCP Inspectorで登録済みツールを確認
Invalid params Zodスキーマと引数の不一致 スキーマ定義を確認。z.optional() の付け忘れに注意
ENOENT ファイルパスの解決失敗 path.resolve() で絶対パスに変換
EACCES 権限不足 chmod でファイル権限を確認

デバッグのコツ

# MCP Inspectorで対話的にテスト
npx @modelcontextprotocol/inspector node ./build/index.js

# Claude Codeのデバッグログ
claude mcp list  # 登録状況確認

# Pythonサーバーのデバッグ
mcp dev server.py  # 開発モードで起動

重要: MCPサーバーの console.log() はJSON-RPCのstdioストリームに書き込まれるため、デバッグ出力には必ず console.error() を使うこと。

// ❌ stdoutに出力 → JSON-RPCが壊れる
console.log("デバッグ情報");

// ✅ stderrに出力 → 安全
console.error("デバッグ情報");

まとめ:MCPサーバー構築の次のステップ

MCPサーバーの構築は、TypeScript SDKなら McpServer クラスに server.tool() でツールを登録するだけ、Python SDKなら FastMCP@mcp.tool() デコレータを付けるだけで始められる。

本記事で紹介した3パターンを組み合わせることで、AIアシスタントにファイル操作・データベース検索・外部API連携のあらゆる機能を追加できる。

次のステップとして推奨するのは以下の3つだ。

  1. 既存の公式サーバーを使ってみるnpx -y @modelcontextprotocol/server-filesystem から始めるのが最も簡単
  2. MCP Inspectorでデバッグ環境を整える — ブラウザでツールをテストできる
  3. 自分の業務に特化したサーバーを作る — 社内API、独自データベース、プロジェクト管理ツールとの連携

AIエージェント開発の全体像を把握するには、フレームワーク比較記事も参考にしてほしい。MCPサーバーは単体でも強力だが、エージェントフレームワークと組み合わせることで、より複雑なワークフローを構築できる。

MCPサーバーを本番運用する場合、Claude APIのコストが気になるところだ。Claude API料金シミュレーターで事前にコストを見積もっておこう。

よくある質問(FAQ)

Q: MCPの仕組みをもっと詳しく理解したい。

A: MCPはホスト(Claude Code等)・クライアント・サーバーの3層で成り立つJSON-RPCベースのプロトコルです。プロトコルの詳細な内部動作はMCPとは何か:AIに手足を与えるプロトコルの仕組みと実践ガイド2026で解説しています。サーバーを作る前に概念を押さえておくと実装がスムーズになります。

Q: MCPの設計思想や正しい使い方を知りたい。samplingとは何か?

A: Pydantic作者のSamuel Colvinが「MCP is all you need」で、MCPの本質とsamplingの正しい理解を解説しています。ツール数を増やすより「意図を伝えるツール設計」が重要というアドバイスは実装方針を根本から変えます。

Q: MCPサーバーの実用的な活用事例を知りたい。AnalyticsやColabと連携できるか?

A: Google Analytics MCPサーバーはGA4の分析をAIへの自然言語指示で完結させる公式ツールです。データ分析系の活用としてもっとも即効性が高い事例の一つです。機械学習・実験環境と連携したい場合はColab MCPでGoogle ColabをMCPサーバー化してAIエージェントから直接操作できます。

参照ソース