Back to skills
SkillHub ClubShip Full StackFull StackIntegration

mcp-builder

DEPRECATED: This skill has been replaced by `mcp-app-builder`. Check if `mcp-app-builder` is available in the skills folder. If not, install it: `npx skills install mcp-use/mcp-use --skill mcp-app-builder` Use `mcp-app-builder` instead of this skill. Build Model Context Protocol (MCP) servers with mcp-use framework. Use when creating MCP servers, defining tools/resources/prompts, working with mcp-use, bootstrapping MCP projects, or when user mentions MCP development, tools, resources, or prompts.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
9,455
Hot score
99
Updated
March 19, 2026
Overall rating
C4.5
Composite score
4.5
Best-practice grade
B73.6

Install command

npx @skill-hub/cli install mcp-use-mcp-use-mcp-builder

Repository

mcp-use/mcp-use

Skill path: skills/mcp-builder

DEPRECATED: This skill has been replaced by `mcp-app-builder`. Check if `mcp-app-builder` is available in the skills folder. If not, install it: `npx skills install mcp-use/mcp-use --skill mcp-app-builder` Use `mcp-app-builder` instead of this skill. Build Model Context Protocol (MCP) servers with mcp-use framework. Use when creating MCP servers, defining tools/resources/prompts, working with mcp-use, bootstrapping MCP projects, or when user mentions MCP development, tools, resources, or prompts.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Integration.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: mcp-use.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install mcp-builder into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/mcp-use/mcp-use before adding mcp-builder to shared team environments
  • Use mcp-builder for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: mcp-builder
description: |
  DEPRECATED: This skill has been replaced by `mcp-app-builder`.
  Check if `mcp-app-builder` is available in the skills folder. If not, install it:
  `npx skills install mcp-use/mcp-use --skill mcp-app-builder`
  Use `mcp-app-builder` instead of this skill.
  
  Build Model Context Protocol (MCP) servers with mcp-use framework.
  Use when creating MCP servers, defining tools/resources/prompts, working with mcp-use,
  bootstrapping MCP projects, or when user mentions MCP development, tools, resources, or prompts.
---

# MCP Server Builder

Build production-ready MCP servers with tools, resources, prompts, and interactive widgets using mcp-use.

## Before You Code

Decompose user requests into tools, widgets, and resources. Decide what needs UI vs text.

Read [design-and-architecture.md](references/design-and-architecture.md): when planning what to build, deciding tool vs widget, or designing UX flows.

## Implementation

- **Tools, resources, prompts** → [tools-and-resources.md](references/tools-and-resources.md): when writing server-side `server.tool()`, `server.resource()`, `server.prompt()` code
- **Visual widgets (React TSX)** → [widgets.md](references/widgets.md): when creating interactive UI widgets in `resources/` folder
- **Response helper API** → [response-helpers.md](references/response-helpers.md): when choosing how to format tool/resource return values
- **URI template patterns** → [resource-templates.md](references/resource-templates.md): when defining parameterized resources

## Quick Reference

```typescript
import { MCPServer, text, object, markdown, html, image, widget, error } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({ name: "my-server", version: "1.0.0" });

// Tool
server.tool(
  { name: "my-tool", description: "...", schema: z.object({ param: z.string().describe("...") }) },
  async ({ param }) => text("result")
);

// Resource
server.resource(
  { uri: "config://settings", name: "Settings", mimeType: "application/json" },
  async () => object({ key: "value" })
);

// Prompt
server.prompt(
  { name: "my-prompt", description: "...", schema: z.object({ topic: z.string() }) },
  async ({ topic }) => text(`Write about ${topic}`)
);

server.listen();
```

**Response helpers:** `text()`, `object()`, `markdown()`, `html()`, `image()`, `audio()`, `binary()`, `error()`, `mix()`, `widget()`

**Server methods:** `server.tool()`, `server.resource()`, `server.resourceTemplate()`, `server.prompt()`, `server.listen()`


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/design-and-architecture.md

