Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/mcp-fastmcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,16 @@ This is because FastMCP automatically appends the `/mcp` path to your base URL f

## Examples

### Hello World Server

A minimal example demonstrating FastMCP integration with Keycard authentication is available at [`examples/hello_world_server/`](examples/hello_world_server/).

```bash
cd examples/hello_world_server
export KEYCARD_ZONE_ID="your-zone-id"
uv sync && uv run python main.py
```

For complete examples and advanced usage patterns, see our [documentation](https://docs.keycard.ai).

## License
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
84 changes: 84 additions & 0 deletions packages/mcp-fastmcp/examples/hello_world_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Hello World MCP Server with Keycard Authentication

A minimal example demonstrating how to add Keycard authentication to a FastMCP server.

## Why Keycard?

Keycard lets you securely connect your AI IDE or agent to external resources. It provides OAuth-based authentication for your MCP server plus auditability—so you know who accessed what.

## Prerequisites

Before running this example, set up Keycard:

1. **Sign up** at [keycard.ai](https://keycard.ai)
2. **Create a zone** — this is your authentication boundary
3. **Configure an identity provider** (Google, Microsoft, etc.) — this is how your users will sign in
4. **Create an MCP resource** with URL `http://localhost:8000/` — this registers your server with Keycard

Once configured, get your **zone ID** from the Keycard console. See [MCP Server Setup](https://docs.keycard.ai/build-with-keycard/mcp-server) for detailed instructions.

## Quick Start

### 1. Set Environment Variables

```bash
export KEYCARD_ZONE_ID="your-zone-id"
export MCP_SERVER_URL="http://localhost:8000/"
```

### 2. Install Dependencies

```bash
cd packages/mcp-fastmcp/examples/hello_world_server
uv sync
```

### 3. Run the Server

```bash
uv run python main.py
```

The server will start on `http://localhost:8000`.

### 4. Verify the Server

Check that OAuth metadata is being served:

```bash
curl http://localhost:8000/.well-known/oauth-authorization-server
```

You should see JSON with `issuer`, `authorization_endpoint`, and other OAuth metadata.

## Testing with MCP Client

Connect to your server using any MCP-compatible client (e.g., Cursor, Claude Desktop) and authenticate through your configured identity provider.

## Adding Delegated Access

To enable the `@grant` decorator for accessing external APIs on behalf of users:

1. Get client credentials from your Keycard zone
2. Set additional environment variables:

```bash
export KEYCARD_CLIENT_ID="your-client-id"
export KEYCARD_CLIENT_SECRET="your-client-secret"
```

3. Uncomment the `get_external_data` tool in `main.py`

## Environment Variables Reference

| Variable | Required | Description |
|----------|----------|-------------|
| `KEYCARD_ZONE_ID` | Yes | Your Keycard zone ID |
| `MCP_SERVER_URL` | No | Server URL (default: `http://localhost:8000/`) |
| `KEYCARD_CLIENT_ID` | No | Client ID for delegated access |
| `KEYCARD_CLIENT_SECRET` | No | Client secret for delegated access |

## Learn More

- [Keycard Documentation](https://docs.keycard.ai)
- [FastMCP Documentation](https://docs.fastmcp.com)
74 changes: 74 additions & 0 deletions packages/mcp-fastmcp/examples/hello_world_server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Hello World MCP Server with Keycard Authentication.

A minimal example demonstrating FastMCP integration with Keycard OAuth.
"""

import os

from fastmcp import FastMCP

from keycardai.mcp.integrations.fastmcp import AuthProvider

# Configure Keycard authentication
# Get your zone_id from console.keycard.ai
auth_provider = AuthProvider(
zone_id=os.getenv("KEYCARD_ZONE_ID", "your-zone-id"),
mcp_server_name="Hello World Server",
mcp_base_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000/"),
)

# Get the RemoteAuthProvider for FastMCP
auth = auth_provider.get_remote_auth_provider()

# Create authenticated FastMCP server
mcp = FastMCP("Hello World Server", auth=auth)


@mcp.tool()
def hello_world(name: str) -> str:
"""Say hello to an authenticated user.

Args:
name: The name to greet

Returns:
A personalized greeting message
"""
return f"Hello, {name}! You are authenticated."


# Example tool with delegated access (uncomment to use)
# Requires KEYCARD_CLIENT_ID and KEYCARD_CLIENT_SECRET env vars
#
# from fastmcp import Context
# from keycardai.mcp.integrations.fastmcp import AccessContext
#
# @mcp.tool()
# @auth_provider.grant("https://api.example.com")
# def get_external_data(ctx: Context, query: str) -> str:
# """Fetch data from an external API using delegated access.
#
# Args:
# ctx: FastMCP context with authentication state
# query: Search query for the external API
#
# Returns:
# Data from the external API or error message
# """
# access_context: AccessContext = ctx.get_state("keycardai")
#
# if access_context.has_errors():
# return f"Token exchange failed: {access_context.get_errors()}"
#
# token = access_context.access("https://api.example.com").access_token
# # Use token to call external API
# return f"Fetched data for query: {query}"


def main():
"""Entry point for the MCP server."""
mcp.run(transport="streamable-http")


if __name__ == "__main__":
main()
16 changes: 16 additions & 0 deletions packages/mcp-fastmcp/examples/hello_world_server/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[project]
name = "hello-world-server"
version = "0.1.0"
description = "A minimal FastMCP server with Keycard authentication"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"keycardai-mcp-fastmcp",
"fastmcp>=2.13.0,<3.0.0",
]

[tool.uv.sources]
keycardai-mcp-fastmcp = { path = "../../", editable = true }

[project.scripts]
hello-world-server = "main:main"
12 changes: 12 additions & 0 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,18 @@ middleware = [

## Examples

### Hello World Server

A minimal example demonstrating low-level MCP integration with Keycard authentication is available at [`examples/hello_world_server/`](examples/hello_world_server/).

```bash
cd examples/hello_world_server
export KEYCARD_ZONE_ID="your-zone-id"
uv sync && uv run python main.py
```

> **Note**: For most use cases, we recommend using the [FastMCP integration](https://pypi.org/project/keycardai-mcp-fastmcp/) which provides a simpler API. This low-level approach is for advanced scenarios requiring more control.

For complete examples and advanced usage patterns, see our [documentation](https://docs.keycard.ai).

## License
Expand Down
1 change: 1 addition & 0 deletions packages/mcp/examples/hello_world_server/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
97 changes: 97 additions & 0 deletions packages/mcp/examples/hello_world_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Hello World MCP Server (Low-Level Integration)

A minimal example using the `keycardai-mcp` package's AuthProvider directly.

**Note**: For most use cases, we recommend using the FastMCP integration (`keycardai-mcp-fastmcp`). This low-level approach is for advanced scenarios requiring more control over the Starlette application.

## Why Keycard?

Keycard lets you securely connect your AI IDE or agent to external resources. It provides OAuth-based authentication for your MCP server plus auditability—so you know who accessed what.

## Prerequisites

Before running this example, set up Keycard:

1. **Sign up** at [keycard.ai](https://keycard.ai)
2. **Create a zone** — this is your authentication boundary
3. **Configure an identity provider** (Google, Microsoft, etc.) — this is how your users will sign in
4. **Create an MCP resource** with URL `http://localhost:8000/` — this registers your server with Keycard

Once configured, get your **zone ID** from the Keycard console. See [MCP Server Setup](https://docs.keycard.ai/build-with-keycard/mcp-server) for detailed instructions.

## When to Use This

- Custom middleware requirements
- Non-standard routing needs
- Integration with existing Starlette applications
- Multi-zone authentication scenarios

## Quick Start

### 1. Set Environment Variables

```bash
export KEYCARD_ZONE_ID="your-zone-id"
export MCP_SERVER_URL="http://localhost:8000/"
```

### 2. Install Dependencies

```bash
cd packages/mcp/examples/hello_world_server
uv sync
```

### 3. Run the Server

```bash
uv run python main.py
```

The server will start on `http://localhost:8000`.

### 4. Verify the Server

Check that OAuth metadata is being served:

```bash
curl http://localhost:8000/.well-known/oauth-authorization-server
```

You should see JSON with `issuer`, `authorization_endpoint`, and other OAuth metadata.

## Key Differences from FastMCP Integration

| Feature | FastMCP Integration | Low-Level MCP |
|---------|---------------------|---------------|
| Auth provider | `get_remote_auth_provider()` | `auth_provider.app(mcp)` |
| AccessContext | `ctx.get_state("keycardai")` | Function parameter |
| Server startup | `mcp.run()` | `uvicorn.run(app)` |

## Adding Delegated Access

To enable the `@grant` decorator for accessing external APIs on behalf of users:

1. Get client credentials from your Keycard zone
2. Set additional environment variables:

```bash
export KEYCARD_CLIENT_ID="your-client-id"
export KEYCARD_CLIENT_SECRET="your-client-secret"
```

3. Uncomment the `get_external_data` tool in `main.py`

## Environment Variables Reference

| Variable | Required | Description |
|----------|----------|-------------|
| `KEYCARD_ZONE_ID` | Yes | Your Keycard zone ID |
| `MCP_SERVER_URL` | No | Server URL (default: `http://localhost:8000/`) |
| `KEYCARD_CLIENT_ID` | No | Client ID for delegated access |
| `KEYCARD_CLIENT_SECRET` | No | Client secret for delegated access |

## Learn More

- [FastMCP Integration Example](../../../mcp-fastmcp/examples/hello_world_server/)
- [Keycard Documentation](https://docs.keycard.ai)
75 changes: 75 additions & 0 deletions packages/mcp/examples/hello_world_server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Hello World MCP Server with Low-Level Keycard Authentication.

A minimal example demonstrating the keycardai-mcp package's AuthProvider
for scenarios requiring more control than the FastMCP integration.
"""

import os

import uvicorn
from mcp.server.fastmcp import FastMCP

from keycardai.mcp.server.auth import AuthProvider

# Configure Keycard authentication
# Get your zone_id from console.keycard.ai
auth_provider = AuthProvider(
zone_id=os.getenv("KEYCARD_ZONE_ID", "your-zone-id"),
mcp_server_name="Hello World Server",
mcp_server_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000/"),
)

# Create MCP server (not authenticated yet)
mcp = FastMCP("Hello World Server")


@mcp.tool()
def hello_world(name: str) -> str:
"""Say hello to an authenticated user.

Args:
name: The name to greet

Returns:
A personalized greeting message
"""
return f"Hello, {name}! You are authenticated."


# Example tool with delegated access (uncomment to use)
# Requires KEYCARD_CLIENT_ID and KEYCARD_CLIENT_SECRET env vars
#
# from keycardai.mcp.server.auth import AccessContext
#
# @mcp.tool()
# @auth_provider.grant("https://api.example.com")
# def get_external_data(access_ctx: AccessContext, query: str) -> str:
# """Fetch data from an external API using delegated access.
#
# Note: Low-level MCP uses AccessContext as a function parameter,
# not retrieved from ctx.get_state().
#
# Args:
# access_ctx: AccessContext with exchanged tokens
# query: Search query for the external API
#
# Returns:
# Data from the external API or error message
# """
# if access_ctx.has_errors():
# return f"Token exchange failed: {access_ctx.get_errors()}"
#
# token = access_ctx.access("https://api.example.com").access_token
# # Use token to call external API
# return f"Fetched data for query: {query}"


def main():
"""Entry point for the MCP server."""
# Wrap the MCP app with Keycard authentication
app = auth_provider.app(mcp)
uvicorn.run(app, host="0.0.0.0", port=8000)


if __name__ == "__main__":
main()
Loading