Skip to main content

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

  • 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 operationId when 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

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:

  1. Command-line argument: mcp_proxy --config /path/to/config.toml
  2. Environment variable: export MCP_PROXY_CONFIG=/path/to/config.toml
  3. Current directory: ./config.toml
  4. Home directory: ~/.mcp_proxy/config.toml
  5. 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:

  1. JSON array (recommended)
export OPENAPI_SPEC_URLS='[
  {"url":"file:///tmp/spec-a.json","path_params":{"environment_id":"prod"}},
  "https://api.example.com/openapi.json"
]'
  1. Comma-separated list
export OPENAPI_SPEC_URLS="file:///tmp/spec-a.json,https://api.example.com/openapi.json"
  1. 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 SpecConfig fields: {"url":"...","token":"...","proxy":"...","base_url":"...","path_types":[...],"path_params":{...}}

Other environment variables used in fallback mode:

  • USE_TACHYON (default true)
  • MCP_TRANSPORT (default stdio)
  • MCP_SERVER_HOST (default 0.0.0.0)
  • MCP_SERVER_PORT (default 8080)
  • VERIFY_SSL (default true)
  • SPEC_FETCH_TIMEOUT_SECONDS (default 10)
  • UPSTREAM_REQUEST_TIMEOUT_SECONDS (default 30)
  • UPSTREAM_MAX_RETRIES (default 3)
  • UPSTREAM_RETRY_BACKOFF_SECONDS (default 0.5)
  • CIRCUIT_BREAKER_ENABLED (default true)
  • CIRCUIT_BREAKER_FAILURE_THRESHOLD (default 5)
  • CIRCUIT_BREAKER_RECOVERY_SECONDS (default 30)

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.json or ../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_incidents
  • bigpanda_get_incident
  • bigpanda_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, Get all 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_params defaults

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:

  1. Check file exists: ls -la config.toml
  2. Use absolute path: mcp_proxy --config /full/path/to/config.toml
  3. Set env var: export MCP_PROXY_CONFIG=/path/to/config.toml
  4. 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:

  1. Verify file exists: ls -la openapi/spec.json
  2. Check file permissions: cat openapi/spec.json (should output JSON)
  3. Use absolute path: url = "/full/path/to/spec.json"
  4. 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:

  1. Check auth.enabled = true
  2. Verify token hash: python -c "import hashlib; print('sha256:' + hashlib.sha256(b'YOUR_TOKEN').hexdigest())"
  3. Ensure token matches in [[auth.tokens]]
  4. Check header format: Authorization: Bearer YOUR_TOKEN (not just Bearer)

For OAuth/JWT:

  1. Check auth.oauth.enabled = true
  2. Verify JWT secret: echo $MCP_OAUTH_SHARED_SECRET
  3. Ensure token is valid JWT: python -c "import jwt; jwt.decode(token, secret, algorithms=['HS256'])"
  4. 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:

  1. Upstream API unreachable
  2. Corporate proxy requires authentication (add proxy in [[specs]])
  3. Network latency
  4. 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

  1. 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)
    
  2. Add to config:

    [[specs]]
    url = "openapi/my-api.json"
    token = "bearer-token-if-needed"
    path_types = ["get", "post"]
    
  3. 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:

  1. Edit mcp_proxy/models.py — add new AuthXyzConfig model
  2. Edit mcp_proxy/openapi_mcp_proxy.py — add _authenticate_xyz() method
  3. Update _authorize_http_caller() to call new method
  4. 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:

  1. Check Troubleshooting section
  2. Review logs: mcp_proxy --config config.toml 2>&1 | tail -50
  3. Open an issue with config (redact secrets), error message, and steps to reproduce

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

glean_mcp_proxy-0.1.0.tar.gz (39.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

glean_mcp_proxy-0.1.0-py3-none-any.whl (33.1 kB view details)

Uploaded Python 3

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

Hashes for glean_mcp_proxy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e9f924d89afd85debd7e404e8a0113ab206ac6f907038502922fd2fd1083304a
MD5 6b46ebafae4072e7da68a2f5f6d968c8
BLAKE2b-256 13dee2dd675da058aa733e6669bc0404e07af8d76042a7eadacbd0fa4e26862b

See more details on using hashes here.

Provenance

The following attestation bundles were made for glean_mcp_proxy-0.1.0.tar.gz:

Publisher: publish.yaml on Glean-llc/mcp-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file glean_mcp_proxy-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for glean_mcp_proxy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 13e8515ff00e63d543daccc01d53a7dda7fb20c3256a65c471ff5b500f51a5d6
MD5 f963533fbb1613e0f6b9a5cc587ddc8e
BLAKE2b-256 00e2544f5711bb8fb5575f190a849fd54071682427bca139a83691a346bb2f58

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page