Skip to main content
Glama
Chawengwit

MCP Data Gateway

by Chawengwit

MCP Data Gateway

Status: OAuth Provider live. Phases 1–9.5 shipped — stdio, HTTP, OAuth 2.0 Authorization Server (RFC 8414/7591/7636/9728), per-user multi-tenant sessions, CORS, STDIO keyring auth, and LLM-facing endpoint metadata. Verified end-to-end against Claude Desktop (STDIO + natural language) and MCP Inspector (HTTP

A Python-based Model Context Protocol (MCP) server that acts as a unified data gateway, enabling Claude (and other MCP clients) to send and receive data across multiple external APIs through a single, secure interface.

Overview

This MCP server provides:

  • Generic data handling for multiple data types

  • Generic API gateway supporting any REST or GraphQL endpoint

  • OAuth 2.0 authentication via an operator-run scripts/oauth_login.py (PKCE flow, 127.0.0.1 callback)

  • Secure credential storage using system keyring

  • Foundation for MCP App evolution in the future

Related MCP server: OpenAPI MCP Server

Features

Feature

Description

Multi-API Support

Connect to any number of external services through unified configuration

REST + GraphQL

Native support for both REST and GraphQL APIs

OAuth 2.0

Full authorization code flow + PKCE, run once via scripts/oauth_login.py

Token Refresh

Silent refresh inside Credentials.get() when ≤ 5 min from expiry

Secure Storage

Credentials stored in OS keyring (Keychain / Credential Manager / Secret Service)

Generic Data Models

Flexible schemas to handle any data shape

Predictable Auth Surface

Tools return AUTH_REQUIRED when no token; operator runs scripts/oauth_login.py to obtain one

Architecture

Files marked (implemented ✓) exist today. Files marked (planned) are upcoming phases.

MCP/
├── src/
│   ├── server.py              # MCP server entry point (implemented ✓)
│   ├── auth/                  # OAuth 2.0 + keyring (implemented ✓)
│   │   ├── oauth.py           # PKCE auth-code flow, callback server bound to 127.0.0.1
│   │   └── credentials.py     # Keyring-backed store with peek/get/store/clear
│   ├── gateway/               # REST/GraphQL HTTP client (implemented ✓)
│   │   ├── api_client.py      # RestClient + GraphQLClient with retry, redacted logging
│   │   └── handlers.py        # Response normalization, size enforcement, error mapping
│   ├── models/                # Pydantic data models (planned)
│   │   └── data_models.py
│   ├── tools/                 # MCP tool definitions (implemented ✓)
│   │   ├── builtin.py         # list_apis tool
│   │   ├── registry.py        # ToolRegistry / ToolSpec
│   │   ├── context.py         # ToolContext dependency container
│   │   ├── auth_resolver.py   # auth.type branching (oauth2/bearer/api_key/None)
│   │   └── mcp_tools.py       # fetch_data/send_data/execute_graphql/get_status
│   ├── config.py              # API config loader with ${VAR} substitution (implemented ✓)
│   ├── events/                # Activity logging (implemented ✓)
│   │   ├── schemas.py         # Pydantic models (audit/debug/usage/insight)
│   │   ├── redaction.py       # Sensitive data redaction
│   │   ├── retention.py       # Per-month file rotation cleanup
│   │   ├── writers.py         # Async JSONL writer + queue
│   │   └── recorder.py        # Public Recorder API
│   └── transport/             # Transport layer (Phase 8 — implemented ✓)
│       ├── stdio.py           # stdio transport (default)
│       └── http.py            # Streamable HTTP + Bearer middleware + loopback guard
├── config/
│   ├── api_configs.json       # API service configurations (committed; uses ${VAR} placeholders)
│   └── api_configs.example.json  # Template with all four auth.type variants
├── docs/
│   └── plan.md                # Implementation plan / roadmap
├── scripts/                   # Operator CLI helpers (implemented ✓)
│   └── oauth_login.py         # Drive OAuth 2.0 + PKCE flow, persist token in keyring
├── tests/
│   ├── auth/                  # Unit tests for src/auth/ (49 cases — implemented ✓)
│   ├── events/                # Unit tests for src/events/ (27 cases, 51 collected w/ parametrize)
│   ├── gateway/               # Unit tests for src/gateway/ (61 cases — implemented ✓)
│   ├── tools/                 # Unit tests for src/tools/ (37 cases — implemented ✓)
│   ├── scripts/               # Unit tests for scripts/ (15 cases — implemented ✓)
│   ├── transport/             # Unit tests for src/transport/ (23 cases — implemented ✓)
│   ├── integration/           # Full-flow + subprocess smoke tests (5 cases — implemented ✓)
│   ├── test_config.py         # Config loader tests
│   ├── test_server.py         # Server bootstrap + _build_oauth_configs tests
│   └── test_example_config.py # Schema-drift guard for api_configs.example.json
├── .claude/commands/          # Slash commands for the dev workflow
│   ├── generate-prp.md        #   /generate-prp INITIAL.md  → PRPs/{feature}.md
│   └── execute-prp.md         #   /execute-prp PRPs/{...}   → implements + validates
├── PRPs/
│   ├── templates/prp_base.md  # Template each PRP fills in
│   └── {feature}.md           # Generated implementation blueprints
├── INITIAL.md                 # Per-feature scope delta (input to /generate-prp)
├── .env.example               # Environment variables template
├── .gitignore                 # Excludes secrets and build artifacts
├── pyproject.toml             # pytest + ruff + mypy configuration
├── requirements.txt           # Runtime dependencies
├── requirements-dev.txt       # Dev/test deps (pytest, ruff, mypy)
├── CLAUDE.md                  # Project rules + Context Engineering workflow
└── README.md                  # This file

MCP Tools

Tool

Description

fetch_data

GET data from a configured API (auto-OAuth if required)

send_data

POST/PUT data to a configured API (auto-OAuth if required)

execute_graphql

Run a GraphQL query or mutation (auto-OAuth if required)

list_apis

List all configured API services

get_status

Show authentication and connection status

Per-module responsibilities and detailed module-by-module breakdown: docs/plan.md § Architecture Overview. Activity logging contract (four categories, retention, redaction): CLAUDE.md § Activity Logging.

Authentication Flow

OAuth login is operator-initiated (one-time, per provider) and tools are read-only with respect to credential acquisition — they never auto-open the browser. This split keeps the request path predictable for clients (Claude Desktop, Codex CLI, etc.) and concentrates the browser flow in a single CLI surface that's easy to script and test.

A) First-time login (operator runs once per provider)
─────────────────────────────────────────────────────
$ python -m scripts.oauth_login github
        ↓
