MCP Authorization
VoltAgent provides an authorization layer for MCP (Model Context Protocol) tools. This allows you to control which tools users can see and execute based on roles, permissions, or any custom logic.
Overview
The MCP authorization layer supports two modes:
- Tool Discovery Filtering (
filterOnDiscovery) - Hide tools from users who don't have permission to use them - Execution-Time Checks (
checkOnExecution) - Verify permissions before each tool call
You can enable either or both modes depending on your security requirements.
Quick Start
Add authorization with the can function:
import { MCPConfiguration } from "@voltagent/core";
const mcp = new MCPConfiguration({
servers: {
myServer: {
type: "http",
url: "http://localhost:3000/mcp",
},
},
authorization: {
can: async ({ toolName, action, userId, context }) => {
const roles = (context?.get("roles") as string[]) ?? [];
// Admin-only tools
if (toolName === "delete_item" && !roles.includes("admin")) {
return { allowed: false, reason: "Only admins can delete items" };
}
return true;
},
filterOnDiscovery: true,
checkOnExecution: true,
},
});
// Get tools with authorization context
const tools = await mcp.getTools({
userId: "user-123",
context: { roles: ["manager"], department: "engineering" },
});
The can Function
The can function receives authorization parameters and returns whether access is allowed.
Parameters
interface MCPCanParams {
/** Tool name (without server prefix) */
toolName: string;
/** Server/resource identifier */
serverName: string;
/** The action being authorized: "discovery" or "execution" */
action: "discovery" | "execution";
/** Tool arguments (only available for "execution" action) */
arguments?: Record<string, unknown>;
/** User identifier */
userId?: string;
/** User-defined context Map */
context?: Map<string | symbol, unknown>;
}
Return Value
Return a boolean or an object with allowed and optional reason:
// Simple boolean
return true;
return false;
// Object with reason (shown in error message when denied)
return { allowed: true };
return { allowed: false, reason: "Insufficient permissions" };
When Actions Are Called
The action parameter tells you whether the check is for listing tools or executing a tool.
Discovery Action
When you call getTools() with filterOnDiscovery: true, the can function is called for each tool with action: "discovery".
Your code:
const mcp = new MCPConfiguration({
servers: { expenses: { type: "http", url: "http://localhost:8080/mcp" } },
authorization: {
can: async (params) => {
console.log("can() called with:", JSON.stringify(params, null, 2));
return true;
},
filterOnDiscovery: true,
},
});
const tools = await mcp.getTools({
userId: "user-123",
context: { roles: ["manager"] },
});
What can receives (for each tool on the server):
{
"toolName": "list_expenses",
"serverName": "expenses",
"action": "discovery",
"userId": "user-123",
"context": { "roles": ["manager"] }
}
{
"toolName": "add_expense",
"serverName": "expenses",
"action": "discovery",
"userId": "user-123",
"context": { "roles": ["manager"] }
}
{
"toolName": "delete_expense",
"serverName": "expenses",
"action": "discovery",
"userId": "user-123",
"context": { "roles": ["manager"] }
}
Note: arguments is undefined during discovery since no tool is being executed yet.