モデルコンテキスト・プロトコル(MCP)は、LLMが外部ツールと相互作用する強力で標準化された方法を提供します。 How do you secure it? セキュリティなしでMCPサーバを露出することは、あなたの家の玄関ドアを広く開けるのと同じです. 誰でも入ってあなたのツールを使用したり、あなたのデータにアクセスしたり、破壊を引き起こしたりすることができます。 このガイドは、Node.js MCP サーバーのセキュリティーを最初から使用する方法を説明します。 私たちは、認証(あなたは誰ですか?)と認証(あなたは何をすることができますか?)をカバーし、このプロジェクトに基づいて実用的なコードサンプルを提供します。 . JSON Web Tokens (JWT) Azureサンプル/mcp-container-ts 目標:無防備から完全に安全に 私たちの目標は、基本的なMCPサーバを採用し、以下のような強力なセキュリティ層を追加することです。 every request to ensure it comes from a known user. Authenticates the user, granting them specific permissions based on their role (e.g., vs. ). Authorizes admin readonly individual tools, so only authorized users can access them. Protects JWT が MCP セキュリティに最適な理由 JWT は、API をセキュリティーするための業界標準であり、いくつかの主要な理由から MCP サーバーに最適です。 Each JWT contains all the information needed to verify a user. The server doesn't need to store session information, which makes it highly scalable—perfect for handling many concurrent requests from AI agents. Stateless: A JWT can carry user details, their role, and specific permissions directly within its payload. Self-Contained: JWTs are digitally signed. If a token is modified in any way, the signature becomes invalid, and the server will reject it. Tamper-Proof: A single JWT can be used to access multiple secured services, which is common in microservice architectures. Portable: セキュリティフローの視覚化 視覚学習者向けに、このセクション図は、完全な認証および認証フローを示しています。 MCP 規格の遵守に関するメモ! このガイドは、MCP サーバーのセキュリティを確保するための実用的で現実的な実装を提供していることに注意することが重要ですが、 を完全に実施 . not MCP認証の公式仕様 この実装は、従来のJWTとロールベースのアクセス制御(RBAC)を使用して強力で、無国籍で、広く理解されているパターンに焦点を当てており、これは多くの用例に十分です。しかし、MCP仕様に完全に準拠するためには、追加の機能を実装する必要があります。 を眺めることをお勧めします。 更新し、将来の改善についての通知を受け取るために。 GitHub リポジトリ ステップ1:役割と許可の定義 コードを書く前に、私たちはセキュリティのルールを定義しなければなりません。どんな役割が存在しますか?それぞれの役割は何をすることができますか? これが私たちの認証システムの基礎です。 わたしたちの ファイル 定義 そして This makes our code clear, readable, and less prone to typpos. これは、私たちのコードを明確に、読み取り可能にし、より少なからずタイポスに敏感にします。 src/auth/authorization.ts UserRole Permission // src/auth/authorization.ts export enum UserRole { ADMIN = "admin", USER = "user", READONLY = "readonly", } export enum Permission { CREATE_TODOS = "create:todos", READ_TODOS = "read:todos", UPDATE_TODOS = "update:todos", DELETE_TODOS = "delete:todos", LIST_TOOLS = "list:tools", } // This interface defines the structure of our authenticated user export interface AuthenticatedUser { id: string; role: UserRole; permissions: Permission[]; } // A simple map to assign default permissions to each role const rolePermissions: Record<UserRole, Permission[]> = { [UserRole.ADMIN]: Object.values(Permission), // Admin gets all permissions [UserRole.USER]: [ Permission.CREATE_TODOS, Permission.READ_TODOS, Permission.UPDATE_TODOS, Permission.LIST_TOOLS, ], [UserRole.READONLY]: [Permission.READ_TODOS, Permission.LIST_TOOLS], }; STEP 2: JWT サービスの作成 次に、JWTに関連するすべての論理を処理するための集中的なサービスが必要です:テストのために新しいトークンを作成し、最も重要なことは、入力トークンを検証することです。 Here is the complete ファイル: 使用する 図書館は重いものを持ち上げる。 src/auth/jwt.ts jsonwebtoken // src/auth/jwt.ts import * as jwt from "jsonwebtoken"; import { AuthenticatedUser, getPermissionsForRole, UserRole, } from "./authorization.js"; // These values should come from environment variables for security const JWT_SECRET = process.env.JWT_SECRET!; const JWT_AUDIENCE = process.env.JWT_AUDIENCE!; const JWT_ISSUER = process.env.JWT_ISSUER!; const JWT_EXPIRY = process.env.JWT_EXPIRY || "2h"; if (!JWT_SECRET || !JWT_AUDIENCE || !JWT_ISSUER) { throw new Error("JWT environment variables are not set!"); } /** * Generates a new JWT for a given user payload. * Useful for testing or generating tokens on demand. */ export function generateToken( user: Partial<AuthenticatedUser> & { id: string } ): string { const payload = { id: user.id, role: user.role || UserRole.USER, permissions: user.permissions || getPermissionsForRole(user.role || UserRole.USER), }; return jwt.sign(payload, JWT_SECRET, { algorithm: "HS256", expiresIn: JWT_EXPIRY, audience: JWT_AUDIENCE, issuer: JWT_ISSUER, }); } /** * Verifies an incoming JWT and returns the authenticated user payload if valid. */ export function verifyToken(token: string): AuthenticatedUser { try { const decoded = jwt.verify(token, JWT_SECRET, { algorithms: ["HS256"], audience: JWT_AUDIENCE, issuer: JWT_ISSUER, }) as jwt.JwtPayload; // Ensure the decoded token has the fields we expect if (typeof decoded.id !== "string" || typeof decoded.role !== "string") { throw new Error("Token payload is missing required fields."); } return { id: decoded.id, role: decoded.role as UserRole, permissions: decoded.permissions || [], }; } catch (error) { // Log the specific error for debugging, but return a generic message console.error("JWT verification failed:", error.message); if (error instanceof jwt.TokenExpiredError) { throw new Error("Token has expired."); } if (error instanceof jwt.JsonWebTokenError) { throw new Error("Invalid token."); } throw new Error("Could not verify token."); } } ステップ3: Authentication Middleware を構築する A "middleware" is a function that runs あなたのメインリクエスト マネージャー. それは私たちのセキュリティチェックを置くのに最適な場所です. This middleware will inspect every incoming request, look for a JWT in the ヘッダー、チェックしてね。 前 Authorization トークンが有効であれば、ユーザーの情報をリクエストオブジェクトに後で使用するために付属します。 エラーが発生し、リクエストが進むのを止める。 401 Unauthorized このタイプのセキュリティを確保するために、私たちはまた、Expressの インターフェイスは、Our 客体 Request user // src/server-middlewares.ts import { Request, Response, NextFunction } from "express"; import { verifyToken, AuthenticatedUser } from "./auth/jwt.js"; // Extend the global Express Request interface to add our custom 'user' property declare global { namespace Express { interface Request { user?: AuthenticatedUser; } } } export function authenticateJWT( req: Request, res: Response, next: NextFunction ): void { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith("Bearer ")) { res.status(401).json({ error: "Authentication required", message: "Authorization header with 'Bearer' scheme must be provided.", }); return; } const token = authHeader.substring(7); // Remove "Bearer " try { const userPayload = verifyToken(token); req.user = userPayload; // Attach user payload to the request next(); // Proceed to the next middleware or request handler } catch (error) { res.status(401).json({ error: "Invalid token", message: error.message, }); } } ステップ4:MCPサーバの保護 今、私たちはすべてのパーツを持っています サーバーを保護するためにそれらを組み合わせましょう。 まず、我々は我々の メイン MCP エンドポイントへの middleware これが保証 求人 有効なJWTが必要です。 authenticateJWT src/index.ts every /mcp // src/index.ts // ... other imports import { authenticateJWT } from "./server-middlewares.js"; // ... const MCP_ENDPOINT = "/mcp"; const app = express(); // Apply security middleware ONLY to the MCP endpoint app.use(MCP_ENDPOINT, authenticateJWT); // ... rest of the file 次に、我々は我々の細かい穀物の許可を執行する。 トレーニング in 認証済みユーザが、認証済みユーザが ツールのリストを返す前に許可します。 ListTools src/server.ts Permission.LIST_TOOLS // src/server.ts // ... other imports import { hasPermission, Permission } from "./auth/authorization.js"; // ... inside the StreamableHTTPServer class private setupServerRequestHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async (request) => { // The user is attached to the request by our middleware const user = this.currentUser; // 1. Check for an authenticated user if (!user) { return this.createRPCErrorResponse("Authentication required."); } // 2. Check if the user has the specific permission to list tools if (!hasPermission(user, Permission.LIST_TOOLS)) { return this.createRPCErrorResponse( "Insufficient permissions to list tools." ); } // 3. If checks pass, filter tools based on user's permissions const allowedTools = TodoTools.filter((tool) => { const requiredPermissions = this.getToolRequiredPermissions(tool.name); // The user must have at least one of the permissions required for the tool return requiredPermissions.some((p) => hasPermission(user, p)); }); return { jsonrpc: "2.0", tools: allowedTools, }; }); // ... other request handlers } この変更により、ユーザーはA role can list tools, but a user without the アクセス許可が拒否される。 readonly LIST_TOOLS 結論と次のステップ おめでとうございます! MCP サーバーの強力な認証および認証レイヤーを成功に実装しました。これらの手順に従って、あなたは: 明確な役割と許可を定義します。 JWTを処理するための集中的なサービスを作成しました。 すべての入力リクエストを保護するためのミドルウェアを構築します。 ツールレベルで強制されたグラナルの許可。 MCP サーバーはもはやオープンドアではなく、セキュアなサービスです. ここから、より多くの役割、より多くの許可、さらにはより複雑なビジネスロジックを権限システムに追加することによって、これらのコンセプトを拡張することができます. 星 わたしたち 更新し、将来の改善についての通知を受け取るために。 GitHub リポジトリ