1. Script builds authorize URL with PKCE
2. Browser opens at provider's authorize page
3. User clicks "Authorize"
4. Callback at http://127.0.0.1:8765/callback receives auth code
5. Script exchanges code for tokens
6. TokenInfo persisted in OS keyring (service=mcp-data-gateway, account=<api_id>)


B) Subsequent tool calls (auto, no UI)
──────────────────────────────────────
1. Claude invokes tool (e.g., fetch_data)
        ↓
2. MCP checks credentials in keyring
        ↓
3a. Valid token                                       →  proceed with API call
3b. Token < 5 min from expiry, refresh_token present  →  silent refresh, then proceed
3c. No token / expired with no refresh                →  return AUTH_REQUIRED *
        ↓
4. Tool returns response (data + metadata, or {error: AUTH_REQUIRED})

* On AUTH_REQUIRED, operator re-runs `python -m scripts.oauth_login <api_id>`
  and then retries the original tool call.

Tech Stack

  • Python 3.10+

  • mcp — Model Context Protocol Python SDK

  • httpx — Async HTTP client (REST + GraphQL)

  • keyring — Cross-platform secure credential storage

  • pydantic — Data validation and modeling

  • python-dotenv — Environment variable management

  • uvicorn + starlette — ASGI server + framework for the HTTP transport (Phase 8)

