Skip to content

Commit cd02bb8

Browse files
committed
Merge remote-tracking branch 'upstream/dev'
2 parents fdb7ea2 + 47d6f7a commit cd02bb8

12 files changed

Lines changed: 815 additions & 28 deletions

File tree

.github/workflows/build-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
3535
echo "Extracted latest release notes from CHANGELOG.md:"
3636
echo -e "$CHANGELOG_CONTENT"
37-
echo "::set-output name=content::$CHANGELOG_ESCAPED"
37+
echo "content=$CHANGELOG_ESCAPED" >> "$GITHUB_OUTPUT"
3838
3939
- name: Create GitHub release
4040
uses: actions/github-script@v7

.github/workflows/publish.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# .github/workflows/publish.yml
2+
name: Publish Python Package
3+
4+
on:
5+
push:
6+
branches:
7+
- main
8+
release:
9+
types: [published] # Triggers workflow when a GitHub release is published
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
publish:
16+
name: Build and Publish to PyPI
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.11" # or your required version
27+
28+
- name: Install uv
29+
run: pip install uv
30+
31+
- name: Build package
32+
run: uv build --no-sources
33+
34+
- name: Publish package to PyPI
35+
env:
36+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
37+
run: |
38+
uv publish

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ and this project adheres to Semantic Versioning.
1616

1717
### Added
1818

