Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **Breaking β€” the modern protocol path is delegated to `createMcpHandler`.** 0.3.0 swapped the peer dependency to `@modelcontextprotocol/server` v2 but kept driving `WebStandardStreamableHTTPServerTransport`, which is the 2025-era transport: sessions, `Mcp-Session-Id`, and validation limited to `mcp-protocol-version` against a version list. The endpoint therefore never served 2026-07-28. It does now, and gets `server/discover`, the `_meta` envelope, MRTR, `resultType` and the inbound validation ladder that emits `-32020` `HeaderMismatch` from the SDK rather than from code here. `legacy` is left at its default `'stateless'`, so 2025-era clients continue to be served, one fresh instance per request. This closes [#8](https://github.com/Max-Health-Inc/mcp-http/issues/8) and the `-32020` item from [#6](https://github.com/Max-Health-Inc/mcp-http/issues/6).
- **Breaking β€” `handleMcpPost` takes a `createServer` factory** instead of a constructed `server`, because the SDK builds one instance per serving unit, per era. The `onError` contract is unchanged, including the `Response` override that `createMcpHandler`'s reporting-only `onerror` cannot express; out-of-band SDK reports are captured and routed through it alongside anything thrown.
- **`mcpHono` builds its handler once** rather than per request. Rebuilding per request silently defeated the authorization-server metadata cache, which lives in the handler's closure, so a `discoverAuthorizationServer` endpoint re-fetched the AS document on every request instead of once per TTL. The Hono `Context` is threaded through `PlatformCtx` instead.

### Fixed

- **A disconnecting client raised an unhandled promise rejection.** The response body is piped through a `TransformStream` so the server is closed only once the body drains. Cancelling that pipe β€” which is what a client disconnecting mid-response does β€” rejects `pipeTo`, and `.finally()` re-raises it, so the rejection was never handled. This is not an SSE edge case: the SDK returns a `ReadableStream` body for ordinary JSON replies too, so it fired on any cancelled request, which on Workers is routine traffic. The pipe and the subsequent `close()` now both swallow their errors. Present in the 0.3.x transport as well, under the same `void pipeTo(...).finally(...)` shape. Found by a test that installs an `unhandledRejection` listener, cancels the body, and asserts nothing fires; it fails against the unfixed version.

### Removed

- **Breaking** β€” `handleMcpPostStateful`, `SessionStore`, and the `stateful` / `sessionTtlMs` options, all deprecated in 0.3.0 and scheduled for this release. Sessions do not exist in 2026-07-28 and server-initiated sampling is replaced by in-result input requests, so there is no replacement to migrate to. Nothing in the known consumer set (`dicom-viewer`, `drypdf`, `legal-web`) used them.
- **Breaking** β€” `Mcp-Session-Id` and `Last-Event-ID` from the default CORS allow-list, and `Mcp-Session-Id` from the exposed headers. The revision no longer defines them. With no default exposed headers left, `Access-Control-Expose-Headers` is omitted rather than emitted empty.
- The `@modelcontextprotocol/sdk` v1 devDependency, dead since the 0.3.0 peer swap and imported nowhere.

## [0.3.2] β€” 2026-08-07

### Fixed
Expand Down
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Built on the **Web Fetch API** β€” runs on Cloudflare Workers, Pages Functions,

## Features

- **Stateless MCP transport** β€” one `WebStandardStreamableHTTPServerTransport` per POST, no session state required
- **2026-07-28 protocol via the SDK** β€” delegates to `createMcpHandler`, so `server/discover`, MRTR, `resultType` and `-32020 HeaderMismatch` come from upstream; 2025-era clients are still served statelessly
- **RFC 9728** protected-resource metadata served automatically, at the path-aware route (`/.well-known/oauth-protected-resource/mcp` for an endpoint mounted at `/mcp`)
- **RFC 8414** `/.well-known/oauth-authorization-server` (optional)
- **Bearer extraction + 401 gate** with `WWW-Authenticate` resource-metadata pointer
Expand Down Expand Up @@ -148,7 +148,7 @@ createMcpHttpHandler({
});
```

The default CORS config allows `*` origins and admits the MCP-required request headers (`Content-Type`, `Authorization`, `MCP-Protocol-Version`, `Mcp-Session-Id`, `Last-Event-ID`).
The default CORS config allows `*` origins and admits the MCP-required request headers (`Content-Type`, `Authorization`, `MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name`).

Per-tool `Mcp-Param-*` headers (SEP-2243) are admitted dynamically: any such header a client lists in its preflight `Access-Control-Request-Headers` is echoed back in `Access-Control-Allow-Headers`. Header names outside that prefix are never reflected, so you still declare your own via `allowHeaders`.

Expand Down Expand Up @@ -267,17 +267,23 @@ The monolithic `@modelcontextprotocol/sdk` was retired in favour of focused pack

`WebStandardStreamableHTTPServerTransport` is API-compatible across the two.

### What the SDK now does better
### Who owns what, as of 0.4.0

Several things this package used to own are available directly from the SDK, usually with more capability. Where that is true the wrapper here is marked `@deprecated` and will be removed in 0.4.0. Your editor will point you at the replacement.
Protocol semantics belong to the SDK. `handleMcpPost` delegates to `createMcpHandler`, so the 2026-07-28 revision, `server/discover`, the `_meta` envelope, MRTR, `resultType`, and the inbound validation ladder that emits `-32020 HeaderMismatch` all come from there rather than being reimplemented here.

| Deprecated here | Use instead | Why |
| --------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `handleMcpPost` | `createMcpHandler` | Also serves the 2026-07-28 revision, its `resultType` discriminator, and `-32020 HeaderMismatch` validation |
| `handleMcpPostStateful`, `SessionStore` | `createMcpHandler` | Sessions are removed in 2026-07-28; this path is 2025-era only |
| `buildProtectedResourceMetadata` | `buildOAuthProtectedResourceMetadata` | Takes your real RFC 8414 document rather than a bare issuer URL |
`legacy` is left at its default `'stateless'`, so 2025-era clients are still served β€” one fresh instance per request, no sessions β€” instead of being turned away.

**One incompatibility worth knowing before you migrate off `handleMcpPost`.** `createMcpHandler`'s `onerror` option is reporting-only and, per the SDK's documentation, "never alters the response". `onError` here may return a `Response` to override the reply. If you depend on that, keep using `handleMcpPost` for now β€” that same incompatibility is why this package still drives the transport itself instead of delegating.
Removed in 0.4.0, with nothing to migrate to because the protocol no longer has them:

| Removed | Why |
| ---------------------------------------------- | --------------------------------------------------------------------------------- |
| `handleMcpPostStateful`, `SessionStore` | Sessions are gone in 2026-07-28; sampling is replaced by in-result input requests |
| `stateful`, `sessionTtlMs` config | Selected the session path |
| `Mcp-Session-Id`, `Last-Event-ID` CORS entries | Named headers the revision no longer defines |

`handleMcpPost` keeps its name and its `onError` contract. Its options changed shape: it now takes a `createServer` factory rather than a constructed `server`, because the SDK builds one instance per serving unit, per era.

**Why `onError` still exists.** `createMcpHandler`'s `onerror` is reporting-only and, per the SDK's documentation, "never alters the response". `onError` here may still return a `Response` to override the reply: the delegation captures out-of-band reports and routes them, plus anything thrown, through the same hook. That is the one piece of behaviour the SDK cannot express, and the reason this wrapper exists at all rather than consumers calling `createMcpHandler` directly.

### What this package still owns

Expand Down
Loading