Quickstart

Prerequisites: Python 3.10+ and pip (or uv).

# 1. Clone and enter the repo
git clone https://github.com/Chawengwit/MCP.git mcp-data-gateway
cd mcp-data-gateway

# 2. Create a virtual environment
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

# 3. Install runtime + dev dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt # only if you want to run pytest

# 4. Copy the env + config templates and edit them
cp .env.example .env                       # add provider client_id / secrets
cp config/api_configs.example.json config/api_configs.json

# 5. Run the server (talks MCP over stdio)
python -m src.server

The server boots, loads config/api_configs.json, starts the Recorder, builds the ToolContext, and registers all five tools (list_apis, fetch_data, send_data, execute_graphql, get_status). On SIGINT/SIGTERM the Recorder queue drains and the server exits cleanly.

First-run note. With the default example config the server will warn "Skipping OAuth config for example_rest_api: missing client_id, client_secret" until you populate EXAMPLE_REST_CLIENT_ID and EXAMPLE_REST_CLIENT_SECRET in .env. The other tools (list_apis, get_status, plus any bearer / api_key / no-auth APIs) work without OAuth setup.

Configuring an API

The shipped config/api_configs.example.json covers all four auth.type paths:

Example entry

auth.type

Use when

example_rest_api

oauth2

Provider supports OAuth 2.0 auth-code flow (Google, GitHub, custom)

example_graphql_api

bearer

You already have a long-lived token in an env var

example_apikey_api

api_key

Provider uses a static key in a custom header

public_no_auth_api

null

Public endpoints with no auth

Never commit literal secrets — every credential field in the file uses a ${ENV_VAR} placeholder. The config loader substitutes from your .env (or the process environment) at startup.

Environment variables

See .env.example for the full annotated template. Most-used variables:

Var

Default

What it does

MCP_API_CONFIG_PATH

config/api_configs.json

Override the API config file path (useful for XDG_CONFIG_HOME, per-environment configs, or CI isolation)

OAUTH_CALLBACK_PORT

8765

Port the OAuth callback HTTP server binds to

MCP_LOG_LEVEL

INFO

DEBUG / INFO / WARN / ERROR

MCP_LOG_DIR

./logs

Where activity-log JSONL files go

MCP_LOG_RETENTION_DAYS

365

Older monthly files are pruned at startup

MCP_LOG_DEBUG_ENABLED

true

Toggles per-request HTTP debug events

MCP_MAX_RESPONSE_BYTES

10485760 (10 MiB)

Response-size cap before truncation / RESPONSE_TOO_LARGE

MCP_REQUEST_TIMEOUT_SEC

30

Per-request timeout for outbound HTTP

Usage

Running Tests

# After installing requirements-dev.txt:
pytest tests/

# Run a specific test file with verbose output
pytest tests/events/test_writers.py -v

# Currently 288 tests passing across src/events/, src/auth/, src/gateway/, src/tools/, src/transport/, scripts/, plus integration + example-config drift tests.

Running the MCP Server

python -m src.server

The server boots, loads config/api_configs.json, starts the Recorder, builds the ToolContext, and registers all five tools (list_apis, fetch_data, send_data, execute_graphql, get_status). On SIGINT/SIGTERM the Recorder queue drains and the server exits cleanly.

Connecting to Claude Desktop

server.py performs its own bootstrap (sys.path + os.chdir + load_dotenv), so a cwd field in the client config is not required — point args at the script directly:

{
  "mcpServers": {
    "data-gateway": {
      "command": "/path/to/mcp-data-gateway/.venv/bin/python",
      "args": ["/path/to/mcp-data-gateway/src/server.py"]
    }
  }
}

