MCPサーバー自作ガイド【2026年版】——Python / TypeScriptで自社専用MCPサーバーを作り、Claude・Cursorから自社データベース・社内システムに安全に接続する

MCPサーバー自作ガイド【2026年版】——Python / TypeScriptで自社専用MCPサーバーを作り、Claude・Cursorから自社データベース・社内システムに安全に接続する


  1. はじめに——「既存MCPを接続する」から「自社MCPを作る」へ
  2. MCPサーバーの仕組みをおさらい——3つのプリミティブ(Tools / Resources / Prompts)
  3. 自社MCPサーバーを作る前の設計判断
    1. 何を「ツール」として公開するか
    2. PythonとTypeScript、どちらで作るか
    3. Stdio(ローカル)とHTTP(リモート)、どちらの接続方式か
  4. 【Python編】最小MCPサーバーを10分で作る
    1. 環境セットアップ(uv)
    2. 最初のMCPサーバー——ツールを1つ動かす
    3. MCP Inspectorでのデバッグ
    4. Claude Desktop / Claude Codeへの接続
  5. 【Python編・実践】社内データベースに接続するMCPサーバー
    1. SQLiteデータベース接続(社内顧客DB・在庫DB)
    2. Resourcesでスキーマ情報を公開する
    3. PostgreSQL / MySQLへの接続拡張
  6. 【TypeScript編】社内WikiとファイルサーバーのMCPサーバー
    1. プロジェクトセットアップ
    2. MarkdownファイルをResourcesとして公開する
    3. REST APIラッパーツールの作り方
  7. Claude Codeで自社MCPサーバーを「バイブコーディング」で作る
    1. 非エンジニアでも作れる:要件の言語化テンプレート
    2. Claude Codeへの指示プロンプト例
  8. セキュリティ設計——社内システムを安全に公開する
    1. アクセス制御の設計原則
    2. 読み取り専用制約と入力バリデーション
    3. APIキーと認証情報の安全な管理
    4. ログ・監査証跡の設計
  9. 社内ユースケース別:MCPサーバー設計テンプレート5選
  10. CursorでのMCPサーバー設定
  11. よくある質問(Q&A)
  12. まとめ——「AIへの窓口」を自社で設計する時代
  13. 参考リンク

  1. はじめに——「既存MCPを接続する」から「自社MCPを作る」へ
  2. MCPサーバーの仕組みをおさらい——3つのプリミティブ(Tools / Resources / Prompts)
  3. 自社MCPサーバーを作る前の設計判断
    1. 何を「ツール」として公開するか
    2. PythonとTypeScript、どちらで作るか
    3. Stdio(ローカル)とHTTP(リモート)、どちらの接続方式か
  4. 【Python編】最小MCPサーバーを10分で作る
    1. 環境セットアップ(uv)
    2. 最初のMCPサーバー——ツールを1つ動かす
    3. MCP Inspectorでのデバッグ
    4. Claude Desktop / Claude Codeへの接続
  5. 【Python編・実践】社内データベースに接続するMCPサーバー
    1. SQLiteデータベース接続(社内顧客DB・在庫DB)
    2. Resourcesでスキーマ情報を公開する
    3. PostgreSQL / MySQLへの接続拡張
  6. 【TypeScript編】社内WikiとファイルサーバーのMCPサーバー
    1. プロジェクトセットアップ
    2. MarkdownファイルをResourcesとして公開する
    3. REST APIラッパーツールの作り方
  7. Claude Codeで自社MCPサーバーを「バイブコーディング」で作る
    1. 非エンジニアでも作れる:要件の言語化テンプレート
    2. Claude Codeへの指示プロンプト例
  8. セキュリティ設計——社内システムを安全に公開する
    1. アクセス制御の設計原則
    2. 読み取り専用制約と入力バリデーション
    3. APIキーと認証情報の安全な管理
    4. ログ・監査証跡の設計
  9. 社内ユースケース別:MCPサーバー設計テンプレート5選
  10. CursorでのMCPサーバー設定
  11. よくある質問(Q&A)
    1. Q1. MCPサーバーの作成にどれくらいの時間がかかりますか?
    2. Q2. 既存のMCPサーバーと自作MCPサーバーを同時に使えますか?
    3. Q3. 本番データベースに直接接続することのリスクは何ですか?
    4. Q4. MCPサーバーのチーム共有はどうすれば良いですか?
    5. Q5. FastMCPとMCP SDKの低レベルAPIの違いは何ですか?
  12. まとめ——「AIへの窓口」を自社で設計する時代
  13. 参考リンク

はじめに——「既存MCPを接続する」から「自社MCPを作る」へ

MCP(Model Context Protocol)は、AIが外部ツール・データベース・サービスとやりとりするための標準プロトコルです。当サイトのMCP構築ガイド(ID 230)では、GitHubやSlackなど既存のMCPサーバーをClaudeに接続する方法を解説しました。

しかし実際のビジネス現場で最も価値が高いのは「既製品のMCPを接続すること」ではなく、「自社の顧客データベース・在庫管理システム・社内Wiki・基幹業務APIにClaudeが直接アクセスできるオリジナルMCPサーバーを作ること」です。

