Askr
Server & APIs

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.

  1. Import the published @askrjs/askr entrypoint.
  2. Keep http transport configuration next to the component, route, or server composition root that owns it.
  3. 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.