19-
- 🔄 **Hot Reload Support for Configuration Files**: Added `--hot-reload` flag to watch your config file for changes and dynamically reload MCP servers without restarting the application—enabling seamless development workflows and runtime configuration updates.
19+
- 🔄 **Hot Reload Support for Configuration Files**: Added \`--hot-reload\` flag to watch your config file for changes and dynamically reload MCP servers without restarting the application—enabling seamless development workflows and runtime configuration updates.
2020
- 🤫 **HTTP Request Filtering for Cleaner Logs**: Added configurable log filtering to reduce noise from frequent HTTP requests, making debugging and monitoring much clearer in production environments.
2121

2222
### Changed
@@ -115,4 +115,4 @@ and this project adheres to Semantic Versioning.
115115

116116
### Fixed
117117

118-
- 🧹 **Cleaner Proxy Output**: Dropped None arguments from proxy requests, resulting in reduced clutter and improved interoperability with servers expecting clean inputs—ensuring more reliable downstream performance with MCP tools.
118+
- 🧹 **Cleaner Proxy Output**: Dropped None arguments from proxy requests, resulting in reduced clutter and improved interoperability with servers expecting clean inputs—ensuring more reliable downstream performance with MCP tools.

OAUTH_GUIDE.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# OAuth 2.1 Support in MCPO
2+
3+
MCPO now supports OAuth 2.1 client-to-server authentication for MCP servers that require it. This enables secure authentication with servers using the Authorization Code flow with refresh tokens.
4+
5+
## Features
6+
7+
- **OAuth 2.1 Authorization Code Flow**: Full support for the modern OAuth 2.1 standard
8+
- **Automatic Token Refresh**: Tokens are automatically refreshed when they expire
9+
- **Persistent Token Storage**: Tokens can be stored in memory or on disk
10+
- **Browser-based Authorization**: Automatically opens browser for user authorization
11+
- **Loopback Callback Server**: Built-in HTTP server for catching OAuth callbacks
12+
13+
## Configuration
14+
15+
### Basic OAuth Configuration (Recommended)
16+
17+
MCPO defaults to **dynamic client registration** for maximum compatibility. Most OAuth servers only need minimal configuration:
18+
19+
```json
20+
{
21+
"mcpServers": {
22+
"my-oauth-server": {
23+
"type": "streamable-http",
24+
"url": "http://localhost:8000/mcp",
25+
"oauth": {
26+
"server_url": "http://localhost:8000"
27+
}
28+
}
29+
}
30+
}
31+
```
32+
33+
This minimal configuration allows MCPO to:
34+
- Automatically discover OAuth endpoints from `/.well-known/oauth-authorization-server`
35+
- Perform dynamic client registration with the server
36+
- Use server-provided scopes and configuration
37+
38+
### Static Client Configuration (Legacy Servers)
39+
40+
For servers that don't support dynamic client registration, you can specify static client metadata:
41+
42+
```json
43+
{
44+
"mcpServers": {
45+
"legacy-oauth-server": {
46+
"type": "streamable-http",
47+
"url": "http://localhost:8000/mcp",
48+
"oauth": {
49+
"server_url": "http://localhost:8000",
50+
"storage_type": "file",
51+
"client_metadata": {
52+
"client_name": "My MCPO Client",
53+
"redirect_uris": ["http://localhost:3030/callback"],
54+
"grant_types": ["authorization_code", "refresh_token"],
55+
"response_types": ["code"]
56+
}
57+
}
58+
}
59+
}
60+
}
61+
```
62+
63+
> ⚠️ **Important**: Avoid hardcoding `scope`, `authorization_endpoint`, or `token_endpoint` in your configuration. These should be automatically discovered from the server's OAuth metadata during the flow.
64+
65+
## OAuth Configuration Options
66+
67+
### Required Fields
68+
69+
- `server_url`: The base URL of the OAuth server (without `/mcp` endpoint)
70+
71+
### Optional Fields
72+
73+
- `callback_port`: Port for the local callback server (default: 3030)
74+
- `use_loopback`: Whether to use automatic browser flow (default: true)
75+
- `storage_type`: Where to store tokens - "memory" or "file" (default: "file")
76+
- `client_metadata`: OAuth client registration metadata
77+
78+
### Client Metadata Fields (for static registration only)
79+
80+
- `client_name`: Display name for your client
81+
- `redirect_uris`: List of allowed redirect URIs
82+
- `grant_types`: OAuth grant types to request (typically `["authorization_code", "refresh_token"]`)
83+
- `response_types`: OAuth response types to accept (typically `["code"]`)
84+
- `token_endpoint_auth_method`: How to authenticate with the token endpoint
85+
86+
**Fields handled automatically (don't set these):**
87+
- `scope`: Automatically provided by the server during dynamic registration
88+
- `authorization_endpoint`: Discovered from server metadata
89+
- `token_endpoint`: Discovered from server metadata
90+
91+
## How It Works
92+
93+
1. **Metadata Discovery**: MCPO automatically discovers OAuth endpoints from the server's `/.well-known/oauth-authorization-server` metadata
94+
2. **Dynamic Registration**: If supported, MCPO registers itself as a client with the OAuth server
95+
3. **Token Check**: MCPO checks for existing valid tokens for this server
96+
4. **Authorization Flow**: If no valid tokens exist, MCPO:
97+
- Opens your browser to the authorization URL
98+
- Starts a local HTTP server to receive the callback
99+
- Exchanges the authorization code for tokens
100+
5. **Token Storage**: Tokens are stored securely:
101+
- **File storage**: In `~/.mcpo/tokens/` directory
102+
- **Memory storage**: Only for the duration of the session
103+
6. **Token Refresh**: When tokens expire, MCPO automatically refreshes them using the refresh token
104+
7. **Authentication**: All requests to the MCP server include the access token
105+
106+
## Storage Types
107+
108+
### File Storage (Recommended)
109+
- Tokens persist between MCPO restarts
110+
- Stored in `~/.mcpo/tokens/` with per-server isolation
111+
- Each server gets its own token file based on a hash of the server name
112+
113+
### Memory Storage
114+
- Tokens only exist for the current session
115+
- Lost when MCPO restarts
116+
- Useful for testing or temporary sessions
117+
118+
## Authorization Flows
119+
120+
### Automatic Browser Flow (Default)
121+
When `use_loopback` is `true`:
122+
1. MCPO opens your default browser to the authorization page
123+
2. After you authorize, you're redirected to `http://localhost:PORT/callback`
124+
3. MCPO's built-in server captures the authorization code
125+
4. The browser shows a success message
126+
127+
### Manual Copy/Paste Flow
128+
When `use_loopback` is `false`:
129+
1. MCPO prints the authorization URL
130+
2. You manually open the URL in a browser
131+
3. After authorization, you copy the full callback URL
132+
4. You paste it back into MCPO when prompted
133+
134+
## Server Support
135+
136+
OAuth authentication is supported for:
137+
-`streamable-http` servers
138+
-`sse` servers (not currently supported)
139+
-`stdio` servers (OAuth not applicable)
140+
141+
## Security Considerations
142+
143+
1. **Token Storage**: File-based tokens are stored in plaintext. For production use, consider implementing encrypted storage
144+
2. **Redirect URIs**: Always use `localhost` for development. For production, use HTTPS URLs
145+
3. **Scopes**: Only request the minimum scopes necessary for your use case
146+
4. **Token Expiry**: Tokens are automatically refreshed, but expired tokens are not automatically deleted
147+
148+
## Troubleshooting
149+
150+
### "OAuth server_url required"
151+
Ensure your configuration includes the `server_url` field in the `oauth` section.
152+
153+
### Browser doesn't open
154+
Check that `use_loopback` is set to `true` and that your system has a default browser configured.
155+
156+
### "No authorization code found"
157+
If using manual flow, ensure you're copying the complete callback URL including all query parameters.
158+
159+
### Port already in use
160+
Change the `callback_port` to an available port number.
161+
162+
### Tokens not persisting
163+
Ensure `storage_type` is set to `"file"` and that MCPO has write permissions to `~/.mcpo/tokens/`.
164+
165+
## Example: Testing OAuth
166+
167+
1. Create a config file with OAuth settings:
168+
```json
169+
{
170+
"mcpServers": {
171+
"test-oauth": {
172+
"type": "streamable-http",
173+
"url": "http://localhost:8000/mcp",
174+
"oauth": {
175+
"server_url": "http://localhost:8000",
176+
"storage_type": "file"
177+
}
178+
}
179+
}
180+
}
181+
```
182+
183+
2. Start MCPO:
184+
```bash
185+
mcpo --config config_oauth.json
186+
```
187+
188+
3. MCPO will:
189+
- Open your browser for authorization
190+
- Capture the callback
191+
- Store tokens
192+
- Connect to the MCP server
193+
194+
4. Subsequent connections will reuse the stored tokens
195+
196+
## Implementation Details
197+
198+
The OAuth implementation uses the Python MCP SDK's built-in `OAuthClientProvider` which handles:
199+
- PKCE (Proof Key for Code Exchange) when required
200+
- Token refresh logic
201+
- Authorization header injection
202+
- Dynamic client registration (if supported by server)
203+
204+
Tokens are bound to individual server instances and isolated from each other, ensuring that each OAuth-enabled server maintains its own authentication session.

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,64 @@ Each tool will be accessible under its own unique route, e.g.:
127127

