McpServer
The main class for building MCP servers with frac.
Import
Section titled “Import”import { McpServer } from "@usefractal/frac/server";Constructor
Section titled “Constructor”const server = new McpServer( serverInfo: { name: string; version: string }, options: McpServerOptions);Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
serverInfo.name | string | Name of your MCP server |
serverInfo.version | string | Version of your server |
options | McpServerOptions | Configuration options |
Options
Section titled “Options”type McpServerOptions = { // Additional options as needed};Properties
Section titled “Properties”express
Section titled “express”The underlying Express app. Use this to register custom routes, middleware, or settings on the HTTP server backing your MCP server.
import { McpServer } from "@usefractal/frac/server";
const server = new McpServer({ name: "my-app", version: "1.0" });
server.express.get("/health", (_req, res) => { res.json({ status: "ok" });});
server.express.set("trust proxy", 1);express.json() is pre-applied. Register your handlers before run(); after run(), frac appends dev-mode middleware (in development), the /mcp route, and the default error handler in that order.
Methods
Section titled “Methods”registerTool
Section titled “registerTool”Register a tool. Provide view to bind the tool to a view. See registerTool for the full API.
server.registerTool(config, handler);Register Express middleware on the underlying HTTP server. Supports optional path filtering.
// Global middlewareserver.use(cors());
// Path-scoped middlewareserver.use("/api", authMiddleware);useOnError
Section titled “useOnError”Register Express error handler on the underlying HTTP server. Supports optional path filtering.
// Global error handlerserver.useOnError((err, req, res, next) => { console.error(err); res.status(500).send('Something broke!');})
// Path-scoped error handlerserver.useOnError('/api', (err, req, res, next) => { console.error(err); res.status(500).send('Something broke!');})There is a default error handler already registered, defined as:
function defaultErrorHandler( err: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction,) { console.error("Error handling MCP request:", err); if (!res.headersSent) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null, }); }}mcpMiddleware
Section titled “mcpMiddleware”Register MCP protocol-level middleware using an onion model. Middleware wraps request/notification handlers and can inspect, modify, or short-circuit them.
Must be registered before calling server.run() or server.connect().
server.mcpMiddleware(handler);server.mcpMiddleware(filter, handler);Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
filter | McpMiddlewareFilter | Optional. Determines which methods the middleware applies to. Omit for catch-all. |
handler | McpMiddlewareFn | The middleware function. Call next() to continue the chain. |
Filter patterns
Section titled “Filter patterns”| Pattern | Example | Matches |
|---|---|---|
| Exact method | "tools/call" | Only tools/call |
| Wildcard | "tools/*" | Any method starting with tools/ |
| Category | "request" | All requests (with extra context) |
| Category | "notification" | All notifications (extra is undefined) |
| Array | ["tools/call", "resources/read"] | Multiple patterns (OR logic) |
Middleware signature
Section titled “Middleware signature”type McpMiddlewareFn = ( request: { method: string; params: Record<string, unknown> }, extra: McpExtra | undefined, next: () => Promise<unknown>,) => Promise<unknown> | unknown;request— the incoming MCP request withmethodandparamsextra— SDK context (McpExtra) for requests,undefinedfor notificationsnext()— invoke the next middleware or original handler. Can only be called once per middleware.
Type-safe filters
Section titled “Type-safe filters”When using an exact method string, both params and extra are narrowed automatically:
server.mcpMiddleware("tools/call", (request, extra, next) => { // request.params is typed as CallToolRequest["params"] // extra is typed as McpExtra (not undefined) console.log(`Tool called: ${request.params.name}`); return next();});Examples
Section titled “Examples”Logging all requests:
server.mcpMiddleware("request", (request, extra, next) => { console.log(`[MCP] ${request.method}`, request.params); return next();});Auth guard on tool calls:
server.mcpMiddleware("tools/call", async (request, extra, next) => { const token = extra.requestInfo?.headers?.["authorization"]; if (!token) { throw new Error("Unauthorized"); } return next();});Modifying params:
server.mcpMiddleware("tools/call", (request, extra, next) => { request.params = { ...request.params, injectedAt: Date.now() }; return next();});Multiple middleware (onion order):
const server = new McpServer({ name: "my-app", version: "1.0" }, {}) .mcpMiddleware((request, extra, next) => { console.log("outer: before"); const result = await next(); console.log("outer: after"); return result; }) .mcpMiddleware((request, extra, next) => { console.log("inner: before"); return next(); });// Logs: outer: before → inner: before → handler → outer: afterType Export Pattern
Section titled “Type Export Pattern”Export the server type for client-side type inference:
const server = new McpServer({ name: "my-app", version: "1.0" }, {}) .registerTool( { name: "search", inputSchema: { query: z.string() }, view: { component: "search" }, }, async ({ query }) => { return { structuredContent: { results: [] } }; }, );
// Export for generateHelpersexport type AppType = typeof server;Auth middlewares
Section titled “Auth middlewares”requireBearerAuth
Section titled “requireBearerAuth”Middleware that requires a valid Bearer token on every request. Use this when every tool needs sign-in.
import { requireBearerAuth } from "@usefractal/frac/server";
server.use( "/mcp", requireBearerAuth({ verifier: { verifyAccessToken } }),);- Valid Bearer token: the request goes through,
authInfocan be accessed in handlerextraargument. - Missing / invalid / expired tokens: returns a 401 (with
WWW-Authenticate), insufficient scopes get a 403.
optionalBearerAuth
Section titled “optionalBearerAuth”Middleware that validates a Bearer token when present, and lets requests through unauthenticated when no Authorization header is sent. Pair with per-tool securitySchemes for mixed-auth servers.
import { optionalBearerAuth } from "@usefractal/frac/server";
server.use( "/mcp", optionalBearerAuth({ verifier: { verifyAccessToken }, }),);Behavior:
- No
Authorizationheader: the request goes through and the downstream handler decides whether to reject based on its ownsecuritySchemes. - Valid Bearer token: the request goes through,
authInfocan be accessed in handlerextraargument. - Missing / invalid / expired tokens: returns a 401 (with
WWW-Authenticate), insufficient scopes get a 403.
Takes the same Options as requireBearerAuth.
Options
Section titled “Options”Both helpers accept the same BearerAuthMiddlewareOptions:
type BearerAuthMiddlewareOptions = { verifier: OAuthTokenVerifier; requiredScopes?: string[]; resourceMetadataUrl?: string;};| Field | Type | Required | Notes |
|---|---|---|---|
verifier | OAuthTokenVerifier | Yes | The provider-specific token check. See Verifier for the contract. |
requiredScopes | string[] | No | Server-wide scope floor: every accepted token must include all listed scopes, or the request is rejected with 403 InsufficientScopeError. Per-tool securitySchemes enforcement layers on top of this. For optionalBearerAuth, this is only checked when a token is actually sent. |
resourceMetadataUrl | string | No | Absolute URL to your OAuth 2.0 Protected Resource Metadata document. When set, it’s appended to the WWW-Authenticate header on 401 responses so clients can discover the authorization server programmatically. |
Verifier
Section titled “Verifier”Both requireBearerAuth and optionalBearerAuth accept a verifier whose only required method is verifyAccessToken(token: string): Promise<AuthInfo>. This is the provider-specific piece you have to write.
Contract:
- Resolve with an
AuthInfodescribing the validated token. The middleware sets it so tool handlers receive it inextra.authInfo. - Throw
InvalidTokenErrorto reject the token because of malformed, bad signature, expired, or any failed claim check (issuer, audience, etc.). The middleware returns a 401 with the rightWWW-Authenticateheader.
You don’t need to check scopes in the verifier: the middleware enforces requiredScopes against authInfo.scopes automatically and returns 403 if a scope is missing.
Required fields on AuthInfo:
| Field | Type | Notes |
|---|---|---|
token | string | The raw bearer token |
clientId | string | OAuth client_id (often the azp or client_id claim). |
scopes | string[] | Scopes granted to the token. Checked by middleware requiredScopes and by per-tool securitySchemes checks. |
expiresAt | number | Unix seconds. Required: requireBearerAuth rejects tokens with no expiration. |
extra | Record<string, unknown> | Optional: anything else you want available in handlers (e.g. sub, email). |
import { type AuthInfo, InvalidTokenError } from "@usefractal/frac/server";
async function verifyAccessToken(token: string): Promise<AuthInfo> { try { // Validate with your provider (JWT verification, introspection, etc.). const payload = await validateToken(token);
return { token, clientId: payload.client_id, scopes: payload.scope.split(" "), expiresAt: payload.exp, extra: { sub: payload.sub }, }; } catch (err) { throw new InvalidTokenError( err instanceof Error ? err.message : "Token validation failed", ); }}mcpAuthMetadataRouter
Section titled “mcpAuthMetadataRouter”Express router that serves the OAuth 2.0 Protected Resource Metadata document at /.well-known/oauth-protected-resource, so clients can discover the authorization server programmatically.
Example:
import { mcpAuthMetadataRouter } from "@usefractal/frac/server";
server.use( mcpAuthMetadataRouter({ oauthMetadata: { issuer: "https://auth.example.com", authorization_endpoint: "https://auth.example.com/authorize", token_endpoint: "https://auth.example.com/token", response_types_supported: ["code"], }, resourceServerUrl: new URL("https://api.example.com/mcp"), scopesSupported: ["search.read"], }),);Exported Types
Section titled “Exported Types”The following middleware types are available from @usefractal/frac/server:
import type { AuthInfo, AuthMetadataOptions, BearerAuthMiddlewareOptions, McpExtra, McpMiddlewareFn, McpMiddlewareFilter, McpTypedMiddlewareFn, McpMethodString, SecurityScheme,} from "@usefractal/frac/server";Related
Section titled “Related”- registerTool - View registration details
- generateHelpers - Client-side type inference
- Type Safety concept - Understanding the type system