自社MCPサーバーを作ると、次のような体験が可能になります。

  • 「先月の売上上位10社のリストを出して、来月の提案書のドラフトを作ってください」——ClaudeがCRMに直接クエリして回答
  • 「在庫が30個を切った商品を教えて、発注メールの文面を作って」——Claudeが在庫DBを検索して一連の作業を実行
  • 「このコードは社内のコーディング規約に違反していますか?」——Claudeが社内Wikiを参照してレビュー
  • 「〇〇のAPIキーはどこに書いてあったっけ?」——Claudeが社内ドキュメントを検索して案内

本記事では、PythonとTypeScript両方の実装例を、コピペで動くコードと共に解説します。また、「非エンジニアでもClaude Codeを使ってバイブコーディングでMCPサーバーを作る」という切り口と、社内システムを公開する際のセキュリティ設計まで体系的に扱います。


MCPサーバーの仕組みをおさらい——3つのプリミティブ(Tools / Resources / Prompts)

MCPサーバーが公開できる機能は3種類(プリミティブ)に分類されます。自社MCPを設計する際の基本単位です。

プリミティブ役割AIの使い方自社実装の例
Tools(ツール)AIが「呼び出す関数」。副作用(データ変更・API呼び出し)を含められるユーザーの指示に応じてAIが能動的に呼び出す顧客検索・在庫照会・メール送信・注文登録
Resources(リソース)AIが「読む文書・データ」。読み取り専用のコンテキスト情報会話の文脈に応じてAIが参照情報として取り込むDBスキーマ定義・APIドキュメント・社内規程・FAQ
Prompts(プロンプト)再利用可能なプロンプトテンプレートユーザーが特定のタスクを始める際のテンプレートとして呼び出す「顧客向け提案書を書く」「コードレビューをする」テンプレート

多くの実務ユースケースではToolsとResourcesの組み合わせで構成します。Promptsは必須ではないため、初回実装ではTools → Resourcesの順で実装を進めるのが合理的です。

MCPのトランスポート方式は2種類です。Stdio(標準入出力)はローカルプロセス間通信で、ローカルマシンでサーバーを起動してClaude DesktopやClaude Codeから接続します。Streamable HTTPはリモートサーバーへの接続方式で、社内ネットワーク上のサーバーやクラウドに設置したMCPサーバーへの接続に使います。本記事ではまずStdioで動作確認し、必要に応じてHTTP方式に移行する手順で解説します。


自社MCPサーバーを作る前の設計判断

何を「ツール」として公開するか

MCPサーバーを作り始める前に、「Claudeに何をさせたいか」を整理します。以下のフォーマットで書き出してください。

【MCPサーバー設計シート】

1. AIに何を「できるように」したいか(ツール候補):
   例:「顧客名で顧客情報を検索する」
       「在庫が指定数量以下の商品一覧を取得する」
       「注文状況を注文番号で照会する」

2. AIに「読ませたい」情報は何か(リソース候補):
   例:「商品カテゴリの一覧と説明」
       「APIエラーコードの意味の対応表」
       「社内のコーディング規約書」

3. データソースは何か:
   例:SQLite / PostgreSQL / MySQL / CSV / REST API / ファイルシステム

4. 接続方式はローカル(Stdio)か社内共有(HTTP)か:
   例:自分のPC上でClaude Codeと使うだけ → Stdio
       チームメンバー全員が使いたい → HTTP(社内サーバー)

5. セキュリティ要件:
   例:読み取り専用のみ / 特定テーブルのみ / ログ記録が必要

PythonとTypeScript、どちらで作るか