```markdown
# Design and Architecture

Before writing code, think about what the user actually wants and how to decompose it into MCP primitives.

## Concepts

- **Tool**: A backend action the AI model can call. Takes input, returns data. Use `server.tool()`.
- **Widget tool**: A tool that returns visual UI. Same `server.tool()` but with a `widget` config and a React component in `resources/`.
- **Resource**: Read-only data the client can fetch. Use `server.resource()` or `server.resourceTemplate()`.
- **Prompt**: A reusable message template. Use `server.prompt()`.

## Step 1: Identify What to Build

Extract the core actions from the user's request. Stick to what they asked -- don't invent extra features.

**Examples:**

| User says | Core actions |
|---|---|
| "weather app" | Get current weather, get forecast |
| "todo list" | Add todo, list todos, complete todo, delete todo |
| "recipe finder" | Search recipes, get recipe details |
| "translator" | Translate text, detect language |
| "stock tracker" | Get stock price, compare stocks |
| "quiz app" | Generate quiz, check answer |

## Step 2: Does It Need a Widget?

For each action, decide if visual UI would meaningfully improve the experience.

**YES → widget** if:
- Browsing or comparing multiple items (search results, product cards)
- Visual data improves understanding (charts, maps, images, dashboards)
- Interactive selection is easier visually (seat picker, calendar, color picker)

**NO → tool only** if:
- Output is simple text (translation, calculation, status check)
- Input is naturally conversational (dates, amounts, descriptions)
- No visual element would meaningfully help

**When in doubt, use a widget** -- it makes the experience better.

## Step 3: Design the API

### Naming

Tools and widgets start with a verb: `get-weather`, `search-recipes`, `add-todo`, `translate-text`.

### One tool = one focused capability

Don't create one massive tool that does everything. Break it into focused actions:

❌ `manage-todos` (too broad)
✅ `add-todo`, `list-todos`, `complete-todo`, `delete-todo`

### One widget per flow

Different flows can have separate widgets. Don't split one flow into multiple widgets.

❌ `search-recipes` widget + `view-recipe` widget (same flow → merge)
✅ `search-recipes` widget (handles both list and detail views) + `meal-planner` widget (different flow)

### Don't lazy-load

Tool calls are expensive. Return all needed data upfront.

❌ `search-recipes` widget + `get-recipe-details` tool (lazy-loading details)
✅ `search-recipes` widget returns full recipe data including details

### Widget handles its own state

Selections, filters, and UI state live in the widget -- not as separate tools.

❌ `select-recipe` tool, `set-filter` tool (these are widget state)
✅ Widget manages selections and filters internally via `useState` or `setState`

### `exposeAsTool` defaults to `false`

Widgets are not auto-registered as tools by default. When you create a custom tool with `widget: { name: "my-widget" }`, omitting `exposeAsTool` in the widget file is correct — the custom tool handles making the widget callable:

```typescript
// resources/my-widget.tsx
export const widgetMetadata: WidgetMetadata = {
  description: "...",
  props: z.object({ ... }),
  // exposeAsTool defaults to false — custom tool definition handles registration
};
```

## Common App Patterns

### Weather App
```
Widget tool: get-weather
  - Input: { city }
  - Widget: temperature, conditions, icon, humidity
  - Output to model: text summary
Tool: get-forecast
  - Input: { city, days }
  - Returns: text or object with daily forecast
```

### Todo List
```
Widget tool: list-todos
  - Widget: interactive checklist with complete/delete buttons
  - Widget calls add-todo, complete-todo, delete-todo via callTool
Tool: add-todo       { title, priority? }
Tool: complete-todo  { id }
Tool: delete-todo    { id }
```

### Recipe Finder
```
Widget tool: search-recipes
  - Input: { query, cuisine? }
  - Widget: recipe cards with images, ingredients, instructions
  - Output to model: text summary of results
Resource: recipe://favorites  (user's saved recipes)
```

### Translator
```
Tool: translate-text
  - Input: { text, targetLanguage, sourceLanguage? }
  - Returns: text (translated result)
Tool: detect-language
  - Input: { text }
  - Returns: object({ language, confidence })
```

### Stock Tracker
```
Widget tool: get-stock
  - Input: { symbol }
  - Widget: price chart, key metrics, news
  - Output to model: price and change summary
Tool: compare-stocks
  - Input: { symbols[] }
  - Returns: object with comparison data
```

## Mock Data Strategy

When the user doesn't specify a real API, use realistic mock data:

```typescript
// Mock data - replace with real API
const mockWeather: Record<string, { temp: number; conditions: string; humidity: number }> = {
  "New York": { temp: 22, conditions: "Partly Cloudy", humidity: 65 },
  "London": { temp: 15, conditions: "Overcast", humidity: 80 },
  "Tokyo": { temp: 28, conditions: "Sunny", humidity: 55 },
  "Paris": { temp: 18, conditions: "Light Rain", humidity: 75 },
};

function getWeather(city: string) {
  // Add slight randomization to feel dynamic
  const base = mockWeather[city] || { temp: 20, conditions: "Clear", humidity: 60 };
  return {
    ...base,
    temp: base.temp + Math.round((Math.random() - 0.5) * 4),
    humidity: base.humidity + Math.round((Math.random() - 0.5) * 10),
  };
}
```

**Guidelines:**
- Use real names (cities, recipes, products) -- not "Example 1"
- Add slight randomization so it feels dynamic
- Structure like a real API would return
- Comment with `// Mock data - replace with real API`

## Iterative Development

When the user asks to modify or extend existing code:

1. **Read** the current `index.ts` to see what exists
2. **Preserve** all existing tools, resources, and widgets
3. **Add** new functionality alongside existing code
4. **Update** existing widget files rather than creating duplicates

```

### references/tools-and-resources.md

```markdown
# Tools, Resources, and Prompts

Server-side implementation patterns for `server.tool()`, `server.resource()`, and `server.prompt()`.

## Tools

Tools are actions the AI model can call.

### Basic Tool

