mcp-role-server

mcp-role-server

A role-based access control MCP server template with authentication, role-based tool exposure, and rate limiting, enabling secure tool management for AI assistants.

Category
访问服务器

README

mcp-role-server

役割ベースのアクセス制御つき MCP サーバーの雛形。 Next.js の App Router で動きます。

認証・ロール別のツール公開・レート制限という、本番で必要になる3点を最初から備えた状態から始められます。 あなたがやることは、自分のツールを書いて一覧に1行足すだけです。


これは何か

MCP(Model Context Protocol)は、AIに道具を渡すための共通の差し込み口です。 公式のサンプルは「とりあえず動く」ところまでを示していますが、実際に業務で使おうとすると、すぐに次の3つが必要になります。

必要になるもの この雛形での扱い
誰が呼んでいるのか Bearer トークンによる認証(タイミング攻撃対策込み)
その人にどこまで見せるか 役割(role)ごとにツールを出し分け
叩かれすぎたらどうするか レート制限(超過時は 429 と Retry-After)

この3つを毎回ゼロから書かなくていい、というのがこの雛形の目的です。


特徴

1. 権限がないツールは「拒否」ではなく「存在しない」

役割に合わないツールは、そもそもサーバーに登録されません。

viewer で tools/list  →  ping, echo
admin  で tools/list  →  ping, echo, server_config

viewer が server_config を呼ぶ  →  "Tool server_config not found"

「権限がありません」と返すのではなく、相手から見て最初から存在しない状態になります。

2. 不明な役割は、必ず弱い方へ倒れる

X-User-Role が未設定・空・知らない値のときは、**必ず viewer(最小権限)**として扱われます。 「知らない値だから admin にしておく」は起こりません。

3. 検査は「安い順」に並んでいる

レート制限(カウンターを見るだけ)
    ↓
認証(暗号計算が走る)
    ↓
サーバー組み立て(一番重い)

大量に叩かれたときに、弾く処理そのもので潰れないようにするための順番です。


動かし方

git clone <このリポジトリ>
cd mcp-role-server
pnpm install

# 環境変数を用意する
cp .env.example .env.local

# シークレットを生成して .env.local の MCP_SHARED_SECRET に入れる
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

pnpm dev

http://localhost:3000/api/mcp で待ち受けます。

動作確認(curl)

SECRET="(.env.local に入れた値)"

curl -s -X POST http://localhost:3000/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $SECRET" \
  -H "X-User-Role: viewer" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
送るもの 返るもの
鍵なし 401 Authorization header missing or malformed
誤った鍵 401 Invalid bearer token
正しい鍵 200 + ツール一覧
上限を超えた回数 429 + Retry-After

自分のツールを足す

3ステップで終わります。server.ts には手を入れません。

1. src/features/mcp/tools/ にファイルを1つ作る

import { z } from "zod";
import type { ToolEntry } from "./types";

export const myTool: ToolEntry = {
  name: "my_tool",
  roles: ["admin"],          // ← ここに書いた役割にだけ公開される
  register(server) {
    server.registerTool(
      "my_tool",
      {
        title: "My tool",
        description: "AI がこれを読んで、使うかどうかを判断します。",
        inputSchema: { query: z.string().min(1) },
      },
      async ({ query }) => ({
        content: [{ type: "text", text: JSON.stringify({ result: query }) }],
      }),
    );
  },
};

2. src/features/mcp/tools/index.ts の TOOLS に1行足す

export const TOOLS: ToolEntry[] = [pingTool, echoTool, serverConfigTool, myTool];

3. 以上です。 roles に書いた役割のサーバーにだけ、自動的に載ります。

description は、AI がツールを選ぶ根拠になります。 ここが曖昧だと、AI は間違ったツールを呼びます。実装よりも文章の精度が効く部分です。


設計の理由

なぜ「拒否」ではなく「登録しない」のか

「権限がありません」と返す方式だと、そのツールが存在することは相手に伝わります。 存在が分かれば「どうすれば呼べるのか」を探られます。

登録しない方式なら、相手から見て最初から存在しません。呼びようがありません。

そしてもう1つ。拒否方式は、各ツールの中に権限チェックを書く必要があります。 ツールが増えるほど、書き忘れが起きます。登録の段階で振り分けてしまえば、書き忘れようがありません。

なぜ役割をヘッダーで受け取るのか

このサーバーは、信頼できる呼び出し元(Bot やゲートウェイ)の後ろに置く前提で設計しています。 本人確認はその手前で済んでおり、このサーバーは「誰として扱うか」だけを受け取ります。

役割の判定を各サービスで二重に実装すると、判定がずれたときに気づけません。 判定は一箇所に集め、後段はその結果を受け取る——という分担です。

この前提が崩れる置き方をすると、そのまま脆弱性になります。(次の警告を参照)

なぜレート制限をメモリで持つのか

この雛形の目的は「認証とロール制御の型を示すこと」であって、レート制限の完全な実装ではありません。 外部ストアを必須にすると、動かすまでの手間が増えて雛形として使いにくくなります。

そのかわり、限界を隠さずに書いています(下記)。差し替える場所は1ファイルに閉じてあります。


⚠️ セキュリティ上の重要な注意

このサーバーを、そのままインターネットに直接公開しないでください。

役割は X-User-Role ヘッダーで受け取っています。つまり——

Authorization: Bearer <正しいトークン>
X-User-Role: admin          ← 送れば admin になれる

トークンを持っている相手なら、ヘッダーを書き換えるだけで admin になれます。

これは想定どおりの動作です。この設計は、次の条件が満たされている場合にのみ安全です。

条件 意味
呼び出し元が信頼できる Bot・BFF・ゲートウェイなど、自分が管理しているものだけが呼ぶ
呼び出し元が本人確認を済ませている 実際のユーザー認証(Clerk / Auth0 / 自前)はその手前で完了している
このエンドポイントが公衆に晒されていない ネットワークやトークン管理で到達経路が絞られている

この条件を満たせない場所に置く場合は、verifyMcpAuth を書き換えて、 役割をヘッダーではなくトークン自体(署名付きトークンなど)から導出してください。


既知の限界

限界 内容 本番でどうするか
レート制限がプロセスメモリ保持 サーバーレスや複数インスタンスでは実行のたびに別のメモリになりうるため、全体としては上限を超えて通ることがある Redis 等の外部ストアに差し替える(rate-limit.ts の1ファイルのみ)
固定ウィンドウ方式 境界で最大2倍通る(59秒目に60回+61秒目に60回) スライディングウィンドウ方式にする
レート制限が全体で1つ 呼び出し元ごとに分かれていない checkRateLimit(key) の key を呼び出し元の識別子にする
役割が2つ固定 admin / viewer のみ McpRole 型と normalizeRole を拡張する

構成

src/
├── app/api/mcp/route.ts        HTTPの入口(レート制限 → 認証 → 応答)
└── features/mcp/
    ├── auth.ts                 Bearer検証・役割の正規化
    ├── rate-limit.ts           固定ウィンドウのレート制限
    ├── server.ts               役割に合うツールだけを載せてサーバーを組み立てる
    └── tools/
        ├── types.ts            ツール定義の型
        ├── index.ts            ★ ツールの一覧(ここに足す)
        ├── ping.ts             引数なしの例
        ├── echo.ts             引数ありの例(zod による入力検証)
        └── server-config.ts    admin 限定の例

環境変数

変数 必須 説明
MCP_SHARED_SECRET ✅ 接続に使う共有シークレット。未設定なら 500 を返し、素通しにはしない
MCP_RATE_LIMIT — 1分あたりの上限リクエスト数(既定 60)

ライセンス

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选