Build an MCP Server
Use build an mcp server with the current published Askr packages.
@askrjs/askr0.0.59
How to use build an mcp server
Register typed tools on one MCP server and select the HTTP, SSE, session, or stdio adapter only at deployment time.
- Import the published @askrjs/askr entrypoint.
- Keep build an mcp server 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) }],
})
);Goal and architecture
`createMcpServer<Dependencies>({ name, version })` from `@askrjs/server/mcp` builds an MCP server you register tools, resources, and prompts on; `registerMcpRoutes(router, path, mcp, options)` mounts it on an HTTP router for the streamable-HTTP transport, or `connectMcpStdio()` from `@askrjs/node/mcp` runs it over stdio for local/CLI clients. Both transports share the same `McpServer` instance, so the tool and resource definitions don't change based on how the server is deployed.
Implementation
`.tool(name, { input, output }, handler)` takes an `@askrjs/schema` `ObjectSchema` for input and an optional output schema; the handler receives an `McpContext<Dependencies>` with `dependencies`, `auth`, `signal`, and helpers like `progress()` and `log()` for long-running work. `.resource(uri, options, handler)` and `.resourceTemplate(template, options, handler)` cover static and parameterized resource reads, and `.prompt(name, { arguments }, handler)` returns the message list a client requests. Call `notifyToolsChanged()` etc. when your available tool set changes at runtime so clients that support list-change notifications stay in sync.
Failure states
A tool handler should set `isError: true` on its `McpToolResult` for a caught, expected failure rather than throwing raw — clients render that distinctly from a transport-level error. If a tool requires elevated access, set `auth` in its `McpPrimitiveOptions` so `McpContext.auth` is checked before the handler runs, not inside the handler body after work has already started. For HTTP transport, `registerMcpRoutes`'s `allowedOrigins`/`allowedHosts` options are your actual security boundary against browser-based cross-origin calls — leaving them unset is not a safe default for anything beyond local development.
Verification
Connect a real MCP client over both transports you support and confirm tool discovery lists exactly the tools you registered, with the schemas you expect. Exercise `signal` cancellation on a long-running tool and confirm it actually aborts the underlying work rather than just returning early while a fetch keeps running server-side. If you're using `sessionStore` for stateful HTTP sessions, kill a session mid-request and confirm `terminateSession()` behavior matches what your client expects on reconnect.