Askr
Server & APIs

Schemas, Auth, Context, and Progress

Use schemas, auth, context, and progress with the current published Askr packages.

  • @askrjs/askr0.0.59

How to use schemas, auth, context, and progress

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 schemas, auth, context, and progress 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) }],
  })
);

Input schemas

Tool `input`, tool `output`, and prompt `arguments` are all `@askrjs/schema` schemas, and each is exposed to clients through generated JSON Schema (`inputSchema`, `outputSchema`) in the corresponding `list` response. Validation runs via `safeParse` before your handler executes; a failed parse short-circuits to a `-32602 Invalid tool arguments` (or `Invalid prompt arguments`) error carrying the schema's `issues`, so handlers never see malformed input. Skip `input` or `arguments` entirely and the server substitutes an empty object schema, which is useful for tools and prompts that take no parameters but still want to appear correctly in a client's UI.

Authorization

Every primitive accepts an optional `auth: AuthRequirement` in its options — the same requirement functions from `@askrjs/auth` used on HTTP routes, like `requireScope('mcp:write')` or `requirePermission('reports:read')`. Before a tool, resource, template, or prompt is listed or invoked, the server calls that requirement against the current `AuthContext` and checks `.allowed`; entries that fail are filtered out of `list` responses entirely and return `Tool not found` / `Resource not found` / `Prompt not found` if called directly, rather than a distinct "forbidden" error. That means unauthorized primitives are invisible, not merely blocked — a client with insufficient scope never learns the tool exists.

Handler context

Every handler receives an `McpContext<Dependencies>` with `dependencies` (whatever you passed into the transport's `dependencies` option), `auth`, the negotiated `client` info and `clientCapabilities` from `initialize`, `protocolRevision`, `transport` (`"http"` or `"stdio"`), an optional `sessionId`, and an `AbortSignal` that fires on cancellation or disconnect. It's the same context shape across transports, so a tool handler doesn't need to branch on whether it's being called over HTTP or stdio. `context.progress(...)` and `context.log(...)` are also part of this object — see progress notifications below.

Progress notifications

Call `context.progress(progress, total?, message?)` from inside a handler to emit a `notifications/progress` message, but only if the original request carried a `_meta.progressToken` — without one, the call is a no-op. `context.log(level, data, logger?)` sends `notifications/message` with one of the standard MCP log levels (`debug` through `emergency`). Both rely on `environment.send`, which is only wired up for transports that support server-initiated messages: the HTTP adapter provides it once a client has an open SSE channel for the session, and stdio provides it for every call since it can always write another line to stdout.