Skip to content

Tools

Tools are functions that your LLM can actively call and decides when to use based on user requests. They enable AI models to perform actions such as writing to databases, calling external APIs, modifying files, or triggering other logic.

For the complete documentation index, see llms.txt. Markdown variants of every page are available by appending .md to the URL.

By default, xmcp detects files under the /src/tools/ directory and registers them as tools, but you can specify a custom directory if you prefer. The directory to use can be configured in the xmcp.config.ts file.

A tool file consists of three main exports:

  • Default: The tool handler function.
  • Schema (optional): The input parameters using Zod schemas.
  • Metadata (optional): The tool's identity and behavior hints. If omitted, the name is inferred from the file name and the description defaults to a placeholder.
src/tools/greet.ts

If you're returning a string or number only, you can shortcut the return value to be the string or number directly.

TypeScript
Info

We encourage to use this shortcut for readability, and restrict the usage of the content array type only for complex responses, like images, audio or videos.

To reuse existing typed functions, opt in to TypeScript schema inference. The compiler can generate runtime input validation and descriptions from TypeScript and JSDoc. Explicit schemas always take precedence.

Schema Definition

The schema defines your tool's input parameters using Zod. Use .describe() on each parameter to help LLMs understand how to use your tool correctly.

src/tools/create-user.ts

Type Inference

The InferSchema utility automatically infers TypeScript types from your Zod schema, giving you full type safety without manual type definitions:

TypeScript
Info

Clear descriptions are crucial for LLM tool discovery. For comprehensive Zod validation options (regex patterns, constraints, transformations), see the Zod documentation.

Metadata

The metadata export defines your tool's identity and provides behavioral hints to LLMs and clients.

src/tools/delete-user.ts

Core Properties

name (required)

  • Unique identifier for the tool
  • Defaults to the filename if not provided
  • Use kebab-case (e.g., get-user-profile)

description (required)

  • Clear explanation of what the tool does
  • Defaults to placeholder if not provided
  • Critical for LLM tool discovery and selection

Icons, tags, and availability

ToolMetadata accepts optional icons, tags, and enabled fields:

TypeScript

Icons are forwarded to MCP clients. Each icon requires src (a URL or data URI) and may include mimeType, sizes, and theme ("light" or "dark"). Tags appear in listings under _meta["xmcp/tags"]; other metadata is preserved. When both tags and _meta["xmcp/tags"] are supplied, tags takes precedence. Tags alone do not change availability. Use component visibility rules to include or exclude components by name or tag in xmcp.config.ts.

Tools are enabled by default. Set enabled: false to skip registration: the tool is absent from tools/list, and tools/call returns a not-found error. Its generated MCP Apps UI resource is also unavailable.

Availability is evaluated during server setup; modules are still imported. Rebuild and restart the server after changing metadata in production.

The HTTP transport example demonstrates icons, tags, and a disabled experimental tool.

Annotations

Behavioral hints that help LLMs and UIs understand how to use your tool:

TypeScript
Info

These hints are advisory only. LLMs may use them to make better decisions about when and how to call your tools, but they don't enforce any behavior.

MCP Apps metadata

Info

MCP Apps widgets work automatically for React tools. Add ui metadata only when you need CSP or rendering hints.

TypeScript

Resource-specific properties:

  • csp.connectDomains - Origins for fetch/XHR/WebSocket connections
  • csp.resourceDomains - Origins for images, scripts, stylesheets, fonts, media
  • domain - Optional dedicated subdomain for the widget's sandbox origin
  • prefersBorder - Request visible border + background (true/false/omitted)

Handler Types

Tools support three types of handlers, each suited for different use cases:

TypeBest ForReturns
StandardData queries, calculations, API callsUnstructured or structured content
Template LiteralSimple widgets with external scriptsHTML string
React ComponentInteractive, stateful widgetsReact component

1. Standard Handlers

Standard handlers are functions that return text, structured content, or simple data. This is the default approach for most tools.

