Skip to content

Commit b2f49ec

Browse files
authored
feat(transport): add Streamable HTTP transport mode (#84)
Adds the http (Streamable HTTP, MCP rev 2025-03-26) transport mode alongside stdio and legacy sse. Includes new CLI flags and config, per-session isolation, DNS-rebinding/Origin defenses, MCP SDK 1.29.0, and full test coverage. Bumps version to 1.3.0 and updates CHANGELOG.
1 parent 610e102 commit b2f49ec

31 files changed

Lines changed: 5186 additions & 244 deletions

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Changelog
22

3+
## [1.3.0] - 2026-05-30
4+
5+
### Added
6+
7+
- **`http` (Streamable HTTP) transport mode** (MCP protocol revision 2025-03-26, single `/mcp` endpoint) alongside `stdio` and legacy `sse` transports (#84)
8+
- New CLI flags: `--transport http`, `--http-host` (default `127.0.0.1`), `--http-port` (default `9444`), `--http-allowed-origins`
9+
- Config support: `transport.httpHost`, `transport.httpPort`, `transport.httpAllowedOrigins` with validation and serialization
10+
- Stateful, isolated sessions: each session gets its own server instance seeded from the primary session's working directory
11+
- **`sse` (HTTP/SSE) transport mode** for the MCP server (#83)
12+
- New CLI flags: `--transport sse`, `--sse-host`, `--sse-port` (default `127.0.0.1:9444`)
13+
- Config file support via the `transport` section
14+
- New integration and unit test suites for the Streamable HTTP transport: handshake/lifecycle, sessions, resources, security, and tool execution
15+
- README documentation for the `http` transport mode, a no-config-file CLI recipe, and a UAT plan
16+
17+
### Changed
18+
19+
- Upgraded MCP SDK to 1.29.0 for `StreamableHTTPServerTransport`
20+
- Refactored shared HTTP logic (origin validation, CORS echo, socket tracking, graceful close) from `transport.ts` into `httpShared.ts`; `closeSseServer` is now a thin wrapper over `closeHttpServer`, so existing SSE callers are unaffected
21+
22+
### Security
23+
24+
- DNS-rebinding defense via Origin allowlisting (loopback + bind host + explicit `httpAllowedOrigins`)
25+
- Request body size cap (4 MB); malformed `Host` header returns 400 instead of crashing
26+
- Per-session isolation prevents one client's `set_current_directory` from affecting another's
27+
328
## [1.2.4] - 2026-05-29
429

530
### Fixed

README.md

Lines changed: 115 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -483,46 +483,103 @@ To get started with configuration:
483483
fully unsafe mode disables those restrictions as well. These two flags are
484484
mutually exclusive; using both at once will fail.
485485

486-
You can start the server with HTTP/SSE transport instead of the default
487-
stdio transport. This allows remote and web-based MCP clients to connect
488-
over HTTP:
486+
You can start the server with an HTTP-based transport instead of the default
487+
stdio transport, so remote and web-based MCP clients can connect over HTTP.
488+
Two HTTP transports are available:
489+
490+
- `http` -- the modern **Streamable HTTP** transport (MCP protocol revision
491+
2025-03-26), serving a single `/mcp` endpoint. This is what current MCP
492+
clients default to and is the recommended HTTP transport.
493+
- `sse` -- the legacy **HTTP+SSE** transport (MCP protocol revision
494+
2024-11-05), using two endpoints (`GET /sse`, `POST /messages`). It is
495+
deprecated by the MCP spec in favor of Streamable HTTP and is kept only for
496+
compatibility with older clients.
497+
498+
The modes are mutually exclusive (selected by `--transport`) and use separate
499+
bind settings (`--http-*` for `http`, `--sse-*` for `sse`).
489500

490501
```bash
491-
# Start with SSE transport on default host/port (127.0.0.1:9444)
492-
npx wcli0 --transport sse
502+
# Streamable HTTP on the default host/port (127.0.0.1:9444), serving /mcp
503+
npx wcli0 --transport http
493504
494505
# Custom port, still bound to localhost
506+
npx wcli0 --transport http --http-host 127.0.0.1 --http-port 3000
507+
508+
# Legacy HTTP+SSE transport
495509
npx wcli0 --transport sse --sse-host 127.0.0.1 --sse-port 3000
496510
```
497511

498512
| Option | Type | Default | Description |
499513
| ------ | ---- | ------- | ----------- |
500-
| `--transport` | string | stdio | Transport protocol: `stdio` or `sse` |
501-
| `--sse-host` | string | 127.0.0.1 | Host address for SSE transport |
502-
| `--sse-port` | number | 9444 | Port for SSE transport |
503-
| `--sse-allowed-origins` | string | (none) | Comma-separated browser origins allowed in addition to loopback hosts and the bind host (e.g. `https://app.example.com,192.168.1.10`). Only the host component is compared. Required for browser clients on a wildcard (`0.0.0.0`) bind. |
504-
505-
When SSE mode is active, clients connect via `GET /sse` to open an SSE
506-
stream and send messages via `POST /messages?sessionId=<id>`. The server
507-
logs the bind address and port on startup. To mitigate DNS-rebinding
508-
attacks, the server validates the request `Origin` header: requests whose
509-
`Origin` is not a loopback host, the configured bind host, or one of the
510-
configured `--sse-allowed-origins` are rejected with `403 Forbidden`, while
511-
non-browser clients that send no `Origin` are allowed.
512-
513-
> **Security:** This transport has no built-in authentication and exposes
514-
> command-execution tools. Keep it bound to `127.0.0.1` (the default) for
515-
> local use. Binding to `0.0.0.0` or any non-loopback address exposes those
516-
> tools to every host that can reach the port; only do so behind an
517-
> authenticated reverse proxy or equivalent access control. Origin validation
518-
> alone does not authenticate non-browser clients.
514+
| `--transport` | string | stdio | Transport protocol: `stdio`, `http` (Streamable HTTP), or `sse` (legacy HTTP+SSE) |
515+
| `--http-host` | string | 127.0.0.1 | Host address for the Streamable HTTP transport (`http` mode) |
516+
| `--http-port` | number | 9444 | Port for the Streamable HTTP transport (`http` mode) |
517+
| `--http-allowed-origins` | string | (none) | Comma-separated browser origins allowed for `http` mode, in addition to loopback hosts and the bind host (e.g. `https://app.example.com,192.168.1.10`). Only the host component is compared. Required for browser clients on a wildcard (`0.0.0.0`) bind. |
518+
| `--sse-host` | string | 127.0.0.1 | Host address for the legacy SSE transport (`sse` mode) |
519+
| `--sse-port` | number | 9444 | Port for the legacy SSE transport (`sse` mode) |
520+
| `--sse-allowed-origins` | string | (none) | Comma-separated browser origins allowed for `sse` mode, in addition to loopback hosts and the bind host. Only the host component is compared. Required for browser clients on a wildcard (`0.0.0.0`) bind. |
521+
522+
When `http` mode is active, clients use a single `/mcp` endpoint:
523+
524+
- `POST /mcp` carries client-to-server JSON-RPC messages. An `initialize`
525+
request with no session id starts a new session; the server returns the
526+
assigned id in the `Mcp-Session-Id` response header, and the client must
527+
send that header on every subsequent request.
528+
- `GET /mcp` opens the optional server-to-client SSE stream for an existing
529+
session.
530+
- `DELETE /mcp` terminates an existing session.
531+
532+
Sessions are stateful and isolated: each session has its own active working
533+
directory, so one client's `set_current_directory` cannot affect another.
534+
Requests carrying an unknown or terminated `Mcp-Session-Id` are rejected with
535+
`404 Not Found`. The server logs the bind address and port on startup (with
536+
`--debug`).
537+
538+
When `sse` mode is active, clients instead connect via `GET /sse` to open an
539+
SSE stream and send messages via `POST /messages?sessionId=<id>`.
540+
541+
**Configuring Streamable HTTP entirely with CLI parameters (no config file).**
542+
Every transport and operational setting can be supplied as an input parameter,
543+
so the server can run as a Streamable HTTP server without any config file:
544+
545+
```bash
546+
npx wcli0 \
547+
--transport http \
548+
--http-host 127.0.0.1 \
549+
--http-port 9444 \
550+
--http-allowed-origins "https://app.example.com,192.168.1.10" \
551+
--shell gitbash \
552+
--allowedDir "D:/work/project" \
553+
--commandTimeout 60 \
554+
--debug
555+
```
556+
557+
CLI parameters also take precedence over a config file, so the same `--http-*`
558+
flags override the corresponding `transport` fields when a `--config` file is
559+
also passed (see the [transport config section](#configuration-inheritance)).
560+
561+
Both HTTP transports validate the request `Origin` header to mitigate
562+
DNS-rebinding attacks: requests whose `Origin` is not a loopback host, the
563+
configured bind host, or one of the configured allowed origins
564+
(`--http-allowed-origins` / `--sse-allowed-origins`) are rejected with
565+
`403 Forbidden`, while non-browser clients that send no `Origin` are allowed.
566+
Allowed browser origins receive CORS headers, and `OPTIONS` preflight requests
567+
are answered with `204`.
568+
569+
> **Security:** Neither HTTP transport has built-in authentication, and both
570+
> expose command-execution tools. Keep the server bound to `127.0.0.1` (the
571+
> default) for local use. Binding to `0.0.0.0` or any non-loopback address
572+
> exposes those tools to every host that can reach the port; only do so behind
573+
> an authenticated reverse proxy or equivalent access control. Origin
574+
> validation alone does not authenticate non-browser clients.
519575
>
520576
> **Wildcard binds and browser origins:** When binding to a wildcard address
521577
> (`0.0.0.0` / `::`), the bind host is not a usable origin to compare against,
522578
> so browser clients reaching the server through its real LAN address (or a
523579
> reverse proxy whose public hostname differs from the bind host) are rejected
524-
> unless their origin is listed in `--sse-allowed-origins` (or the
525-
> `transport.sseAllowedOrigins` config array). Non-browser clients are
580+
> unless their origin is listed in `--http-allowed-origins` /
581+
> `--sse-allowed-origins` (or the `transport.httpAllowedOrigins` /
582+
> `transport.sseAllowedOrigins` config arrays). Non-browser clients are
526583
> unaffected, since they send no `Origin`.
527584
528585
1. **Update your Claude Desktop configuration** to use your config file:
@@ -827,7 +884,21 @@ You can override the mount point at startup using the `--wslMountPoint` CLI flag
827884

828885
### Configuration Inheritance
829886

830-
The transport section can also be set in the config file:
887+
The transport section can also be set in the config file. For the Streamable
888+
HTTP transport (`http` mode):
889+
890+
```json
891+
{
892+
"transport": {
893+
"mode": "http",
894+
"httpHost": "127.0.0.1",
895+
"httpPort": 9444,
896+
"httpAllowedOrigins": ["https://app.example.com", "192.168.1.10"]
897+
}
898+
}
899+
```
900+
901+
For the legacy HTTP+SSE transport (`sse` mode):
831902

832903
```json
833904
{
@@ -840,11 +911,23 @@ The transport section can also be set in the config file:
840911
}
841912
```
842913

843-
`sseAllowedOrigins` is optional and defaults to an empty list. Each entry is an
844-
origin URL or a bare host; only the host component is compared (case-insensitively).
845-
846-
CLI flags (`--transport`, `--sse-host`, `--sse-port`, `--sse-allowed-origins`)
847-
override config file values.
914+
| Field | Type | Default | Applies to | Description |
915+
| ----- | ---- | ------- | ---------- | ----------- |
916+
| `mode` | string | `stdio` | all | `stdio`, `http`, or `sse` |
917+
| `httpHost` | string | `127.0.0.1` | `http` | Bind host for the Streamable HTTP transport |
918+
| `httpPort` | number | `9444` | `http` | Bind port for the Streamable HTTP transport (integer `1..65535`) |
919+
| `httpAllowedOrigins` | string[] | `[]` | `http` | Browser origins allowed in addition to loopback hosts and `httpHost` |
920+
| `sseHost` | string | `127.0.0.1` | `sse` | Bind host for the legacy SSE transport |
921+
| `ssePort` | number | `9444` | `sse` | Bind port for the legacy SSE transport (integer `1..65535`) |
922+
| `sseAllowedOrigins` | string[] | `[]` | `sse` | Browser origins allowed in addition to loopback hosts and `sseHost` |
923+
924+
The `*AllowedOrigins` lists are optional and default to an empty list. Each
925+
entry is an origin URL or a bare host; only the host component is compared
926+
(case-insensitively).
927+
928+
CLI flags override config-file values: `--transport`, `--http-host`,
929+
`--http-port`, `--http-allowed-origins`, `--sse-host`, `--sse-port`, and
930+
`--sse-allowed-origins`.
848931

849932
The inheritance system works as follows:
850933

0 commit comments

Comments
 (0)