# @tmcp/transport-http

> Transport for TMCP using HTTP

Latest version **0.9.0** (published 2026-08-14) · MIT license · 0 weekly downloads

## Install

```sh
npm install @tmcp/transport-http
pnpm add @tmcp/transport-http
yarn add @tmcp/transport-http
bun add @tmcp/transport-http
```

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.9.0 |
| Published | 2026-08-14 |
| First published | 2025-07-15 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 2 |
| Unpacked size | 57 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 203 |
| Maintainers | pablopang |
| Keywords | tmcp, http, transport |

## Links

- npm: https://www.npmjs.com/package/@tmcp/transport-http
- Repository: https://github.com/paoloricciuti/tmcp
- npm.io page: https://npm.io/package/@tmcp/transport-http

## Dependencies (2)

- [esm-env](https://npm.io/package/esm-env.md) ^1.2.2
- [@tmcp/session-manager](https://npm.io/package/@tmcp/session-manager.md) ^0.3.0

## Recent versions

- 0.9.0 (latest) — 2026-08-14
- 0.9.0-next.0 (next) — 2026-08-06
- 0.8.6 — 2026-05-14
- 0.8.5 — 2026-03-09
- 0.8.4 — 2026-01-23
- 0.8.3 — 2025-12-06
- 0.8.2 — 2025-11-13
- 0.8.1 — 2025-11-06
- 0.8.0 — 2025-10-29
- 0.7.1 — 2025-10-23
- 0.7.0 — 2025-10-18
- 0.6.3 — 2025-09-26
- 0.6.2 — 2025-09-21
- 0.6.1 — 2025-09-20
- 0.6.0 — 2025-08-27
- … 14 more at https://npm.io/package/@tmcp/transport-http/versions

## README

# @tmcp/transport-http

An HTTP transport implementation for TMCP (TypeScript Model Context Protocol) servers. It supports both initialization-based MCP sessions and the sessionless per-request protocol introduced in MCP `2026-07-28`.

## Installation

```bash
pnpm add @tmcp/transport-http tmcp
```

## Usage

### Basic Setup

```javascript
import { McpServer } from 'tmcp';
import { HttpTransport } from '@tmcp/transport-http';

// Create your MCP server
const server = new McpServer(
	{
		name: 'my-http-server',
		version: '1.0.0',
		description: 'My HTTP MCP server',
	},
	{
		adapter: new YourSchemaAdapter(),
		capabilities: {
			tools: { listChanged: true },
			prompts: { listChanged: true },
			resources: { listChanged: true },
		},
	},
);

// Add your tools, prompts, and resources
server.tool(
	{
		name: 'example_tool',
		description: 'An example tool',
	},
	async () => {
		return {
			content: [{ type: 'text', text: 'Hello from HTTP!' }],
		};
	},
);

// Create the HTTP transport (defaults to '/mcp' path)
const transport = new HttpTransport(server);

// Use with your preferred HTTP server
// Example with Node.js built-in server + @remix-run/
import * as http from 'node:http';
import { createRequestListener } from '@remix-run/node-fetch-server';

const httpServer = http.createServer(
	createRequestListener(async (request) => {
		const response = await transport.respond(request);
		return response ?? new Response(null, { status: 404 });
	}),
);

httpServer.listen(3000, () => {
	console.log('MCP HTTP server listening on port 3000');
});
```

### With Custom Configuration

```javascript
const transport = new HttpTransport(server, {
	// Custom MCP endpoint path (default: '/mcp', use null to respond on every path)
	path: '/api/mcp',
	// Custom session ID generation for initialization-based clients
	getSessionId: () => {
		return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
	},
});
```

> [!NOTE]
> When the transport runs in development mode and you omit the `path` option, a warning is emitted. Future releases will treat an `undefined` path as "respond on every path", so set the property explicitly (for example `path: '/mcp'` or `path: null`) to lock in the behavior you want today.

### With Custom Context

You can pass custom context data to your MCP server for each request. This is useful for authentication, user information, database connections, etc.

```javascript
// Define your custom context type
interface MyContext {
    userId: string;
    permissions: string[];
    database: DatabaseConnection;
}

// Create server with custom context
const server = new McpServer(serverInfo, options).withContext<MyContext>();

server.tool(
    {
        name: 'get-user-profile',
        description: 'Get the current user profile',
    },
    async () => {
        // Access custom context in your handler
        const { userId, database } = server.ctx.custom!;
        const profile = await database.users.findById(userId);

        return {
            content: [
                { type: 'text', text: `User profile: ${JSON.stringify(profile)}` }
            ],
        };
    },
);

// Create transport (it will be typed to accept your custom context)
const transport = new HttpTransport(server);

// then in the handler
const response = await transport.respond(req, {
	userId,
	permissions,
	database: req.locals.db,
});
```

Handlers can also observe request cancellation through `server.ctx.signal`. The HTTP transport aborts this signal when the incoming request is aborted or the client closes its request-scoped SSE response. Cancellation is cooperative, so handlers should pass the signal to cancellable work or check `signal.aborted` at suitable boundaries.

### Session Management

The HTTP transport supports custom session managers for different deployment scenarios:

#### In-Memory Sessions (Default)

```javascript
import {
	InMemoryStreamSessionManager,
	InMemoryInfoSessionManager,
	InMemorySubscriptionManager,
} from '@tmcp/session-manager';

const transport = new HttpTransport(server, {
	sessionManager: {
		streams: new InMemoryStreamSessionManager(),
		info: new InMemoryInfoSessionManager(),
	},
	subscriptionManager: new InMemorySubscriptionManager(),
});
```

#### Redis Sessions (Multi-Server/Serverless)

For deployments across multiple servers or serverless environments where sessions need to be shared:

```javascript
import {
	RedisStreamSessionManager,
	RedisInfoSessionManager,
	RedisSubscriptionManager,
} from '@tmcp/session-manager-redis';

const transport = new HttpTransport(server, {
	sessionManager: {
		streams: new RedisStreamSessionManager('redis://localhost:6379'),
		info: new RedisInfoSessionManager('redis://localhost:6379'),
	},
	subscriptionManager: new RedisSubscriptionManager('redis://localhost:6379'),
});
```

**When to use Redis sessions:**

- **Multi-server deployments**: When your application runs on multiple servers and clients might connect to different instances
- **Serverless deployments**: When your transport is deployed on serverless platforms where instances are ephemeral (attention, serverless environment generally kills SSE request after a not-so-long amount of time, this means notification from the server will not reach the client after the shutdown)
- **Load balancing**: When using load balancers that might route requests to different server instances

## Features

- **🌐 HTTP/SSE Communication**: Uses Server-Sent Events for real-time bidirectional communication
- **🔄 Dual Protocol Support**: Supports both initialization-based sessions and sessionless per-request calls
- **📡 Streaming Responses**: Supports streaming responses through SSE
- **🛤️ Configurable Path**: Customizable MCP endpoint path with automatic filtering (set `path` to `null` to respond everywhere)
- **🔧 Framework Agnostic**: Works with any HTTP server framework (Fastify, Bun, Deno, etc.)
- **⚡ Real-time Updates**: Server can push notifications and updates to connected clients
- **🛡️ Error Handling**: Graceful error handling for malformed requests
- **🔀 Multiple HTTP Methods**: Supports legacy GET/DELETE session behavior alongside POST-only per-request calls
- **🧠 Session Metadata**: Automatically persists client capabilities, info, and log levels and exposes them via `server.ctx.sessionInfo`

## API

### `HttpTransport`

#### Constructor

```typescript
new HttpTransport(server: McpServer, options?: HttpTransportOptions)
```

Creates a new HTTP transport instance.

**Parameters:**

- `server` - A TMCP server instance to handle incoming requests
- `options` - Optional configuration for the transport

**Options:**

```typescript
interface HttpTransportOptions {
	getSessionId?: () => string; // Custom session ID generator
	path?: string | null; // MCP endpoint path (default: '/mcp', null responds on every path)
	oauth?: OAuth; // an oauth provider generated from @tmcp/auth
	cors?: CorsConfig | boolean; // CORS configuration
	allowedOrigins?: string | string[] | true; // Cross-origin request security policy
	sessionManager?: {
		streams?: StreamSessionManager;
		info?: InfoSessionManager;
	}; // Provide custom managers; defaults to in-memory implementations
	subscriptionManager?: SubscriptionManager; // Per-request subscription routing
	disableSse?: boolean; // Disable SSE stream endpoint (GET returns 405)
}
```

If you omit `sessionManager` the transport creates `InMemoryStreamSessionManager` and `InMemoryInfoSessionManager` instances for initialization-based clients. Per-request clients never create, read, or return session IDs. You can override either field independently (for example, Redis streams with in-memory metadata during development).

If you omit `subscriptionManager`, the transport creates an `InMemorySubscriptionManager`. Supply a distributed implementation when `subscriptions/listen` requests and change publication may reach different server instances.

Requests without an `Origin` header and same-origin browser requests are accepted. When `allowedOrigins` is omitted, cross-origin requests are also accepted and the transport warns on the first one. Set an explicit origin or origin list to restrict access, `[]` to reject every cross-origin request, or `true` to explicitly allow every origin without a warning. This request security policy is intentionally independent from `cors`, which only controls response headers.

### Disabling SSE Streams

If your deployment doesn't support long-lived SSE connections (e.g., some serverless environments), you can disable the GET endpoint:

```javascript
const transport = new HttpTransport(server, {
	disableSse: true,
});
```

When `disableSse` is `true`, GET requests to the MCP endpoint return `405 Method Not Allowed`, so legacy session-negotiated clients cannot receive server-initiated notifications through a GET stream. Per-request clients can still open modern `subscriptions/listen` streams through POST.

#### Methods

##### `respond(request: Request, customContext?: T): Promise<Response | null>`

Processes an HTTP request and returns a Response with Server-Sent Events, or null if the request path doesn't match the configured MCP path.

**Parameters:**

- `request` - A Web API Request object containing the JSON-RPC message
- `customContext` - Optional custom context data to pass to the MCP server for this request

**Returns:**

- A Response object with SSE stream for ongoing communication, or null if the request path doesn't match the MCP endpoint

**HTTP Methods:**

- **POST**: Processes MCP messages and returns short-lived event stream responses
- **GET**: Establishes long-lived connections for server notifications
- **DELETE**: Disconnects sessions and cleans up resources

##### `closeSubscription(response)`

Gracefully complete the per-request subscription represented by the exact `Response` returned from `respond()`:

```javascript
const response = await transport.respond(listen_request);
await transport.closeSubscription(response);
```

Each listen POST receives an opaque internal routing origin. `Mcp-Session-Id` is never used as modern subscription identity, even when the caller provides it. Closing the response body cancels the stream; HTTP clients do not send `notifications/cancelled` for `2026-07-28` subscriptions.

##### `close()`

Cancel every active per-request subscription owned by this transport.

## Protocol Details

### Per-Request Protocol (`2026-07-28`)

Each request or notification uses its own POST. A conforming request includes:

- `MCP-Protocol-Version`, matching `params._meta.io.modelcontextprotocol/protocolVersion`.
- `Mcp-Method`, matching the JSON-RPC method.
- `Mcp-Name` for `tools/call`, `prompts/get`, and `resources/read`, matching `params.name` or `params.uri`.
- Any recognized `Mcp-Param-*` values declared by `x-mcp-header` annotations in a tool's input JSON Schema.

`Mcp-Name` and parameter headers use the specification's `=?base64?...?=` sentinel when a value cannot be represented safely as plain ASCII. Header names are compared case-insensitively; values remain case-sensitive.

Successful requests use request-scoped SSE and include `X-Accel-Buffering: no`. Accepted notifications return HTTP 202 without a body. Header mismatches and unsupported versions return HTTP 400; unknown or unavailable methods return HTTP 404. Per-request traffic ignores `Mcp-Session-Id` and `Last-Event-ID`, never returns a session ID, and returns HTTP 405 for GET and DELETE.

Initialization-based clients continue to use the behavior documented below on the same transport. Their session header, GET stream, DELETE lifecycle, and JSON-RPC response POSTs remain supported.

### HTTP Methods

The transport supports three HTTP methods:

#### POST - Message Processing

Clients send JSON-RPC messages via HTTP POST requests. Each POST accepts one JSON-RPC message; batch arrays are rejected with `-32600 Invalid Request`. The following example shows initialization-based session behavior:

```http
POST /mcp HTTP/1.1
Content-Type: application/json
mcp-session-id: optional-session-id

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}
```

Response: Short-lived event stream that closes after sending the response:

```http
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
mcp-session-id: generated-or-provided-session-id

data: {"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}

```

#### GET - Notification Stream

Establishes long-lived connections for server notifications:

```http
GET /mcp HTTP/1.1
mcp-session-id: optional-session-id
```

Response: Long-lived event stream for server notifications:

```http
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
mcp-session-id: generated-or-provided-session-id

data: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}

```

#### DELETE - Session Disconnect

Disconnects a session and cleans up resources:

```http
DELETE /mcp HTTP/1.1
mcp-session-id: session-to-disconnect
```

Response:

```http
HTTP/1.1 200 OK
mcp-session-id: session-to-disconnect
```

### Legacy Session Management

- **Session ID Header**: `mcp-session-id`
- **Automatic Generation**: If no session ID is provided, one is generated automatically
- **Session Persistence**: Sessions persist across multiple requests until the client disconnects
- **Server Notifications**: Server can send notifications to all active sessions

## Framework Examples

### Bun

```javascript
import { McpServer } from 'tmcp';
import { HttpTransport } from '@tmcp/transport-http';

const server = new McpServer(/* ... */);
const transport = new HttpTransport(server);

Bun.serve({
	port: 3000,
	async fetch(req) {
		const response = await transport.respond(req);
		if (response === null) {
			return new Response('Not Found', { status: 404 });
		}
		return response;
	},
});
```

### Deno

```javascript
import { McpServer } from 'tmcp';
import { HttpTransport } from '@tmcp/transport-http';

const server = new McpServer(/* ... */);
const transport = new HttpTransport(server);

Deno.serve({ port: 3000 }, async (req) => {
	const response = await transport.respond(req);
	if (response === null) {
		return new Response('Not Found', { status: 404 });
	}
	return response;
});
```

### `srvx`

If you want the same experience across Deno, Bun, and Node.js, you can use [srvx](https://srvx.h3.dev/).

```js
import { McpServer } from 'tmcp';
import { HttpTransport } from '@tmcp/transport-http';
import { serve } from 'srvx';

const server = new McpServer(/* ... */);
const transport = new HttpTransport(server);

serve({
	async fetch(req) {
		const response = await transport.respond(req);
		if (response === null) {
			return new Response('Not Found', { status: 404 });
		}
		return response;
	},
});
```

## Error Handling

The transport includes comprehensive error handling:

- **Malformed JSON**: Invalid JSON requests return appropriate error responses
- **Session Management**: Automatic cleanup of disconnected sessions
- **Server Errors**: Server processing errors are propagated to clients

## Development

```bash
# Install dependencies
pnpm install

# Generate TypeScript declarations
pnpm generate:types

# Lint the code
pnpm lint
```

## Requirements

- Node.js 16+ (for native ES modules and Web API support)
- A TMCP server instance
- An HTTP server framework or runtime
- A schema adapter (Zod, Valibot, etc.)

## Related Packages

- [`tmcp`](../tmcp) - Core TMCP server implementation
- [`@tmcp/transport-stdio`](../transport-stdio) - Standard I/O transport
- [`@tmcp/adapter-zod`](../adapter-zod) - Zod schema adapter
- [`@tmcp/adapter-valibot`](../adapter-valibot) - Valibot schema adapter

## Acknowledgments

Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm.

## License

MIT

---
_Source: https://npm.io/package/@tmcp/transport-http · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