When to use:

  • Performing calculations or data transformations
  • Calling external APIs and returning results
  • Querying databases
  • Any task that returns text or structured data without UI interaction
src/tools/calculate.ts

Elicitation

Tool handlers also receive an extra argument. Use extra.elicit() when you want the client to collect a small piece of user input before the tool continues.

When the client sends an MCP initialize request, extra.clientInfo is available with protocol-level client identity (name, version, and optional fields like title). In stdio, xmcp keeps that identity after initialization for the lifetime of the connection.

HTTP transports are strictly stateless. Tool calls only receive extra.clientInfo when the current request includes client identity. For post-initialize tool calls, repeat the identity with request headers:

http
src/tools/preview-elicitation.ts

Quick check with MCPJam

  1. From the repo root, run pnpm --dir examples/http-transport dev.
  2. In another terminal, run npx @mcpjam/inspector@latest.
  3. Connect MCPJam to http://127.0.0.1:3001/mcp.
  4. Call preview-elicitation.
  5. MCPJam opens a small form with an environment select. Accepting returns action: "accept" plus content.environment. Cancel or decline returns the matching action.
Callout

extra.elicit() uses server-initiated requests, which exist on 2025-era MCP connections only. On protocol revision 2026-07-28 it throws with a message pointing at inputRequired() below — the multi round-trip replacement that works on both eras.

Multi round-trip input (inputRequired)

Protocol revision 2026-07-28 replaces server-initiated requests with multi round-trip requests: the tool returns an input_required result describing what it needs, the client collects it, and retries the same tool call with inputResponses attached. xmcp re-exports the SDK helpers, so a tool that needs user input before continuing looks like this:

src/tools/preview-input-required.ts

The handler runs once per round: the first call returns the embedded elicitation, the retry finds the answer in extra.inputResponses and completes. On 2025-era connections the SDK's legacy shim converts the inputRequired return into a real elicitation request automatically, so the same tool serves both client generations. To carry server state across rounds (remember: it round-trips through the client), mint and verify it with createRequestStateCodec, also re-exported from xmcp.

Sampling

Use extra.sample() when a tool needs an LLM completion from the connected client. The client keeps control of model access, selection, and permissions, so the server needs no model API key. Sampling only works when the connected client advertises the sampling capability; other clients reject the request.

The request takes messages (text, image, or audio content), a required maxTokens, and optional systemPrompt, modelPreferences (model hints plus cost/speed/intelligence priorities), temperature, stopSequences, includeContext, and metadata. The result contains the model the client picked, the assistant content, and an optional stopReason.

src/tools/preview-sampling.ts

Quick check with MCPJam

  1. From the repo root, run pnpm --dir examples/http-transport dev.
  2. In another terminal, run npx @mcpjam/inspector@latest.
  3. Connect MCPJam to http://127.0.0.1:3001/mcp.
  4. Call preview-sampling with any text.
  5. MCPJam shows the incoming sampling request for approval. Approving runs the completion with its configured model and the tool returns the model, content, and stopReason from the result.
Callout

Like extra.elicit(), extra.sample() uses server-initiated requests, which exist on 2025-era MCP connections only. On protocol revision 2026-07-28 it throws with a message pointing at inputRequired.createMessage() — the multi round-trip replacement described above. Sampling with tools/toolChoice and task-augmented sampling are not wired through xmcp yet and are rejected with a clear error.

2. Template Literal Handlers

Return HTML directly to create interactive widgets. xmcp automatically generates the widget resource.

src/tools/show-chart.ts

3. React Component Handlers

Return React components for interactive, composable widgets. xmcp renders the component to HTML and generates a widget resource automatically.

src/tools/interactive-todo.tsx

Setup Requirements:

  1. Use .tsx file extension for React component tools
  2. Install React dependencies: npm install react react-dom
  3. Configure tsconfig.json:
JSON
Info

If you're building schema-driven MCP app UIs with @xmcp-dev/ui, use UI rendering. It covers both the packaged path and the deeper custom-renderer escape hatches.

