AI-callable functions with typed input/output schemas, validation, and execution context
Tools are typed actions that execute operations with side effects. They’re the primary way to enable an AI model to interact with external systems—calling APIs, modifying data, performing calculations, or triggering workflows.
This feature implements the MCP Tools specification. FrontMCP handles all protocol details automatically.
Nx users: Scaffold with nx g @frontmcp/nx:tool my-tool --project my-app. See Tool Generator.
import { tool } from '@frontmcp/sdk';import { z } from '@frontmcp/sdk';const GreetTool = tool({ name: 'greet', description: 'Greets a user by name', inputSchema: { name: z.string() },})(({ name }) => `Hello, ${name}!`);
You can mix local tool classes with tools loaded from npm packages or proxied from remote MCP servers:
import { App, Tool } from '@frontmcp/sdk';@App({ id: 'my-app', name: 'My Application', tools: [ GreetTool, // Local class Tool.esm('@acme/tools@^1.0.0', 'echo'), // Single tool from npm Tool.remote('https://api.example.com/mcp', 'search'), // Single tool from remote server ],})class MyApp {}
Tool.esm() loads a single named tool from an npm package at runtime. Tool.remote() proxies a single tool from a remote MCP server.
For loading entire apps (all tools, resources, prompts), use App.esm() or App.remote().
By default a structured outputSchema is advertised as the tool’s outputSchema (JSON Schema) in tools/list. The output policy lets you control where that schema surfaces. It is declarable on @Tool, @App, and @FrontMcp, and the effective value for a tool is resolved with a Tool > App > server > default cascade — mirroring the OpenAPI adapter’s outputSchema.mode.
(Default) Advertise the schema as the tool’s outputSchema (JSON Schema).
'description'
Fold a readable rendering of the schema into the tool description, and omit outputSchema.
'both'
Advertise as outputSchemaand fold it into the description.
'none'
Do not expose the output schema anywhere.
When the schema is folded into the description ('description' / 'both'), schemaDescriptionFormat picks the rendering: 'summary' (a compact human-readable property list) or 'jsonSchema' (a fenced JSON Schema code block).
Output-schema exposure only applies to structured object outputs (a Zod raw shape or a top-level z.object) — the same forms that get advertised as outputSchema. Primitive, media, multi-content, and union outputs flow through content and have nothing object-shaped to expose. Runtime output validation still applies to every form regardless of schemaMode.
Examples are indexed for semantic search with 2x weight, helping users find the right tools faster.
LLM Understanding
The codecall:describe tool returns up to 5 examples per tool to help LLMs understand usage patterns.
If you don’t provide examples, FrontMCP auto-generates smart examples based on tool intent (create, list, get, update, delete, search). User-provided examples always take priority.
Class-based tools have access to the full request context (FrontMcpContext) including tracing, timing, and authentication:
@Tool({ name: 'traced-tool', inputSchema: { query: z.string() },})class TracedTool extends ToolContext { async execute({ query }: { query: string }) { // Full request context const ctx = this.context; // Tracing (W3C Trace Context) console.log(ctx.requestId); // Unique request ID console.log(ctx.traceContext.traceId); // W3C Trace ID console.log(ctx.traceContext.parentId); // Parent span ID (if present) // Authentication console.log(ctx.authInfo.token); // JWT token // Typed identity helpers also available via this.auth (FrontMcpAuthContext) // Session console.log(ctx.sessionId); // MCP session ID // Timing marks for performance tracking ctx.mark('db-query-start'); await this.queryDatabase(); ctx.mark('db-query-end'); console.log(ctx.elapsed('db-query-start', 'db-query-end')); // ms // Request metadata console.log(ctx.metadata.userAgent); console.log(ctx.metadata.clientIp); return `Processed: ${query}`; }}
this.context returns the active FrontMcpContext. For typed identity (roles, scopes, claims) prefer this.auth.
See Request Context for complete API reference.
Progress notifications are only sent if the client includes a progressToken in the request’s _meta field.
If no token is provided, this.progress() returns false and silently succeeds.
The progressToken is automatically extracted from the request’s _meta field and made available via the ToolCallExtra type for advanced use cases:
import type { ToolCallExtra } from '@frontmcp/sdk';// In custom tool handlers or plugins, you can access the progressToken directlyconst extra: ToolCallExtra = { authInfo: { ... }, progressToken: 'token-123', // From request._meta.progressToken};
For most use cases, simply use this.progress() which handles the token automatically.
When true, the server will send notifications/tools/list_changed when tools are added or removed
The SDK sets listChanged: true when you have any tools registered, enabling clients to receive real-time notifications when tools are dynamically added or removed.
When tools change dynamically (e.g., via adapters or plugins), FrontMCP automatically sends notifications/tools/list_changed to connected clients. Clients that support this notification will refresh their tool list.
For the full protocol specification, see MCP Tools.
Tools can render visual widgets alongside their responses. This enables rich, interactive presentations of tool outputs—weather cards, order summaries, data tables, and more.
import { type TemplateContext } from '@frontmcp/sdk';ui: { // Annotate `ctx` explicitly under strict / noImplicitAny. template: (ctx: TemplateContext<MyInput, MyOutput>) => `<p>${ctx.helpers.escapeHtml(ctx.output.message)}</p>`,}
TypeScript: annotate the ctx parameter (TS7006). When you inline an HTML template as an arrow function, you need to annotate ctx. Because ui.template is a union of multiple callable shapes (TemplateBuilderFn | string | ((props: any) => any) | FileSource), TypeScript can’t pick a single contextual type for the arrow’s parameter, so template: (ctx) => … errors with Parameter 'ctx' implicitly has an 'any' type. Either annotate ctx: TemplateContext<MyInput, MyOutput> (imported from @frontmcp/sdk), or move the widget into its own .tsx file and use the FileSource form (template: { file: … }) — both avoid the inference gap.
How HTML is delivered: 'auto' (default), 'inline', 'static', 'hybrid', etc.
htmlResponsePrefix
Text prefix for Claude dual-payload HTML block (default: 'Here is the visual result')
mdxComponents
Custom components for MDX templates
hydrate
Enable client-side React hydration for interactivity
.tsx/.jsx widgets require @frontmcp/ui installed. When ui.template points at a .tsx/.jsx file (the recommended pattern for non-trivial widgets), FrontMCP injects an auto-generated React mount that imports McpBridgeProvider from @frontmcp/ui/react. Install @frontmcp/ui in the consuming project at the same version as @frontmcp/sdk — without it, server-side bundling fails.In the default resourceMode: 'cdn' mode, react / react-dom stay external and load from the CDN at runtime, so only @frontmcp/ui needs to be present on disk. When the framework selects resourceMode: 'inline' — either explicitly or via host detection (Claude, #456) — react and react-dom must also be resolvable from the consuming project so esbuild can bundle them into the widget; install them as devDependencies if your project doesn’t already pull them in. See building-tool-ui for the install snippet and the per-host trade-offs.
FileSource paths resolve against process.cwd(), not the tool file. A bare template: { file: './widget.tsx' } from src/tools/foo.tool.ts looks for <cwd>/widget.tsx, not src/tools/widget.tsx — and the mismatch only surfaces at tool-call time as ENOENT. Always anchor relative paths to the tool source with fileURLToPath(new URL('./widget.tsx', import.meta.url)) (from node:url), or pass an absolute path:
In a CommonJS project ("type": "commonjs"), import.meta.url is unavailable — anchor with join(__dirname, 'weather.widget.tsx') (from node:path) instead. Both forms are invariant to process.cwd().
resourceMode is host-detected for .tsx widgets. When you leave resourceMode unset, FrontMCP picks the right default per connecting client: Claude auto-switches to 'inline' (React bundled in — #456), so the widget actually renders in Claude’s sandboxed iframe. Other hosts keep 'cdn' (smaller payload, fetched from esm.sh). Set the value explicitly to opt out of detection:
ui: { template: { file: widgetPath }, // Leave resourceMode unset to host-detect (recommended). // Or pin it explicitly: // resourceMode: 'inline', // always bundle React inline}
Detection only applies to per-call rendering (inline / hybrid / lean serving modes). servingMode: 'static' widgets compile at server startup with no client context — set resourceMode: 'inline' explicitly when a static widget needs to render in Claude.
ui.csp is honored by Claude (#455). FrontMCP now attaches ui.csp to the resources/read content item’s _meta.ui.csp (and _meta['ui/csp']), not just to the tool listing. Claude only reads CSP from the resource content — declarations on the tool were previously silently ignored. Use ui.csp.connectDomains for fetch/XHR/WebSocket allow-lists and ui.csp.resourceDomains for images / scripts / fonts / styles.
Widget files use the *.widget.tsx naming convention..tsx/.jsx widgets are bundled separately by uipack/esbuild at render time. The tsconfig.json scaffolded by frontmcp init excludes **/*.widget.tsx / **/*.widget.jsx from the server typecheck so widgets don’t force the project to set jsx: 'react-jsx' or pull in @types/react. Running frontmcp init on an existing project also adds these excludes. For IDE typecheck of widget sources, add a sibling tsconfig.widget.json with jsx: 'react-jsx' and include: ['src/**/*.widget.tsx'].