Configuration
August 16, 2026 · View on GitHub
This project uses the v2 JSON configuration below. The v1 schema (top-level
server and clients keys) has been removed: rename them to mcpProxy and
mcpServers, and lift each client's config object into the server entry
itself, so that
{"clients": {"fetch": {"type": "stdio", "config": {"command": "uvx", "args": ["mcp-server-fetch"]}}}}
becomes
{"mcpServers": {"fetch": {"command": "uvx", "args": ["mcp-server-fetch"]}}}
panicIfInvalid, logEnabled and authTokens move into the entry's options,
and the proxy's globalAuthTokens become mcpProxy.options.authTokens.
- Online converter (build Claude config from your proxy): https://tbxark.github.io/mcp-proxy
Full Example
{
"mcpProxy": {
"baseURL": "https://mcp.example.com",
"addr": ":9090",
"name": "MCP Proxy",
"version": "1.0.0",
"type": "streamable-http", // or "sse" (default)
"options": {
"panicIfInvalid": false,
"logEnabled": true,
"authTokens": ["DefaultToken"]
}
},
"mcpServers": {
"github": {
// stdio client
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>" },
"options": {
"toolFilter": {
"mode": "block",
"list": ["create_or_update_file"]
}
}
},
"fetch": {
// stdio client
"command": "uvx",
"args": ["mcp-server-fetch"],
"options": {
"panicIfInvalid": true,
"logEnabled": false,
"authTokens": ["SpecificToken"]
}
},
"amap": {
// SSE client
"url": "https://mcp.amap.com/sse?key=<YOUR_TOKEN>",
"options": {
"disabled": true
}
},
"notion": {
// streamable-http client requiring interactive OAuth (no static
// bearer token accepted) - see "oauth" below
"url": "https://mcp.notion.com/mcp",
"transportType": "streamable-http",
"oauth": {
"scopes": []
}
}
}
}
mcpProxy
baseURL: Public URL base used to build client endpoints.addr: Bind address (e.g.:9090).name,version: Server identity for MCP handshake.type:sse(default) orstreamable-http.options: Defaults inherited bymcpServers.*.options(can be overridden per server).startupGracePeriod(duration string, default"30s"): how long/_readyzreportsinitializingwhile clients are still connecting. After it, the proxy reports ready and any straggler mounts when it finishes, so one slow server cannot keep the whole proxy out of rotation.
mcpServers
Each entry's key is the server name, which becomes its route
(<baseURL path>/<serverName>/). It must be a clean relative URL path: it
cannot be empty, contain ., .. or empty segments, start or end with /,
or contain \, {, } or control characters. Multiple segments are allowed
(see the token-in-the-route trick in USAGE.md).
Each entry defines a downstream MCP server. Supported client types:
stdio(implicit whencommandis set): run a subprocess via stdio.sse(implicit whenurlis set andtransportType≠streamable-http): connect via Server‑Sent Events.streamable-http(requirestransportType: "streamable-http"): connect via HTTP streaming.
Common fields:
command,args,env— forstdioclients.url,headers— forsseandstreamable-httpclients.timeout— request timeout forsseandstreamable-http. Write it as a duration string:"timeout": "30s". A bare number means nanoseconds ("timeout": 30is 30ns, not 30 seconds), so anything under a millisecond is rejected at startup rather than silently failing every request.oauth— forsseandstreamable-httpclients that require interactive OAuth instead of (or in addition to)headers(see below).options— per‑server overrides and filters (see below).
oauth
Some remote MCP servers (e.g. Notion's hosted MCP) require the full OAuth
2.1 authorization-code flow and reject static bearer tokens outright. Set
an oauth block on an sse/streamable-http server to have mcp-proxy act
as the OAuth client on the downstream connection:
clientId,clientSecret(optional): static client credentials. Omit both to use RFC 7591 dynamic client registration, which is performed automatically the first time you authorize.redirectUri(optional): local callback URL used during the one-time interactive authorization. Defaults tohttp://localhost:8090/oauth/callback. It must usehttp, a loopback host (localhost,127.0.0.1, or::1), an explicit port, and a non-root callback path. The callback listener never binds to a public interface.scopes(optional): OAuth scopes to request.pkceDisabled(bool, optional): disable PKCE. PKCE is enabled by default.authServerMetadataUrl(optional): override discovery of the authorization server's metadata document. Needed for providers whose protected-resource metadata (RFC 9728) advertises anauthorization_serversentry with a non-empty path (e.g.https://mcp.example.com/v1/mcp): this library's discovery always appends/.well-known/oauth-authorization-serverafter the full issuer URL (OpenID Connect Discovery convention), but RFC 8414 requires inserting it before the path when one is present, and some providers (Datadog, at time of writing) only serve the document at the RFC 8414 location. If servers connected viaoauthfail discovery, check<issuer>/.well-known/oauth-authorization-servervs.<scheme>://<host>/.well-known/oauth-authorization-server<path>by hand and set this field to whichever one responds.
Tokens are persisted to <user config dir>/mcp-proxy/oauth/<server>.json
(e.g. ~/.config/mcp-proxy/oauth/notion.json on Linux) and refreshed
automatically using the stored refresh token as they expire. Before the
daemon can use an oauth-configured server, you must authorize it once —
see the -authorize flag in USAGE.md.
When clientId is left empty, the dynamically-registered client (RFC
7591) is also persisted, to <user config dir>/mcp-proxy/oauth/<server>.client.json.
This matters: registration only happens inside the one-off -authorize
process, so without persisting it, a freshly started daemon would build a
new OAuth handler with an empty client ID and every token refresh would
silently be rejected by the provider once the access token expires -
looking, from the logs, like a refresh failure with no obvious cause.
options
panicIfInvalid(bool): If true, startup fails when a client cannot initialize.pingInterval(duration string, default"30s"): how often the connection is probed to keep it alive and to notice it died. This is how quickly/_readyzturnsdegradedafter a downstream goes away.logEnabled(bool): Log requests and events for this client.authTokens([]string): Valid bearer tokens; requests must includeAuthorization: <token>.toolFilter(object): Selectively expose tools to the proxy:mode:alloworblock.list: List of tool names.
Disabled(bool): Enable or disable this server. Disabled servers are skipped at startup.
Notes:
mcpProxy.options.authTokensserves as the default token set if a server omitsoptions.authTokens.- To discover tool names for filtering, start without a filter and check logs for lines like
<server> Adding tool <name>.