Expose OpenAPI endpoints to MCP
Project description
OpenAPI MCP Proxy
A flexible, production-ready proxy that dynamically exposes OpenAPI-described REST APIs as Model Context Protocol (MCP) tools. Automatically keep your MCP surface in sync with any REST API without manual tool definitions.
Author: David Grimm david.grimm@wellsfargo.com
Table of Contents
- Features
- Quick Start
- Installation
- Configuration
- Usage
- Authentication
- Architecture
- Testing & Coverage
- Troubleshooting
- Development
Features
- Dynamic Tool Generation — Automatically create MCP tools from OpenAPI specs
- Multi-Spec Support — Proxy multiple APIs simultaneously
- Dual Transport — stdio (default) or HTTP with optional HTTPS/TLS
- Framework Flexibility — FastMCP or Tachyon MCP (configurable)
- Static & OAuth Auth — Support both static bearer tokens and JWT/OAuth2
- Path Parameter Templating — Set default values per spec
- HTTP Method Filtering — Expose only specific methods (GET, POST, etc.)
- Readable Tool Names — Uses OpenAPI
operationIdwhen available (with clean fallback naming) - Startup Tool Summary — Logs per-spec tool inventory (name, method, path, summary) at boot
- Proxy Support — Route through corporate HTTP proxies
- Per-Spec Overrides — Token, base URL, proxy per API
- SSL/TLS — HTTPS, mutual TLS, and certificate validation
- Type-Safe Configuration — Pydantic v2 validation
Quick Start
1. Install
# For FastMCP (default, recommended)
poetry install --extras fastmcp
# OR for Tachyon MCP
poetry install --extras tachyon
2. Create a configuration file (config.toml)
[server]
use_tachyon = false # Use FastMCP (true for Tachyon)
transport = "stdio" # or "http"
host = "0.0.0.0"
port = 8080
verify_ssl = true
[[specs]]
url = "file:///path/to/spec.json" # Can be file://, http://, or https://
token = "your-bearer-token" # Optional: for APIs requiring auth
path_types = ["get", "post"] # Optional: filter by HTTP method
[specs.path_params] # Optional: default values for path params
environment_id = "my-env-123"
3. Run
# Use config.toml in current directory
mcp_proxy
# Or specify config path
mcp_proxy --config /path/to/config.toml
# Or set env var
export MCP_PROXY_CONFIG=/path/to/config.toml
mcp_proxy
✅ Success!
Your MCP tools are now available. Connect with:
- VS Code Copilot: Point to stdio endpoint
- OpenAI API: Use HTTP transport at
http://localhost:8080 - MCP Inspector:
mcp inspect stdio "mcp_proxy"(testable)
Installation
Prerequisites
- Python 3.12 or later
- Poetry (https://python-poetry.org/docs/#installation)
From Source
git clone <repository>
cd App-emap-lib-mcp-proxy
# FastMCP (default, recommended)
poetry install --extras fastmcp
# Tachyon MCP (enterprise)
poetry install --extras tachyon
# All frameworks
poetry install --extras all
# Add development/testing tools to any of the above
poetry install --extras fastmcp --with dev
Verify Installation
poetry run mcp_proxy --help
# Output:
# usage: mcp_proxy [-h] [-c PATH]
#
# OpenAPI MCP Proxy — expose OpenAPI endpoints as MCP tools
#
# options:
# -h, --help show this help message and exit
# -c PATH, --config PATH
# Path to config TOML file (overrides MCP_PROXY_CONFIG env var)
Configuration
Configuration Discovery
The proxy looks for configuration in this order:
- Command-line argument:
mcp_proxy --config /path/to/config.toml - Environment variable:
export MCP_PROXY_CONFIG=/path/to/config.toml - Current directory:
./config.toml - Home directory:
~/.mcp_proxy/config.toml - Environment fallback: Uses legacy environment variables if no TOML is found
Environment Fallback Format (Legacy)
If no TOML config file is found via auto-discovery, the proxy falls back to environment variables.
The required spec source is OPENAPI_SPEC_URLS.
OPENAPI_SPEC_URLS supports these formats:
- JSON array (recommended)
export OPENAPI_SPEC_URLS='[
{"url":"file:///tmp/spec-a.json","path_params":{"environment_id":"prod"}},
"https://api.example.com/openapi.json"
]'
- Comma-separated list
export OPENAPI_SPEC_URLS="file:///tmp/spec-a.json,https://api.example.com/openapi.json"
- Comma-separated with line continuation
export OPENAPI_SPEC_URLS="file:///tmp/spec-a.json,\\
https://api.example.com/openapi.json"
Accepted JSON array items:
- String URL/path:
"file:///tmp/spec.json" - Object with
SpecConfigfields:{"url":"...","token":"...","proxy":"...","base_url":"...","path_types":[...],"path_params":{...}}
Other environment variables used in fallback mode:
USE_TACHYON(defaulttrue)MCP_TRANSPORT(defaultstdio)MCP_SERVER_HOST(default0.0.0.0)MCP_SERVER_PORT(default8080)VERIFY_SSL(defaulttrue)SPEC_FETCH_TIMEOUT_SECONDS(default10)UPSTREAM_REQUEST_TIMEOUT_SECONDS(default30)UPSTREAM_MAX_RETRIES(default3)UPSTREAM_RETRY_BACKOFF_SECONDS(default0.5)CIRCUIT_BREAKER_ENABLED(defaulttrue)CIRCUIT_BREAKER_FAILURE_THRESHOLD(default5)CIRCUIT_BREAKER_RECOVERY_SECONDS(default30)
Note: Spec payloads are JSON-only. .yaml / .yml specs and non-JSON responses are rejected at startup.
TOML Format
All configuration uses TOML (Tom's Obvious, Minimal Language). Start with this template:
[server]
# Framework selection
use_tachyon = false # false = FastMCP, true = Tachyon MCP
# Transport mode
transport = "stdio" # "stdio" (default, local) or "http" (remote/API)
host = "0.0.0.0" # HTTP server listen address
port = 8080 # HTTP server port (1-65535)
# SSL/TLS Configuration (optional, HTTP transport only)
verify_ssl = true # Verify upstream API certificates
# ssl_keyfile = "/path/to/server.key" # Enable HTTPS
# ssl_certfile = "/path/to/server.crt" # Enable HTTPS
# ssl_ca_certs = "/path/to/ca-bundle.crt" # Mutual TLS client verification
# ssl_keyfile_passphrase = "secret" # If key file is encrypted
# Timeout settings
spec_fetch_timeout_seconds = 10.0 # Timeout for fetching OpenAPI specs
upstream_request_timeout_seconds = 30.0 # Timeout for proxied upstream API calls
# Retry / backoff (upstream API calls only)
upstream_max_retries = 3 # 0 = no retries
upstream_retry_backoff_seconds = 0.5 # Doubles each attempt (0.5s, 1s, 2s, …)
# Circuit breaker
circuit_breaker_enabled = true # Fast-fail after repeated upstream failures
circuit_breaker_failure_threshold = 5 # Consecutive failures before circuit opens
circuit_breaker_recovery_seconds = 30.0 # Seconds before a half-open probe is allowed
[[specs]]
# OpenAPI specification
url = "file:///path/to/spec.json" # file://, http://, or https://
# url = "https://api.example.com/openapi.json"
# Optional: short name prefix for tool names (overrides spec info.title)
# name = "myapi" # Tools become: myapi_get_users, myapi_post_orders, etc.
# Optional: credentials for fetching spec or upstream API
token = "Bearer my-token" # If spec URL or API requires auth
proxy = "http://proxy.example.com:8080" # Route through corporate proxy
# Optional: override base URL from spec
base_url = "https://api.example.com"
# Optional: filter which HTTP methods to expose as tools
path_types = ["get", "post", "put", "delete"] # Defaults to all methods
# Optional: default values for templated path parameters
[specs.path_params]
environment_id = "prod-env-123"
region = "us-east-1"
# Add more specs by repeating [[specs]] section
[[specs]]
url = "file:///path/to/spec2.json"
path_types = ["get"]
Server Settings
| Option | Type | Default | Description |
|---|---|---|---|
use_tachyon |
bool | false |
Framework: true = Tachyon, false = FastMCP |
transport |
string | "stdio" |
"stdio" (stdio MCP) or "http" (HTTP server) |
host |
string | "0.0.0.0" |
HTTP listen address (ignored for stdio) |
port |
integer | 8080 |
HTTP port, 1–65535 (ignored for stdio) |
verify_ssl |
bool | true |
Verify upstream API SSL certificates |
spec_fetch_timeout_seconds |
float | 10.0 |
Timeout in seconds for fetching OpenAPI specs at startup |
upstream_request_timeout_seconds |
float | 30.0 |
Timeout in seconds for each proxied upstream API call |
upstream_max_retries |
integer | 3 |
Retry attempts on timeout/connection error (0 = no retries) |
upstream_retry_backoff_seconds |
float | 0.5 |
Initial backoff between retries; doubles each attempt (0.5s → 1s → 2s …) |
circuit_breaker_enabled |
bool | true |
Fast-fail requests when an upstream is repeatedly failing |
circuit_breaker_failure_threshold |
integer | 5 |
Consecutive failures before the circuit opens |
circuit_breaker_recovery_seconds |
float | 30.0 |
Seconds before the circuit allows a probe request (half-open) |
ssl_keyfile |
string | — | Path to TLS private key (enables HTTPS) |
ssl_certfile |
string | — | Path to TLS certificate (PEM/CRT format) |
ssl_ca_certs |
string | — | Path to CA bundle for client certificate verification |
ssl_keyfile_passphrase |
string | — | Passphrase for encrypted TLS key file |
OpenAPI Specs
Each [[specs]] section defines one OpenAPI API to proxy:
| Option | Type | Required | Description |
|---|---|---|---|
url |
string | ✅ | OpenAPI spec location: file://, http://, or https:// |
name |
string | — | Override the API name prefix used in all tool names (e.g., "bigpanda") |
token |
string | — | Bearer token for API or spec authentication |
proxy |
string | — | HTTP proxy URL for upstream requests |
base_url |
string | — | Override the API servers[0].url from spec |
path_types |
list | — | Filter HTTP methods: ["get", "post", ...] |
path_params |
dict | — | Default values for path parameters |
The name field is useful when the spec's info.title is verbose or version-stamped (e.g., "Alert Pipeline API v1.3.0-SNAPSHOT"), which would otherwise produce unwieldy tool names. Setting name = "alerts" keeps tools short and readable: alerts_get_incidents instead of alert_pipeline_api_v1_3_0_snapshot_get_incidents.
Spec URL Resolution
Relative paths are resolved intelligently:
-
Bare relative path (e.g.,
openapi/spec.json):- First tries relative to current working directory (CWD)
- Falls back to relative to config file directory
- Use this for specs in your project repository
-
Explicit dot-relative (e.g.,
./openapi/spec.jsonor../specs/api.json):- Always resolved relative to config file directory
- Use this for specs outside your CWD
Examples:
# Current directory strategy
[[specs]]
url = "openapi/bigpanda.json" # Tries ./openapi/bigpanda.json first
# Explicit relative to config directory
[[specs]]
url = "../shared-specs/api.json" # Relative to config file location
# Absolute or remote
[[specs]]
url = "/etc/mcp-proxy/specs/api.json"
[[specs]]
url = "https://api.example.com/openapi.json"
Path Parameters
Template path parameters with default values:
[[specs]]
url = "file:///path/to/spec.json"
[specs.path_params]
environment_id = "prod-123" # Replaces {environment_id} in paths
account_id = "acc-456" # Can template multiple values
region = "us-east-1"
When a tool is invoked, these defaults are used if the caller doesn't provide the value.
Filtering by HTTP Method
Expose only specific HTTP methods:
[[specs]]
url = "file:///path/to/spec.json"
path_types = ["get"] # Only GET operations → tools
[[specs]]
url = "file:///path/to/spec.json"
path_types = ["get", "post"] # GET and POST only
# Omit path_types to expose all methods
[[specs]]
url = "file:///path/to/spec.json"
Usage
stdio Transport (Default)
Purpose: MCP over standard input/output (stdio). Used by:
- VS Code Copilot Chat
- Claude Desktop
- MCP Inspector (local testing)
Dynamic Spec Management
- Any MCP client connecting via stdout/stdin
Setup:
[server]
transport = "stdio" # or omit (default)
Run:
mcp_proxy --config config.toml
In VS Code Copilot:
Add to .vscode/settings.json or codeowners.json:
{
"github.copilot.agent.endpoints": [
{
"codespaceId": "my-project",
"provider": "stdio",
"command": "mcp_proxy",
"args": ["--config", "/path/to/config.toml"]
}
]
}
HTTP Transport
Purpose: MCP over HTTP. Used by:
- Remote clients via REST API
- Web applications
- Services that can't use stdio
Setup:
[server]
transport = "http"
host = "0.0.0.0" # Listen on all interfaces
port = 8080 # Or your preferred port
Run:
mcp_proxy --config config.toml
HTTP Endpoints (FastMCP):
POST /mcp/— MCP Streamable HTTP endpoint (tool discovery + invocation via MCP protocol)GET /sse/— SSE transport endpoint (when using SSE transport)
Note: In FastMCP HTTP mode, the startup banner may display /mcp (without trailing slash).
Use MCP clients against /mcp/; legacy /tools and /call routes are not used.
Quick connectivity check:
curl -i http://localhost:8080/mcp/
Enable HTTPS (TLS):
[server]
transport = "http"
port = 443
ssl_keyfile = "/etc/ssl/private/server.key"
ssl_certfile = "/etc/ssl/certs/server.crt"
Mutual TLS (mTLS):
[server]
ssl_keyfile = "/etc/ssl/private/server.key"
ssl_certfile = "/etc/ssl/certs/server.crt"
ssl_ca_certs = "/etc/ssl/certs/ca-bundle.crt" # Client cert verification
Framework Selection
FastMCP (default, recommended):
- Lightweight, async-first
- Fewer dependencies
- Better performance
- Install:
poetry install --extras fastmcp
[server]
use_tachyon = false # FastMCP
Tachyon MCP (enterprise, optional):
- Integrations with LangChain
- Advanced enterprise features
- Install:
poetry install --extras tachyon
[server]
use_tachyon = true # Tachyon MCP
Tool Naming and Startup Summary
Tool names are generated to stay readable while remaining unique across specs.
- Preferred source: OpenAPI
operationId(camelCase converted to snake_case) - Fallback source: method + normalized path segments
- Multi-spec collisions: de-duplicated by suffix (
_2,_3, ...)
Example names:
bigpanda_search_incidentsbigpanda_get_incidentbigpanda_list_alert_tags
At startup, the proxy logs a per-spec inventory including:
- Spec name and source URL
- Tool count for that spec
- Tool name, method, path, and short description/summary
Example header:
========================================================================
MCP Proxy Startup Summary - 11 tool(s) registered
========================================================================
Authentication
Authentication is optional and disabled by default. Enable only if your API or tool invocation requires it.
Token-Based Authentication
Use static bearer tokens (pre-shared, hashed tokens):
1. Generate a token hash
Use the included token utility:
# Generate a new random token (prints token + hash + save reminder)
token
# Generate and include a ready-to-paste TOML snippet for a user
token --user svc-readonly
# Hash a specific known token value
token --token "my-secret-token-12345"
# Output hash only (for scripting)
token --quiet
# Control token length (default 32 bytes = 64-char hex token)
token --length 48
Example output:
Token: 3f8a2c1d... ← save this; used in Authorization header
Token hash: sha256:abc123def456... ← put this in config TOML
IMPORTANT: Save the token value now — it cannot be recovered from the hash.
2. Configure in TOML
[auth]
enabled = true
mode = "static" # Only static tokens
header_name = "Authorization"
require_bearer_prefix = true
[[auth.users]]
user_id = "svc-readonly"
display_name = "Service Readonly"
roles = ["reader"]
scopes = ["proxy.read"]
[[auth.tokens]]
token_hash = "sha256:abc123def456..." # From step 1
user_id = "svc-readonly" # Links to auth.users
scopes = ["proxy.read"]
active = true
# expires_at = "2027-12-31T23:59:59Z" # Optional expiration
3. Call tools with token
# HTTP transport (MCP endpoint)
curl -i http://localhost:8080/mcp/ \
-H "Authorization: Bearer my-secret-token-12345"
Use an MCP client (VS Code Copilot, MCP Inspector, SDK client) to enumerate and invoke tools over /mcp/.
OAuth/JWT Authentication
Use JWT tokens (signed, expiring credentials):
1. Configure JWT issuer
[auth]
enabled = true
mode = "oauth" # Only OAuth/JWT
header_name = "Authorization"
require_bearer_prefix = true
[auth.oauth]
enabled = true
issuer = "https://auth.example.com" # JWT issuer
audience = "mcp-proxy" # Expected audience (`aud`)
algorithms = ["HS256"] # Signed with HMAC-SHA256
shared_secret_env = "MCP_OAUTH_SHARED_SECRET" # Env var holding secret
2. Provide JWT secret
export MCP_OAUTH_SHARED_SECRET="your-shared-secret"
mcp_proxy --config config.toml
Or in production, use a .env file (not committed):
# .env.local (add to .gitignore)
MCP_OAUTH_SHARED_SECRET=your-production-secret
3. Generate a JWT
import jwt
import json
from datetime import datetime, timedelta, timezone
payload = {
"iss": "https://auth.example.com",
"aud": "mcp-proxy",
"sub": "user-123",
"exp": datetime.now(timezone.utc) + timedelta(hours=1)
}
token = jwt.encode(payload, "your-shared-secret", algorithm="HS256")
print(token)
4. Call tools with JWT
curl -i http://localhost:8080/mcp/ \
-H "Authorization: Bearer <your-jwt-token>"
Dual-Mode Authentication
Accept either static tokens OR JWT (both simultaneously):
[auth]
enabled = true
mode = "either" # Accept static OR OAuth
[[auth.users]]
user_id = "svc-bot"
roles = ["reader"]
[[auth.tokens]]
token_hash = "sha256:abc123..." # Static token
user_id = "svc-bot"
active = true
[auth.oauth]
enabled = true
issuer = "https://auth.example.com"
audience = "mcp-proxy"
algorithms = ["HS256"]
shared_secret_env = "MCP_OAUTH_SHARED_SECRET"
Now the proxy accepts either:
- Requests with header
Authorization: Bearer my-static-token - Requests with header
Authorization: Bearer <jwt-token>
Roles and RBAC
Roles enforce which HTTP methods a user can invoke. Role names map directly to HTTP method names.
| Role | Permits |
|---|---|
get |
GET tools (read-only) |
post |
POST tools (create) |
put |
PUT tools (replace) |
patch |
PATCH tools (update) |
delete |
DELETE tools (destroy) |
Rules:
- A user with an empty roles list is unrestricted — can call any tool
- A user with roles can only call tools whose HTTP method is in their role list
- Role names are case-insensitive (
GET,get,Getall match) - A 403 Forbidden is returned when a role check fails (upstream API is never called)
Example — tiered service accounts:
[[auth.users]]
user_id = "svc-readonly"
roles = ["get"] # May only call GET tools
[[auth.users]]
user_id = "svc-writer"
roles = ["get", "post", "patch"] # Read + create + update, no delete
[[auth.users]]
user_id = "svc-admin"
roles = [] # Unrestricted — all methods permitted
For OAuth/JWT, roles are read from the roles claim in the token payload:
{
"sub": "user-123",
"roles": ["get", "post"],
"exp": 1234567890
}
Disabling Authentication
[auth]
enabled = false # or omit the entire [auth] section
When disabled:
- All HTTP requests are accepted
- No Authorization header required
- All tools available to all callers
Architecture
How It Works
┌─────────────────────┐
│ MCP Client │ (VS Code, Claude Desktop, etc.)
└──────────┬──────────┘
│
┌──────▼──────────────────────┐
│ OpenAPI MCP Proxy │
│ ┌────────────────────────┐ │
│ │ Transport Layer │ │ stdio or HTTP
│ │ (stdio or FastMCP HTTP)│ │
│ └──────────┬─────────────┘ │
│ │ │
│ ┌──────────▼──────────────┐ │
│ │ Auth Enforcement │ │ (if enabled)
│ │ (static token/JWT) │ │
│ └──────────┬──────────────┘ │
│ │ │
│ ┌──────────▼──────────────┐ │
│ │ OpenAPI Spec Loader │ │
│ │ (validates & parses) │ │
│ └──────────┬──────────────┘ │
│ │ │
│ ┌──────────▼──────────────┐ │
│ │ Tool Registration │ │ (one tool per operation)
│ │ (filters by path_types)│ │
│ └──────────┬──────────────┘ │
│ │ │
│ ┌──────────▼──────────────┐ │
│ │ HTTP Proxy │ │ Route to upstream API
│ │ (template paths,auth) │ │
│ └──────────┬──────────────┘ │
└─────────────┼──────────────────┘
│
┌────────▼────────┐
│ Upstream REST API│ (BigPanda, custom services, etc.)
└─────────────────┘
Key Components
1. Config Loader (app_config.py)
- Reads TOML config with Pydantic validation
- Handles env var fallbacks
- Discovers config file in standard locations
2. Spec Manager (openapi_mcp_proxy.py)
- Fetches OpenAPI specs (file://, http://, https://)
- Validates against OpenAPI 3.0/3.1 schema
- Caches specs to avoid re-fetching
3. Tool Builder (openapi_mcp_proxy.py)
- For each operation in the OpenAPI spec, registers one MCP tool
- Filters by
path_types(HTTP method) - Injects
path_paramsdefaults
4. HTTP Proxy
- Routes tool invocations to upstream API
- Handles template parameters
- Preserves headers, authentication
- Returns JSON-serializable results
Testing & Coverage
Run Tests
# Install including dev dependencies
poetry install --extras fastmcp --with dev
# Run all tests
poetry run pytest
# Run with coverage
poetry run pytest --cov=mcp_proxy --cov-report=html
# Run specific test file
poetry run pytest tests/test_openapi_builder_unit.py -v
# Run specific test function
poetry run pytest tests/test_openapi_builder_unit.py::test_builder_initialization -v
Coverage Goals
Current test coverage: 85% of mcp_proxy/ module.
Coverage by file:
openapi_mcp_proxy.py: 85% (core proxy logic)app_config.py: 85%+ (config loading)models.py: 90%+ (Pydantic models)
What's tested:
- Configuration parsing (valid, invalid, edge cases)
- Spec loading (local files, remote HTTP, invalid specs)
- Tool registration (filtering, parameter templating)
- Path resolution (CWD vs config-dir relative paths)
- Authentication (static tokens, JWT/OAuth)
- HTTP proxy routing (headers, auth, error cases)
- Startup behavior (graceful errors, exit codes)
Test Files
tests/
├── test_openapi_builder_unit.py # Builder, routing, auth (38 tests)
├── test_app_config_unit.py # Config loading (8 tests)
└── conftest.py # Pytest fixtures (optional)
Troubleshooting
Issue: Config file not found
Error:
RuntimeError: Config file not found or invalid
Solutions:
- Check file exists:
ls -la config.toml - Use absolute path:
mcp_proxy --config /full/path/to/config.toml - Set env var:
export MCP_PROXY_CONFIG=/path/to/config.toml - Verify TOML syntax:
python -c "import tomli; tomli.load(open('config.toml', 'rb'))"
Issue: OpenAPI spec fails to load
Error:
RuntimeError: Failed to load spec from file://...
Solutions:
- Verify file exists:
ls -la openapi/spec.json - Check file permissions:
cat openapi/spec.json(should output JSON) - Use absolute path:
url = "/full/path/to/spec.json" - Validate spec syntax:
python -c "import json; json.load(open('openapi/spec.json'))"
Issue: Path parameters not working
Problem: Tool receives {environment_id} instead of actual value
Solution: Define in config:
[[specs]]
url = "..."
[specs.path_params]
environment_id = "prod-123"
Issue: Authentication fails with 401
Error: All requests return {"error": "Unauthorized"}
Solutions:
For static tokens:
- Check
auth.enabled = true - Verify token hash:
python -c "import hashlib; print('sha256:' + hashlib.sha256(b'YOUR_TOKEN').hexdigest())" - Ensure token matches in
[[auth.tokens]] - Check header format:
Authorization: Bearer YOUR_TOKEN(not justBearer)
For OAuth/JWT:
- Check
auth.oauth.enabled = true - Verify JWT secret:
echo $MCP_OAUTH_SHARED_SECRET - Ensure token is valid JWT:
python -c "import jwt; jwt.decode(token, secret, algorithms=['HS256'])" - Check issuer and audience match config
Issue: Same token works locally, fails in production
Likely cause: Different config files or env vars in production.
Debug:
# Check which config is loaded
mcp_proxy --config config.toml 2>&1 | grep "Loading config"
# Verify env vars
echo $MCP_PROXY_CONFIG
echo $MCP_OAUTH_SHARED_SECRET
Issue: Proxy hangs or times out
Possible causes:
- Upstream API unreachable
- Corporate proxy requires authentication (add
proxyin[[specs]]) - Network latency
- Default timeouts too short for slow APIs
Solutions:
# Test upstream API directly
curl -H "Bearer YOUR_TOKEN" https://api.example.com/health
# Check proxy connectivity
curl -x http://corporate-proxy:8080 https://api.example.com
Tune timeouts and retries in config.toml:
[server]
spec_fetch_timeout_seconds = 30.0 # Default 10 — increase for slow spec endpoints
upstream_request_timeout_seconds = 60.0 # Default 30 — increase for slow upstream APIs
upstream_max_retries = 5 # Default 3 — more retries for flaky upstreams
upstream_retry_backoff_seconds = 1.0 # Default 0.5 — longer initial wait between retries
circuit_breaker_failure_threshold = 10 # Default 5 — raise if upstreams are normally unreliable
circuit_breaker_recovery_seconds = 60.0 # Default 30 — longer recovery window
Issue: UnicodeDecodeError or encoding issues
Error:
UnicodeDecodeError: 'utf-8' codec can't decode byte...
Solution: Update to Python 3.12+:
python --version # Should be 3.12.0 or later
Development
Project Structure
App-emap-lib-mcp-proxy/
├── mcp_proxy/
│ ├── __main__.py # CLI entry point
│ ├── __init__.py # Package init
│ ├── models.py # Pydantic config models
│ ├── app_config.py # Config loading logic
│ └── openapi_mcp_proxy.py # Core proxy builder & HTTP server
├── config/
│ └── bp.toml # Example configuration
├── openapi/
│ └── bp_openapi.json # Example OpenAPI spec
├── tests/
│ ├── test_openapi_builder_unit.py
│ ├── test_app_config_unit.py
│ └── conftest.py
├── docs/
│ └── CONFIGURATION.md # Configuration reference
├── pyproject.toml # Project metadata & dependencies
├── requirements.txt # Pip requirements
└── README.md # This file
Adding a New OpenAPI Spec
-
Obtain the OpenAPI spec:
# Download to local file curl https://api.example.com/openapi.json -o openapi/my-api.json # Or, keep remote # (URL will be fetched at startup)
-
Add to config:
[[specs]] url = "openapi/my-api.json" token = "bearer-token-if-needed" path_types = ["get", "post"]
-
Test:
mcp_proxy --config config.toml # Check logs for "Registered X tools from my-api.json"
Creating Custom Authentication
To add new auth methods beyond static tokens and OAuth:
- Edit
mcp_proxy/models.py— add newAuthXyzConfigmodel - Edit
mcp_proxy/openapi_mcp_proxy.py— add_authenticate_xyz()method - Update
_authorize_http_caller()to call new method - Add tests in
tests/test_openapi_builder_unit.py
Running Tests Locally
# Full suite
pytest tests/ -v
# With coverage report
pytest tests/ --cov=mcp_proxy --cov-report=term-missing
# Generate HTML coverage report
pytest tests/ --cov=mcp_proxy --cov-report=html
# Open htmlcov/index.html in browser
Linting & Type Checking
# Type check (if mypy available)
poetry run mypy mcp_proxy/
# Format code
poetry run black mcp_proxy/ tests/
# Lint
poetry run flake8 mcp_proxy/ tests/
Building & Packaging
# Create distribution
poetry build
# Publish to PyPI (requires authentication)
poetry publish
# Install locally from source (choose a framework)
poetry install --extras fastmcp
# Install with all frameworks and dev tools
poetry install --extras all --with dev
License
Proprietary — Wells Fargo Confidential
Support
For issues or questions:
- Check Troubleshooting section
- Review logs:
mcp_proxy --config config.toml 2>&1 | tail -50 - Open an issue with config (redact secrets), error message, and steps to reproduce
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file glean_mcp_proxy-0.1.0.tar.gz.
File metadata
- Download URL: glean_mcp_proxy-0.1.0.tar.gz
- Upload date:
- Size: 39.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9f924d89afd85debd7e404e8a0113ab206ac6f907038502922fd2fd1083304a
|
|
| MD5 |
6b46ebafae4072e7da68a2f5f6d968c8
|
|
| BLAKE2b-256 |
13dee2dd675da058aa733e6669bc0404e07af8d76042a7eadacbd0fa4e26862b
|
Provenance
The following attestation bundles were made for glean_mcp_proxy-0.1.0.tar.gz:
Publisher:
publish.yaml on Glean-llc/mcp-proxy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
glean_mcp_proxy-0.1.0.tar.gz -
Subject digest:
e9f924d89afd85debd7e404e8a0113ab206ac6f907038502922fd2fd1083304a - Sigstore transparency entry: 1118044612
- Sigstore integration time:
-
Permalink:
Glean-llc/mcp-proxy@fd8b470a2f2862038d55e28ed72323c72b97a29c -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Glean-llc
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@fd8b470a2f2862038d55e28ed72323c72b97a29c -
Trigger Event:
release
-
Statement type:
File details
Details for the file glean_mcp_proxy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: glean_mcp_proxy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13e8515ff00e63d543daccc01d53a7dda7fb20c3256a65c471ff5b500f51a5d6
|
|
| MD5 |
f963533fbb1613e0f6b9a5cc587ddc8e
|
|
| BLAKE2b-256 |
00e2544f5711bb8fb5575f190a849fd54071682427bca139a83691a346bb2f58
|
Provenance
The following attestation bundles were made for glean_mcp_proxy-0.1.0-py3-none-any.whl:
Publisher:
publish.yaml on Glean-llc/mcp-proxy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
glean_mcp_proxy-0.1.0-py3-none-any.whl -
Subject digest:
13e8515ff00e63d543daccc01d53a7dda7fb20c3256a65c471ff5b500f51a5d6 - Sigstore transparency entry: 1118044659
- Sigstore integration time:
-
Permalink:
Glean-llc/mcp-proxy@fd8b470a2f2862038d55e28ed72323c72b97a29c -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Glean-llc
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@fd8b470a2f2862038d55e28ed72323c72b97a29c -
Trigger Event:
release
-
Statement type: