Skip to main content

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.

PyPI version

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) or af (after call)
  • Action: allow, deny, or elicit (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

  1. Initial Request: User authenticates via OAuth
  2. Macaroon Creation: Middleware creates macaroon with policies from YAML
  3. Pre-call Enforcement: Checks tool access and rate limits
  4. Tool Execution: Your function runs normally
  5. Post-call Enforcement: Applies field redaction or prompts for permissions
  6. Progressive Auth: If elicit action, user prompted for permission
  7. 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 OAuth
  • policies.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_key securely (use environment variables or secrets management)
  • Use HTTPS in production to protect macaroon transmission
  • Set appropriate elicit_expiry values 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

mcp_macaroon_middleware-1.1.1.tar.gz (18.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_macaroon_middleware-1.1.1-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

Details for the file mcp_macaroon_middleware-1.1.1.tar.gz.

File metadata

  • Download URL: mcp_macaroon_middleware-1.1.1.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

Hashes for mcp_macaroon_middleware-1.1.1.tar.gz
Algorithm Hash digest
SHA256 f7c5e847b343a34a7f9d1ab297d27b8901f616683873db7c5640f32e347bd1ba
MD5 c09dd3b49f366ca7ad07d875513a9495
BLAKE2b-256 d5e5c42180767e4c88c6a807b7bcd8ca12f34f1cd9f1d0234089b888dd405447

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_macaroon_middleware-1.1.1.tar.gz:

Publisher: release.yaml on indreshp135/CSE-227-OPENMCP

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mcp_macaroon_middleware-1.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_macaroon_middleware-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6fb60945d76b92a97b6e2c161105a5f52ac901258f72fdd656ed8d106c5da17a
MD5 9b4bf8bf310108f04f85b53f16a9aee5
BLAKE2b-256 2e8cd30e678eaa086bf495978603da685e2e6904a75dfcd8ccb51d4867d71fdd

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_macaroon_middleware-1.1.1-py3-none-any.whl:

Publisher: release.yaml on indreshp135/CSE-227-OPENMCP

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page