HTTP Transport
Use http transport with the current published Askr packages.
@askrjs/askr0.0.59
How to use http 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 http 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) }],
})
);HTTP requests
`registerMcpRoutes(router, path, mcp, options)` mounts POST, GET, and DELETE handlers for `path` on an existing `Router`. POST requires `content-type: application/json`, an `accept` header including `application/json` or `text/event-stream` (or `*/*`), and a body under `options.maxRequestBytes` (1 MiB by default) — violations return `415`, `406`, and `413` respectively before the body is even parsed. Origin and host checks run first via `options.allowedOrigins` / `options.allowedHosts`, and requests with an unrecognized `mcp-protocol-version` header are rejected with `400` before reaching the MCP server.
Responses
A successful POST that produces a result returns `200` with the JSON-RPC response body; a notification or request that yields no result (like `notifications/initialized`) returns a bare `202` with no body. When `options.stateful` is true, the first `initialize` call gets a fresh `mcp-session-id` header (a `crypto.randomUUID()`), which the client must echo on every subsequent request; a missing or unknown session ID on a stateful deployment produces `400` or `404`. Malformed JSON in the request body returns a JSON-RPC `-32700 Parse error` at HTTP `400`, and JSON-RPC batch arrays are rejected outright with `-32600`.
Protected resource metadata
Pass `resource` (and optionally `authorizationServers`) in the options and the adapter automatically serves `GET /.well-known/oauth-protected-resource`, backed by `protectedResourceMetadata(resource, authorizationServers)`. The returned object is frozen and includes `resource`, `authorization_servers`, and a fixed `bearer_methods_supported: ["header"]`, matching the OAuth protected-resource metadata clients use for discovery. You can also call `protectedResourceMetadata` directly if you need to serve that document from a route the adapter doesn't own.
Deployment
Statefulness is opt-in: pass `stateful: true` to get session IDs, SSE push, and the DELETE-to-terminate flow; omit it and every POST is handled independently with `methodNotAllowed` returned for GET and DELETE. By default sessions live in an in-memory `Set`, which only works for a single process — supply your own `sessionStore` (matching the `create`/`has`/`delete` shape) if you're running more than one instance behind a load balancer. Set `allowedOrigins` and `allowedHosts` for any endpoint reachable from a browser to guard against DNS-rebinding-style requests, and tune `heartbeatInterval` (default 30 seconds) if intermediaries close idle connections faster than that.