Python SDKTypeScript SDK
SDKの成熟度安定(公式: mcpやや成熟(公式: @modelcontextprotocol/sdk
記述スタイルデコレータ形式(@mcp.tool())で直感的メソッド形式(server.tool())でやや明示的
データベース接続SQLAlchemy・psycopg2等の豊富なORMPrisma・Drizzle・pg等が使える
得意領域データ処理・ML・スクリプト・既存Pythonシステムとの統合Node.jsエコシステム・REST APIラッパー・Webサービス統合
バイブコーディング適性🔴 高(Claudeが最も得意な言語の1つ)🔴 高(同様にClaudeが得意)
推奨シーン既存PythonのDB操作コードを流用したい・データ分析系既存のNode.js/JS資産がある・フロントエンドとの統合

迷った場合はPythonを選んでください。MCPの公式ドキュメントがPythonを中心に整備されており、デコレータ構文のシンプルさから、非エンジニアがClaude Codeのバイブコーディングで作る場合の成功率が高い傾向があります。

Stdio(ローカル)とHTTP(リモート)、どちらの接続方式か

個人利用・チーム試験であればStdioで十分です。「一人で使うMCPサーバー → Stdio」「チーム全員が使うMCPサーバー → HTTP」が基本方針です。本記事ではStdioで動作確認した後、HTTP方式への移行ポイントを補足します。


【Python編】最小MCPサーバーを10分で作る

環境セットアップ(uv)

uvはPythonの高速パッケージマネージャーで、MCP公式ドキュメントが推奨するツールです。

# uvのインストール(macOS / Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windowsの場合
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# バージョン確認
uv --version

# プロジェクト作成
uv init my-company-mcp
cd my-company-mcp

# MCP SDKのインストール
uv add "mcp[cli]"

# ディレクトリ構成
# my-company-mcp/
# ├── server.py        ← MCPサーバーの本体
# ├── .env             ← APIキー・DB接続情報(Gitには含めない)
# └── pyproject.toml

最初のMCPサーバー——ツールを1つ動かす

まず最もシンプルな構成でMCPサーバーを動かします。

# server.py ——— 最小構成のMCPサーバー

from mcp.server.fastmcp import FastMCP

# MCPサーバーを作成(名前はClaude側に表示される)
mcp = FastMCP("my-company-server")


# ===================================================
# ツールの定義
# @mcp.tool() デコレータで「Claudeが呼べる関数」を定義する
# 関数名・引数・docstringが「AIへの説明」になる
# ===================================================

@mcp.tool()
def get_greeting(name: str) -> str:
    """
    指定された名前に対して挨拶メッセージを返す。

    Args:
        name: 挨拶する相手の名前

    Returns:
        挨拶メッセージ文字列
    """
    return f"こんにちは、{name}さん!本日もよろしくお願いします。"


@mcp.tool()
def calculate_tax(price: float, tax_rate: float = 0.10) -> dict:
    """
    税込み価格と消費税額を計算する。

    Args:
        price: 税抜き価格(円)
        tax_rate: 税率(デフォルト: 0.10 = 10%)

    Returns:
        tax_amount(消費税額)とtotal_price(税込価格)を含む辞書
    """
    tax_amount = price * tax_rate
    total_price = price + tax_amount
    return {
        "tax_amount": round(tax_amount),
        "total_price": round(total_price),
        "tax_rate_percent": f"{tax_rate * 100:.0f}%"
    }


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

このファイルを実行する前に、MCP Inspectorで動作確認します。

MCP Inspectorでのデバッグ

MCP Inspectorは、MCPサーバーをClaude Desktopに接続する前にブラウザ上でインタラクティブにテストできるツールです。

# MCP Inspectorの起動(uvで実行)
uv run mcp dev server.py

# ブラウザで http://127.0.0.1:6274 を開く
# → 登録されたツール・リソース・プロンプトの一覧が表示される
# → 各ツールのパラメータを入力して「Execute」で動作確認できる

# 正常に起動すると以下のようなメッセージが出る:
# MCP Inspector v0.x.x
# Connecting to server...
# Connected! Tools available: get_greeting, calculate_tax

Inspectorで各ツールの動作を確認できたら、Claude Desktopに接続します。

Claude Desktop / Claude Codeへの接続

Claude Desktop(GUI)への接続:

# 設定ファイルの場所
# macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
# Windows: %APPDATA%\Claude\claude_desktop_config.json

# 以下を追加・編集する(JSONの形式)
{
  "mcpServers": {
    "my-company-server": {
      "command": "uv",
      "args": [
        "--directory",
        "/絶対パス/my-company-mcp",
        "run",
        "server.py"
      ]
    }
  }
}

# 注意:/絶対パス/my-company-mcp を実際のフルパスに変更すること
# macOSの場合: /Users/あなたのユーザー名/my-company-mcp
# Windowsの場合: C:\\Users\\あなたのユーザー名\\my-company-mcp

# 設定を保存後、Claude Desktopを完全に終了して再起動する
# → チャット画面下部にMCPツールのアイコンが表示されれば成功

Claude Code(CLI)への接続:

# Claude CodeのCLIでMCPサーバーを登録
claude mcp add my-company-server -- uv run --directory /絶対パス/my-company-mcp server.py

# 登録確認
claude mcp list

# Claude Codeのチャットで動作確認
# /mcp コマンドで接続中のMCPサーバーとツール一覧を確認できる

【Python編・実践】社内データベースに接続するMCPサーバー

SQLiteデータベース接続(社内顧客DB・在庫DB)

実務で最もよくあるユースケース——社内のSQLiteまたはローカルデータベースをMCP経由でClaudeに読ませる実装です。

# server.py ——— 顧客DB・在庫DB接続MCPサーバー(SQLite版)

import sqlite3
from typing import Optional
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("company-db-server")

# データベースのパス(.envで管理することを推奨)
DB_PATH = "./company.db"


def get_connection() -> sqlite3.Connection:
    """データベース接続を取得する(共通ヘルパー)"""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row  # カラム名でアクセスできるようにする
    return conn


# ===================================================
# 【顧客データ関連ツール】
# ===================================================

@mcp.tool()
def search_customers(
    keyword: str,
    limit: int = 10
) -> list[dict]:
    """
    顧客名・メールアドレス・会社名でキーワード検索を実行する。

    Args:
        keyword: 検索キーワード(部分一致)
        limit: 取得件数(最大50件)

    Returns:
        顧客情報のリスト(id, name, email, company, phone, created_at)
    """
    limit = min(limit, 50)  # 最大件数の制限(セキュリティ対策)
    with get_connection() as conn:
        cursor = conn.execute(
            """
            SELECT id, name, email, company, phone,
                   strftime('%Y-%m-%d', created_at) as created_at
            FROM customers
            WHERE name LIKE ? OR email LIKE ? OR company LIKE ?
            ORDER BY name
            LIMIT ?
            """,
            (f"%{keyword}%", f"%{keyword}%", f"%{keyword}%", limit)
        )
        rows = cursor.fetchall()
    return [dict(row) for row in rows]


@mcp.tool()
def get_customer_detail(customer_id: int) -> dict | None:
    """
    顧客IDで詳細情報を取得する。

    Args:
        customer_id: 顧客ID(整数)

    Returns:
        顧客の詳細情報。IDが存在しない場合はNone
    """
    with get_connection() as conn:
        cursor = conn.execute(
            "SELECT * FROM customers WHERE id = ?",
            (customer_id,)
        )
        row = cursor.fetchone()
    return dict(row) if row else None


@mcp.tool()
def get_customer_orders(
    customer_id: int,
    limit: int = 20
) -> list[dict]:
    """
    指定した顧客の注文履歴を新しい順に取得する。

    Args:
        customer_id: 顧客ID
        limit: 取得件数(最大100件)

    Returns:
        注文履歴のリスト(order_id, order_date, product_name, quantity, total_amount, status)
    """
    limit = min(limit, 100)
    with get_connection() as conn:
        cursor = conn.execute(
            """
            SELECT o.id as order_id,
                   strftime('%Y-%m-%d', o.order_date) as order_date,
                   p.name as product_name,
                   oi.quantity,
                   oi.quantity * oi.unit_price as total_amount,
                   o.status
            FROM orders o
            JOIN order_items oi ON o.id = oi.order_id
            JOIN products p ON oi.product_id = p.id
            WHERE o.customer_id = ?
            ORDER BY o.order_date DESC
            LIMIT ?
            """,
            (customer_id, limit)
        )
        rows = cursor.fetchall()
    return [dict(row) for row in rows]


# ===================================================
# 【在庫管理関連ツール】
# ===================================================

@mcp.tool()
def get_low_stock_products(threshold: int = 30) -> list[dict]:
    """
    在庫数が指定した閾値以下の商品一覧を取得する。

    Args:
        threshold: 在庫アラートの閾値(デフォルト: 30個)

    Returns:
        在庫不足商品のリスト(id, name, sku, stock_quantity, reorder_point)
    """
    with get_connection() as conn:
        cursor = conn.execute(
            """
            SELECT id, name, sku, stock_quantity, reorder_point, unit_price
            FROM products
            WHERE stock_quantity <= ?
            ORDER BY stock_quantity ASC
            """,
            (threshold,)
        )
        rows = cursor.fetchall()
    return [dict(row) for row in rows]


@mcp.tool()
def search_products(keyword: str, limit: int = 20) -> list[dict]:
    """
    商品名・SKUで商品を検索する。

    Args:
        keyword: 検索キーワード
        limit: 取得件数(最大50件)

    Returns:
        商品のリスト
    """
    limit = min(limit, 50)
    with get_connection() as conn:
        cursor = conn.execute(
            """
            SELECT id, name, sku, category, stock_quantity,
                   unit_price, reorder_point
            FROM products
            WHERE name LIKE ? OR sku LIKE ?
            ORDER BY name
            LIMIT ?
            """,
            (f"%{keyword}%", f"%{keyword}%", limit)
        )
        rows = cursor.fetchall()
    return [dict(row) for row in rows]


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

Resourcesでスキーマ情報を公開する

Resourcesを使うと、「AIが会話の文脈として参照する背景情報」を提供できます。DBのスキーマ定義を公開することで、ClaudeがSQLを自動生成する際に正確なテーブル構造を参照できます。

# server.pyに以下を追加

@mcp.resource("db://schema")
def get_database_schema() -> str:
    """
    社内データベースのスキーマ定義を返す。
    ClaudeがSQL生成や質問回答に活用するための参照情報。
    """
    with get_connection() as conn:
        # SQLiteのスキーマ情報を取得
        cursor = conn.execute(
            "SELECT name, sql FROM sqlite_master WHERE type='table' ORDER BY name"
        )
        tables = cursor.fetchall()

    schema_text = "# 社内データベース スキーマ定義\n\n"
    for table in tables:
        schema_text += f"## テーブル: {table['name']}\n"
        schema_text += f"```sql\n{table['sql']}\n```\n\n"

    return schema_text


@mcp.resource("docs://business-rules")
def get_business_rules() -> str:
    """
    社内の業務ルール・定義を返す。
    顧客ステータスの定義、注文ステータスの意味など。
    """
    return """
# 業務ルール・定義集

## 顧客ステータス
- active: 現在アクティブな顧客(過去12ヶ月以内に購入あり)
- inactive: 12ヶ月以上購入なし
- vip: 年間購入金額が100万円以上の顧客

## 注文ステータス
- pending: 注文受付済み・未処理
- processing: 処理中・倉庫ピッキング中
- shipped: 出荷済み
- delivered: 配達完了
- cancelled: キャンセル済み

## 在庫アラート基準
- reorder_point: 発注点(この数量を下回ったら発注を検討)
- 緊急発注: 在庫が10個以下
- 通常アラート: 在庫がreorder_point以下

## 消費税
- 標準税率: 10%
- 軽減税率(食料品等): 8%
    """

PostgreSQL / MySQLへの接続拡張

本番環境のPostgreSQL・MySQLに接続する場合は、接続ドライバを変更します。セキュリティのため、接続情報は必ず.envファイルで管理します。

# PostgreSQL接続の場合:
# uv add psycopg2-binary python-dotenv

# MySQL接続の場合:
# uv add pymysql python-dotenv

# .env ファイル(Gitにはコミットしない!)
# DATABASE_URL=postgresql://user:password@localhost:5432/company_db

# server.py の接続部分を以下に変更(PostgreSQL例)

import os
from dotenv import load_dotenv
import psycopg2
import psycopg2.extras

load_dotenv()

def get_connection():
    """PostgreSQLデータベース接続を取得する"""
    return psycopg2.connect(
        os.environ["DATABASE_URL"],
        cursor_factory=psycopg2.extras.RealDictCursor  # カラム名でアクセス可能に
    )

# ツールの内部のSQL構文も一部変更が必要:
# SQLiteの LIKE → PostgreSQLの ILIKE(大文字小文字を無視)
# SQLiteの strftime → PostgreSQLの to_char または DATE_TRUNC

【TypeScript編】社内WikiとファイルサーバーのMCPサーバー

プロジェクトセットアップ

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

# 必要パッケージのインストール
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node

# package.json を編集して以下を設定
# {
#   "type": "module",
#   "scripts": {
#     "build": "tsc",
#     "start": "node dist/index.js",
#     "dev": "tsx src/index.ts"
#   }
# }

# tsconfig.json を作成
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
EOF

mkdir src
touch src/index.ts

MarkdownファイルをResourcesとして公開する

社内WikiがMarkdownファイルで管理されている場合、ファイルシステムを直接読み込んでClaudeに公開できます。

// src/index.ts ——— 社内WikiのMCPサーバー(TypeScript版)

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";

// ===================================================
// 設定
// ===================================================

// 社内WikiのMarkdownファイルが格納されているディレクトリ
const WIKI_DIR = process.env.WIKI_DIR ?? "./wiki";

const server = new McpServer({
  name: "company-wiki-server",
  version: "1.0.0",
});

// ===================================================
// ツール定義
// ===================================================

// Wikiファイルの一覧を取得するツール
server.tool(
  "list_wiki_pages",
  "社内Wikiのページ一覧を取得する。利用可能なドキュメントのタイトルとファイル名を返す",
  {},  // 引数なし
  async () => {
    const files = await fs.readdir(WIKI_DIR);
    const mdFiles = files.filter((f) => f.endsWith(".md"));
    const pages = mdFiles.map((f) => ({
      filename: f,
      title: f.replace(".md", "").replace(/-/g, " "),
    }));
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(pages, null, 2),
        },
      ],
    };
  }
);