On macOS, this lives at ~/Library/Application Support/Claude/claude_desktop_config.json. After editing, fully quit and relaunch Claude Desktop; the 🔌 menu should list data-gateway with all five tools.

Connecting to OpenAI Codex CLI

Codex CLI uses TOML at ~/.codex/config.toml:

[mcp_servers.data-gateway]
command = "/path/to/mcp-data-gateway/.venv/bin/python"
args = ["/path/to/mcp-data-gateway/src/server.py"]

Verified against codex CLI — list_apis, get_status, and fetch_data round-trip identically to Claude Desktop.

Other MCP clients (Cursor, Cline, etc.)

Any client that supports stdio MCP servers will work with the same shape: a command and an args list.

HTTP transport (ChatGPT Connectors, MCP Inspector, custom HTTP clients)

Set MCP_TRANSPORT=http to expose the server as a Streamable HTTP endpoint instead of stdio.

# Generate a strong bearer token
export MCP_HTTP_BEARER_TOKEN="$(python -c 'import secrets; print(secrets.token_hex(32))')"

# Run on loopback (default host/port)
MCP_TRANSPORT=http \
MCP_HTTP_HOST=127.0.0.1 MCP_HTTP_PORT=8080 \
python -m src.server

The server prints its bind address to stderr and serves the MCP protocol at POST /mcp, GET /mcp, and DELETE /mcp.

Test from curl:

# Without the token → 401
curl -i -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'

# With the token → initializes a session
curl -i -X POST http://127.0.0.1:8080/mcp \
  -H "Authorization: Bearer $MCP_HTTP_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-11-25","capabilities":{},
                 "clientInfo":{"name":"curl","version":"1.0"}}}'

The response includes a Mcp-Session-Id header — echo it on every subsequent request via -H "mcp-session-id: <value>".

Connect from MCP Inspector (great for interactive debugging):

npx @modelcontextprotocol/inspector
# UI opens at http://localhost:6274
# Transport: Streamable HTTP
# URL:       http://127.0.0.1:8080/mcp
# Auth:      Bearer <your token>

Connect from ChatGPT Custom Connectors: paste the public URL of the server (via reverse proxy / Cloudflare Tunnel / ngrok) and the bearer token. ChatGPT talks server-to-server, so no CORS configuration is needed.

Loopback safety guard. The server refuses to start if MCP_HTTP_HOST is non-loopback (0.0.0.0, public IP, etc.) and MCP_HTTP_BEARER_TOKEN is unset or empty — preventing accidental unauthenticated public binds. Set the token, bind to 127.0.0.1, or enable the OAuth Provider (see below).

OAuth Provider — Claude.ai "Add custom connector" (Phase 9)

When MCP_OAUTH_ENCRYPTION_KEY is set, the HTTP transport additionally exposes an OAuth 2.0 Authorization Server so Claude.ai (and any MCP client that speaks OAuth 2.1 + RFC 9728) can connect.

# 1. Generate a Fernet key once and add it to .env. Rotating it later invalidates
#    every stored Service API session — keep it stable.
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# 2. Configure the Service API entry in config/api_configs.json with
#    auth.type=session_login. See config/api_configs.example.json for the shape.

# 3. Start the server with OAuth Provider on (loopback or ngrok-tunnelled HTTPS).
MCP_TRANSPORT=http \
MCP_OAUTH_ENCRYPTION_KEY=<paste fernet key> \
MCP_OAUTH_ISSUER=https://<your-ngrok-host>.ngrok-free.app \
python -m src.server

# 4. In Claude.ai, "Add custom connector" → paste the issuer URL.
#    Claude.ai will discover /.well-known/oauth-authorization-server,
#    POST /register, then redirect users to /authorize. Each user pastes
#    their Service API api_key + secret_key into the consent form;
#    the encrypted session is stored under their Service API user_id.

Operator inspection (no plaintext output):