```typescript
import { MCPServer, text, object, error } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "my-server",
  version: "1.0.0",
  baseUrl: process.env.MCP_URL || "http://localhost:3000",
});

server.tool(
  {
    name: "translate-text",
    description: "Translate text between languages",
    schema: z.object({
      text: z.string().describe("Text to translate"),
      targetLanguage: z.string().describe("Target language (e.g., 'Spanish', 'French')"),
      sourceLanguage: z.string().optional().describe("Source language (auto-detected if omitted)"),
    }),
  },
  async ({ text: inputText, targetLanguage, sourceLanguage }) => {
    // Your logic here
    const translated = await translateAPI(inputText, targetLanguage, sourceLanguage);
    return text(`${translated}`);
  }
);
```

### Tool with Widget

When a tool returns visual UI, add `widget` config and return `widget()`:

```typescript
import { widget, text } from "mcp-use/server";

server.tool(
  {
    name: "get-weather",
    description: "Get current weather for a city",
    schema: z.object({
      city: z.string().describe("City name"),
    }),
    widget: {
      name: "weather-display",    // Must match resources/weather-display.tsx
      invoking: "Fetching weather...",
      invoked: "Weather loaded",
    },
  },
  async ({ city }) => {
    const data = getWeather(city);
    return widget({
      props: { city, temp: data.temp, conditions: data.conditions },  // Sent to widget UI
      output: text(`Weather in ${city}: ${data.temp}°C, ${data.conditions}`),  // Model sees this
    });
  }
);
```

### Tool Annotations

Declare the nature of your tool:

```typescript
server.tool(
  {
    name: "delete-item",
    description: "Delete an item permanently",
    schema: z.object({ id: z.string().describe("Item ID") }),
    annotations: {
      destructiveHint: true,    // Deletes or overwrites data
      readOnlyHint: false,      // Has side effects
      openWorldHint: false,     // Stays within user's account
    },
  },
  async ({ id }) => {
    await deleteItem(id);
    return text(`Item ${id} deleted.`);
  }
);
```

### Tool Context

The second parameter to tool callbacks provides advanced capabilities:

```typescript
server.tool(
  { name: "process-data", schema: z.object({ data: z.string() }) },
  async ({ data }, ctx) => {
    // Progress reporting
    await ctx.reportProgress?.(0, 100, "Starting...");

    // Structured logging
    await ctx.log("info", `Processing ${data.length} chars`);

    // Check client capabilities
    if (ctx.client.can("sampling")) {
      // Ask the LLM to help process
      const result = await ctx.sample(`Summarize this: ${data}`);
    }

    await ctx.reportProgress?.(100, 100, "Done");
    return text("Processed successfully");
  }
);
```

### Structured Output

Use `outputSchema` for typed, validated output:

```typescript
server.tool(
  {
    name: "get-stats",
    schema: z.object({ period: z.string() }),
    outputSchema: z.object({
      total: z.number(),
      average: z.number(),
      trend: z.enum(["up", "down", "flat"]),
    }),
  },
  async ({ period }) => {
    return object({ total: 150, average: 42.5, trend: "up" });
  }
);
```

## Resources

Resources expose read-only data clients can fetch.

### Static Resource

```typescript
import { object, text, markdown } from "mcp-use/server";

server.resource(
  {
    uri: "config://settings",
    name: "Application Settings",
    description: "Current server configuration",
    mimeType: "application/json",
  },
  async () => object({ theme: "dark", version: "1.0.0", language: "en" })
);

server.resource(
  {
    uri: "docs://guide",
    name: "User Guide",
    mimeType: "text/markdown",
  },
  async () => markdown("# User Guide\n\nWelcome to the app!")
);
```

### Dynamic Resource

```typescript
server.resource(
  {
    uri: "stats://current",
    name: "Current Stats",
    mimeType: "application/json",
  },
  async () => {
    const stats = await getStats();
    return object(stats);
  }
);
```

### Parameterized Resource (Templates)

```typescript
server.resourceTemplate(
  {
    uriTemplate: "user://{userId}/profile",
    name: "User Profile",
    description: "Get user profile by ID",
    mimeType: "application/json",
  },
  async (uri, { userId }) => {
    const user = await fetchUser(userId);
    return object(user);
  }
);
```

For advanced URI patterns, see [resource-templates.md](resource-templates.md).

## Prompts

Prompts are reusable message templates for AI interactions.

```typescript
server.prompt(
  {
    name: "code-review",
    description: "Generate a code review for the given language",
    schema: z.object({
      language: z.string().describe("Programming language"),
      focusArea: z.string().optional().describe("Specific area to focus on"),
    }),
  },
  async ({ language, focusArea }) => {
    const focus = focusArea ? ` Focus on ${focusArea}.` : "";
    return text(`Please review this ${language} code for best practices and potential issues.${focus}`);
  }
);
```

## Zod Schema Best Practices