128128
Each with a dedicated OpenAPI schema and proxy handler. Access full schema UI at: `http://localhost:8000/<tool>/docs` (e.g. /memory/docs, /time/docs)
129129

130+
### 🔐 OAuth 2.1 Authentication
131+
132+
mcpo supports OAuth 2.1 authentication for MCP servers that require it. The implementation defaults to **dynamic client registration**, so most servers only need minimal configuration:
133+
134+
```json
135+
{
136+
"mcpServers": {
137+
"oauth-protected-server": {
138+
"type": "streamable-http",
139+
"url": "http://localhost:8000/mcp",
140+
"oauth": {
141+
"server_url": "http://localhost:8000"
142+
}
143+
}
144+
}
145+
}
146+
```
147+
148+
#### OAuth Configuration Options
149+
150+
**Basic Options:**
151+
- `server_url` (required): OAuth server base URL
152+
- `storage_type`: "file" (persistent) or "memory" (session-only, default: "file")
153+
- `callback_port`: Local port for OAuth callback (default: 3030)
154+
- `use_loopback`: Auto-open browser for auth (default: true)
155+
156+
**Advanced Options (rarely needed):**
157+
For servers that don't support dynamic client registration, you can specify static client metadata:
158+
159+
```json
160+
{
161+
"mcpServers": {
162+
"legacy-oauth-server": {
163+
"type": "streamable-http",
164+
"url": "http://api.example.com/mcp",
165+
"oauth": {
166+
"server_url": "http://api.example.com",
167+
"client_metadata": {
168+
"client_name": "My MCPO Client",
169+
"redirect_uris": ["http://localhost:3030/callback"]
170+
}
171+
}
172+
}
173+
}
174+
}
175+
```
176+
177+
> **Note**: Avoid setting `scope`, `authorization_endpoint`, or `token_endpoint` in the config. These are automatically discovered from the server's OAuth metadata during the dynamic registration flow.
178+
179+
On first connection, mcpo will:
180+
1. Perform dynamic client registration (if supported)
181+
2. Open your browser for authorization
182+
3. Capture the OAuth callback automatically
183+
4. Store tokens securely (in `~/.mcpo/tokens/` for file storage)
184+
5. Use tokens for all subsequent requests
185+
186+
OAuth is supported for `streamable-http` server types. See [OAUTH_GUIDE.md](OAUTH_GUIDE.md) for detailed documentation.
187+
130188
## 🔧 Requirements
131189

132190
- Python 3.8+

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcpo"
3-
version = "0.0.16"
3+
version = "0.0.17"
44
description = "A simple, secure MCP-to-OpenAPI proxy server"
55
authors = [
66
{ name = "Timothy Jaeryang Baek", email = "tim@openwebui.com" }
@@ -10,8 +10,8 @@ requires-python = ">=3.11"
1010
dependencies = [
1111
"click>=8.1.8",
1212
"fastapi>=0.115.12",
13-
"mcp>=1.12.1",
14-
"mcp[cli]>=1.12.1",
13+
"mcp>=1.12.4",
14+
"mcp[cli]>=1.12.4",
1515
"passlib[bcrypt]>=1.7.4",
1616
"pydantic>=2.11.1",
1717
"pyjwt[crypto]>=2.10.1",

0 commit comments

Comments
 (0)