# https://github.com/sparfenyuk/mcp-proxy Project Manual

Generated at: 2026-06-14 03:28:35 UTC

## Table of Contents

- [Introduction and Architecture](#page-1)
- [HTTP Server: CORS, Authentication, and Stateless Mode](#page-2)
- [Stdio Server, CLI, and Public Tunnel](#page-3)
- [Troubleshooting, Known Issues, and Release History](#page-4)

<a id='page-1'></a>

## Introduction and Architecture

### Related Pages

Related topics: [HTTP Server: CORS, Authentication, and Stateless Mode](#page-2), [Stdio Server, CLI, and Public Tunnel](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md)
- [package.json](https://github.com/punkpeye/mcp-proxy/blob/main/package.json)
- [jsr.json](https://github.com/punkpeye/mcp-proxy/blob/main/jsr.json)
- [eslint.config.ts](https://github.com/punkpeye/mcp-proxy/blob/main/eslint.config.ts)
- [src/index.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/index.ts)
- [src/proxyServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/proxyServer.ts)
- [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts)
- [src/authentication.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.ts)
- [src/InMemoryEventStore.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/InMemoryEventStore.ts)
- [src/startHTTPServer.test.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startHTTPServer.test.ts)
</details>

# Introduction and Architecture

## Purpose and Scope

`mcp-proxy` is a TypeScript proxy that bridges [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers using `stdio` transport with clients that speak Streamable HTTP or Server-Sent Events (SSE). It enables any stdio-based MCP server — including those distributed as `npx` packages or local scripts — to be exposed over HTTP endpoints such as `/mcp` and `/sse`, making them consumable by browser-based IDEs, containerized deployments, and remote MCP clients.

The project is dual-published to both npm and the JSR registry, distributed as pure ESM, and exposes a single CLI binary (`mcp-proxy`) plus a programmatic API. The description in [`package.json`](https://github.com/punkpeye/mcp-proxy/blob/main/package.json) states: *"A TypeScript SSE proxy for MCP servers that use stdio transport."* The package is MIT-licensed (see [`package.json`](https://github.com/punkpeye/mcp-proxy/blob/main/package.json)) and depends only on the official `@modelcontextprotocol/sdk` and the `pipenet` library for stream piping (see [`package.json`](https://github.com/punkpeye/mcp-proxy/blob/main/package.json)).

The [`README.md`](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) notes that the proxy is the same component used internally by [FastMCP](https://github.com/punkpeye/fastmcp) to enable streamable HTTP and SSE for its own servers.

## High-Level Architecture

The proxy sits between a downstream MCP client and an upstream `stdio`-only MCP server, translating between transport layers without altering MCP semantics. The core data flow is:

```mermaid
flowchart LR
    A[HTTP/SSE Client] -->|Streamable HTTP / SSE| B(startHTTPServer)
    B -->|JSON-RPC messages| C{proxyServer}
    C -->|MCP requests| D(Client)
    D -->|stdio frames| E[StdioClientTransport]
    E -->|stdin/stdout| F[Child MCP Server Process]
    F -->|stdout frames| E
    E -->|JSON-RPC responses| D
    D --> C
    C --> B
    B -->|HTTP / SSE frames| A
```

In the reverse direction, the same proxy can be started as a `stdio` server that connects to an upstream HTTP/SSE MCP server. The test suite in [`src/startStdioServer.test.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/startStdioServer.test.ts) exercises this mode by forking an SSE+StreamableHTTP-compatible SDK example and verifying bidirectional message flow.

## Core Modules

### `proxyServer` — the message router

[`src/proxyServer.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/proxyServer.ts) exports the `proxyServer` function, which wires a downstream `Client` to an upstream `Server`. It conditionally registers request handlers (`tools/list`, `tools/call`, `resources/read`, `prompts/get`, `complete`, `subscribe`, `unsubscribe`, etc.) only for capabilities the upstream server advertises, and forwards logging notifications in both directions when `serverCapabilities.logging` is present. This conditional registration — visible in handlers such as `ListToolsRequestSchema`, `CallToolRequestSchema`, and `ReadResourceRequestSchema` in [`src/proxyServer.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/proxyServer.ts) — ensures the proxy never claims capabilities the upstream does not have.

### `StdioClientTransport` — process bridge

[`src/StdioClientTransport.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts) is a fork of the SDK's `StdioClientTransport`. It spawns a child process (the upstream MCP server) with the configured `command`, `args`, `cwd`, `env`, and `shell` options, then pipes JSON-RPC messages over its `stdin`/`stdout`. Stderr handling defaults to `inherit` (matching Node's `child_process.spawn` semantics), and a `JSONFilterTransform` strips non-JSON noise from the output stream. Events such as process spawn, exit, and error are surfaced through the `onEvent` callback.

### `InMemoryEventStore` — resumability buffer

[`src/InMemoryEventStore.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/InMemoryEventStore.ts) implements the SDK's `EventStore` interface using an in-memory `Map`. It assigns monotonic event IDs (a stream ID plus a timestamp+counter suffix) and supports `replayEventsAfter()` so a streamable-HTTP client can resume a dropped SSE connection by replaying missed events. The file header notes that the store is intended for examples and testing rather than production persistence.

### Public entry point

[`src/index.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/index.ts) (declared as the JSR export in [`jsr.json`](https://github.com/punkpeye/mcp-proxy/blob/main/jsr.json)) re-exports `proxyServer`, `startHTTPServer`, `startStdioServer`, the `StdioServerParameters` and `TransportEvent` types, and the authentication helpers, providing a single import surface for programmatic users.

## Authentication and Configuration

[`src/authentication.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.ts) defines the `AuthConfig` interface and the `AuthenticationMiddleware` class. The middleware supports two modes:

| Mode | Mechanism | Trigger |
|------|-----------|---------|
| API key | `X-API-Key` header validation against `config.apiKey` | Any non-empty config |
| OAuth Bearer | `Authorization: Bearer …` validated by a user-supplied `authenticate` callback | `config.oauth` present |
| Scope challenge | `WWW-Authenticate` response with `error="insufficient_scope"`, `scope=`, and `resource_metadata=` | `getScopeChallengeResponse()` |

The tests in [`src/authentication.test.ts`](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.test.ts) verify that the `WWW-Authenticate` header is correctly ordered and escaped, and that 403 responses include the `insufficient_scope` JSON-RPC error code `-32001`.

## Known Issues and Community Notes

Several regressions and edge cases documented in the issue tracker have direct architectural implications:

- **Request-handling order regression (6.x)**: per issue [#61](https://github.com/punkpeye/mcp-proxy/issues/61), `onUnhandledRequest` was consuming the body before MCP handlers could read it, causing `POST /mcp` to 404. This was addressed by the `skip onUnhandledRequest for MCP protocol endpoints` fix in [v6.4.6](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.6).
- **Auth vs. health checks (issue [#58](https://github.com/punkpeye/mcp-proxy/issues/58))**: `GET /health` and `GET /ready` previously returned 401 when `apiKey` was enabled. The `move onUnhandledRequest before auth middleware` fix shipped in [v6.4.5](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.5) restores reachability for container probes.
- **Session timeout regression (issue [#57](https://github.com/punkpeye/mcp-proxy/issues/57))**: the SDK bump in [v6.4.4](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.4) aligned idle session timeouts with Node's 5-second `keepAliveTimeout`, surfacing "Session not found" errors.
- **Malformed request URLs (issue [#66](https://github.com/punkpeye/mcp-proxy/issues/66))**: a request target of `//` triggers an uncaught `ERR_INVALID_URL` that crashes the process.
- **SSE → Streamable HTTP bridging (issue [#20](https://github.com/punkpeye/mcp-proxy/issues/20))**: a long-standing open feature request to use mcp-proxy to convert existing SSE servers into Streamable HTTP endpoints.

## See Also

- [Authentication Middleware](./Authentication-Middleware) — deep dive into `AuthConfig`, Bearer validation, and scope challenges
- [HTTP Server](./HTTP-Server) — endpoint layout, CORS defaults, and the `/mcp` + `/sse` route table
- [Stdio Transport](./Stdio-Transport) — child-process lifecycle, stderr handling, and shell mode
- [Changelog](https://github.com/punkpeye/mcp-proxy/releases) — version-by-version regression notes

---

<a id='page-2'></a>

## HTTP Server: CORS, Authentication, and Stateless Mode

### Related Pages

Related topics: [Introduction and Architecture](#page-1), [Stdio Server, CLI, and Public Tunnel](#page-3), [Troubleshooting, Known Issues, and Release History](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/startHTTPServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startHTTPServer.ts)
- [src/authentication.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.ts)
- [src/InMemoryEventStore.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/InMemoryEventStore.ts)
- [src/startHTTPServer.test.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startHTTPServer.test.ts)
- [src/authentication.test.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.test.ts)
- [src/index.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/index.ts)
- [package.json](https://github.com/punkpeye/mcp-proxy/blob/main/package.json)
- [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md)
</details>

# HTTP Server: CORS, Authentication, and Stateless Mode

## Overview

The `startHTTPServer` function in `src/startHTTPServer.ts` exposes an MCP server (typically stdio-based) over HTTP, supporting both streamable HTTP (`/mcp`) and legacy SSE (`/sse`) endpoints. It is the public programmatic entry point — re-exported from `src/index.ts:6` — and powers the [FastMCP](https://github.com/punkpeye/fastmcp) streamable HTTP/SSE feature. The HTTP server orchestrates three cross-cutting concerns:

- **CORS** — browser-friendly access from MCP web clients
- **Authentication** — API key and OAuth 2.0 bearer token validation
- **Stateless mode** — server-less transport suitable for short-lived, request-scoped sessions

These three areas have been the focus of multiple bug fixes (see [v6.4.5](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.5), [v6.4.6](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.6), [v6.5.0](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.5.0), [v6.5.1](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.5.1)) and are the source of the most common user-reported issues.

## CORS Configuration

CORS is enabled by default and is fully configurable. The default policy, defined in `src/startHTTPServer.ts:24-31`, is permissive but explicit:

| Field | Default Value |
|-------|---------------|
| `allowedHeaders` | `Content-Type, Authorization, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-Id` |
| `credentials` | `true` |
| `exposedHeaders` | `["Mcp-Session-Id"]` |
| `methods` | `["GET", "POST", "OPTIONS"]` |
| `origin` | `"*"` |

The `cors` option accepts a boolean or a `CorsOptions` object (the type is exported from `src/index.ts:5`). When `false`, CORS is fully disabled; when `true` or `undefined`, defaults apply; otherwise user values are merged with the defaults (`src/startHTTPServer.ts:33-46`). The actual `Access-Control-Allow-*` headers are written by the `corsMiddleware` function (`src/startHTTPServer.ts:48-110`).

A common pattern is to restrict origins to a known allowlist while keeping wildcard headers:

```typescript
await startHTTPServer({
  createServer: async () => { /* ... */ },
  port: 3000,
  cors: {
    origin: ["https://myapp.com", "https://admin.myapp.com"],
    allowedHeaders: "*",
  },
});
```

**CLI flag:** The `--corsAddAllowedHeader` flag, added in [v6.5.0](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.5.0), appends to the default `Access-Control-Allow-Headers` list — useful when a custom auth header blocks browser preflight under `--apiKey`. For broader overrides (origin allowlist, wildcard headers, disabling CORS), use the programmatic `cors` option. Source: [README.md]()

> **Migration note (5.5.6 → 5.9.0+):** Versions older than 5.9.0 sent wildcard `Access-Control-Allow-Headers` automatically. To preserve that behavior, explicitly set `cors: { allowedHeaders: "*" }`. Source: [README.md]()

## Authentication

Authentication is delegated to `AuthenticationMiddleware` in `src/authentication.ts:9-`. The middleware supports two strategies, configured via the `AuthConfig` interface (`src/authentication.ts:1-13`).

### API Key

Passing `apiKey` to `startHTTPServer` causes the middleware to require an `X-API-Key` header on every request. Requests with missing or mismatched keys receive a `401` with an optional `WWW-Authenticate` header when OAuth is also configured. Source: [src/authentication.test.ts:32-40]()

### OAuth 2.0 Bearer Tokens

The `oauth` sub-config (`src/authentication.ts:2-12`) covers the protected-resource metadata model. The middleware builds a `WWW-Authenticate` challenge that includes `error`, `error_description`, `realm`, and `resource_metadata` fields. When `protectedResource.resource` is set, the resource URL is suffixed with `/.well-known/oauth-protected-resource`. Source: [src/authentication.test.ts:73-90]()

### Scope Challenge (Step-Up Auth)

Implemented in response to [issue #49](https://github.com/punkpeye/mcp-proxy/issues/49), `getScopeChallengeResponse` returns a `403` with a `WWW-Authenticate: Bearer error="insufficient_scope" ...` header listing the required scopes. The HTTP server detects scope-challenge errors thrown during request handling and short-circuits to this response. Source: [src/startHTTPServer.ts:130-145]()

### Request Flow & Known Issues

```mermaid
flowchart TD
    A[HTTP Request] --> B{Path matches MCP endpoint?}
    B -- Yes --> C[authMiddleware.validateRequest]
    B -- No --> D[onUnhandledRequest or /ping /health /ready]
    C -- Valid --> E[StreamableHTTPServerTransport.handleRequest]
    C -- Invalid --> F[401 with WWW-Authenticate]
    D -- Health/Ready --> G[Return 200 OK]
    D -- Other --> H[Custom handler or 404]
    E --> I{Error type?}
    I -- ScopeChallenge --> J[403 insufficient_scope]
    I -- AuthError --> F
    I -- Other --> K[500]
```

> **Community issues resolved in this flow:**
> - [#58](https://github.com/punkpeye/mcp-proxy/issues/58) — `/health` and `/ready` returned 401 under `apiKey` because the auth middleware ran before `onUnhandledRequest`. Fixed in [v6.4.5](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.5) by moving `onUnhandledRequest` before auth (commit `4c532c3`).
> - [#61](https://github.com/punkpeye/mcp-proxy/issues/61) — FastMCP's httpStream POST `/mcp` returned 404 in 6.x because `onUnhandledRequest` consumed the request body. Fixed in [v6.4.6](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.4.6) by skipping `onUnhandledRequest` for MCP protocol endpoints (commit `a2243b0`).

## Stateless Mode

Setting `stateless: true` (CLI: `--stateless`) tells `startHTTPServer` not to track MCP sessions. The server uses `StreamableHTTPServerTransport` with `sessionIdGenerator: undefined`, so no `Mcp-Session-Id` is issued. Source: [src/startHTTPServer.ts:101-130]()

Key behaviors:

- `authenticate` runs on every request instead of once per session.
- Stateless clients without a `sessionId` now receive `405` for non-initialize requests — a fix introduced in [v6.5.1](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.5.1) via commit `bf7d464`.
- When no `eventStore` is supplied, `InMemoryEventStore` is used so that `StreamableHTTPServerTransport` can replay missed SSE events to late subscribers. Source: [src/InMemoryEventStore.ts]()

The stateless path is verified by tests in `src/startHTTPServer.test.ts:150-220` for OAuth bearer token flows and authentication-error handling.

> **Community issues:**
> - [#57](https://github.com/punkpeye/mcp-proxy/issues/57) — v6.4.4 bumped `@modelcontextprotocol/sdk` to `^1.27.1` (see `package.json`), causing sessions to be destroyed after 5s idle (Node's default `keepAliveTimeout`). Stateless mode sidesteps this by avoiding session tracking entirely.
> - [#55](https://github.com/punkpeye/mcp-proxy/issues/55) — Python stdio servers occasionally fail with "Expected server to respond to ping". Stateless mode is recommended for such short-lived containers because there is no persistent MCP session to keep alive.

## Summary

The HTTP server in `mcp-proxy` is a thin but configurable layer over the MCP SDK's `StreamableHTTPServerTransport`. CORS defaults favor browser compatibility, authentication supports both static API keys and OAuth 2.0 with scope challenges, and stateless mode is the recommended path for serverless deployments. Most issues filed against this code (health endpoints, body consumption, session lifetime) are now resolved in the 6.4.5 → 6.5.1 release window.

## See Also

- [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) — full programmatic and CLI usage
- [src/proxyServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/proxyServer.ts) — request/response forwarding logic
- [src/startStdioServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startStdioServer.ts) — the stdio ↔ HTTP counterpart
- [src/tapTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/tapTransport.ts) — transport event logger
- [v6.5.1 release notes](https://github.com/punkpeye/mcp-proxy/releases/tag/v6.5.1) — latest bug fixes

---

<a id='page-3'></a>

## Stdio Server, CLI, and Public Tunnel

### Related Pages

Related topics: [Introduction and Architecture](#page-1), [HTTP Server: CORS, Authentication, and Stateless Mode](#page-2)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/bin/mcp-proxy.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/bin/mcp-proxy.ts)
- [src/startStdioServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startStdioServer.ts)
- [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts)
- [src/proxyServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/proxyServer.ts)
- [src/JSONFilterTransform.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/JSONFilterTransform.ts)
- [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md)
- [package.json](https://github.com/punkpeye/mcp-proxy/blob/main/package.json)
- [src/startStdioServer.test.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startStdioServer.test.ts)
</details>

# Stdio Server, CLI, and Public Tunnel

The `mcp-proxy` project exposes a single executable that has three coordinated responsibilities: (1) presenting a CLI surface for launching and configuring the proxy, (2) wiring a `stdio`-based MCP server into the proxy by spawning it as a child process, and (3) optionally publishing the resulting HTTP/SSE endpoint through a public tunnel. This page documents how those three pieces fit together, which files implement them, and the operational caveats users most often encounter (Python unbuffered mode, Node.js `--shell` quirks, and subdomain availability).

## Command-Line Interface

The CLI entry point is shipped through the package's `bin` field, which maps the `mcp-proxy` command to the compiled ESM bundle in `dist/bin/mcp-proxy.mjs`. Source: [package.json](https://github.com/punkpeye/mcp-proxy/blob/main/package.json) (`"bin": { "mcp-proxy": "dist/bin/mcp-proxy.mjs" }`). The build pipeline produces this artifact via `tsdown`, with `src/bin/mcp-proxy.ts` declared as one of two entries (the other being `src/index.ts`). Source: [package.json](https://github.com/punkpeye/mcp-proxy/blob/main/package.json) (`tsdown.entry: ["src/index.ts", "src/bin/mcp-proxy.ts"]`).

The README documents two invocation patterns that the CLI parser (`yargs`, listed under devDependencies) supports. The first pattern is the "simple usage" form, where every token after `mcp-proxy` is forwarded to the child process: `npx mcp-proxy npx -y @anthropic/mcp-server-filesystem /path`. The second pattern introduces the `--` separator so the proxy can consume its own options (for example `--port 8080 --shell`) while still passing the remainder through to the child command: `npx mcp-proxy --port 8080 --shell -- tsx server.js`. Source: [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) ("Quickstart / Command-line"). The README also notes that the `--` separator is optional when no proxy options are required, which matches how `yargs` parses its own argument boundary.

Public-facing options surfaced by the CLI include `--port`, `--shell`, `--debug`, `--tunnel`, and `--tunnelSubdomain`. When the tunnel is active, the CLI prints a line such as `tunnel established at https://abcdefghij.tunnel.gla.ma` once the tunnel handshake completes, and the user should rely on the actually-printed URL rather than the requested subdomain. Source: [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) ("Public Tunnel").

## Stdio Server Mode

The `stdio` server role is implemented by `src/startStdioServer.ts`, which is consumed both by the CLI (when run without `--port` or `--tunnel`) and by programmatic users. In stdio mode the proxy itself is the MCP server: it reads JSON-RPC messages from its own stdin and writes responses to its own stdout, while internally talking to a downstream server through one of the available transports. Tests for this mode live in `src/startStdioServer.test.ts`, where fixtures such as `src/fixtures/simple-stdio-proxy-server.ts` are launched via `StdioClientTransport` to verify round-trips including notification streams. Source: [src/startStdioServer.test.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startStdioServer.test.ts).

The child-process driver is a custom `StdioClientTransport` forked from the upstream TypeScript SDK's `stdio.ts` implementation. It accepts a `command`, optional `args[]`, an `env` map, an optional `cwd`, an `onEvent` callback, a `shell` boolean, and a stderr handling mode. The header comment in the file explicitly calls out the fork origin so future maintainers know where to rebase changes. Source: [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts) (file-level `/** Forked from ... */` docblock). The transport pipes the child's stdout through a `JSONFilterTransform` (also maintained in this repo) so stray log lines emitted on stdout do not corrupt the MCP framing, and it exposes `onEvent` for lifecycle observation. Source: [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts) (import of `JSONFilterTransform`; `StdioServerParameters` type).

A common community-reported failure in this mode is the Python "Expected server to respond to ping" timeout. The README addresses it by recommending unbuffered Python execution: either `npx mcp-proxy -- python -u -m your_package.mcp_server` or `PYTHONUNBUFFERED=1 npx mcp-proxy -- python -m your_package.mcp_server`, plus the rule that MCP stdio servers should keep stdout reserved for protocol frames and route diagnostic output to stderr. Source: [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) ("Troubleshooting Python stdio servers"). A related Node.js issue, "Unable to proxy nodejs-based MCP server" (issue #22), has been observed to silently time out; the proxy is normally the correct tool for that case, so a timeout typically indicates buffering or process-tree issues rather than a protocol mismatch.

## Public Tunnel

When the CLI is started with `--tunnel`, the proxy arranges for the local HTTP/SSE port (the same `/mcp` and `/sse` endpoints described in the HTTP server path) to be reachable over the public internet through a tunnel. The README demonstrates the canonical invocation: `npx mcp-proxy --port 8080 --tunnel -- tsx server.js`. A specific subdomain can be requested with `--tunnelSubdomain myapp`, and the proxy prints the live URL once the handshake completes. Source: [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) ("Public Tunnel"). Because subdomains are not guaranteed, the README explicitly warns that the requested name may not be available and the user should read the actual URL from the console output rather than assuming the requested one. Source: [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md) (`> [!NOTE] The requested subdomain may not be available...`).

The tunnel is intended for short-lived sharing, webhook testing, and remote development, not as a production ingress. Operations teams combining tunnels with `apiKey` authentication should be aware of the related health-endpoint regression: with `apiKey` enabled, `GET /health` and `GET /ready` were returning 401 before reaching `onUnhandledRequest`, breaking Docker `HEALTHCHECK` and Kubernetes probes. The fix shipped in v6.4.5 ("move onUnhandledRequest before auth middleware") and a follow-up in v6.4.6 added a guard that skips `onUnhandledRequest` for MCP protocol endpoints. Source: release notes for v6.4.5 and v6.4.6.

## Operational Data Flow

The combined CLI → stdio → optional tunnel path can be summarized as follows.

```mermaid
flowchart LR
    User["User (shell)"] --> CLI["mcp-proxy CLI<br/>(src/bin/mcp-proxy.ts)"]
    CLI -->|spawns| Child["Child stdio MCP server<br/>(tsx, python -u, npx ...)"]
    CLI -->|exposes| HTTP["HTTP server<br/>/mcp + /sse"]
    CLI -->|if --tunnel| Tunnel["Public tunnel<br/>https://*.tunnel.gla.ma"]
    Child <-->|JSON-RPC over stdin/stdout| Stdio["StdioClientTransport<br/>(forked from SDK)"]
    Stdio --> Proxy["proxyServer<br/>(src/proxyServer.ts)"]
    Proxy <--> HTTP
    Remote["Remote MCP client"] --> Tunnel
    Tunnel --> HTTP
```

## See Also

- [HTTP Server, Authentication, and Health Endpoints](https://github.com/punkpeye/mcp-proxy/wiki) — covers `/mcp`, `/sse`, `apiKey`, OAuth scope challenges, and the `/health` and `/ready` regression history.
- [MCP Protocol and SDK Integration](https://github.com/punkpeye/mcp-proxy/wiki) — explains how `proxyServer` wires the SDK's `Server` and `Client` together and how session/keep-alive tuning is handled.
- [Releases and Versioning](https://github.com/punkpeye/mcp-proxy/releases) — semantic-release pipeline that produces the `v6.x` tags referenced above.

---

<a id='page-4'></a>

## Troubleshooting, Known Issues, and Release History

### Related Pages

Related topics: [HTTP Server: CORS, Authentication, and Stateless Mode](#page-2), [Stdio Server, CLI, and Public Tunnel](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [package.json](https://github.com/punkpeye/mcp-proxy/blob/main/package.json)
- [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md)
- [src/startHTTPServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startHTTPServer.ts)
- [src/authentication.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.ts)
- [src/authentication.test.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.test.ts)
- [src/InMemoryEventStore.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/InMemoryEventStore.ts)
- [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts)
- [jsr.json](https://github.com/punkpeye/mcp-proxy/blob/main/jsr.json)
</details>

# Troubleshooting, Known Issues, and Release History

This page consolidates the known limitations, recurring failure modes, and version history of the `mcp-proxy` project. It is intended for operators and integrators who need to diagnose problems in production, evaluate upgrade paths, or understand the regression surface across recent releases. The information below is derived directly from the source tree, the test suite, and the public issue tracker referenced in the community context.

## Architecture Context for Troubleshooting

`mcp-proxy` is a TypeScript proxy that exposes a `stdio`-based MCP server over streamable HTTP and SSE endpoints. The HTTP entry point is implemented in [src/startHTTPServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startHTTPServer.ts), which wires Express middleware for CORS, authentication, and a custom `onUnhandledRequest` fallback before delegating to the MCP `StreamableHTTPServerTransport` from `@modelcontextprotocol/sdk`. The `stdio` child process is managed by [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts), and event replay for resumable streams is provided by the in-memory store in [src/InMemoryEventStore.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/InMemoryEventStore.ts). Authentication is encapsulated in [src/authentication.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.ts) and supports both `apiKey` and OAuth `protectedResource` configurations.

```mermaid
flowchart LR
  Client[HTTP/SSE Client] -->|request| Middleware{CORS + Auth + onUnhandledRequest}
  Middleware -->|/mcp or /sse| Transport[StreamableHTTPServerTransport]
  Transport --> Proxy[proxyServer]
  Proxy --> Stdio[StdioClientTransport]
  Stdio --> Child[Child MCP server process]
  Transport -.replay.-> EventStore[InMemoryEventStore]
```

This pipeline is relevant for troubleshooting because most reported issues originate in the middleware ordering, the SDK transport, or the child process boundary. Source: [src/startHTTPServer.ts:1-120]().

## Known Issues and Failure Modes

### Server Crashes on Malformed Request Targets

The HTTP server terminates the Node.js process when it receives a request whose target is `//`. The crash surfaces as an uncaught `ERR_INVALID_URL` thrown by Node's HTTP parser. The issue is reported in [#66](https://github.com/punkpeye/mcp-proxy/issues/66) and reproduces in production by sending a raw `//` request line. Mitigations should include a fronting reverse proxy that normalizes request lines or a process supervisor that restarts the service.

### 401 Blocking Health Probes

When `apiKey` authentication is configured, `GET /health` and `GET /ready` are intercepted by the auth middleware before `onUnhandledRequest` runs, returning `401`. This breaks container orchestrator health checks. The fix shipped in v6.4.5 (commit `4c532c3`, referenced in the community context) reorders the middleware so that `onUnhandledRequest` runs before authentication. As an interim workaround, operators can omit the `apiKey` for internal probe traffic. Source: [src/authentication.ts:90-140](), [src/startHTTPServer.ts:1-120]().

### 404 on `POST /mcp` with FastMCP httpStream

Since the 6.x series, FastMCP's `httpStream` transport returns `404` for `POST /mcp` because the `onUnhandledRequest` hook consumes the request body before the MCP transport handlers run. The fix shipped in v6.4.6 (commit `a2243b0`) skips `onUnhandledRequest` for MCP protocol endpoints. Issue [#61](https://github.com/punkpeye/mcp-proxy/issues/61) documents the regression from the 5.x line.

### Session Regression in v6.4.4

Bumping `@modelcontextprotocol/sdk` from `^1.24.3` to `^1.27.1` caused MCP sessions to be destroyed after the Node.js default `keepAliveTimeout` of roughly 5 seconds. Clients receive `Session not found` on subsequent tool calls. Issue [#57](https://github.com/punkpeye/mcp-proxy/issues/57) tracks the regression. Workarounds include pinning to the previous SDK version or tuning `server.keepAliveTimeout` in deployment manifests.

### Python `stdio` Servers Failing Ping

Python MCP servers running under `mcp-proxy` may fail with `Expected server to respond to ping` when invoked inside containerized environments such as Glama Docker. Issue [#55](https://github.com/punkpeye/mcp-proxy/issues/55) attributes the failure to process startup timing and Python interpreter resolution in slim images. Adding `python3 -u` and explicit `PYTHONUNBUFFERED=1` is the recommended mitigation.

### Silent Timeout When Proxying Node Servers

Issue [#22](https://github.com/punkpeye/mcp-proxy/issues/22) reports that proxying Node.js-based MCP servers (for example, the `everything` reference server) results in silent timeouts even with `--debug`. The most common root cause is mismatched `--` separator handling in the CLI argument parser. The README distinguishes two invocation patterns: simple usage without mcp-proxy options versus the explicit `--` form, e.g. `npx mcp-proxy --port 8080 --shell -- tsx server.js`. Source: [README.md:20-40]().

### ServerNotification Validation Warnings

Issue [#33](https://github.com/punkpeye/mcp-proxy/issues/33) reports `WARNING:root:Failed to validate notification: 13 validation errors for ServerNotification` originating from a Python upstream. The warning is emitted by the upstream server itself when the proxy forwards a notification that does not match the SDK's expected schema. Filtering is recommended on the upstream side rather than mutating notifications in the proxy.

### License Metadata Mismatch

Issue [#63](https://github.com/punkpeye/mcp-proxy/issues/63) notes that `package.json` declares `MIT` while the `LICENSE` file contains the BSD 2-Clause text. The published JSR and npm metadata both record `MIT` (see [package.json:20](), [jsr.json:1-10]()), so the npm/JSR distribution is MIT; downstream consumers embedding the BSD text should be aware of the discrepancy until the repository is normalized.

## Release History and Upgrade Path

The most recent releases on the `main` branch (per [package.json:1-10]() and the community release notes) emphasize HTTP transport correctness, CORS configurability, and SDK compatibility.

| Version | Date | Highlights | Related Issue |
| --- | --- | --- | --- |
| v6.5.1 | 2026-05-20 | Returns `405` for stateless clients without `sessionId` | — |
| v6.5.0 | 2026-05-12 | Adds `--corsAddAllowedHeader` CLI flag | — |
| v6.4.6 | 2026-04-13 | Skips `onUnhandledRequest` for MCP protocol endpoints | [#61](https://github.com/punkpeye/mcp-proxy/issues/61) |
| v6.4.5 | 2026-04-08 | Moves `onUnhandledRequest` before auth middleware | [#58](https://github.com/punkpeye/mcp-proxy/issues/58) |
| v6.4.4 | 2026-03-13 | Updates MCP SDK (introduces session regression) | [#57](https://github.com/punkpeye/mcp-proxy/issues/57) |

Operators pinned on v6.4.4 should consider v6.4.5 or later to recover the session keep-alive behavior, and at minimum v6.4.6 to restore `POST /mcp` handling for FastMCP transports. The `--corsAddAllowedHeader` flag introduced in v6.5.0 is the recommended path for clients that send custom headers under CORS preflight, which is otherwise rejected by the default allow-list. Source: [README.md:1-40](), [src/startHTTPServer.ts:1-120]().

## Diagnostic Checklist

1. Reproduce the failure with `--debug` and capture the stderr stream for uncaught exception traces (relevant for [#66](https://github.com/punkpeye/mcp-proxy/issues/66)).
2. Verify the CLI invocation uses the documented `--` separator when mcp-proxy options are present. Source: [README.md:20-40]().
3. If health checks fail, confirm the proxy version is at least v6.4.5 so that `onUnhandledRequest` precedes auth. Source: [src/authentication.ts:90-140]().
4. For Python upstream servers, set `PYTHONUNBUFFERED=1` and invoke with `python3 -u` to avoid ping timeouts. Source: [src/StdioClientTransport.ts:1-60]().
5. When integrating FastMCP `httpStream`, upgrade to v6.4.6 or later to avoid the `POST /mcp` 404 regression. Source: [src/InMemoryEventStore.ts:1-40]().

## See Also

- Project README: [README.md](https://github.com/punkpeye/mcp-proxy/blob/main/README.md)
- HTTP server entry point: [src/startHTTPServer.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/startHTTPServer.ts)
- Authentication module: [src/authentication.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/authentication.ts)
- Stdio transport: [src/StdioClientTransport.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/StdioClientTransport.ts)
- Resumability store: [src/InMemoryEventStore.ts](https://github.com/punkpeye/mcp-proxy/blob/main/src/InMemoryEventStore.ts)

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: sparfenyuk/mcp-proxy

Summary: Found 11 structured pitfall item(s), including 2 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/sparfenyuk/mcp-proxy/issues/168

## 2. Security or permission risk - Security or permission risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/sparfenyuk/mcp-proxy/issues/108

## 3. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/sparfenyuk/mcp-proxy/issues/224

## 4. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.host_targets | https://github.com/sparfenyuk/mcp-proxy

## 5. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/sparfenyuk/mcp-proxy

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/sparfenyuk/mcp-proxy/issues/214

## 7. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/sparfenyuk/mcp-proxy

## 8. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/sparfenyuk/mcp-proxy

## 9. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/sparfenyuk/mcp-proxy

## 10. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/sparfenyuk/mcp-proxy

## 11. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/sparfenyuk/mcp-proxy

<!-- canonical_name: sparfenyuk/mcp-proxy; human_manual_source: deepwiki_human_wiki -->