```typescript
// Good: descriptive, with constraints
const schema = z.object({
  city: z.string().describe("City name (e.g., 'New York', 'Tokyo')"),
  units: z.enum(["celsius", "fahrenheit"]).optional().describe("Temperature units"),
  limit: z.number().min(1).max(50).optional().describe("Max results to return"),
});

// Bad: no descriptions
const schema = z.object({
  city: z.string(),
  units: z.string(),
  limit: z.number(),
});
```

**Rules:**
- Always add `.describe()` to every field
- Use `.optional()` for non-required fields
- Add validation (`.min()`, `.max()`, `.enum()`) where appropriate
- Use `z.enum()` instead of `z.string()` when there's a fixed set of values

## Error Handling

```typescript
import { text, error } from "mcp-use/server";

server.tool(
  { name: "fetch-data", schema: z.object({ id: z.string() }) },
  async ({ id }) => {
    try {
      const data = await fetchFromAPI(id);
      if (!data) {
        return error(`No data found for ID: ${id}`);
      }
      return object(data);
    } catch (err) {
      return error(`Failed to fetch data: ${err instanceof Error ? err.message : "Unknown error"}`);
    }
  }
);
```

## Environment Variables

When your server needs API keys or configuration:

```typescript
// index.ts - read from environment
const API_KEY = process.env.WEATHER_API_KEY;

server.tool(
  { name: "get-weather", schema: z.object({ city: z.string() }) },
  async ({ city }) => {
    if (!API_KEY) {
      return error("WEATHER_API_KEY not configured. Please set it in the Env tab.");
    }
    const data = await fetch(`https://api.weather.com/v1?key=${API_KEY}&city=${city}`);
    // ...
  }
);
```

Create a `.env.example` documenting required variables:
```
# Weather API key (get from weatherapi.com)
WEATHER_API_KEY=
```

## Custom HTTP Routes

MCPServer extends Hono, so you can add custom API endpoints:

```typescript
server.get("/api/health", (c) => c.json({ status: "ok" }));

server.post("/api/webhook", async (c) => {
  const body = await c.req.json();
  // Handle webhook
  return c.json({ received: true });
});
```

## Server Startup

```typescript
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
server.listen(PORT);
```

```

### references/widgets.md

```markdown
# Widgets

Create interactive visual UIs for your MCP tools using React components.

## How Widgets Work

1. You create a React component in `resources/` folder
2. The component exports `widgetMetadata` (description + props schema) and a default React component
3. mcp-use auto-registers it as both a tool and a resource
4. When the tool is called, the widget renders with the tool's output data

## Widget File Patterns

### Single File

```
resources/weather-display.tsx     → widget name: "weather-display"
resources/recipe-card.tsx         → widget name: "recipe-card"
```

### Folder-Based (for complex widgets)

```
resources/product-search/
  widget.tsx                      → entry point (required name)
  components/ProductCard.tsx
  hooks/useFilter.ts
  types.ts
```

**Naming**: File/folder name becomes the widget name. Use kebab-case.

## Creating a Widget

### Step 1: Create the Widget File

```tsx
// resources/weather-display.tsx
import { McpUseProvider, useWidget, type WidgetMetadata } from "mcp-use/react";
import { z } from "zod";

export const widgetMetadata: WidgetMetadata = {
  description: "Display current weather conditions for a city",
  props: z.object({
    city: z.string().describe("City name"),
    temp: z.number().describe("Temperature in Celsius"),
    conditions: z.string().describe("Weather conditions"),
    humidity: z.number().describe("Humidity percentage"),
  }),
};

export default function WeatherDisplay() {
  const { props, isPending } = useWidget();

  if (isPending) {
    return (
      <McpUseProvider autoSize>
        <div style={{ padding: 16, textAlign: "center" }}>Loading weather...</div>
      </McpUseProvider>
    );
  }

  return (
    <McpUseProvider autoSize>
      <div style={{ padding: 20, borderRadius: 12, background: "#f0f9ff" }}>
        <h2 style={{ margin: 0, fontSize: 24 }}>{props.city}</h2>
        <div style={{ fontSize: 48, fontWeight: "bold" }}>{props.temp}°C</div>
        <p style={{ color: "#666" }}>{props.conditions}</p>
        <p style={{ color: "#999", fontSize: 14 }}>Humidity: {props.humidity}%</p>
      </div>
    </McpUseProvider>
  );
}
```

### Step 2: Register the Tool

```typescript
// index.ts
import { MCPServer, widget, text } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "weather-server",
  version: "1.0.0",
  baseUrl: process.env.MCP_URL || "http://localhost:3000",
});

server.tool(
  {
    name: "get-weather",
    description: "Get current weather for a city",
    schema: z.object({
      city: z.string().describe("City name"),
    }),
    widget: {
      name: "weather-display",       // Must match resources/weather-display.tsx
      invoking: "Fetching weather...",
      invoked: "Weather loaded",
    },
  },
  async ({ city }) => {
    const data = getWeather(city);
    return widget({
      props: { city, temp: data.temp, conditions: data.conditions, humidity: data.humidity },
      output: text(`Weather in ${city}: ${data.temp}°C, ${data.conditions}`),
    });
  }
);

