Contents
MCP の概要と業務活用例
| 要素 | 説明 |
|---|---|
| Model | 呼び出す大規模言語モデル(Claude、GPT‑4 など) |
| Context | リクエストごとの状態情報。会話履歴やユーザー属性、ドメイン固有のメタデータを格納 |
| Host | MCP エンドポイントを提供するサーバー側コンポーネント。モデル呼び出し・ツール実行を仲介 |
| Client | Cursor、Windsurf など MCP に対応したフロントエンド。HTTP POST で Host にリクエスト送信 |
| Server | Host がデプロイされているインフラ(VM、K8s、サーバーレス等) |
ポイント
*「モデルは外部サービス」「業務ロジックは自前サーバー」という責務分離により、機密情報が外部 LLM に漏れない安全な構成を実現できます。
業務シーンの具体例
| シナリオ | フロー概要 |
|---|---|
| 社内 AI アシスタント | Slack で質問 → Slack Bot(Client)が /mcp/v1/chat に POST → Host が社内データベースと組み合わせて Claude を呼び出し、回答を返す |
| ナレッジ検索 & 要約 | 文書管理システムがメタ情報(最終更新日・タグ)を Context として送信 → Model が最新ドキュメントだけを対象に要約し、結果を UI に表示 |
| 業務フロー自動化 | 業務システムから「請求書作成」指示を送ると、Host が内部 RPA ツールを呼び出し、生成したテキストを Model で校正して返す |
開発環境構築
1. Node.js / TypeScript 環境
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# nvm が未インストールの場合 curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # LTS 系 (v20) をインストール & 使用 nvm install --lts nvm use --lts # pnpm の有効化とインストール corepack enable pnpm i -g pnpm # プロジェクト作成 mkdir mcp-server-node && cd mcp-server-node pnpm init -y pnpm add typescript ts-node @types/node express pino pnpm add --save-dev eslint prettier jest @types/jest ts-jest |
補足:
corepack enableでpnpmがシステムに組み込まれ、バージョン管理が楽になります。
2. Python 3.12 環境
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# macOS/Homebrew の例 brew install pyenv pyenv install 3.12.0 pyenv local 3.12.0 # 仮想環境作成 & 有効化 python -m venv .venv source .venv/bin/activate # 必要パッケージインストール pip install fastapi uvicorn[standard] loguru pydantic |
注記:MCP 用の公式 SDK は 2024 年 5 月時点で公開されていません。その代わりに JSON Schema + AJV(Node)/pydantic(Python) によるバリデーションを推奨します。
サーバー実装のベストプラクティス
1. プロジェクト構成
|
1 2 3 4 5 6 7 |
mcp-server-node/ ├─ src/ │ ├─ index.ts # エントリーポイント │ └─ mcp-schema.json # JSON Schema(入力バリデーション) ├─ tsconfig.json └─ package.json |
package.json の主要設定例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "name": "mcp-server-node", "version": "0.1.0", "type": "module", "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "start": "node dist/index.js", "test": "jest" }, "dependencies": { "express": "^4.18.2", "pino": "^9.0.0", "ajv": "^8.12.0" }, "devDependencies": { "typescript": "^5.4.2", "ts-node": "^10.9.2", "@types/express": "^4.17.17", "jest": "^29.7.0", "ts-jest": "^29.1.0" } } |
tsconfig.json のポイント
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "nodenext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src/**/*.ts"] } |
2. JSON Schema によるリクエストバリデーション(Node)
src/mcp-schema.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MCP Request", "type": "object", "properties": { "id": { "type": "string" }, "model": { "type": "string", "enum": ["claude", "gpt4"] }, "prompt": { "type": "string", "minLength": 1 }, "context": { "type": "object", "additionalProperties": false, "properties": { "user_id": { "type": "string" }, "locale": { "type": "string", "pattern": "^[a-z]{2}-[A-Z]{2}$" } }, "required": ["user_id"] } }, "required": ["id", "model", "prompt", "context"], "additionalProperties": false } |
3. 実装例(src/index.ts)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import express from "express"; import pino from "pino"; import { readFileSync } from "fs"; import Ajv from "ajv"; const logger = pino({ level: "info" }); const app = express(); app.use(express.json()); // ---------- バリデーション ---------- const schema = JSON.parse(readFileSync("./src/mcp-schema.json", "utf8")); const ajv = new Ajv({ allErrors: true, strict: false }); const validate = ajv.compile(schema); // ---------- Model 呼び出しラッパー(ダミー実装) ---------- async function callModel(model: string, prompt: string, context: Record<string, any>) { // 実際には Anthropic/Claude SDK、OpenAI SDK 等を呼び出す return `【${model}】 ${prompt} (ctx=${Object.keys(context).length})`; } // ---------- MCP エンドポイント ---------- app.post("/mcp/v1/chat", async (req, res) => { if (!validate(req.body)) { logger.warn({ errors: validate.errors }, "Invalid MCP request"); return res.status(400).json({ error: "Bad Request", details: validate.errors }); } const { id, model, prompt, context } = req.body; logger.info({ requestId: id, model }, "MCP request received"); try { const result = await callModel(model, prompt, context); res.json({ id, content: result }); } catch (e) { logger.error({ err: e }, "Model call failed"); res.status(500).json({ error: "Internal Server Error" }); } }); // ---------- サーバー起動 ---------- const PORT = process.env.PORT ?? 3000; app.listen(PORT, () => logger.info(`MCP server listening on ${PORT}`)); |
ポイントまとめ
- AJV + JSON Schema が公式 SDK の代替として機能し、入力の型検証を徹底できます。
callModelを差し替えるだけで Claude・GPT‑4 どちらでも利用可能です。
認証・認可の具体例(JWT)
1. Node.js 側ミドルウェア
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import jwt from "jsonwebtoken"; const JWT_SECRET = process.env.JWT_SECRET ?? "change-me-in-prod"; /* 認証ミドルウェア */ function authMiddleware(req: express.Request, res: express.Response, next: express.NextFunction) { const authHeader = req.headers.authorization; if (!authHeader?.startsWith("Bearer ")) { return res.status(401).json({ error: "Missing token" }); } const token = authHeader.split(" ")[1]; try { const payload = jwt.verify(token, JWT_SECRET); // @ts-ignore: 型は実装に合わせて調整 req.user = payload; next(); } catch (e) { return res.status(403).json({ error: "Invalid token" }); } } // エンドポイントに適用 app.post("/mcp/v1/chat", authMiddleware, async (req, res) => { /* 前述と同様 */ }); |
- ベストプラクティス
JWT_SECRETは必ず環境変数で管理し、コードにハードコーディングしない。- トークンの有効期限(exp)を設定し、リフレッシュトークン方式を併用する。
2. Python (FastAPI) 側実装例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from fastapi import FastAPI, Request, HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import jwt from loguru import logger app = FastAPI() security = HTTPBearer() JWT_SECRET = "change-me-in-prod" def verify_jwt(credentials: HTTPAuthorizationCredentials = Depends(security)): token = credentials.credentials try: payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) return payload # 任意で User オブジェクトに変換可 except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Token expired") except jwt.InvalidTokenError: raise HTTPException(status_code=403, detail="Invalid token") @app.post("/mcp/v1/chat") async def mcp_chat(request: Request, user: dict = Depends(verify_jwt)): body = await request.json() # ここで pydantic によるバリデーションを実施(省略) logger.info("User {} called MCP", user.get("sub")) return {"id": body["id"], "content": f"Echo from {body['model']}"} |
- ポイント
HTTPBearerが自動でAuthorization: Bearer <token>を抽出。loguruによる構造化ログでユーザー情報を併記し、監査に備える。
ローカルテストとデバッグ手法
1. cURL でのリクエスト例(JWT 付き)
|
1 2 3 4 5 6 7 8 9 10 11 |
TOKEN=$(jwt encode --secret "change-me-in-prod" '{"sub":"alice","exp":$(date -d "+1 hour" +%s)}') curl -X POST http://localhost:3000/mcp/v1/chat \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "id":"req-001", "model":"claude", "prompt":"今日の売上を教えて", "context":{"user_id":"alice","locale":"ja-JP"} }' |
期待されるレスポンス
|
1 2 3 4 5 |
{ "id": "req-001", "content": "【claude】 今日の売上を教えて (ctx=2)" } |
2. VS Code のデバッグ設定(Node)
.vscode/launch.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch MCP Server", "program": "${workspaceFolder}/src/index.ts", "runtimeArgs": ["-r", "ts-node/register"], "env": { "JWT_SECRET": "dev-secret" } } ] } |
ブレークポイントを validate(req.body) 前後に置くと、スキーマ違反時の内部状態がすぐ確認できます。
3. ログの可視化
- Node:
pino-prettyを dev ディペンデンシーに追加し、npm run dev | pino-prettyで整形表示。 - Python:
loguruの標準出力はカラー対応なので、ターミナル上で見やすくなります。
本番運用に向けたセキュリティ対策と Docker デプロイ
1. 脆弱性情報の取り扱い
2024 年 4 月に Anthropic が報告した「MCP に関する JSON デシリアライズ脆弱性」は、公式 SDK が存在しないことから多くの実装で独自バリデーションが行われていた点が問題でした。
現在は JSON Schema + AJV / pydantic の組み合わせで additionalProperties: false を明示的に設定すれば、同様のインジェクションは防げます。
参考情報(英語)
Anthropic Security Advisory – “MCP JSON Injection” (2024‑04‑15) — https://www.anthropic.com/security/advisories/2024-04-mcp-json-injection
2. 認可レイヤーの強化
- RBAC:JWT の
scopeクレームで「chat:write」「admin」等を定義し、エンドポイントごとにチェック。 - Rate Limiting:
express-rate-limit(Node)や FastAPI のslowapiを利用し、1 分間あたりのリクエスト上限を設定。
|
1 2 3 4 5 6 7 8 9 10 11 |
import rateLimit from "express-rate-limit"; app.use( rateLimit({ windowMs: 60_000, max: 100, // IP あたり 1 分 100 リクエスト standardHeaders: true, legacyHeaders: false, }) ); |
3. Docker マルチステージビルド
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# ---------- Build Stage ---------- FROM node:20-alpine AS builder WORKDIR /app # pnpm とキャッシュ活用 RUN corepack enable && apk add --no-cache python3 make g++ COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY tsconfig.json src/ ./ RUN pnpm run build # => dist/ # ---------- Runtime Stage ---------- FROM node:20-alpine AS runtime WORKDIR /app # 非特権ユーザー作成 RUN addgroup -S app && adduser -S app -G app USER app COPY --from=builder /app/dist ./dist COPY package.json ./ RUN pnpm install --prod --frozen-lockfile EXPOSE 3000 ENV NODE_ENV=production CMD ["node", "dist/index.js"] |
4. CI/CD パイプライン(GitHub Actions の例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
name: CI / CD on: push: branches: [main] jobs: build-test-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # ---------- Lint & Test ---------- - name: Set up Node uses: actions/setup-node@v3 with: node-version: '20' cache: 'pnpm' - run: pnpm install --frozen-lockfile - run: pnpm run lint - run: pnpm test # ---------- Docker Build ---------- - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - name: Build & push image run: | docker build -t myorg/mcp-server:${{ github.sha }} . docker push myorg/mcp-server:${{ github.sha }} # ---------- Trivy Scan ---------- - name: Scan image with Trivy uses: aquasecurity/trivy-action@master with: image-ref: myorg/mcp-server:${{ github.sha }} format: 'table' exit-code: '1' # 脆弱性が見つかったら失敗 # ---------- Deploy to Kubernetes ---------- - name: Deploy to GKE uses: google-github-actions/deploy-cloudrun@v0 with: service: mcp-server image: myorg/mcp-server:${{ github.sha }} env_vars: JWT_SECRET=${{ secrets.JWT_SECRET }} |
5. 運用時のモニタリング
| 項目 | 推奨ツール |
|---|---|
| ログ集約 | Loki + Grafana、もしくは Elastic Stack |
| メトリクス | Prometheus(express-prom-bundle) |
| アラート | Alertmanager → Slack/メール |
まとめ
1️⃣ MCP の責務分離で、機密データを自前サーバーに残しつつ外部 LLM を安全に利用できる。
2️⃣ Node と Python の環境構築は nvm / pnpm と pyenv / virtualenv が標準的で、バージョン衝突を防げる。
3️⃣ 公式 SDK が未提供のため、JSON Schema + AJV(Node)/pydantic(Python) を用いた入力検証が必須。
4️⃣ JWT による認証+RBAC・レートリミットで認可を強化し、外部からの不正アクセスを防止する。
5️⃣ Docker のマルチステージビルド + CI/CD(テスト・脆弱性スキャン)で本番環境への安全なデプロイが実現できる。
詳細コードやサンプルリポジトリは下記 GitHub に公開しています。
GitHub: https://github.com/your-org/mcp-server-sample
このガイドを元に、社内の AI アシスタントやナレッジ検索サービスを安全かつ高速に構築してください。 🚀