A production-grade, policy-as-code middleware for FastMCP servers that uses macaroons for fine-grained, dynamic, and capability-based authorization.
Project description
MCP Macaroon Middleware
Production-grade macaroon-based authorization middleware for FastMCP servers with fine-grained, progressive policy enforcement.
Overview
mcp_macaroon_middleware provides cryptographic capability-based authorization for FastMCP servers using macaroons. Unlike traditional OAuth/JWT tokens, macaroons support attenuation (adding restrictions without re-signing), offline verification, and progressive authorization where permissions can be dynamically elicited from users.
Key Benefits:
- Decentralized Authorization: Verify tokens without callback to authorization server
- Progressive Permissions: Request additional permissions on-demand via user elicitation
- Fine-Grained Control: Pre-call and post-call policies with field-level access control
- Policy-as-Code: Define authorization rules as Python functions with full type safety
Features
- ๐ Macaroon-based tokens with cryptographic verification
- ๐ Flexible policy system with pre-call and post-call execution stages
- ๐ฏ Built-in enforcers for tool access, field redaction, and rate limiting
- ๐ Progressive authorization via user elicitation with time-bound grants
- ๐จ Pluggable architecture for custom policy enforcers
- โก In-memory caching with per-user macaroon state
- ๐ก๏ธ FastMCP integration via standard middleware hooks
Installation
pip install mcp-macaroon-middleware
For development:
pip install -e '.[dev]'
Quick Start
1. Define Policies (policies.yaml)
config:
secret_key: "your-secret-key-here"
elicit_expiry: 3600 # Permission grants valid for 1 hour
policies:
# Tool-level access control
- "bf:read_emails:tool_access:allow"
# Rate limiting (2 attempts)
- "bf:read_emails:allow_attempts:allow:2"
# Field-level permissions
- "af:read_emails:field_access:allow:subject"
- "af:read_emails:field_access:deny:body"
- "af:read_emails:field_access:elicit:attachments"
Caveat Format: {phase}:{tool}:{policy}:{action}:{params...}
- Phase:
bf(before call) oraf(after call) - Action:
allow,deny, orelicit(prompt user) - Params: Policy-specific parameters (fields, counts, etc.)
2. Create FastMCP Server
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider
from mcp_macaroon_middleware import MacaroonMiddleware
# Initialize with OAuth provider
auth_provider = GitHubProvider(
client_id=GITHUB_CLIENT_ID,
client_secret=GITHUB_CLIENT_SECRET,
base_url="http://localhost:9001"
)
mcp = FastMCP(name="Secure API", auth=auth_provider)
# Add macaroon middleware
mcp.add_middleware(MacaroonMiddleware(config_path="./policies.yaml"))
@mcp.tool
def read_emails(sender: str, last_n: int = 1):
"""Read emails with field-level authorization."""
return [
{
"subject": "Meeting Tomorrow",
"body": "Let's discuss the project...",
"attachments": ["proposal.pdf"]
}
]
mcp.run(transport="http", port=9001)
3. Request Flow
- Initial Request: User authenticates via OAuth
- Macaroon Creation: Middleware creates macaroon with policies from YAML
- Pre-call Enforcement: Checks tool access and rate limits
- Tool Execution: Your function runs normally
- Post-call Enforcement: Applies field redaction or prompts for permissions
- Progressive Auth: If
elicitaction, user prompted for permission - Cached State: Updated macaroon stored for subsequent requests
Policy System
Built-in Enforcers
Tool Access Control
# In policies.yaml
- "bf:read_emails:tool_access:allow" # Grant access
- "bf:send_email:tool_access:deny" # Block access
Field-Level Redaction
# Redact specific fields from responses
- "af:read_emails:field_access:deny:body"
- "af:read_emails:field_access:deny:attachments"
Progressive Authorization
# Prompt user for permission on first access
- "af:read_emails:field_access:elicit:attachments"
# User prompted: "Grant permission for: af:read_emails:field_access:elicit:attachments?"
# If approved, adds time-bound caveat: "...elicit:attachments:time<20250324T120000Z"
Rate Limiting
# Allow 3 calls, then deny
- "bf:api_call:allow_attempts:allow:3"
Custom Enforcers
Create custom policies by registering enforcement functions:
from mcp_macaroon_middleware import policy_enforcer, Caveat
from typing import List
@policy_enforcer("business_hours")
def enforce_business_hours(
caveat: Caveat,
context: Context,
result: ToolResult,
macaroon: Macaroon
) -> List[Caveat]:
"""Only allow access during business hours (9 AM - 5 PM)."""
from datetime import datetime
hour = datetime.now().hour
if not (9 <= hour < 17):
raise PolicyViolationError("Access only allowed during business hours")
return [] # No new caveats to add
Use in policies.yaml:
- "bf:sensitive_tool:business_hours:allow"
Advanced Example: Document Ownership
@policy_enforcer("document_owner")
def enforce_document_ownership(
caveat: Caveat,
context: Context,
result: ToolResult,
macaroon: Macaroon,
document_id: str
) -> List[Caveat]:
"""Verify user owns the document."""
user_id = context.get("user_id")
# Check ownership in your DB
if not db.check_owner(document_id, user_id):
raise PolicyViolationError(f"User {user_id} doesn't own document {document_id}")
return []
# In policies.yaml - pass document_id as parameter
- "bf:get_document:document_owner:allow:doc_123"
Architecture
โโโโโโโโโโโโโโโโโโโ
โ OAuth Token โ
โ (GitHub, etc) โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MacaroonMiddleware โ
โ โข Creates macaroon โ
โ โข Caches per-user โ
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PolicyEngine โ
โ โข Parse caveats โ
โ โข Execute enforcers โ
โ โข Handle elicitation โ
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โโโโโโดโโโโโ
โผ โผ
โโโโโโโโโ โโโโโโโโโ
โ BEFOREโ โ AFTER โ
โ phase โ โ phase โ
โโโโโโโโโ โโโโโโโโโ
โ โ
โผ โผ
Tool Field
Access Redaction
Configuration
YAML Configuration
config:
secret_key: "..." # Macaroon signing key
elicit_expiry: 3600 # Permission grant TTL (seconds)
policies:
- "bf:tool:policy:action:params..."
Examples
See the examples/ directory:
server.py- Complete FastMCP server with GitHub OAuthpolicies.yaml- Sample policy configuration
Run the example:
cd examples
pip install -r requirements.txt
python server.py
API Reference
MacaroonMiddleware
MacaroonMiddleware(config_path: str)
Parameters:
config_path: Path to policies.yaml configuration file
Methods:
on_call_tool(context, call_next): Main middleware hook
Policy Enforcer Decorator
@policy_enforcer(policy_name: str)
def my_enforcer(
caveat: Caveat,
context: Context,
result: ToolResult,
macaroon: Macaroon,
*params
) -> List[Caveat]:
"""
Args:
caveat: Parsed caveat object
context: FastMCP request context
result: Tool execution result (None for pre-call)
macaroon: Current macaroon instance
*params: Additional parameters from caveat string
Returns:
List of new caveats to add to macaroon
"""
pass
Caveat Model
@dataclass
class Caveat:
raw: str # Original caveat string
execution_phase: ExecutionPhase # BEFORE or AFTER
tool_name: str # Target tool
policy_name: str # Policy enforcer name
action: ActionType # ALLOW, DENY, or ELICIT
params: Tuple[str, ...] # Policy parameters
expiry: Optional[datetime] # Expiration time
Development
Running Tests
pytest
Building
python -m build
Version Bump
bump2version patch # or minor, major
git push --tags
Contributing
See CONTRIBUTING.md for guidelines.
Security Considerations
- Store
secret_keysecurely (use environment variables or secrets management) - Use HTTPS in production to protect macaroon transmission
- Set appropriate
elicit_expiryvalues for your use case - Review and audit custom policy enforcers for security vulnerabilities
- Consider implementing additional caveats for IP restrictions, time windows, etc.
License
MIT License - see LICENSE file.
Citation
@software{mcp_macaroon_middleware,
author = {Indresh Pradeepkumar, Neil Grover, Ravi Gadgil},
title = {MCP Macaroon Middleware},
year = {2025},
url = {https://github.com/indreshp135/CSE-227-OPENMCP}
}
Links
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_macaroon_middleware-1.2.0.tar.gz.
File metadata
- Download URL: mcp_macaroon_middleware-1.2.0.tar.gz
- Upload date:
- Size: 18.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
832a810a501c568cb436f00ca4d8e288157f9941ca65feef3cfcefa1eebeec9a
|
|
| MD5 |
98e458e194de46a2d5ca93c89f3cac83
|
|
| BLAKE2b-256 |
bba73399380e5ed8a1d4f0e764fe0749a8abb4635f308ae4245d51246c33f892
|
Provenance
The following attestation bundles were made for mcp_macaroon_middleware-1.2.0.tar.gz:
Publisher:
release.yaml on indreshp135/CSE-227-OPENMCP
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_macaroon_middleware-1.2.0.tar.gz -
Subject digest:
832a810a501c568cb436f00ca4d8e288157f9941ca65feef3cfcefa1eebeec9a - Sigstore transparency entry: 731270174
- Sigstore integration time:
-
Permalink:
indreshp135/CSE-227-OPENMCP@d52481592e27bcd9408206f72ad29ced14dd9565 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/indreshp135
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@d52481592e27bcd9408206f72ad29ced14dd9565 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mcp_macaroon_middleware-1.2.0-py3-none-any.whl.
File metadata
- Download URL: mcp_macaroon_middleware-1.2.0-py3-none-any.whl
- Upload date:
- Size: 19.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9ed7d6e68eef42369fe9112477d20d08744a3892675e52db5ca26bbc8869e46
|
|
| MD5 |
84461490bedab9134cff5e80822ba271
|
|
| BLAKE2b-256 |
e69b0b8d3f57680e1e2a77e0d2d7f7c2e99f44b7bac2f9eb3b5e2f3af304501c
|
Provenance
The following attestation bundles were made for mcp_macaroon_middleware-1.2.0-py3-none-any.whl:
Publisher:
release.yaml on indreshp135/CSE-227-OPENMCP
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_macaroon_middleware-1.2.0-py3-none-any.whl -
Subject digest:
a9ed7d6e68eef42369fe9112477d20d08744a3892675e52db5ca26bbc8869e46 - Sigstore transparency entry: 731270175
- Sigstore integration time:
-
Permalink:
indreshp135/CSE-227-OPENMCP@d52481592e27bcd9408206f72ad29ced14dd9565 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/indreshp135
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@d52481592e27bcd9408206f72ad29ced14dd9565 -
Trigger Event:
release
-
Statement type: