> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dedaluslabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting Started

> Protect MCP servers with DAuth or external OAuth

MCP servers can require OAuth 2.1 tokens. Choose between **DAuth** (Dedalus Auth) for managed authentication with credential isolation, or bring your own authorization server.

## DAuth (Dedalus Auth)

DAuth is Dedalus's managed authorization system. It provides OAuth 2.1 token issuance with a key security property: **credentials never leave a sealed execution boundary**.

### Why DAuth?

Traditional credential handling exposes secrets to your application code. DAuth isolates credentials in a secure enclave—your MCP server receives an opaque connection handle, not raw API keys.

* **Credentials never exposed** — Encrypted client-side, decrypted only in a sealed execution boundary
* **Opaque handles** — Your code references connections by handle, never sees raw secrets
* **Sender-constrained tokens** — Tokens are cryptographically bound to the client; stolen tokens are unusable
* **Networkless execution** — Credential decryption and API calls happen entirely within an isolated enclave; raw secrets never traverse the network

<Card title="How DAuth Works" icon="diagram-project" href="/sdk/mcp/python/dauth-architecture">
  Learn how credential isolation and sealed execution protect your secrets.
</Card>

### Quick Start

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import MCPServer
from dedalus_mcp.server import AuthorizationConfig

server = MCPServer(
    "protected-server",
    authorization=AuthorizationConfig(
        enabled=True,
        required_scopes=["read"],
    ),
)
```

By default, `authorization_servers` points to `https://as.dedaluslabs.ai` (the DAuth control plane).

For a complete working example with GitHub and Supabase integrations:

<Card title="Example MCP Server" icon="github" href="https://github.com/dedalus-labs/example-dedalus-mcp">
  Production-ready server with GitHub and Supabase integrations.
</Card>

Unauthenticated requests get `401` with a `WWW-Authenticate` challenge pointing to the protected resource metadata.

### Server-level Scopes

All requests must have these scopes. Scope names are arbitrary strings you define—common patterns are `read`/`write` for general access or `resource:action` (e.g., `files:delete`) for fine-grained control.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
authorization=AuthorizationConfig(
    enabled=True,
    required_scopes=["read", "write"],  # Required for all tools
```

### Per-tool Scopes

Gate sensitive tools with additional scope requirements:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import tool

@tool(description="List files")
def list_files(path: str) -> list[str]:
    return os.listdir(path)  # No extra scopes needed

@tool(description="Delete file", required_scopes=["files:delete"])
def delete_file(path: str) -> dict:
    os.remove(path)
    return {"deleted": path}
```

A token with `read` can call `list_files`. Calling `delete_file` without `files:delete` returns an error:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
	"isError": true,
	"content": [
		{
			"type": "text",
			"text": "Tool \"delete_file\" requires scopes: ['files:delete']. Missing: ['files:delete']"
		}
	]
}
```

### Configuration Options

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
AuthorizationConfig(
    enabled=True,
    authorization_servers=["https://as.dedaluslabs.ai"],  # DAuth (default)
    required_scopes=["read"],
    metadata_path="/.well-known/oauth-protected-resource",
    cache_ttl=300,
    fail_open=False,
)
```

| Option                  | Default                                 | Description                          |
| ----------------------- | --------------------------------------- | ------------------------------------ |
| `enabled`               | `False`                                 | Enable authorization enforcement     |
| `authorization_servers` | `["https://as.dedaluslabs.ai"]`         | DAuth or custom OAuth AS URLs        |
| `required_scopes`       | `[]`                                    | Scopes required for all requests     |
| `metadata_path`         | `/.well-known/oauth-protected-resource` | PRM endpoint path                    |
| `cache_ttl`             | `300`                                   | Cache duration for metadata          |
| `fail_open`             | `False`                                 | Allow requests when validation fails |

### Access Claims in Tools

Inspect the authenticated user in your tools:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import tool, get_context

@tool(description="Get current user")
def whoami() -> dict:
    ctx = get_context()
    auth = ctx.auth  # AuthorizationContext or None

    if auth is None:
        return {"user": "anonymous"}

    return {
        "subject": auth.subject,
        "scopes": auth.scopes,
        "claims": auth.claims,
    }
```

### DPoP Support

DAuth uses DPoP (Demonstrating Proof-of-Possession) by default. Tokens are cryptographically bound to the client's key—even if a token is stolen, it's useless without the corresponding private key.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
server = MCPServer(
    "dpop-server",
    authorization=AuthorizationConfig(
        enabled=True,
        dpop_required=True,
    ),
)
```

### Environment variable

Remember to add these variable to your environment. DAuth works natively with Dedalus SDK, therefore an API key is needed. Get your API key from the [dashboard](https://dedaluslabs.ai/dashboard/api-keys)

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# Dedalus Platform (for _client.py testing)
DEDALUS_API_KEY=dsk-live-...
DEDALUS_API_URL=https://api.dedaluslabs.ai
DEDALUS_AS_URL=https://as.dedaluslabs.ai
```

## External Authorization Servers

Use your own OAuth 2.1 provider instead of DAuth. This is useful when integrating with existing identity infrastructure.

<Note>
  External authorization servers don't provide the sealed execution model. Your MCP server will
  handle credentials directly.
</Note>

### Custom Provider

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp.server import AuthorizationConfig, JWTValidatorConfig, JWTValidator

server = MCPServer(
    "my-server",
    authorization=AuthorizationConfig(
        enabled=True,
        authorization_servers=["https://auth.mycompany.com"],
        required_scopes=["api:access"],
    ),
)

# Configure JWT validation for your provider
jwt_config = JWTValidatorConfig(
    jwks_uri="https://auth.mycompany.com/.well-known/jwks.json",
    issuer="https://auth.mycompany.com",
    audience="https://my-mcp-server.example.com",
)

server.set_authorization_provider(JWTValidator(jwt_config))
```

### Auth0

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
jwt_config = JWTValidatorConfig(
    jwks_uri="https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json",
    issuer="https://YOUR_DOMAIN.auth0.com/",
    audience="https://my-mcp-api",
)
```

### Okta

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
jwt_config = JWTValidatorConfig(
    jwks_uri="https://YOUR_DOMAIN.okta.com/oauth2/default/v1/keys",
    issuer="https://YOUR_DOMAIN.okta.com/oauth2/default",
    audience="api://my-mcp-server",
)
```

### Keycloak

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
jwt_config = JWTValidatorConfig(
    jwks_uri="https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs",
    issuer="https://keycloak.example.com/realms/myrealm",
    audience="my-mcp-client",
)
```

***

## Testing

Test authorization with the full server and client:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import pytest
from dedalus_mcp import MCPServer, tool
from dedalus_mcp.server import AuthorizationConfig
from dedalus_mcp.client import MCPClient, BearerAuth

@tool(description="Delete file", required_scopes=["files:delete"])
def delete_file(path: str) -> dict:
    return {"deleted": path}

@pytest.fixture
async def protected_server():
    server = MCPServer(
        "test",
        authorization=AuthorizationConfig(enabled=True, required_scopes=["read"]),
    )
    server.collect(delete_file)
    task = asyncio.create_task(server.serve())
    await asyncio.sleep(0.1)
    yield server
    task.cancel()

async def test_with_valid_token(protected_server):
    # Use a test token with required scopes
    client = await MCPClient.connect(
        "http://127.0.0.1:8000/mcp",
        auth=BearerAuth(access_token="test-token-with-scopes")
    )
    result = await client.call_tool("delete_file", {"path": "/tmp/test.txt"})
    await client.close()
```