// Wikiページを検索するツール
server.tool(
  "search_wiki",
  "社内Wikiをキーワードで全文検索する。マッチしたページ名と該当箇所の抜粋を返す",
  {
    query: z.string().describe("検索キーワード"),
    max_results: z.number().optional().describe("最大取得件数(デフォルト5)"),
  },
  async ({ query, max_results = 5 }) => {
    const files = await fs.readdir(WIKI_DIR);
    const mdFiles = files.filter((f) => f.endsWith(".md"));
    const results: { filename: string; excerpt: string }[] = [];

    for (const file of mdFiles) {
      if (results.length >= max_results) break;
      const content = await fs.readFile(path.join(WIKI_DIR, file), "utf-8");
      const lowerContent = content.toLowerCase();
      const lowerQuery = query.toLowerCase();

      if (lowerContent.includes(lowerQuery)) {
        // キーワードが含まれる行を前後5行と共に抜粋
        const lines = content.split("\n");
        const matchIndex = lines.findIndex((l) =>
          l.toLowerCase().includes(lowerQuery)
        );
        const startLine = Math.max(0, matchIndex - 2);
        const endLine = Math.min(lines.length, matchIndex + 5);
        const excerpt = lines.slice(startLine, endLine).join("\n");
        results.push({ filename: file, excerpt });
      }
    }

    return {
      content: [
        {
          type: "text",
          text: results.length > 0
            ? JSON.stringify(results, null, 2)
            : `「${query}」に一致するページが見つかりませんでした`,
        },
      ],
    };
  }
);