python -m scripts.oauth_admin list-clients
python -m scripts.oauth_admin list-tokens
python -m scripts.oauth_admin list-sessions
python -m scripts.oauth_admin revoke-token --token <full opaque token>

The Phase 8 static-bearer path is unchanged when the OAuth Provider is on; both auth methods are accepted at /mcp. OAuth tokens carry a user_id into audit/usage/insight logs, static-bearer tokens carry user_id=null.

Example Interactions

Once connected, Claude can:

  • List configured APIs: "Show me the available API services"

  • Fetch data: "Get the user list from example_api"

  • Send data: "Create a new record in example_api with this data..."

  • Execute GraphQL: "Run this GraphQL query against my API..."

The first time Claude uses a tool requiring authentication, your browser will open automatically for OAuth login.

Response Format

All MCP tools return structured JSON: {data, metadata} on success, {error} on failure. Large responses truncate (success + cursor) where safe; binary/streaming emit RESPONSE_TOO_LARGE. GraphQL surfaces partial-success (data + errors).

For the full spec — exact field shapes, the error-code table, the truncation rule, and the GraphQL handling — see CLAUDE.md § Response Format Conventions.

OAuth Setup

For each oauth2 API in api_configs.json:

  1. Register an OAuth application with the provider (GitHub OAuth Apps, Google Cloud OAuth client, etc.) and set the redirect URI to http://127.0.0.1:8765/callback exactly. Use 127.0.0.1, not localhost — browsers may treat them as different origins for OAuth state tracking.

  2. Copy the resulting client ID and client secret into .env under names matching your api_configs.json placeholders. By convention: ${API_ID_UPPER}_CLIENT_ID and ${API_ID_UPPER}_CLIENT_SECRET — e.g. GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET for api_id="github".

  3. Run the operator login helper once per provider:

    python -m scripts.oauth_login github            # initial login
    python -m scripts.oauth_login github --clear    # delete + re-auth

    The script opens your browser at the provider's authorize URL, captures the auth code via the one-shot 127.0.0.1 callback server, exchanges it for tokens, and stores them in your OS keyring. Subsequent runs reuse the stored token; the gateway auto-refreshes when ≤ 5 minutes remain.

Why a separate script? MCP tools (fetch_data, send_data, execute_graphql) intentionally do not auto-trigger the browser flow — they return AUTH_REQUIRED so the client (Claude / Codex / etc.) can surface it cleanly. The login script is the operator-side counterpart.

If the default port 8765 is in use on your machine, set OAUTH_CALLBACK_PORT=<free-port> in .env and update the redirect URI you registered with the provider.

Keyring Setup (per OS)

Tokens are stored in the OS-native secure keyring. No additional setup is needed on most desktops:

OS

Backend

Action required

macOS

Keychain

None — works out of the box

Windows

Credential Manager

None — works out of the box

Linux (desktop)

Secret Service (gnome-keyring / KWallet)

Ensure your session has one running (most distros do)

Linux (headless / CI / Docker)

None by default

pip install keyrings.alt for a file backend, OR provide tokens via bearer auth (token_env) and skip OAuth

If keyring is unavailable, Credentials raises CredentialStorageError("No keyring backend available. Install 'keyrings.alt' …") on the first OAuth API call. The error is fail-loud by design — the gateway will not silently fall back to a less secure store.

Troubleshooting

Symptom

Likely cause

Fix

Server logs "Skipping OAuth config for X: missing client_id, client_secret"

.env placeholders not set

Populate the matching ${VAR} in .env, restart

OAuth tool hangs after the browser popup

OAUTH_CALLBACK_PORT blocked or in use

Free the port or set a different one in .env; update the provider's redirect URI to match

OAuth callback returns 400 with "State parameter mismatch"

Browser opened a stale tab from a previous flow

Close all browser tabs pointing at 127.0.0.1:8765, retry