Return Values

Tools support multiple return formats depending on your needs:

Simple Values

Return strings or numbers directly - xmcp automatically wraps them in the proper format:

TypeScript

Content Array

Return an object with a content array for rich media responses:

TypeScript

Supported content types:

  • text - Plain text content
  • image - Base64-encoded images with mimeType
  • audio - Base64-encoded audio with mimeType
  • resource_link - Links to MCP resources
  • resource - Embedded text or binary resources

Media helpers

Use image, audio, and embeddedResource to build content blocks from bytes or text. Put the returned blocks in a tool's content array; they also work as prompt message content.

src/tools/media-preview.ts
HelperInputResult
image(bytes, mimeType)Uint8Array, Node Buffer, or ArrayBufferAn image block with base64 data
audio(bytes, mimeType)The same byte typesAn audio block with base64 data
embeddedResource(uri, content, mimeType?)A string or the same byte typesA resource block with text or base64 blob

Pass raw bytes to image and audio, with an explicit MIME type. They encode the bytes without detecting or converting the media format. Byte views encode only their visible range. embeddedResource keeps strings as text and does not register the URI in the server's resource catalog.

On Cloudflare Workers, import the same helpers from xmcp/cloudflare. They use Web APIs without depending on Buffer or the filesystem. See the runnable Cloudflare Workers example.

Files in Node

The xmcp/node entry provides asynchronous file-reading helpers:

src/tools/chart.ts

Supply an existing file path (relative to the running process's working directory) or a file: URL. audioFromFile(path, mimeType) reads audio in the same way. embeddedResourceFromFile(path, uri, mimeType?) always returns a base64 resource blob, including for text files. For a text resource, pass the decoded text to embeddedResource instead. File-reading errors propagate to the caller. Include these files when deploying your application; xmcp does not copy them into the compiled output.

Structured Outputs

You can declare an outputSchema when your tool returns structuredContent to enforce validation:

TypeScript

Return structured data using the structuredContent property:

TypeScript

structuredContent works without declaring outputSchema. If outputSchema is declared and structuredContent is returned, structuredContent must conform to it. If your handler returns a primitive (string or number) and outputSchema has exactly one field that accepts it, xmcp auto-injects it into structuredContent using that field. Undeclared keys are rejected when validating structuredContent against outputSchema. You can also return a plain object directly (for example return content) and xmcp will treat it as structuredContent when outputSchema is declared. When structuredContent is returned without content, xmcp auto-generates a text fallback (JSON.stringify(structuredContent)) for compatibility with clients that only render content.

Combined Response

Return both content and structuredContent for backwards compatibility. If the client cannot process structured outputs, it will fallback to content.

TypeScript

Troubleshooting

Tool Loading Errors

When xmcp starts, it loads every file under your tools directory.

  • Empty tool files are skipped with a friendly warning
  • Files without a default export are skipped with a friendly warning
  • Real syntax or import errors still fail normally so you can see the full stack trace

For example, if src/tools/draft.ts is empty, startup will log:

Text

If the file exists but does not export a default handler, startup will log:

Text
Info

Friendly handling is intentionally limited to empty files and missing default exports. Invalid implementations and real import/syntax errors still surface as normal runtime errors.

CLI Scaffolding

You can use the CLI to scaffold tools, resources, and prompts.

Create a tool

Shell

Create a resource

Shell

Create a prompt

Shell

Output

Each command creates a starter file in the default directory for that primitive:

  • xmcp create tool my-tool → src/tools/my-tool.ts
  • xmcp create resource my-resource → src/resources/my-resource.ts
  • xmcp create prompt my-prompt → src/prompts/my-prompt.ts

The generated file already includes the basic exports you need to continue:

  • tools: schema, metadata, and a default function
  • resources: metadata and a default function
  • prompts: schema, metadata, and a default function

So instead of starting from an empty file, you get a ready-to-edit template with placeholder descriptions and example return values.

One framework to rule them all