server.listen();
```

## Required Widget Exports

Every widget file MUST export:

1. **`widgetMetadata`** — Object with `description` and `props` (Zod schema):

```typescript
export const widgetMetadata: WidgetMetadata = {
  description: "Human-readable description of what this widget shows",
  props: z.object({ /* Zod schema for widget input */ }),
};
```

2. **Default React component** — The UI:

```typescript
export default function MyWidget() { ... }
```

### WidgetMetadata Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `description` | `string` | Yes | What the widget displays |
| `props` | `z.ZodObject` | Yes | Zod schema for widget input data |
| `exposeAsTool` | `boolean` | No | Auto-register as tool (default: `false`) |
| `toolOutput` | `CallToolResult \| (params => CallToolResult)` | No | What the AI model sees |
| `title` | `string` | No | Display title |
| `annotations` | `object` | No | `readOnlyHint`, `destructiveHint`, etc. |
| `metadata` | `object` | No | CSP, border, resize config, invocation status text |
| `metadata.invoking` | `string` | No | Status text while tool runs — shown as shimmer in inspector (auto-default: `"Loading {name}..."`) |
| `metadata.invoked` | `string` | No | Status text after tool completes — shown in inspector (auto-default: `"{name} ready"`) |

**Invocation status text** in `metadata` is protocol-agnostic and works for both `mcpApps` and `appsSdk` widgets. For tools using `widget: { name, invoking, invoked }` in the tool config, the `invoking`/`invoked` values in `widget:` take effect instead.

### `exposeAsTool` defaults to `false`

Widgets are registered as MCP resources only by default. When you define a custom tool with `widget: { name: "my-widget" }`, omitting `exposeAsTool` is correct — the custom tool handles making the widget callable:

```typescript
export const widgetMetadata: WidgetMetadata = {
  description: "Weather display",
  props: z.object({ city: z.string(), temp: z.number() }),
  // exposeAsTool defaults to false — custom tool handles registration
};
```

Set `exposeAsTool: true` to auto-register a widget as a tool without a custom tool definition.

### `toolOutput`

Control what the AI model sees when the auto-registered tool is called:

```typescript
export const widgetMetadata: WidgetMetadata = {
  description: "Recipe card",
  props: z.object({ name: z.string(), ingredients: z.array(z.string()) }),
  toolOutput: (params) => text(`Showing recipe: ${params.name} (${params.ingredients.length} ingredients)`),
};
```

## `useWidget` Hook

The primary hook for accessing widget data and capabilities.

```typescript
const {
  // Core data
  props,              // Widget input data (from tool's widget() call or auto-registered tool)
  isPending,          // true while tool is still executing (props may be partial)
  toolInput,          // Original tool input arguments
  output,             // Additional tool output data
  metadata,           // Response metadata

  // Persistent state
  state,              // Persisted widget state (survives re-renders)
  setState,           // Update persistent state: setState(newState) or setState(prev => newState)

  // Host environment
  theme,              // 'light' | 'dark'
  displayMode,        // 'inline' | 'pip' | 'fullscreen'
  safeArea,           // { insets: { top, bottom, left, right } }
  maxHeight,          // Max available height in pixels
  userAgent,          // { device: { type }, capabilities: { hover, touch } }
  locale,             // User locale (e.g., 'en-US')
  timeZone,           // IANA timezone

  // Actions
  callTool,           // Call another MCP tool: callTool("tool-name", { args })
  sendFollowUpMessage,// Trigger LLM response: sendFollowUpMessage("analyze this")
  openExternal,       // Open external URL: openExternal("https://example.com")
  requestDisplayMode, // Request mode change: requestDisplayMode("fullscreen")
  mcp_url,            // MCP server base URL for custom API requests
} = useWidget();
```

### Loading State (Critical)

Widgets render BEFORE tool execution completes. **Always handle `isPending`:**

```tsx
const { props, isPending } = useWidget();

if (isPending) {
  return <McpUseProvider autoSize><div>Loading...</div></McpUseProvider>;
}

// Now props are safe to use
return <McpUseProvider autoSize><div>{props.city}: {props.temp}°C</div></McpUseProvider>;
```

### Calling Other Tools

```tsx
const { callTool } = useWidget();

const handleRefresh = async () => {
  try {
    const result = await callTool("get-weather", { city: "Tokyo" });
    console.log(result.content);
  } catch (err) {
    console.error("Tool call failed:", err);
  }
};
```

### Triggering LLM Response

```tsx
const { sendFollowUpMessage } = useWidget();

<button onClick={() => sendFollowUpMessage("Compare the weather in these cities")}>
  Ask AI to Compare
</button>
```

### Persistent State

```tsx
const { state, setState } = useWidget();

// Set state
await setState({ favorites: [...(state?.favorites || []), city] });

// Update with function
await setState((prev) => ({ ...prev, count: (prev?.count || 0) + 1 }));
```

## Convenience Hooks

For simpler use cases:

```typescript
import { useWidgetProps, useWidgetTheme, useWidgetState } from "mcp-use/react";

