Skip to content

Commit 327930f

Browse files
committed
Add conformance auth server for OAuth server authentication testing
Adds a new example server that implements OAuth bearer token authentication for use with the MCP conformance test framework's server auth tests. The server: - Returns 401 with WWW-Authenticate header for unauthenticated requests - Serves Protected Resource Metadata at /.well-known/oauth-protected-resource - Validates tokens starting with 'test-token' or 'cc-token' - Implements echo and test-tool tools for testing authenticated calls Usage: MCP_CONFORMANCE_AUTH_SERVER_URL=http://localhost:3000 \ uv run mcp-conformance-auth-server
1 parent d52937b commit 327930f

File tree

5 files changed

+213
-0
lines changed

5 files changed

+213
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# MCP Conformance Auth Server
2+
3+
A minimal MCP server with OAuth authentication for conformance testing.
4+
5+
This server is designed to work with the MCP conformance test framework's server auth tests.
6+
7+
## Features
8+
9+
- Bearer token authentication with validation
10+
- Protected Resource Metadata (PRM) endpoint at `/.well-known/oauth-protected-resource`
11+
- Simple tools for testing authenticated calls
12+
13+
## Usage
14+
15+
### Prerequisites
16+
17+
You need to set the `MCP_CONFORMANCE_AUTH_SERVER_URL` environment variable to point to the authorization server that will issue tokens.
18+
19+
### Running the server
20+
21+
```bash
22+
# From the python-sdk root directory
23+
cd examples/servers/conformance-auth-server
24+
25+
# Install dependencies
26+
uv sync
27+
28+
# Run the server
29+
MCP_CONFORMANCE_AUTH_SERVER_URL=http://localhost:3000 uv run mcp-conformance-auth-server
30+
```
31+
32+
### With conformance tests
33+
34+
```bash
35+
# Run the conformance test with this server
36+
npx @modelcontextprotocol/conformance server --suite auth \
37+
--auth-command 'uv run --directory examples/servers/conformance-auth-server mcp-conformance-auth-server'
38+
```
39+
40+
## Configuration
41+
42+
- `MCP_CONFORMANCE_AUTH_SERVER_URL` (required): URL of the authorization server
43+
- `PORT` (optional): Server port (default: 3001)
44+
- `--log-level`: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
45+
46+
## Token Validation
47+
48+
The server accepts Bearer tokens that start with:
49+
- `test-token` - Standard test tokens
50+
- `cc-token` - Client credentials tokens
51+
52+
These are the token formats issued by the conformance test framework's fake authorization server.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""MCP conformance auth test server."""
2+
3+
__version__ = "0.1.0"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""CLI entry point for the MCP conformance auth server."""
2+
3+
from .server import main
4+
5+
if __name__ == "__main__":
6+
main()
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
"""
3+
MCP Auth Test Server - Conformance Test Server with Authentication
4+
5+
A minimal MCP server that requires Bearer token authentication.
6+
This server is used for testing OAuth authentication flows in conformance tests.
7+
8+
Required environment variables:
9+
- MCP_CONFORMANCE_AUTH_SERVER_URL: URL of the authorization server
10+
11+
Optional environment variables:
12+
- PORT: Server port (default: 3001)
13+
"""
14+
15+
import logging
16+
import os
17+
import sys
18+
19+
import click
20+
from mcp.server.auth.provider import AccessToken, TokenVerifier
21+
from mcp.server.auth.settings import AuthSettings
22+
from mcp.server.fastmcp import FastMCP
23+
from pydantic import AnyHttpUrl
24+
25+
logger = logging.getLogger(__name__)
26+
27+
28+
class ConformanceTokenVerifier(TokenVerifier):
29+
"""
30+
Token verifier for conformance testing.
31+
32+
Validates Bearer tokens that start with 'test-token' or 'cc-token'
33+
(as issued by the fake auth server).
34+
"""
35+
36+
async def verify_token(self, token: str) -> AccessToken | None:
37+
"""Verify a bearer token and return access info if valid."""
38+
# Accept tokens that start with 'test-token' or 'cc-token'
39+
if token.startswith("test-token") or token.startswith("cc-token"):
40+
return AccessToken(
41+
token=token,
42+
client_id="conformance-test-client",
43+
scopes=["mcp:read", "mcp:write"],
44+
)
45+
return None
46+
47+
48+
def create_server(auth_server_url: str, port: int) -> FastMCP:
49+
"""Create and configure the MCP auth test server."""
50+
base_url = f"http://localhost:{port}"
51+
52+
mcp = FastMCP(
53+
name="mcp-auth-test-server",
54+
token_verifier=ConformanceTokenVerifier(),
55+
auth=AuthSettings(
56+
issuer_url=AnyHttpUrl(auth_server_url),
57+
resource_server_url=AnyHttpUrl(base_url),
58+
required_scopes=[], # No specific scopes required for conformance tests
59+
),
60+
json_response=True,
61+
port=port,
62+
)
63+
64+
@mcp.tool()
65+
def echo(message: str = "No message provided") -> str:
66+
"""Echoes back the provided message - used for testing authenticated calls."""
67+
return f"Echo: {message}"
68+
69+
@mcp.tool(name="test-tool")
70+
def test_tool() -> str:
71+
"""A simple test tool that returns a success message."""
72+
return "test"
73+
74+
return mcp
75+
76+
77+
@click.command()
78+
@click.option("--port", default=None, type=int, help="Port to listen on for HTTP")
79+
@click.option(
80+
"--log-level",
81+
default="INFO",
82+
help="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
83+
)
84+
def main(port: int | None, log_level: str) -> int:
85+
"""Run the MCP Auth Test Server."""
86+
logging.basicConfig(
87+
level=getattr(logging, log_level.upper()),
88+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
89+
)
90+
91+
# Check for required environment variable
92+
auth_server_url = os.environ.get("MCP_CONFORMANCE_AUTH_SERVER_URL")
93+
if not auth_server_url:
94+
logger.error("Error: MCP_CONFORMANCE_AUTH_SERVER_URL environment variable is required")
95+
logger.error(
96+
"Usage: MCP_CONFORMANCE_AUTH_SERVER_URL=http://localhost:3000 python -m mcp_conformance_auth_server"
97+
)
98+
sys.exit(1)
99+
100+
# Get port from argument or environment
101+
if port is None:
102+
port = int(os.environ.get("PORT", "3001"))
103+
104+
logger.info(f"Starting MCP Auth Test Server on port {port}")
105+
logger.info(f"Endpoint will be: http://localhost:{port}/mcp")
106+
logger.info(f"PRM endpoint: http://localhost:{port}/.well-known/oauth-protected-resource")
107+
logger.info(f"Auth server: {auth_server_url}")
108+
109+
mcp = create_server(auth_server_url, port)
110+
mcp.run(transport="streamable-http")
111+
112+
return 0
113+
114+
115+
if __name__ == "__main__":
116+
main()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[project]
2+
name = "mcp-conformance-auth-server"
3+
version = "0.1.0"
4+
description = "MCP conformance auth test server for OAuth authentication testing"
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
authors = [{ name = "Anthropic, PBC." }]
8+
keywords = ["mcp", "llm", "oauth", "conformance", "testing"]
9+
license = { text = "MIT" }
10+
dependencies = ["click>=8.2.0", "mcp", "starlette", "uvicorn"]
11+
12+
[project.scripts]
13+
mcp-conformance-auth-server = "mcp_conformance_auth_server.server:main"
14+
15+
[build-system]
16+
requires = ["hatchling"]
17+
build-backend = "hatchling.build"
18+
19+
[tool.hatch.build.targets.wheel]
20+
packages = ["mcp_conformance_auth_server"]
21+
22+
[tool.pyright]
23+
include = ["mcp_conformance_auth_server"]
24+
venvPath = "."
25+
venv = ".venv"
26+
27+
[tool.ruff.lint]
28+
select = ["E", "F", "I"]
29+
ignore = []
30+
31+
[tool.ruff]
32+
line-length = 120
33+
target-version = "py310"
34+
35+
[dependency-groups]
36+
dev = ["pyright>=1.1.378", "pytest>=8.3.3", "ruff>=0.6.9"]

0 commit comments

Comments
 (0)