Metadata-Version: 2.4
Name: doorman-mcp
Version: 0.1.1
Summary: One-line OAuth for self-hosted MCP servers. Fail-closed by default.
Project-URL: Homepage, https://github.com/manshahH/doorman-mcp
Project-URL: Repository, https://github.com/manshahH/doorman-mcp
Project-URL: Bug Tracker, https://github.com/manshahH/doorman-mcp/issues
Author-email: Manshah Hussain Bangash <manshahhussain.b8@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: authentication,fastmcp,mcp,model-context-protocol,oauth
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
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: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: fastmcp<4,>=3.4
Description-Content-Type: text/markdown

# doorman

**One-line OAuth for self-hosted MCP servers.**

53% of self-hosted API services ship with static API keys. Only 8.5% implement proper OAuth. For MCP servers exposed to AI agents and human clients alike, that gap is not a configuration choice: it is a vulnerability. Doorman closes it in one line.

---

## Before / After

### Before: 28 lines of boilerplate every time

```python
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider

mcp = FastMCP("My Server")

# Manually validate every required credential
client_id = os.environ.get("GITHUB_CLIENT_ID")
if not client_id:
    raise ValueError("GITHUB_CLIENT_ID is required")

client_secret = os.environ.get("GITHUB_CLIENT_SECRET")
if not client_secret:
    raise ValueError("GITHUB_CLIENT_SECRET is required")

jwt_secret = os.environ.get("DOORMAN_JWT_SECRET")
if not jwt_secret:
    raise ValueError("DOORMAN_JWT_SECRET is required")

mcp.auth = GitHubProvider(
    client_id=client_id,
    client_secret=client_secret,
    base_url=os.environ.get("DOORMAN_BASE_URL", "http://127.0.0.1:8000"),
    required_scopes=["read:user", "user:email"],
    allowed_client_redirect_uris=["http://localhost:*", "http://127.0.0.1:*"],
    jwt_signing_key=jwt_secret,
    require_authorization_consent=False,
)
```

### After: one line

```python
import doorman
doorman.protect(mcp, github=True)
```

---

## Quickstart

### 1. Install

```bash
pip install doorman-mcp
```

### 2. Set environment variables

Generate a strong JWT secret:

```bash
python -c "import secrets; print(secrets.token_urlsafe(48))"
```

Then export all required variables for your shell:

**macOS / Linux (bash/zsh):**

```bash
export GITHUB_CLIENT_ID=your_client_id
export GITHUB_CLIENT_SECRET=your_client_secret
export DOORMAN_JWT_SECRET=paste_the_generated_secret_here
export DOORMAN_BASE_URL=http://127.0.0.1:8000
```

**Windows (PowerShell):**

```powershell
$env:GITHUB_CLIENT_ID = "your_client_id"
$env:GITHUB_CLIENT_SECRET = "your_client_secret"
$env:DOORMAN_JWT_SECRET = "paste_the_generated_secret_here"
$env:DOORMAN_BASE_URL = "http://127.0.0.1:8000"
```

Variables set this way last only for the current terminal session. For real deployments use a secrets manager or a gitignored `.env` file; never commit credentials.

### 3. Create a GitHub OAuth App

In **GitHub → Settings → Developer settings → OAuth Apps → New OAuth App**:

- **Homepage URL**: `http://127.0.0.1:8000`
- **Authorization callback URL**: `http://127.0.0.1:8000/auth/callback`

### 4. Wire doorman into your server

```python
from fastmcp import FastMCP
import doorman

mcp = FastMCP("My Server")
doorman.protect(mcp, github=True)

@mcp.tool()
def hello() -> str:
    return "authenticated!"

mcp.run()
```

### 5. Run and connect

```bash
python my_server.py
```

Any MCP client that supports OAuth 2.0 can now connect. The client is redirected to GitHub, authenticates, and receives a short-lived JWT: no static keys, no copy-paste credentials.

---

## Fail-closed by design

Doorman's contract: **if auth cannot be configured, the server refuses to start**.

- Missing `GITHUB_CLIENT_ID`? `ValueError`; the server does not start.
- Missing `DOORMAN_JWT_SECRET`? `ValueError`; the server does not start.
- No provider specified at all? `ValueError`; the server does not start.

There is exactly one escape hatch:

```python
doorman.protect(mcp, allow_unauthenticated=True)
```

This emits a loud `UserWarning` and bypasses all auth. It is intended for local development only and must be opted into by name; you cannot accidentally end up in an unprotected state.

---

## Built on FastMCP

Doorman is a thin configuration layer on top of [FastMCP](https://github.com/jlowin/fastmcp)'s first-class OAuth primitives. It does not replace or wrap FastMCP; it reads your environment, validates credentials, and calls `GitHubProvider` with safe defaults. All MCP protocol handling, session management, and transport concerns remain in FastMCP.

---

## License

MIT: see [LICENSE](LICENSE).
