# Schemas, Auth, Context, and Progress

> Schemas, Auth, Context, and Progress at the Askr server boundary, with validated input and explicit failure states.

Source: [https://askrjs.com/docs/mcp/context-and-progress](https://askrjs.com/docs/mcp/context-and-progress)

Status: stable. Packages: @askrjs/askr.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

Create one MCP server, register typed primitives on it, and let the selected transport supply auth, cancellation, sessions, and progress.

```tsx
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.

## Documentation navigation

[Previous](https://askrjs.com/docs/mcp/primitives/index.md) | [Next](https://askrjs.com/docs/mcp/http-transport/index.md)