// Wikiページの全文を取得するツール
server.tool(
  "get_wiki_page",
  "指定したファイル名のWikiページ全文を取得する",
  {
    filename: z.string().describe("ファイル名(例: coding-standards.md)"),
  },
  async ({ filename }) => {
    // ディレクトリトラバーサル攻撃を防ぐ(セキュリティ必須)
    const safeName = path.basename(filename);
    const filePath = path.join(WIKI_DIR, safeName);

    try {
      const content = await fs.readFile(filePath, "utf-8");
      return {
        content: [{ type: "text", text: content }],
      };
    } catch {
      return {
        content: [{ type: "text", text: `ファイル「${safeName}」が見つかりませんでした` }],
        isError: true,
      };
    }
  }
);

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

REST APIラッパーツールの作り方

社内システムがREST APIを公開している場合、APIのラッパーとしてMCPツールを定義します。

// 社内RESTAPIへのツール呼び出し例(TypeScript)
// src/index.ts に追加

const API_BASE_URL = process.env.INTERNAL_API_URL ?? "http://localhost:8080/api";
const API_TOKEN = process.env.INTERNAL_API_TOKEN ?? "";

server.tool(
  "get_sales_summary",
  "指定期間の売上サマリーを社内APIから取得する",
  {
    start_date: z.string().describe("開始日(YYYY-MM-DD形式)"),
    end_date: z.string().describe("終了日(YYYY-MM-DD形式)"),
    group_by: z.enum(["day", "week", "month"]).optional()
              .describe("集計単位(デフォルト: month)"),
  },
  async ({ start_date, end_date, group_by = "month" }) => {
    const url = `${API_BASE_URL}/sales/summary?` +
      `start=${start_date}&end=${end_date}&group_by=${group_by}`;

    const response = await fetch(url, {
      headers: {
        "Authorization": `Bearer ${API_TOKEN}`,
        "Content-Type": "application/json",
      },
    });

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

    const data = await response.json();
    return {
      content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
    };
  }
);

