Skip to content

Latest commit

 

History

History
213 lines (166 loc) · 6.96 KB

File metadata and controls

213 lines (166 loc) · 6.96 KB

Security

pymcp-kit ships optional authentication and authorization hooks. They are off by default and are activated through create_app(...) or MiddlewareConfig(...).

Auth Request Flow

When auth is enabled, the HTTP middleware authenticates the Bearer token before the JSON-RPC dispatcher runs. Authorization then evaluates the resolved MCP method, tool name, resource URI, prompt name, or task ID before the handler is called.

sequenceDiagram
    autonumber
    participant Client
    participant HTTP as HTTP Transport
    participant Authn as Authenticator
    participant Authz as Authorizer
    participant Dispatcher
    participant Handler

    alt no Bearer token and require_authn=True
        Client->>HTTP: POST /mcp without Authorization
        HTTP-->>Client: 401 Bearer challenge<br/>resource_metadata URL
        Client->>HTTP: GET /.well-known/oauth-protected-resource
        HTTP-->>Client: protected resource metadata<br/>authorization_servers and scopes
    else Bearer token supplied
        Client->>HTTP: POST /mcp<br/>Authorization: Bearer token
        HTTP->>Authn: authenticate(request)
        alt TokenMapAuthenticator
            Authn-->>HTTP: Principal from local token map
        else TokenIntrospectionAuthenticator
            Authn->>Authn: POST token to introspection endpoint
            Authn-->>HTTP: Principal from active token claims
        end
        HTTP->>Dispatcher: dispatch with principal
        Dispatcher->>Authz: authorize(principal, AuthzRequest)
        alt request allowed
            Authz-->>Dispatcher: allow
            Dispatcher->>Handler: run MCP method handler
            Handler-->>Client: JSON-RPC result
        else request denied
            Authz-->>Dispatcher: AuthorizationError
            Dispatcher-->>Client: JSON-RPC error or 403/insufficient_scope challenge
        end
    end
Loading

Bearer Token Authentication

TokenMapAuthenticator maps static Bearer tokens to Principal objects:

from pymcp import create_app
from pymcp.security import TokenMapAuthenticator


app = create_app(
    authn=TokenMapAuthenticator(
        {
            "secret-token": {
                "subject": "alice",
                "display_name": "Alice",
                "roles": ["ops"],
                "scopes": ["tools:*", "tasks:*"],
                "claims": {"tenant": "acme"},
            }
        }
    ),
    require_authn=True,
)

With require_authn=True, unauthenticated requests receive 401.

By default, the WWW-Authenticate challenge derives resource_metadata from the inbound request. If your deployment needs a public URL that differs from the request URL, set resource_metadata_url on OAuthProtectedResourceConfig.

Token Introspection

TokenIntrospectionAuthenticator validates each Bearer token against a remote introspection endpoint and builds the request principal from the returned claims:

from pymcp import create_app
from pymcp.security import TokenIntrospectionAuthenticator


app = create_app(
    authn=TokenIntrospectionAuthenticator(
        "https://auth.example.com/oauth2/introspect",
        client_id="pymcp-server",
        client_secret="super-secret",
        expected_audience="mcp-api",
        expected_resource="https://mcp.example.com/test-mcp",
        expected_issuer="https://auth.example.com",
    ),
    require_authn=True,
)

Use this when your deployment validates opaque access tokens through an authorization server or gateway instead of local JWT verification. Set expected_audience and/or expected_resource to the public MCP resource URI or audience your authorization server issues tokens for.

Access tokens in the URI query string are rejected by the HTTP auth middleware.

OAuth Protected Resource Metadata

Configure OAuthProtectedResourceConfig when clients should discover the MCP resource server metadata described by RFC 9728:

from pymcp import create_app
from pymcp.security import OAuthProtectedResourceConfig, TokenMapAuthenticator


app = create_app(
    authn=TokenMapAuthenticator({"secret-token": {"subject": "alice"}}),
    require_authn=True,
    oauth=OAuthProtectedResourceConfig(
        authorization_servers=["https://auth.example.com"],
        resource="https://mcp.example.com/mcp",
        scopes_supported=["tools:read", "tools:call"],
        bearer_methods_supported=["header"],
    ),
)

The HTTP transport serves metadata at:

  • /.well-known/oauth-protected-resource
  • /.well-known/oauth-protected-resource/mcp

Authentication failures include a Bearer WWW-Authenticate challenge with a resource_metadata URL. Set resource_metadata_url when the public URL differs from the inbound request URL behind a proxy or gateway.

Rule-Based Authorization

RuleBasedAuthorizer evaluates ordered allow/deny rules:

from pymcp import create_app
from pymcp.security import RuleBasedAuthorizer, TokenMapAuthenticator


authn = TokenMapAuthenticator(
    {
        "secret-token": {
            "subject": "alice",
            "roles": ["ops"],
            "scopes": ["tools:*", "tasks:*"],
        }
    }
)

authz = RuleBasedAuthorizer(
    {
        "default_effect": "deny",
        "hide_unauthorized_capabilities": True,
        "hide_unauthorized_tools": True,
        "rules": [
            {
                "methods": ["initialize", "ping", "tools/list", "tasks/list", "tasks/get", "tasks/result"],
                "allow_subjects": ["alice"],
                "effect": "allow",
            },
            {
                "methods": ["tools/call"],
                "tool": "deploy_*",
                "allow_roles": ["ops"],
                "effect": "allow",
            },
        ],
    }
)

app = create_app(
    authn=authn,
    authz=authz,
    require_authn=True,
)

Loading Authz Rules From JSON

from pymcp.security import RuleBasedAuthorizer, load_json_config


authz = RuleBasedAuthorizer(load_json_config("authz_policy.json"))

Useful Knobs

  • hide_unauthorized_capabilities: remove disallowed capability fragments from initialize
  • hide_unauthorized_tools: filter tools/list output to only visible tools
  • auth_exempt_paths: skip auth middleware on selected HTTP routes

Task Visibility Under Auth

When authentication is enabled, task ownership follows the authenticated principal rather than the raw session ID. That matters for tasks/list, tasks/get, tasks/cancel, and tasks/result.

Practical Layout

  • keep token maps or authn setup in config.py
  • keep JSON authz policies in a separate file
  • pass authn, authz, and require_authn into create_app()
  • use capability and tool filtering if clients should not even discover restricted functionality

Scope Boundary

pymcp-kit provides HTTP authentication hooks, authorization hooks, and OAuth protected-resource metadata discovery for MCP resource servers.

It does not implement a full authorization server, a full OAuth client, or token issuance flows. Those belong in your deployment architecture, not in this server toolkit.