Metadata-Version: 2.4
Name: mcp-authz
Version: 0.1.0
Summary: Authorization for MCP Python servers: OAuth resource server, policy, and permission-gated capabilities
Project-URL: Homepage, https://github.com/jagreehal/mcp-authz
Project-URL: Repository, https://github.com/jagreehal/mcp-authz.git
Project-URL: Issues, https://github.com/jagreehal/mcp-authz/issues
Author-email: Jag Reehal <jag@jagreehal.com>
License-Expression: MIT
License-File: LICENSE
Keywords: authorization,mcp,model-context-protocol,oauth,rbac
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: mcp<3,>=2.0.0
Requires-Dist: pyjwt[crypto]<3,>=2.10.1
Provides-Extra: dev
Requires-Dist: build>=1.3; extra == 'dev'
Requires-Dist: httpx>=0.28; extra == 'dev'
Requires-Dist: mypy>=1.17; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.1; extra == 'dev'
Requires-Dist: pytest>=8.4; extra == 'dev'
Requires-Dist: ruff>=0.12; extra == 'dev'
Description-Content-Type: text/markdown

# mcp-authz (Python)

Authorization for servers built with the official MCP Python SDK v2. It uses
the SDK's `MCPServer`, `TokenVerifier`, `AccessToken`, `AuthSettings`, and
server-middleware interfaces rather than replacing the protocol stack.

```bash
pip install mcp-authz
```

```python
from mcp.server.auth.settings import AuthSettings
from mcp_authz import AuthorizedMCPServer, JwtVerifier, current_principal, define_policy

policy = define_policy({
    "roles": {
        "reader": ["cases:read"],
        "editor": ["cases:read", "cases:write"],
    },
    "rules": [
        {"match": {"domain": "acme.com"}, "role": "reader"},
        {"match": {"email": "alice@acme.com"}, "role": "editor"},
    ],
})

server = AuthorizedMCPServer(
    "cases",
    policy=policy,
    token_verifier=JwtVerifier(
        issuer="https://auth.acme.com",
        jwks_uri="https://auth.acme.com/.well-known/jwks.json",
        resource="https://mcp.acme.com/mcp",
    ),
    auth=AuthSettings(
        issuer_url="https://auth.acme.com",
        resource_server_url="https://mcp.acme.com/mcp",
        required_scopes=["mcp"],
    ),
)

@server.tool(permission="cases:read")
def get_case(case_id: str) -> dict[str, str]:
    return {"id": case_id}

app = server.streamable_http_app(stateless_http=True)
```

The verifier checks signature, issuer, resource audience, expiry, stable
subject, and verified email. Stable identities are `(issuer, sub)`, and email
or domain policy rules match only verified email identities. Policy is deny-by-default; matching deny rules
override grants. Unauthorized tools, prompts, and resources are filtered from
discovery and rejected if called directly.

Tools, prompts, and resources are first-class MCP capabilities:

```python
@server.prompt(permission="reports:generate")
def release_report(version: str) -> str: ...

@server.resource("case://{case_id}", permission="cases:read")
def case(case_id: str) -> str: ...
```

Inside a handler, `current_principal()` exposes the authorization context for
tenant and row-level decisions. The package performs the declared capability
permission check; the application uses the resulting principal to constrain
its own domain query.

The official Python SDK currently exposes only global HTTP scope requirements.
`AuthSettings.required_scopes` therefore enforces the baseline scope, while
per-capability permissions control discovery and invocation. The shared routing
and scope helpers pin the intended step-up behavior, but this package does not
double-verify tokens or patch private SDK routes to manufacture transport-level
per-capability challenges.

The package runs the same language-neutral policy, scope, and routing fixtures
as `mcp-authz`, preventing the two implementations from drifting.

## Development

```bash
uv sync --all-extras
uv run ruff check src tests
uv run mypy src tests
uv run pytest
uv build
```
