Add the Access Request MCP server to your agent
Install, configure, and run the Permit.io Access Request MCP server, then connect the server to an MCP client or add its tools to your own Model Context Protocol (MCP) server. This page is for AI agent builders who already have an agent and want the agent to request access and approvals that a human reviews.
To learn what the server does and when to use it, see the Access Request MCP overview. For a complete agent built step by step, see the food ordering demo.
Choose how to run the server
| Mode | Who uses the tools | Where the server runs |
|---|---|---|
| Local | A reviewer with Permit credentials lists, approves, and denies requests from an AI assistant such as Claude Desktop. | On the reviewer's machine, as a subprocess of the MCP client. Credentials come from a .env file. |
| Hosted | End users send access requests and approval requests from inside your AI application. | In your backend, next to the LLM (large language model) and the agent framework, such as LangGraph or LangChain. |
Both modes use the same server code and the same environment variables.
Prerequisites
- Python 3.10 or later.
- uv 0.6.1 or later, the Python package and virtual environment manager the project uses.
- A Permit.io account and your environment API key. See Get your API key.
- A resource in your Permit policy for the agent to request access to, with the roles users can request.
- The Permit Elements that store requests:
- A User Management element for access requests.
- An Operation Approval element and an Approval Management element for operation approvals.
The food ordering demo creates an example resource and all three elements.
Install the server
The editable install adds the server's dependencies from its pyproject.toml: mcp, permit (the Permit Python SDK), httpx, python-dotenv, and aiosqlite.
Clone the Access Request MCP server repository
Clone the repository from GitHub and change to the project directory:
git clone https://github.com/permitio/permit-mcp
cd permit-mcp
Create and activate a virtual environment
Create the environment with uv, then activate the environment for your operating system:
uv venv
source .venv/bin/activate # For Windows: .venv\Scripts\activate
If the uv command isn't found, install uv with the uv installation instructions.
Install the project
Install the project and its dependencies in editable mode:
uv pip install -e .
Configure the environment variables
The server reads its Permit connection and element IDs from a .env file in the project root.
Create the .env file
Copy the example file, then set the variables in the table:
cp .env.example .env
Environment variable reference
| Variable | Description |
|---|---|
PERMIT_API_KEY | Your environment API key. See Get your API key. |
PERMIT_PDP_URL | The URL of the policy decision point (PDP) the server sends permission checks to. Defaults to the Cloud PDP, https://cloudpdp.api.permit.io. |
TENANT | The tenant key of the users and resource instances. Defaults to default. |
PROJECT_ID | Your Permit project ID or key. See Get project ID or key. |
ENV_ID | Your Permit environment ID or key. See Get environment ID or key. |
RESOURCE_KEY | The key of the resource type the requests are for, for example restaurants. |
ACCESS_ELEMENTS_CONFIG_ID | The config ID of the User Management element that stores access requests. The ID appears in the element's embed code after you click Get Code. |
OPERATION_ELEMENTS_CONFIG_ID | The config ID of the Approval Management element that stores operation approvals. The ID appears in the element's embed code after you click Get Code. |
Example .env file
In this example, the PDP runs on your machine, the resource is restaurants, and the element config IDs are restaurant-requests and dish-requests:
PERMIT_API_KEY=pk_123abc456
PERMIT_PDP_URL=http://localhost:7766
TENANT=default
PROJECT_ID=proj_abc123
ENV_ID=env_prod
RESOURCE_KEY=restaurants
ACCESS_ELEMENTS_CONFIG_ID=restaurant-requests
OPERATION_ELEMENTS_CONFIG_ID=dish-requests
.env file out of version controlThe .env file holds your environment API key. Anyone with that key can change the environment's policy through the Permit API. Don't commit the file.
Choose a PDP for your policy model
The Cloud PDP evaluates role-based access control (RBAC) and relationship-based access control (ReBAC) policies. If your policy uses attribute-based access control (ABAC), the Cloud PDP can't evaluate the policy: run an Edge PDP container and point PERMIT_PDP_URL at the container:
PERMIT_PDP_URL=http://localhost:7766
To start the container on port 7766, see Run the PDP. For the policy models each PDP supports, see Cloud PDP capabilities.
Sync users with their names
The list tools return the requesting user's Permit profile with each request. Sync users with a first_name, so a reviewer sees who sent a request instead of a bare user key:
await permit.api.sync_user({
"key": user_id,
"first_name": firstname
})
Run the server
Run the server locally from an MCP client for development and review, or deploy the server with your AI application.
Connect the server to Claude Desktop
Claude Desktop starts the server as a subprocess and sends tool calls to the server over standard input and output (stdio). Add this entry to the mcpServers object in your Claude Desktop configuration file:
{
"mcpServers": {
"permit": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/src/permit_mcp",
"run",
"server.py"
]
}
}
}
Replace /ABSOLUTE/PATH/TO/PARENT/FOLDER with the absolute path of your cloned permit-mcp directory. Restart Claude Desktop. The permit server's tools, such as list_access_requests, appear in Claude Desktop's tool list.
Run the server from the command line
To check your configuration without an MCP client, change to the src/permit_mcp directory of the cloned repository and run:
uv run server.py
The server logs Starting Permit MCP server... and waits for MCP messages on standard input. The server loads the .env file from the project root. Press Ctrl+C to stop the server.
Deploy the server with your application
In a hosted deployment, your backend runs the MCP server next to the LLM application:
- The MCP server holds the Permit API key and calls the Permit API and the PDP.
- The agent, for example a LangGraph or LangChain agent, calls the MCP tools when a user asks for access or approval.
- Reviewers approve or deny requests in the Permit Elements embedded in your application, through the Permit API, or through the approve and deny tools.
Authenticate your end users in the backend, and pass the authenticated user's key to the tools. The tools act as whichever user_id the agent passes, so an agent that takes the user ID from the prompt lets a user act as another user.
For a hosted example with a FastAPI backend, a WebSocket chat endpoint, and tools filtered by user role, see Serve the agent with FastAPI and a CLI.
Add the tools to your own MCP server
Import the PermitServer class into your own FastMCP server to use the access request tools next to your domain tools. You can:
- Add domain tools, for example to list, update, or delete your application's entities.
- Check permissions with
permit.check()inside your tools, and send an access request when a check denies. - Remove Permit tools that a group of users must not call.
Register the built-in tools
PermitServer registers the Permit tools on the FastMCP instance you pass to it:
from mcp.server.fastmcp import FastMCP
from src.permit_mcp.server import PermitServer
mcp = FastMCP("custom_server_name")
permit_server = PermitServer(mcp)
The mcp instance exposes every tool in the tool reference. The src.permit_mcp.server import path works when you run your server from the root of the cloned repository.
Exclude tools
Pass tool names in exclude_tools to keep them off your server:
permit_server = PermitServer(
mcp,
exclude_tools=['create_access_request', 'create_operation_approval']
)
Your agent can call every other Permit tool. The excluded tools aren't registered.
Add custom tools
Register your own tools on the same instance with the @mcp.tool() decorator:
from typing import List
@mcp.tool()
async def list_dishes(user_id: str, restaurant_id: str) -> List[str]:
# Custom logic to fetch dish data from a database
...
The food ordering demo builds a complete list_dishes tool and an order_dish tool that check permissions with the Permit SDK. See Add food ordering tools to the MCP server.
Tool reference
PermitServer registers these tools. Any MCP client or agent framework that supports MCP tools can call them, for example Claude Desktop, or LangGraph through langchain-mcp-adapters. Each tool calls the Permit API with the PROJECT_ID, ENV_ID, TENANT, and RESOURCE_KEY from the .env file. A failed API call returns a tool error with the status code and response body.
Access request tools
| Tool | What it does | Parameters |
|---|---|---|
create_access_request | Requests a role for a user, through the User Management element set in ACCESS_ELEMENTS_CONFIG_ID. | user_id (required): the user who requests access. role (required): the role key or ID requested. reason (required): why the user needs access. resource_instance (optional): the resource instance key or ID. Required for ReBAC policies. |
list_access_requests | Lists access requests that the user can see, with the requesting user's profile. | user_id (required): the user who views the list. status (optional): pending, approved, denied, or canceled. role (optional). resource_instance (optional). page (default 1). per_page (default 30, maximum 100). |
approve_access_request | Approves an access request. Permit assigns the requested role. | user_id (required): the reviewer. access_request_id (required): from list_access_requests. reviewer_comment (optional). |
deny_access_request | Denies an access request. | user_id (required): the reviewer. access_request_id (required). reviewer_comment (optional). |
Operation approval tools
| Tool | What it does | Parameters |
|---|---|---|
create_operation_approval | Requests one-time approval of an operation on a resource instance, through the element set in OPERATION_ELEMENTS_CONFIG_ID. | user_id (required): the user who requests approval. reason (required). resource_instance (optional): the resource instance key or ID. Required for ReBAC policies. |
list_operation_approvals | Lists operation approval requests, with the requesting user's profile. | user_id (required): the user who views the list. status (optional). resource_instance (optional). page (default 1). per_page (default 30, maximum 100). |
approve_operation_approval | Approves an operation approval request. Permit assigns the _Approved_ role on the resource instance. | user_id (required): the reviewer. operation_approval_id (required): from list_operation_approvals. reviewer_comment (optional). |
deny_operation_approval | Denies an operation approval request. | user_id (required): the reviewer. operation_approval_id (required). reviewer_comment (optional). |
Resource tools
| Tool | What it does | Parameters |
|---|---|---|
list_resource_instances | Lists the instances of RESOURCE_KEY in TENANT, with each instance's ID and key. Agents call this tool first to get the resource_instance value for the other tools. | page (default 1). per_page (default 100, maximum 100). |
Connect the tools to an agent
The food ordering demo shows two ways to connect the tools to an agent:
- A LangGraph client with human review: LangGraph loads the tools with
langchain-mcp-adapters, and pauses withinterrupt()before approval tools run. - A FastAPI backend and a CLI: the backend authenticates users, filters the tools by role, and passes them to Gemini over a WebSocket chat.
Best practices
Use named users
Sync each user with a first_name, as in Sync users with their names. A reviewer who sees only user keys can't tell who sent a request, and audit trails are harder to read.
await permit.api.sync_user({
"key": user_id,
"first_name": firstname
})
Give resource instances readable attributes
With relationship-based access control (ReBAC), the agent and the reviewer identify resource instances by key:
- Give each resource instance, such as a restaurant, a unique key that doesn't change.
- When the key isn't the instance's name, store the name and other facts the reviewer needs, such as
allowed_for_children, as attributes.
await permit.api.resource_instances.create({
"resource": "restaurants",
"key": restaurant_id,
"tenant": TENANT,
"attributes": {
"name": restaurant_name,
"allowed_for_children": bool(allowed_for_children)
}
})
Enforce permissions in policy, not in code
Don't hardcode who can do what in your tools. Call await permit.check() in each tool, and define the rules in your Permit policy. You change a rule in the policy without redeploying the agent, and each decision appears in the Permit audit log.
Log tool calls
Log each tool call your agent makes, especially access requests and approvals, in your application's structured logs. Permit records permission checks in the audit log. Your own logs record the prompt and the tool arguments that led to each check.
Support asynchronous approvals
A reviewer doesn't always answer right away. Let reviewers approve in an embedded Approval Management element or User Management element, and have the agent tell the user that the request is pending instead of waiting.
Test with several roles and scenarios
Test that:
- Users without permission can't perform sensitive actions.
- Only users with the reviewer role can approve requests.
- The agent handles a denied request and a failed tool call.
To generate permission-check test cases from your policy, use permit test generate e2e in the Permit CLI. See Policy testing commands in the Permit CLI.
Pause the agent for approval with LangGraph
LangGraph's interrupt() function pauses a graph before a tool runs and resumes the graph with a human decision. Use interrupt() before high-risk tools, such as approve_access_request. See Add human review with interrupt().
Next steps
- Food ordering demo: build a complete agent with access requests, operation approvals, and human review.
- Operation Approval element: embed the element where users request approvals.
- Permit MCP Gateway: authorize every MCP tool call without changing your MCP servers.
- Questions: join the Permit.io Slack community.