Claude Codeで自社MCPサーバーを「バイブコーディング」で作る

非エンジニアでも作れる:要件の言語化テンプレート

コーディングに不慣れでも、「何をしたいか」を正確に言語化できれば、Claude Codeが実装します。以下のテンプレートを使って要件を整理し、Claude Codeに渡してください。

【MCPサーバー自作 要件テンプレート】

## 目的
(例)「社内の顧客管理システムのデータをClaudeから参照できるようにしたい」

## データソース
(例)「MySQLデータベース(ローカルの顧客管理システム)」
(例)「CSVファイル(毎月更新される売上データ)」
(例)「社内のRESTful API(http://internal.example.com/api)」

## Claudeにできるようにしたいこと(ツール)
(例)
1. 顧客名で顧客を検索できる
2. 顧客IDで注文履歴を確認できる
3. 在庫が少ない商品の一覧を取得できる

## Claudeに読ませたい背景情報(リソース)
(例)「テーブルのスキーマ定義(どんなカラムがあるか)」
(例)「業務用語の定義集(ステータスコードの意味など)」

## セキュリティ要件
(例)「読み取り専用にしたい(INSERTやUPDATEは禁止)」
(例)「個人情報(メール・電話番号)は返さない」

## 使用言語
Python / TypeScript(どちらかを選ぶ)

Claude Codeへの指示プロンプト例

上記の要件テンプレートを使って、Claude Codeに以下のように指示します。

以下の要件で、Python製のMCPサーバーを作成してください。

MCPの公式ドキュメント(https://modelcontextprotocol.io)の
FastMCP(mcp[cli]ライブラリ)を使ってください。

---
【要件】
目的: 社内の顧客管理SQLiteデータベースをClaudeから参照できるようにしたい

データソース: SQLiteデータベース(./company.db)
 以下のテーブルがある:
 - customers (id, name, email, company, phone, status, created_at)
 - orders (id, customer_id, order_date, status, total_amount)
 - products (id, name, sku, category, stock_quantity, unit_price)

Claudeにできるようにしたいこと:
1. 顧客名・会社名でキーワード検索できる
2. 顧客IDで注文履歴を確認できる(直近20件)
3. 在庫が指定数量以下の商品一覧を取得できる

Claudeに読ませたい背景情報:
- テーブルスキーマをResourcesとして公開する
- ステータスコードの定義一覧をResourcesとして公開する

セキュリティ要件:
- SELECT文のみ(書き込み操作は一切禁止)
- 取得件数の上限を設ける(最大50件)
- SQLインジェクションを防ぐためプリペアドステートメントを使う

使用言語: Python(FastMCP使用)
---

server.pyを作成し、日本語コメントを充実させてください。
また、Claude Desktopへの設定方法もコメントで記載してください。

Claude Codeはこの要件に基づいてserver.pyの完全なコードを生成します。生成されたコードをmcp dev server.pyでMCP Inspectorに通してテストし、問題があれば「〇〇のツールが正しく動いていません。エラーは△△です。修正してください。」と伝えることで反復的に改善できます。


セキュリティ設計——社内システムを安全に公開する

自社MCPサーバーはAIが社内システムに直接アクセスする「窓口」になります。セキュリティ設計は実装と並行して行う必要があります。MCPサーバーのセキュリティについては当サイトの「MCPサーバーセキュリティ完全ガイド」も合わせてご参照ください。

アクセス制御の設計原則

最小権限の原則(Principle of Least Privilege)を徹底します。MCPサーバーが使うDBアカウントには、必要最小限の権限だけを付与します。

-- PostgreSQLの場合:読み取り専用ユーザーの作成
CREATE USER mcp_readonly WITH PASSWORD 'strong_password_here';

-- 必要なテーブルのみ読み取り権限を付与
GRANT CONNECT ON DATABASE company_db TO mcp_readonly;
GRANT USAGE ON SCHEMA public TO mcp_readonly;
GRANT SELECT ON TABLE customers, orders, products TO mcp_readonly;

-- 書き込み権限は付与しない(INSERT / UPDATE / DELETE は拒否)

-- MySQLの場合:
CREATE USER 'mcp_readonly'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT ON company_db.customers TO 'mcp_readonly'@'localhost';
GRANT SELECT ON company_db.orders TO 'mcp_readonly'@'localhost';
GRANT SELECT ON company_db.products TO 'mcp_readonly'@'localhost';
FLUSH PRIVILEGES;

読み取り専用制約と入力バリデーション

アプリケーションレベルでも書き込み操作を禁止します。

# server.py に追加するセキュリティレイヤー(Python版)

import re

# 危険なSQLキーワードのブラックリスト
FORBIDDEN_SQL_KEYWORDS = [
    "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER",
    "TRUNCATE", "EXEC", "EXECUTE", "--", "/*", "*/"
]

def safe_query(conn, sql: str, params: tuple = ()) -> list:
    """
    読み取り専用SQLのみ実行する安全なクエリ実行関数。

    - SELECT文以外は拒否
    - 危険なキーワードをブロック
    - プリペアドステートメント(パラメータ化クエリ)を使用
    """
    sql_upper = sql.upper().strip()

    # SELECT以外のSQL文を拒否
    if not sql_upper.startswith("SELECT"):
        raise PermissionError("読み取り専用クエリのみ許可されています")

    # 危険なキーワードのチェック
    for keyword in FORBIDDEN_SQL_KEYWORDS:
        if keyword in sql_upper:
            raise PermissionError(f"禁止されているキーワードが含まれています: {keyword}")

    # プリペアドステートメントで実行(SQLインジェクション防止)
    cursor = conn.execute(sql, params)
    return cursor.fetchall()

APIキーと認証情報の安全な管理

# .env ファイル(必ずGitignoreに追加すること)
DATABASE_URL=postgresql://mcp_readonly:password@localhost:5432/company_db
INTERNAL_API_TOKEN=your_secret_token_here

# .gitignore に追加
echo ".env" >> .gitignore
echo "*.env" >> .gitignore

# server.py での読み込み
import os
from dotenv import load_dotenv

load_dotenv()

DB_URL = os.environ.get("DATABASE_URL")
if not DB_URL:
    raise ValueError("DATABASE_URL環境変数が設定されていません")

ログ・監査証跡の設計

社内システムへのAIアクセスは、どのツールがいつ・どんなパラメータで呼ばれたかをログとして記録することが重要です。インシデント発生時の調査と、AIの行動パターン把握の両方に使えます。

# server.py に監査ログを追加(Python版)

import logging
import json
from datetime import datetime

# 監査ログのセットアップ
audit_logger = logging.getLogger("mcp_audit")
audit_handler = logging.FileHandler("./logs/mcp_audit.log")
audit_handler.setFormatter(logging.Formatter('%(message)s'))
audit_logger.addHandler(audit_handler)
audit_logger.setLevel(logging.INFO)

def log_tool_call(tool_name: str, params: dict, result_count: int = 0):
    """ツール呼び出しの監査ログを記録する"""
    log_entry = {
        "timestamp": datetime.now().isoformat(),
        "tool": tool_name,
        "params": params,
        "result_count": result_count,
    }
    audit_logger.info(json.dumps(log_entry, ensure_ascii=False))


# ツール定義の中で呼び出す例
@mcp.tool()
def search_customers(keyword: str, limit: int = 10) -> list[dict]:
    """顧客を検索する(監査ログ付き)"""
    results = []
    # ... DBクエリ実行 ...
    log_tool_call("search_customers", {"keyword": keyword, "limit": limit}, len(results))
    return results

社内ユースケース別:MCPサーバー設計テンプレート5選

ユースケース公開するTools公開するResourcesデータソース推奨言語
① 顧客・CRM統合顧客検索・注文履歴照会・問い合わせ履歴取得顧客ステータス定義・サポートフローPostgreSQL / MySQL / Salesforce APIPython
② 社内Wiki・ナレッジベースページ検索・ページ全文取得・ページ一覧サイトマップ・更新履歴Markdownファイル / Confluence API / Notion APITypeScript
③ 在庫・購買管理在庫照会・発注点アラート確認・仕入先情報取得商品カテゴリ定義・発注ルールSQLite / PostgreSQL / ERPシステムAPIPython
④ コードレビュー支援ファイル読み込み・Gitログ取得・テスト結果取得コーディング規約・アーキテクチャドキュメントファイルシステム / Git / CI/CD APITypeScript
⑤ 売上・経営ダッシュボード期間別売上集計・担当者別実績・予実対比KPI定義・目標値・計算式定義PostgreSQL / Google Sheets API / BI APIPython

CursorでのMCPサーバー設定

CursorもMCPをサポートしており、作成したMCPサーバーをCursorのAIエディタから利用できます。Claude Codeとは設定ファイルの場所が異なります。

# Cursorのグローバル設定ファイルの場所
# macOS: ~/.cursor/mcp.json
# Windows: %APPDATA%\Cursor\User\mcp.json

# または、プロジェクトルートの .cursor/mcp.json(プロジェクト固有の設定)

# .cursor/mcp.json の設定例
{
  "mcpServers": {
    "company-db-server": {
      "command": "uv",
      "args": [
        "--directory",
        "/絶対パス/my-company-mcp",
        "run",
        "server.py"
      ],
      "env": {
        "DATABASE_URL": "postgresql://mcp_readonly:password@localhost:5432/company_db"
      }
    },
    "company-wiki-server": {
      "command": "node",
      "args": ["/絶対パス/company-wiki-mcp/dist/index.js"],
      "env": {
        "WIKI_DIR": "/絶対パス/wiki-files"
      }
    }
  }
}

設定後、Cursor内でChatパネルを開き「@ メニュー」からMCPサーバーのツールを選択できます。Cursorのエージェントモードでは、コーディング中に自動的にMCPツールを呼び出してデータベースのスキーマを参照しながらコードを生成するといった用途が特に有用です。


よくある質問(Q&A)

Q1. MCPサーバーの作成にどれくらいの時間がかかりますか?

シンプルなSQLite接続のPython製MCPサーバー(ツール3〜5個)であれば、本記事のコードをベースにして1〜2時間で動作確認まで完了できます。Claude Codeのバイブコーディングを活用すれば、要件の言語化から実装まで30〜60分で完了した事例もあります。複雑なシステムとの統合・セキュリティ設計・チーム共有のためのHTTP方式への移行を含めると、半日〜1日程度が目安です。

Q2. 既存のMCPサーバーと自作MCPサーバーを同時に使えますか?

はい。Claude Desktopのclaude_desktop_config.json・Claude Codeのclaude mcp addコマンド・Cursorの.cursor/mcp.jsonは、複数のMCPサーバーの同時登録に対応しています。GitHubの公式MCPサーバー・Slackの公式MCPサーバー・自社カスタムMCPサーバーを並べて使えます。ツール名が衝突しないよう、自作サーバーのツール名には会社名や用途の prefix(例:crm_search_customers)を付けることをお勧めします。

Q3. 本番データベースに直接接続することのリスクは何ですか?

主なリスクは①AIの誤ったツール呼び出しによる意図しないデータアクセス、②プロンプトインジェクション攻撃による不正クエリの実行、③接続情報の漏洩です。本記事で解説したように、①②は読み取り専用ユーザー・入力バリデーション・取得件数制限で緩和できます。③は.envファイル管理と接続情報のGitコミット除外で対応します。本番DBへの接続に不安がある場合は、まずレプリカ・読み取り専用のスタンバイDB・または本番データの匿名化コピーに接続する形から始めることをお勧めします。

Q4. MCPサーバーのチーム共有はどうすれば良いですか?

チーム共有には2つのアプローチがあります。①コードのみ共有:MCPサーバーのコードをGitリポジトリで共有し、各自のPC上でuv runで起動する。②サービスとして共有:社内ネットワーク上のサーバーにMCPサーバーをデプロイし、HTTP方式で接続する。①はシンプルですぐ始められ、②は接続情報の統合管理・ログの集中管理ができます。まず①から始めて、チームへの展開が確定したら②に移行する進め方が現実的です。

Q5. FastMCPとMCP SDKの低レベルAPIの違いは何ですか?

FastMCPはMCP Python SDKの上に構築された高レベルのラッパーで、デコレータ形式(@mcp.tool())で直感的にツールを定義できます。本記事のほぼすべてのPythonコードはFastMCPを使っています。MCP SDKの低レベルAPIは、より細かい制御(カスタムエラーハンドリング・ストリーミングレスポンス・カスタムトランスポート)が必要な場合に使います。ほとんどの社内MCPサーバーの用途にはFastMCPで十分です。


まとめ——「AIへの窓口」を自社で設計する時代

本記事で解説してきた内容の核心を振り返ります。

MCPサーバーは、ClaudeやCursorなどのAIが「外界とやりとりする窓口」です。既製品のMCPサーバーを接続することで得られる利便性は確かにありますが、自社の競争力の源泉となる固有のデータ(顧客情報・在庫・ナレッジ)をAIが扱えるようにするためには、自社専用MCPサーバーを設計・実装することが不可欠です。

Python(FastMCP)では@mcp.tool()デコレータ1つでAIが使えるツールが完成します。TypeScriptではserver.tool()で同じことができます。データソースはSQLite・PostgreSQL・MySQL・REST API・ファイルシステムのいずれにも接続できます。

非エンジニアでもClaude Codeへの要件伝達(バイブコーディング)によってサーバーを生成し、MCP Inspectorでテストして、Claude Desktopに接続するという流れを1日で完結させることが可能です。

セキュリティの基本は「最小権限・読み取り専用ユーザー・入力バリデーション・監査ログ」の4本柱です。AIが社内データにアクセスする「窓口」を設計する権限と責任は、外部サービスに委ねるのではなく自社で持つという姿勢が、AI時代の組織的なデータ活用の出発点になります。


参考リンク

免責事項:本記事は2026年3月時点の情報に基づく情報提供です。MCP Python SDK・TypeScript SDKはバージョンアップが頻繁で、APIの変更に伴いコードの動作が変わる場合があります。実装の際は各SDKの公式ドキュメントおよびCHANGELOGを必ず確認してください。社内データベースや機密情報をMCPサーバー経由でAIに公開する場合は、自社のセキュリティポリシー・情報管理規程に沿った設計・承認プロセスを経てから実施してください。

コメント

タイトルとURLをコピーしました