CredentialStorageError: No keyring backend available

Headless Linux without DBus / Secret Service

pip install keyrings.alt, OR use bearer-token auth

Tool returns RESPONSE_TOO_LARGE on a binary download

Hit MCP_MAX_RESPONSE_BYTES cap (binary cannot be safely truncated)

Raise the cap, or use the API's pagination, or stream out-of-band

GraphQL response shows both data and errors

Intentional — GraphQL allows partial success; both are surfaced to Claude

Not a bug. See CLAUDE.md § GraphQL Specifics

Server smoke test fails with "Server exited before ready marker"

A required env var (MCP_LOG_DIR etc.) is unset or points to an unwritable path

Check stderr for the actual exception; fix permissions or the var

401 returned after the tool call worked previously

Refresh token expired or revoked at the provider

Delete the keyring entry (or call Credentials.clear(api_id)) and re-auth

Module import error: ModuleNotFoundError: keyrings.alt (Linux only)

Tried to use the keyrings.alt fallback but didn't install it

pip install keyrings.alt

For the full failure-modes table that this section is derived from, see CLAUDE.md § Common Failure Modes.

Logging (Operator-Only)

The Recorder writes one append-only JSONL file per (category, month) under $MCP_LOG_DIR (default ./logs/):

logs/
├── audit/   YYYY-MM.jsonl   who/when/what — security & compliance
├── debug/   YYYY-MM.jsonl   full HTTP exchange (redacted) — troubleshooting
├── usage/   YYYY-MM.jsonl   per-call latency / sizes — analytics
└── insight/ YYYY-MM.jsonl   tool args + response summaries — Claude's request patterns
  • Rotation is automatic per month; the current month is never deleted.

  • Old files are pruned on server startup according to MCP_LOG_RETENTION_DAYS (default 365).

  • All four streams pass through the central redaction helpers (src/events/redaction.py); Authorization headers, access_token, refresh_token, client_secret, password, api_key, secret, and any per-API redact_fields are replaced with <redacted>.

These logs are operator-only. No MCP tool exposes them to Claude. Treat the directory like any production audit log: review before sharing, retain per your org's policy, and back up if you need historical analytics.

To enable verbose request tracing temporarily:

MCP_LOG_DEBUG_ENABLED=true MCP_LOG_LEVEL=DEBUG python -m src.server

All logs go to stderr (stdout is reserved for the MCP JSON-RPC protocol).

For the full debug/logging strategy and env-var reference, see CLAUDE.md § Debug & Logging Strategy.

Development Workflow

This project uses a Context Engineering workflow for non-trivial features. The full description lives in CLAUDE.md § Context Engineering Workflow. Quick summary:

1. Edit INITIAL.md          ← describe ONE feature (delta vs docs/plan.md)
2. /generate-prp INITIAL.md ← AI researches and writes PRPs/{feature}.md
3. /execute-prp PRPs/{...}  ← AI implements + runs ruff/mypy/pytest until green

src/events/ is the project's reference implementation — new code mirrors its patterns. See CLAUDE.md § Reference Implementation.

For small fixes (single-line changes, doc edits, etc.) skip the workflow and edit directly.

Development Roadmap

Phase

What

Status

1

Project Setup

✅ done

2

Core MCP Server

✅ done — list_apis, registry, config loader, graceful shutdown

3

Authentication (OAuth + keyring)

✅ done — PKCE flow, callback on 127.0.0.1, Credentials with concurrent-refresh lock, 49 tests

4

API Gateway (REST + GraphQL)

✅ done — RestClient + GraphQLClient, retry on 429/5xx + transport errors, redacted logging, response normalization, GraphQL partial-success preserved, 61 tests

5

Tools & Integration

✅ done — fetch_data/send_data/execute_graphql/get_status, auth.type branching (oauth2/bearer/api_key/null), Recorder triple per call, secret redaction in insight events, 37 tests

6