// Just props
const props = useWidgetProps<MyProps>();

// Just theme
const theme = useWidgetTheme(); // 'light' | 'dark'

// Just state (like useState)
const [state, setState] = useWidgetState<MyState>({ count: 0 });
```

## McpUseProvider

Wrap your widget content in `McpUseProvider`:

```tsx
import { McpUseProvider } from "mcp-use/react";

export default function MyWidget() {
  return (
    <McpUseProvider autoSize>
      <div>Widget content</div>
    </McpUseProvider>
  );
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| `autoSize` | `boolean` | `false` | Auto-resize widget height to fit content |
| `viewControls` | `boolean \| "pip" \| "fullscreen"` | `false` | Show display mode control buttons |
| `debugger` | `boolean` | `false` | Show debug inspector overlay |

## Styling

Both inline styles and Tailwind classes work:

```tsx
// Inline styles
<div style={{ padding: 20, borderRadius: 12, background: "#f0f9ff" }}>

// Tailwind
<div className="p-5 rounded-xl bg-blue-50">
```

## `widget()` Response Helper

Used in tool callbacks to send data to the widget:

```typescript
import { widget, text } from "mcp-use/server";

return widget({
  props: { city: "Tokyo", temp: 25 },              // Sent to widget via useWidget().props
  output: text("Weather in Tokyo: 25°C"),           // What the AI model sees
  message: "Current weather for Tokyo",             // Optional text override
});
```

| Field | Type | Description |
|---|---|---|
| `props` | `Record<string, any>` | Data for the widget UI (hidden from model) |
| `output` | `CallToolResult` | Response helper result the model sees (`text()`, `object()`, etc.) |
| `message` | `string` | Optional text message override |

## Tool `widget` Config

```typescript
server.tool({
  name: "tool-name",
  schema: z.object({ ... }),
  widget: {
    name: "widget-name",           // Must match resources/ file/folder name
    invoking: "Loading...",         // Text shown while tool runs
    invoked: "Ready",              // Text shown when complete
    widgetAccessible: true,         // Widget can call other tools (default: true)
  },
}, async (input) => { ... });
```

## Complete End-to-End Example

**`index.ts`:**

```typescript
import { MCPServer, widget, text, object } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "recipe-finder",
  version: "1.0.0",
  baseUrl: process.env.MCP_URL || "http://localhost:3000",
});

const mockRecipes = [
  { id: "1", name: "Pasta Carbonara", cuisine: "Italian", time: 30, ingredients: ["pasta", "eggs", "bacon", "parmesan"] },
  { id: "2", name: "Chicken Tikka", cuisine: "Indian", time: 45, ingredients: ["chicken", "yogurt", "spices", "rice"] },
  { id: "3", name: "Sushi Rolls", cuisine: "Japanese", time: 60, ingredients: ["rice", "nori", "fish", "avocado"] },
];

server.tool(
  {
    name: "search-recipes",
    description: "Search for recipes by query or cuisine",
    schema: z.object({
      query: z.string().describe("Search query (e.g., 'pasta', 'chicken')"),
      cuisine: z.string().optional().describe("Filter by cuisine"),
    }),
    widget: {
      name: "recipe-list",
      invoking: "Searching recipes...",
      invoked: "Recipes found",
    },
  },
  async ({ query, cuisine }) => {
    const results = mockRecipes.filter(r =>
      r.name.toLowerCase().includes(query.toLowerCase()) ||
      (cuisine && r.cuisine.toLowerCase() === cuisine.toLowerCase())
    );
    return widget({
      props: { recipes: results, query },
      output: text(`Found ${results.length} recipes for "${query}"`),
    });
  }
);

server.listen();
```

**`resources/recipe-list.tsx`:**

```tsx
import { McpUseProvider, useWidget, type WidgetMetadata } from "mcp-use/react";
import { z } from "zod";

export const widgetMetadata: WidgetMetadata = {
  description: "Display recipe search results",
  props: z.object({
    recipes: z.array(z.object({
      id: z.string(),
      name: z.string(),
      cuisine: z.string(),
      time: z.number(),
      ingredients: z.array(z.string()),
    })),
    query: z.string(),
  }),
  exposeAsTool: false,
};

export default function RecipeList() {
  const { props, isPending } = useWidget();

  if (isPending) {
    return <McpUseProvider autoSize><div style={{ padding: 16 }}>Searching...</div></McpUseProvider>;
  }

  return (
    <McpUseProvider autoSize>
      <div style={{ padding: 16 }}>
        <h2 style={{ margin: "0 0 12px" }}>Recipes for "{props.query}"</h2>
        {props.recipes.length === 0 ? (
          <p style={{ color: "#999" }}>No recipes found.</p>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            {props.recipes.map((recipe) => (
              <div key={recipe.id} style={{
                padding: 16, borderRadius: 8, border: "1px solid #e5e7eb", background: "#fff"
              }}>
                <h3 style={{ margin: "0 0 4px" }}>{recipe.name}</h3>
                <p style={{ margin: 0, color: "#666", fontSize: 14 }}>
                  {recipe.cuisine} · {recipe.time} min · {recipe.ingredients.join(", ")}
                </p>
              </div>
            ))}
          </div>
        )}
      </div>
    </McpUseProvider>
  );
}
```

```

### references/response-helpers.md

```markdown
# Response Helpers Reference

Complete reference for mcp-use response helpers.

All helpers are imported from `mcp-use/server`:

```typescript
import { text, object, markdown, html, image, audio, binary, error, mix, widget, resource } from "mcp-use/server";
```

## Table of Contents

- [Text Responses](#text-responses)
- [JSON Responses](#json-responses)
- [Markdown Responses](#markdown-responses)
- [HTML Responses](#html-responses)
- [Error Responses](#error-responses)
- [Binary Responses](#binary-responses)
- [Embedded Resources](#embedded-resources)
- [Mixed Responses](#mixed-responses)
- [Widget Responses](#widget-responses)
- [Autocompletion](#autocompletion)

## Text Responses

```typescript
import { text } from 'mcp-use/server';

// Simple text
return text("Hello, world!");

// Multi-line text
return text(`
  Analysis complete.
  Found 15 items.
  Processing took 2.3 seconds.
`);
```

## JSON Responses

```typescript
import { object } from 'mcp-use/server';

// Object response
return object({
  status: "success",
  count: 42,
  items: ["a", "b", "c"]
});

// Nested objects
return object({
  user: { id: 1, name: "John" },
  metadata: { created: new Date().toISOString() }
});
```

## Markdown Responses

```typescript
import { markdown } from 'mcp-use/server';

return markdown(`
# Report Title

## Summary
- Item 1: **complete**
- Item 2: *in progress*

## Details
\`\`\`json
{ "score": 95 }
\`\`\`
`);
```

## HTML Responses

```typescript
import { html } from 'mcp-use/server';

return html(`
  <div style="padding: 20px;">
    <h1>Welcome</h1>
    <p>This is <strong>HTML</strong> content.</p>
  </div>
`);
```

## Error Responses

```typescript
import { error } from 'mcp-use/server';

// Simple error
return error("Something went wrong");

// With context
return error(`User ${userId} not found`);

// In try/catch
try {
  const data = await fetchData(id);
  return object(data);
} catch (err) {
  return error(`Failed to fetch: ${err instanceof Error ? err.message : "Unknown error"}`);
}
```

The `error()` helper sets `isError: true` on the response, signaling to the model that the operation failed.

## Binary Responses

### Images

```typescript
import { image } from 'mcp-use/server';

// From base64 data
return image(base64Data, "image/png");

// From buffer
return image(imageBuffer, "image/jpeg");

// From file path (async)
return await image("/path/to/image.png");
```

### Audio

```typescript
import { audio } from 'mcp-use/server';

// From base64 data
return audio(base64Data, "audio/wav");

// From buffer
return audio(audioBuffer, "audio/mp3");

// From file path (async)
return await audio("/path/to/audio.mp3");
```

### Generic Binary

```typescript
import { binary } from 'mcp-use/server';

// PDF
return binary(pdfBuffer, "application/pdf");

// ZIP
return binary(zipBuffer, "application/zip");

// Any binary data
return binary(data, "application/octet-stream");
```

## Embedded Resources

Embed a resource reference inside a tool response:

```typescript
import { resource, text } from 'mcp-use/server';

// 2-arg: uri + helper result
return resource("report://analysis-123", text("Full report content here..."));

// 3-arg: uri + mimeType + raw text
return resource("data://export", "application/json", '{"items": [1, 2, 3]}');
```

## Mixed Responses

Combine multiple content types:

```typescript
import { mix, text, object, markdown, resource } from 'mcp-use/server';

// Multiple content items
return mix(
  text("Analysis complete:"),
  object({ score: 95, status: "pass" }),
  markdown("## Recommendations\n- Optimize query\n- Add index")
);

// With embedded resource
return mix(
  text("Report generated:"),
  resource("report://analysis-123", text("Full report content here...")),
  object({ id: "analysis-123", timestamp: Date.now() })
);
```

## Widget Responses

Return interactive widgets from tools:

```typescript
import { widget, text } from 'mcp-use/server';

server.tool(
  {
    name: "show-data",
    schema: z.object({ query: z.string() }),
    widget: {
      name: "data-display",      // Widget in resources/
      invoking: "Loading...",
      invoked: "Data loaded"
    }
  },
  async ({ query }) => {
    const data = await fetchData(query);

    return widget({
      // Props passed to widget (hidden from model)
      props: {
        items: data.items,
        query: query,
        total: data.total
      },
      // Output shown to model
      output: text(`Found ${data.total} results for "${query}"`),
      // Optional message
      message: `Displaying ${data.total} results`
    });
  }
);
```

### Widget Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `props` | object | Data passed to widget component |
| `output` | ResponseHelper | Content shown to the AI model |
| `message` | string | Optional text message |

### Widget Tool Configuration

| Field | Description |
|-------|-------------|
| `widget.name` | Name of widget file in `resources/` |
| `widget.invoking` | Text shown while tool executes |
| `widget.invoked` | Text shown after completion |

## Autocompletion

Add autocompletion to prompt arguments and resource template parameters:

```typescript
import { completable } from 'mcp-use/server';

// Static list
server.prompt(
  {
    name: "code-review",
    schema: z.object({
      language: completable(z.string(), ["python", "typescript", "go", "rust", "java"]),
    }),
  },
  async ({ language }) => text(`Review this ${language} code.`)
);

// Dynamic callback
server.prompt(
  {
    name: "get-user",
    schema: z.object({
      username: completable(z.string(), async (value) => {
        const users = await searchUsers(value);
        return users.map(u => u.name);
      }),
    }),
  },
  async ({ username }) => text(`Get info for ${username}`)
);
```

## Quick Reference Table

| Helper | Return Type | Use When |
|--------|------------|----------|
| `text(str)` | Plain text | Simple text responses |
| `object(data)` | JSON | Structured data |
| `markdown(str)` | Markdown | Formatted text with headings, lists, code |
| `html(str)` | HTML | Rich HTML content |
| `image(data, mime?)` | Image | Base64 or file path images |
| `audio(data, mime?)` | Audio | Base64 or file path audio |
| `binary(data, mime)` | Binary | PDFs, ZIPs, other binary |
| `error(msg)` | Error | Operation failed |
| `resource(uri, content)` | Resource | Embed a resource reference |
| `mix(...results)` | Combined | Multiple content types in one response |
| `widget({ props, output })` | Widget | Interactive UI with data for the widget component |

```

### references/resource-templates.md

```markdown
# Resource Templates Reference

Parameterized resources using URI templates.

## Basic Resource Template

```typescript
server.resourceTemplate(
  {
    uriTemplate: "user://{userId}/profile",
    name: "User Profile",
    description: "Get user profile by ID",
    mimeType: "application/json"
  },
  async ({ userId }) => {
    const user = await fetchUser(userId);
    return object(user);
  }
);
```

## URI Template Patterns

### Single Parameter

```typescript
// user://123/profile
server.resourceTemplate({
  uriTemplate: "user://{userId}/profile",
  // ...
});
```

### Multiple Parameters

```typescript
// org://acme/team/engineering
server.resourceTemplate({
  uriTemplate: "org://{orgId}/team/{teamId}",
  name: "Team Details",
  // ...
}, async ({ orgId, teamId }) => {
  return object(await fetchTeam(orgId, teamId));
});
```

### Optional Parameters

```typescript
// file://documents or file://documents?format=json
server.resourceTemplate({
  uriTemplate: "file://{path}",
  name: "File Content",
  // ...
}, async ({ path }, { searchParams }) => {
  const format = searchParams?.get('format') || 'text';
  const content = await readFile(path);
  return format === 'json' ? object(content) : text(content);
});
```

## URI Scheme Conventions

| Scheme | Use Case | Example |
|--------|----------|---------|
| `config://` | Configuration data | `config://settings`, `config://env` |
| `user://` | User-related data | `user://{id}/profile` |
| `docs://` | Documentation | `docs://api`, `docs://guide` |
| `stats://` | Statistics/metrics | `stats://current`, `stats://daily` |
| `file://` | File content | `file://{path}` |
| `db://` | Database records | `db://users/{id}` |
| `api://` | API endpoints | `api://weather/{city}` |
| `ui://` | UI widgets | `ui://widget/{name}.html` |

## Complete Example

```typescript
import { MCPServer, object, text, markdown } from "mcp-use/server";

const server = new MCPServer({
  name: "data-server",
  version: "1.0.0"
});

// Static resource
server.resource(
  {
    uri: "config://database",
    name: "Database Config",
    mimeType: "application/json"
  },
  async () => object({ host: "localhost", port: 5432 })
);

// Parameterized resource
server.resourceTemplate(
  {
    uriTemplate: "user://{userId}",
    name: "User Data",
    description: "Fetch user by ID",
    mimeType: "application/json"
  },
  async ({ userId }) => {
    const user = await db.users.findById(userId);
    if (!user) throw new Error(`User ${userId} not found`);
    return object(user);
  }
);

// Nested template
server.resourceTemplate(
  {
    uriTemplate: "user://{userId}/posts/{postId}",
    name: "User Post",
    description: "Fetch specific post by user",
    mimeType: "application/json"
  },
  async ({ userId, postId }) => {
    const post = await db.posts.findOne({ userId, id: postId });
    return object(post);
  }
);

// Documentation resource
server.resource(
  {
    uri: "docs://api",
    name: "API Documentation",
    mimeType: "text/markdown"
  },
  async () => markdown(`
# API Documentation

## Endpoints
- GET /users - List all users
- GET /users/:id - Get user by ID
  `)
);
```

```

mcp-builder | SkillHub