Skip to content

Commit a9ab865

Browse files
authored
Feature: OAuth authentication + Streamable HTTP (#13)
* rename * rename * rename * rename * fix: examples * examples * examples oauth oauth * ignore * references * reference * fix: stdio * fix: streamable http * fix: oauth * fix: oauth * rm: anaconda oauth example
1 parent 2d857b7 commit a9ab865

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+6848
-758
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*.pyc
1515
**/__pycache__
1616

17+
**/config.json
18+
1719
# Byte-compiled / optimized / DLL files
1820
__pycache__/
1921
*.py[cod]

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,15 +369,15 @@ See the [Git-File Example README](examples/git-file/README.md) for complete docu
369369

370370
Production-ready example with GitHub OAuth2 authentication.
371371

372-
**Location:** [`examples/mcp-auth/`](examples/mcp-auth/)
372+
**Location:** [`references/oauth/`](references/oauth/)
373373

374374
**Features:**
375375
- OAuth2 authentication flow
376376
- JWT tokens
377377
- Protected MCP server endpoints
378378
- Pydantic AI agent integration
379379

380-
See the [MCP Auth Example README](examples/mcp-auth/README.md) for details.
380+
See the [MCP Auth Example README](references/oauth/README.md) for details.
381381

382382
## 🗂️ Resources
383383

docs/ARCHITECTURE.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ The MCP Compose exposes all the tools of the managed MCP Servers as a single uni
2525
A `Managed MCP Server` can be:
2626

2727
- **`Embedded`**: Python packages that implement MCP servers using the Python MCP SDK. These are loaded in-process and configured via a manual list in `mcp_compose.toml`.
28-
- **`Proxied`**: External MCP servers accessible via `STDIO` or `SSE (Server-Sent Events)`. The startup commands and configurations for these proxied servers are defined in `mcp_compose.toml`.
28+
- **`Proxied`**: External MCP servers accessible via `STDIO`, `Streamable HTTP`, or `SSE (Server-Sent Events, deprecated)`. The startup commands and configurations for these proxied servers are defined in `mcp_compose.toml`.
2929

30-
The supported exposed **transports** are the official MCP transports: `STDIO` and `SSE (Server-Sent Events)`.
30+
The supported exposed **transports** are the official MCP transports:
31+
- **STDIO**: For subprocess communication (agent spawns server)
32+
- **Streamable HTTP**: Modern HTTP-based transport (recommended for production)
33+
- **SSE (deprecated)**: Server-Sent Events for streaming (use Streamable HTTP instead)
3134

3235
## Component Architecture
3336

@@ -38,7 +41,7 @@ The supported exposed **transports** are the official MCP transports: `STDIO` an
3841
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
3942
│ │ Authn │ │ Authz │ │ Transport │ │
4043
│ │ Middleware │ │ Middleware │ │ Layer │ │
41-
│ │ │ │ │ │ (STDIO/SSE) │ │
44+
│ │ │ │ │ │ (STDIO/HTTP/SSE) │ │
4245
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
4346
├───────────────────────────────────────────────────────────────┤
4447
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
@@ -50,7 +53,7 @@ The supported exposed **transports** are the official MCP transports: `STDIO` an
5053
│ Managed MCP Servers │
5154
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
5255
│ │ Embedded │ │ Proxied │ │ Proxied │ │
53-
│ │ Python │ │ (STDIO) │ │ (SSE) │ │
56+
│ │ Python │ │ (STDIO) │ │ (HTTP/SSE) │ │
5457
│ │ Packages │ │ │ │ │ │
5558
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
5659
└───────────────────────────────────────────────────────────────┘
@@ -78,16 +81,24 @@ All configuration is managed through a single `mcp_compose.toml` file. The previ
7881
name = "my-unified-server"
7982
conflict_resolution = "prefix" # prefix, suffix, ignore, error, override, custom
8083
log_level = "INFO"
81-
port = 8080 # For HTTP/SSE transport and REST API
84+
port = 8080 # For HTTP transport and REST API
8285

8386
# ============================================================================
8487
# Transport Configuration
8588
# ============================================================================
8689
[transport]
90+
# STDIO transport - for subprocess communication
8791
stdio_enabled = true
88-
sse_enabled = true # Server-Sent Events for streaming
89-
sse_path = "/sse" # SSE endpoint path
90-
sse_cors_enabled = true # Enable CORS for SSE
92+
93+
# Streamable HTTP transport (recommended) - modern HTTP-based MCP transport
94+
streamable_http_enabled = true
95+
streamable_http_path = "/mcp"
96+
streamable_http_cors_enabled = true
97+
98+
# SSE transport (deprecated) - use Streamable HTTP instead
99+
sse_enabled = false
100+
sse_path = "/sse"
101+
sse_cors_enabled = true
91102

92103
# ============================================================================
93104
# Authentication Configuration
@@ -111,11 +122,32 @@ issuer = "mcp-composer"
111122
audience = "mcp-clients"
112123

113124
# OAuth2/OIDC Authentication
125+
# Supports two modes:
126+
# 1. Generic token validation (validates bearer tokens via userinfo/introspection)
127+
# 2. OAuth2 authorization flow (for known providers like google, github, microsoft)
128+
129+
# Example 1: Generic OAuth2 token validation (recommended for server-side auth)
114130
[authentication.oauth2]
115-
provider = "auth0" # auth0, keycloak, custom
116-
client_id = "${OAUTH_CLIENT_ID}"
117-
client_secret = "${OAUTH_CLIENT_SECRET}"
118-
discovery_url = "${OAUTH_DISCOVERY_URL}"
131+
provider = "generic" # Use generic for standard OAuth2/OIDC token validation
132+
issuer_url = "https://id.example.com" # OIDC issuer for auto-discovery
133+
# Or explicit endpoints:
134+
# userinfo_endpoint = "https://id.example.com/userinfo"
135+
# introspection_endpoint = "https://id.example.com/oauth/introspect"
136+
client_id = "${OAUTH_CLIENT_ID}" # Optional, for introspection
137+
client_secret = "${OAUTH_CLIENT_SECRET}" # Optional, for introspection
138+
user_id_claim = "sub" # Claim to use for user identification
139+
# required_scopes = ["openid", "profile"] # Optional scope requirements
140+
141+
# Example 2: OAuth2 authorization flow (for client-side auth)
142+
# [authentication.oauth2]
143+
# provider = "github" # google, github, microsoft, auth0
144+
# client_id = "${OAUTH_CLIENT_ID}"
145+
# client_secret = "${OAUTH_CLIENT_SECRET}"
146+
# redirect_uri = "http://localhost:8080/oauth/callback"
147+
# scopes = ["read:user"]
148+
149+
# Legacy configuration (deprecated, use issuer_url instead)
150+
# discovery_url = "${OAUTH_DISCOVERY_URL}"
119151

120152
# Mutual TLS Authentication
121153
[authentication.mtls]

docs/USER_GUIDE.md

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ MCP Compose is a comprehensive solution for managing and composing multiple Mode
2121
- **Web UI**: Modern web interface for management and monitoring
2222
- **REST API**: Full-featured API for programmatic control
2323
- **Real-time Monitoring**: Live logs, metrics, and status updates
24-
- **Protocol Translation**: Support for STDIO and SSE transports
24+
- **Protocol Translation**: Support for STDIO, Streamable HTTP, and SSE (deprecated) transports
2525

2626
### Who Should Use This?
2727

@@ -150,7 +150,7 @@ The dashboard shows:
150150

151151
Each server card shows:
152152
- Command and arguments
153-
- Transport type (STDIO/SSE)
153+
- Transport type (STDIO/Streamable HTTP/SSE)
154154
- Process ID (when running)
155155
- Uptime (formatted as hours and minutes)
156156
- Restart count
@@ -404,7 +404,7 @@ conflict_resolution = "prefix" # Strategy for name conflicts
404404
name = "filesystem" # Unique server identifier
405405
command = "python" # Executable to run
406406
args = ["-m", "mcp_server_filesystem", "/data"] # Command arguments
407-
transport = "stdio" # Transport type (stdio or sse)
407+
transport = "stdio" # Transport type (stdio, streamable-http, or sse)
408408
env = { DEBUG = "1" } # Optional environment variables
409409
auto_start = true # Start automatically (default: true)
410410
```
@@ -451,6 +451,79 @@ LOG_LEVEL = "INFO"
451451
CACHE_DIR = "/tmp/cache"
452452
```
453453

454+
### Authentication Configuration
455+
456+
MCP Compose supports multiple authentication providers for securing access to your composed MCP servers.
457+
458+
#### API Key Authentication
459+
460+
```toml
461+
[authentication]
462+
enabled = true
463+
providers = ["api_key"]
464+
default_provider = "api_key"
465+
466+
[authentication.api_key]
467+
header_name = "X-API-Key"
468+
keys = ["your-api-key-1", "your-api-key-2"]
469+
```
470+
471+
Clients send the API key in the header:
472+
```bash
473+
curl -H "X-API-Key: your-api-key-1" http://localhost:8080/mcp
474+
```
475+
476+
#### OAuth2/OIDC Authentication (Generic)
477+
478+
For validating OAuth2 bearer tokens with any OIDC provider:
479+
480+
```toml
481+
[authentication]
482+
enabled = true
483+
providers = ["oauth2"]
484+
default_provider = "oauth2"
485+
486+
[authentication.oauth2]
487+
provider = "generic"
488+
# OIDC issuer URL for auto-discovery of endpoints
489+
issuer_url = "https://id.example.com"
490+
491+
# Or provide explicit endpoints:
492+
# userinfo_endpoint = "https://id.example.com/userinfo"
493+
# introspection_endpoint = "https://id.example.com/oauth/introspect"
494+
495+
# Claim to use for user identification (default: "sub")
496+
user_id_claim = "sub"
497+
498+
# Optional: Client credentials for token introspection
499+
# client_id = "your-client-id"
500+
# client_secret = "your-client-secret"
501+
502+
# Optional: Required scopes
503+
# required_scopes = ["openid", "profile"]
504+
```
505+
506+
Clients send bearer tokens:
507+
```bash
508+
curl -H "Authorization: Bearer <oauth-token>" http://localhost:8080/mcp
509+
```
510+
511+
#### Anaconda Authentication
512+
513+
For validating tokens with Anaconda Cloud:
514+
515+
```toml
516+
[authentication]
517+
enabled = true
518+
providers = ["anaconda"]
519+
default_provider = "anaconda"
520+
521+
[authentication.anaconda]
522+
domain = "anaconda.com"
523+
```
524+
525+
See the `examples/anaconda-token/` and `examples/anaconda-oauth/` directories for complete examples.
526+
454527
## Server Management
455528

456529
### Starting Servers

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88

99
[![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
1010

11-
# ✨ MCP Compose
11+
# ✨ MCP Compose Examples
File renamed without changes.
Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
The composer manages multiple MCP servers and exposes them through a unified endpoint.
88
99
Features:
10-
- Connection to MCP Compose via SSE transport
10+
- Connection to MCP Compose via Streamable HTTP transport
1111
- Interactive CLI interface powered by pydantic-ai
1212
- Access to Calculator and Echo server tools through the composer
13-
- Uses Anthropic Claude Sonnet 4.5 model
13+
- Uses Anthropic Claude Sonnet 4 model
1414
1515
Usage:
1616
# First start the composer server:
@@ -36,7 +36,7 @@
3636
# Pydantic AI imports
3737
try:
3838
from pydantic_ai import Agent
39-
from pydantic_ai.mcp import MCPServerSSE
39+
from pydantic_ai.mcp import MCPServerStreamableHTTP
4040
HAS_PYDANTIC_AI = True
4141
except ImportError:
4242
HAS_PYDANTIC_AI = False
@@ -104,20 +104,18 @@ def create_agent(model: str = "anthropic:claude-sonnet-4-0", server_url: str = "
104104
# Get Anaconda access token
105105
access_token = get_anaconda_token()
106106

107-
print(f"\n📡 Connecting to MCP Compose: {server_url}/sse")
107+
print(f"\n📡 Connecting to MCP Compose: {server_url}/mcp")
108108
print(" Unified access to Calculator and Echo servers")
109109
print(" Using Anaconda bearer token authentication")
110110

111-
# Create MCP server connection with SSE transport and authentication
112-
mcp_server = MCPServerSSE(
113-
url=f"{server_url}/sse",
111+
# Create MCP server connection with Streamable HTTP transport and authentication
112+
mcp_server = MCPServerStreamableHTTP(
113+
url=f"{server_url}/mcp",
114114
headers={
115115
"Authorization": f"Bearer {access_token}"
116116
},
117-
# Increase read timeout for long-running tool calls
118-
read_timeout=300.0, # 5 minutes
119-
# Allow retries for transient failures
120-
max_retries=2
117+
# Increase timeout for long-running tool calls
118+
timeout=300.0, # 5 minutes
121119
)
122120

123121
print(f"\n🤖 Initializing Agent with {model}")
@@ -181,15 +179,15 @@ def main():
181179
async def list_tools(access_token: str):
182180
"""List all tools available from the MCP server"""
183181
try:
184-
# Import MCP SDK client
182+
# Import MCP SDK client for streamable HTTP
185183
from mcp import ClientSession
186-
from mcp.client.sse import sse_client
184+
from mcp.client.streamable_http import streamablehttp_client
187185

188-
# Connect using SSE client with authentication
189-
async with sse_client(
190-
"http://localhost:8080/sse",
186+
# Connect using Streamable HTTP client with authentication
187+
async with streamablehttp_client(
188+
"http://localhost:8080/mcp",
191189
headers={"Authorization": f"Bearer {access_token}"}
192-
) as (read, write):
190+
) as (read, write, _):
193191
async with ClientSession(read, write) as session:
194192
# Initialize the session
195193
await session.initialize()

0 commit comments

Comments
 (0)