Testing & Polish

✅ done — subprocess smoke test, example-config schema-drift guard, README expanded with Quickstart / OAuth / Keyring per OS / Troubleshooting / Logging

7

Activity Logging (src/events/)

✅ done — 27 test cases (51 collected with parametrization)

Post-v0

GitHub OAuth integration

✅ done — scripts/oauth_login.py operator CLI, real GitHub OAuth flow, verified on Claude Desktop and Codex CLI, 15 new tests

8

HTTP Transport

✅ done — MCP_TRANSPORT={stdio,http} switch, Streamable HTTP via uvicorn + Starlette, Bearer-token middleware, loopback-bind safety guard, per-arg env fallback for run_http settings, single-tenant; 41 new tests (38 transport unit + 3 HTTP subprocess smoke) — 288 total

9

OAuth Provider (RFC 8414/7591/7636/9728)

✅ done — src/oauth_provider/, encrypted SQLite store, consent HTML form, opaque tokens, per-user service_session via contextvar, 93 new tests

9.1

Form-encoded login + api_key fingerprint user_id

✅ done — Taximail-style Service APIs (POST form login, no stable per-user id in response)

9.2

Contextvar bridge for per-request session

✅ done — OAuth middleware publishes user_id; tool handlers fetch the matching Service-API session at call time

9.3

CORS middleware + RFC 9728 strict URL

✅ done — src/transport/cors.py, slash-boundary path match, strict-variant protected-resource discovery

9.4

Keyring-backed session_login for STDIO

✅ done — scripts/session_login.py operator CLI; Claude Desktop natural language reaches Service APIs

9.5

LLM-facing endpoint metadata

✅ done — description / required_params / param_hints surfaced through list_apis; LLM picks correct filters on the first try; 432 total tests

10

Production Deploy Recipe

🔵 next — Dockerfile + Caddy (Let's Encrypt) + VPS guide; unlocks Claude Desktop's "Add custom connector" and Claude.ai web

Per-phase deliverables and verification plan: docs/plan.md. Operator playbook for testing both transports end-to-end: docs/testing-user-ux.md. Future scalability ideas (multi-tenant, caching, additional transports) live in docs/plan.md § Future Scalability.

Security

  • All credentials stored in OS-level secure keyring (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux)

  • .env file excluded from version control via .gitignore

  • OAuth uses standard authorization code flow with PKCE (no implicit grant)

  • Tokens never logged or exposed in error messages — Pydantic Field(repr=False) keeps secret fields out of repr() and f-string output

  • OAuthConfig rejects non-HTTPS authorize_url / token_url at validation time

  • Local callback server binds to 127.0.0.1 (not localhost) and only during the OAuth flow; closes immediately after the auth code is received

License

TBD

Contributing

Phases 1–9.5 are shipped — STDIO + HTTP transports, GitHub OAuth, the full OAuth Provider (RFC 8414/7591/7636/9728), keyring-backed STDIO session_login, CORS, and LLM-facing endpoint metadata are all in main with 432 passing tests. The active feature delta is Phase 10 — Production Deploy Recipe (Dockerfile + Caddy + VPS guide) in INITIAL.md; implementation goes through /generate-prp INITIAL.md/execute-prp PRPs/{...}.md (see the Development Workflow section). For bug fixes and small edits, open a PR directly. Contribution guidelines (style, commit format, review process) will be formalized as the project gains contributors.

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    An MCP server that enables Claude Code to access Auth0-protected APIs by handling OAuth authentication flows and securely proxying API requests with user credentials.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A generic MCP server that dynamically converts OpenAPI-defined REST APIs into tools for LLMs like Claude. It supports multiple authentication methods and transport protocols, enabling seamless interaction with any OpenAPI-compliant API.
    8 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that connects Claude to Acumatica ERP with per-user OAuth authentication, role-based access control, and automatic redaction of sensitive fields.
    Apache 2.0