MCP Role-Based Access Control (RBAC) Setup
RBAC gives MCP servers a way to say a caller's role determines which tools it can touch. Here's how to design roles that map to real MCP primitives (tools, resources, prompts) instead of a vague admin/user split.
Authentication answers "who is this caller." RBAC answers the separate question: "given who they are, which specific tools, resources, and prompts can they actually touch." An MCP server with real users at different trust levels needs both.
Role Definitions Mapped to MCP Primitives
{
"roles": {
"admin": ["tools:*", "resources:*", "prompts:*"],
"developer": ["tools:read", "tools:execute", "resources:read"],
"viewer": ["resources:read"]
}
}
Notice the permission strings map directly onto MCP's actual method namespaces (tools/call, resources/read) rather than an arbitrary internal permission scheme — this makes enforcement a straightforward lookup at the point where a JSON-RPC method is dispatched, rather than a separate parallel system you have to keep in sync.
Enforcing It at the Right Layer
function checkPermission(role: string, action: string): boolean {
const permissions = roleDefinitions[role] || [];
const [category] = action.split(":");
return permissions.includes(action) || permissions.includes(`${category}:*`);
}
// Applied at the tools/call dispatch point:
server.setRequestHandler("tools/call", async (request, { role }) => {
if (!checkPermission(role, "tools:execute")) {
throw new Error("Forbidden: insufficient role permissions");
}
// ... proceed with the actual tool call
});
The critical detail: this check needs to happen at the actual dispatch point for every request, not just once at connection time. A viewer role that gets read access at login shouldn't be able to invoke a write-capable tool an hour later just because the initial connection was accepted.
Per-Tool Scoping Beyond Broad Roles
For servers with more than a handful of tools, a flat admin/developer/viewer split often isn't granular enough — a real production pattern is scoping permissions per individual tool rather than per broad category:
{
"roles": {
"support-agent": ["tools:lookup_order", "tools:issue_refund_under_500", "resources:read"],
"support-lead": ["tools:lookup_order", "tools:issue_refund", "resources:read"]
}
}
This is the same principle behind every payment-capable MCP server covered on this site — a support agent role that can look up orders and issue small refunds, but not arbitrary-amount refunds, meaningfully limits the damage from a compromised or over-eager AI agent compared to a single "can do refunds" permission.
Multi-Tenant Considerations
If your MCP server serves multiple separate customers or organizations, role checks alone aren't enough — you also need tenant-scoping, so a "developer" role at Tenant A can't read Tenant B's resources even though both hold the identical role name. Bake the tenant ID into the permission check itself, not just the role name, or you end up with a role system that's correct in isolation but leaky across tenant boundaries.
Join the Discussion
Discussion (0)
No comments yet. Be the first to share your thoughts!