Askr
Server & APIs

MCP

Use mcp with the current published Askr packages.

  • @askrjs/server/mcp0.0.3
  • @askrjs/node/mcp0.0.3

How to use mcp

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/server/mcp and @askrjs/node/mcp entrypoint.
  2. Keep mcp 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) }],
  })
);

Server model

`createMcpServer(options)` from `@askrjs/server/mcp` builds a single in-process registry of tools, resources, resource templates, and prompts, addressed by name or URI. Registration methods (`tool`, `resource`, `resourceTemplate`, `prompt`) return the same server instance, so you chain them at startup instead of scattering registration across files. The server itself is transport-agnostic: it only knows how to validate a JSON-RPC message and call `handle(message, environment)`, leaving HTTP, SSE, and stdio delivery to separate adapters. `options.pageSize` (default 50, minimum 1) controls how many items `tools/list`, `resources/list`, and `prompts/list` return per page.

Tools and resources

`server.tool(name, options, handler)` registers a callable with an optional `input` and `output` schema; if you omit `input`, an empty object schema is used, and arguments are validated with `safeParse` before your handler ever runs. `server.resource(uri, options, handler)` binds a fixed URI to a handler that returns one `McpContent` or an array of them, while `server.resourceTemplate(template, options, handler)` matches variable segments like `{id}` against incoming URIs and hands the extracted variables to the handler. A tool handler's thrown errors are caught and turned into a result with `isError: true` rather than a JSON-RPC transport error, so callers always get a structured response for `tools/call`.

Transports

Two transports ship today: `registerMcpRoutes` from `@askrjs/server/mcp` mounts HTTP (plus optional SSE) routes on an existing `Router`, and `connectMcpStdio` from `@askrjs/node/mcp` wires the same `McpServer` to `process.stdin`/`process.stdout` for CLI-style clients. Both adapters do the same job: turn a raw message plus a transport-specific `McpRequestEnvironment` (dependencies, auth, signal, optional `send`) into a call to `mcp.handle(...)`. Because the server has no transport code of its own, adding a new one is a matter of constructing that environment object correctly, not reimplementing dispatch.

Operational checklist

Before shipping, confirm `pageSize` matches what your clients expect for large tool/resource lists, and that every primitive requiring auth sets `options.auth` to an `AuthRequirement` rather than relying on transport-level checks. For HTTP, decide whether you need `stateful: true` for session-scoped push notifications, and set `allowedOrigins`/`allowedHosts` if the endpoint is reachable from a browser. For stdio, make sure `diagnostics` is wired to a stream your process supervisor captures, since stdio errors are written there rather than thrown.