SSE Transport
Use sse transport with the current published Askr packages.
@askrjs/askr0.0.59
How to use sse transport
Create one MCP server, register typed primitives on it, and let the selected transport supply auth, cancellation, sessions, and progress.
- Import the published @askrjs/askr entrypoint.
- Keep sse transport configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { schema } from '@askrjs/schema';
import { createMcpServer } from '@askrjs/server/mcp';
export const mcp = createMcpServer({
name: 'project-tools',
version: '1.0.0',
}).tool(
'lookup-project',
{ input: schema.object({ id: schema.uuid() }) },
async (context, { id }) => ({
content: [{ type: 'text', text: await lookup(id, context.signal) }],
})
);Event stream
When `options.stateful` is true, `GET path` with `accept: text/event-stream` and a valid `mcp-session-id` header opens a Server-Sent Events channel backed by `createEventStream` from `@askrjs/server/http`. The server writes an initial `connected` comment, then forwards any message sent through the session's `send` function — including tool progress, log notifications, and `list_changed` events — as `event: message` frames. A GET without a stateful session, without the right `accept` header, or against an unknown session ID returns `405`, `406`, or `404` respectively, before any stream is opened.
Reconnect behavior
Opening a new GET for a session that already has an open channel closes the previous one first — `channels.get(sessionId)?.close()` runs before the new stream is registered, so a reconnecting client cleanly replaces its old connection rather than leaving two channels competing for the same session. There's no last-event-ID replay built in: a reconnect gets a fresh `connected` comment and only sees messages sent after it (re)connects, so handlers that need delivery guarantees should design around that rather than assuming SSE resumption fills gaps.
Cancellation
The event stream closes when the client disconnects (its `AbortSignal` fires) or when the session is explicitly terminated via `DELETE path`, which also calls `mcp.terminateSession(sessionId)` on the underlying server. `createEventStream`'s heartbeat (`options.heartbeatInterval`, default 30000ms) keeps the connection alive through idle periods but isn't itself a cancellation signal — actual teardown is driven by the request's abort signal or the explicit DELETE. Handlers that receive a long-running tool call can watch `context.signal` to stop work early if the underlying stream goes away mid-request.
Proxy configuration
SSE is a long-lived HTTP response, so anything sitting between client and server — a reverse proxy, load balancer, or CDN — needs to avoid buffering or timing out the connection early; the adapter's own heartbeat comment is there specifically to keep such intermediaries from treating the connection as idle. If you front the MCP endpoint with something that enforces its own read timeout, set it comfortably above `heartbeatInterval`, and if you terminate TLS at a proxy, make sure the `origin`/`host` headers the adapter checks (`allowedOrigins`, `allowedHosts`) still reflect the values you expect after any header rewriting.