> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build your first MCP App

> Create, run, and verify an MCP App with a view-bound tool.

Create a TypeScript MCP App, run it locally, and verify that a tool returns a React view in the Inspector.

## What you'll build

This guide creates a local server with:

* an MCP endpoint at `http://localhost:3000/mcp`
* an Inspector at `http://localhost:3000/mcp/inspector`
* a `show-app` tool that returns a view
* a `say-hello` tool that the view calls
* a React view at `views/my-view/view.tsx`

## Prerequisites

* Node.js 22.22.2 or higher
* Basic TypeScript and React knowledge

## Create the project

Run the MCP Apps template and install its dependencies:

```bash theme={null}
npx create-mcp-use-app@latest my-mcp-app --template mcp-apps --install
cd my-mcp-app
```

With a project name and `--template`, the scaffolder does not prompt, and it installs dependencies only with `--install`. If you omit `--install`, run `npm install` in the project before you start the server. The scaffolder also installs the `mcp-apps-builder` coding-agent skill, which requires Git. Pass `--no-skills` to skip it.

The template also creates `README.md`, `.gitignore`, and the installed coding-agent skills. These are the files you edit most often:

```text theme={null}
my-mcp-app/
├── index.ts
├── mcp-env.d.ts
├── views/
│   └── my-view/
│       ├── view.tsx
│       ├── view.css
│       ├── icons.tsx
│       ├── MeshGradient.tsx
│       └── Tooltip.tsx
├── public/
│   ├── icon.svg
│   └── logo-mcp-use.svg
├── package.json
└── tsconfig.json
```

`public/icon.svg` is the server icon declared in `index.ts`. The view renders `public/logo-mcp-use.svg` with the `Image` component from `mcp-use/react`, which resolves root-relative paths against the `public/` folder.

## Run the server

Start the development server:

```bash theme={null}
npm run dev
```

This starts the MCP server, view build pipeline, and Inspector. Keep the process running while you test the app.

## Inspect the view tool

Open `index.ts` and find the `show-app` tool. The template also configures server branding and registers a second tool, `say-hello`, without a view. This excerpt shows the view-bound path you need to understand first.

```typescript theme={null}
// index.ts excerpt; server and z are defined above.
const showAppInputSchema = z.object({
  appName: z
    .string()
    .optional()
    .describe(
      "Optional title shown in the MCP App card instead of the default",
    ),
});

const showAppOutputSchema = z.object({
  message: z.string().describe("Message to display in the MCP App"),
});

export const showApp = server.tool(
  {
    name: "show-app",
    title: "Show MCP App",
    description: "Display the MCP App starter view",
    inputSchema: showAppInputSchema,
    outputSchema: showAppOutputSchema,
    view: {
      name: "my-view",
      description: "MCP App starter card with interactive demo hooks",
      prefersBorder: false,
      csp: {
        resourceDomains: [
          "https://fonts.googleapis.com",
          "https://fonts.gstatic.com",
        ],
      },
    },
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
  },
  async ({ appName }, ctx) => {
    // Brief delay so the pending skeleton is visible during dev/inspector demos.
    await new Promise((resolve) => setTimeout(resolve, 1500));

    console.log(`The app name was: ${appName}`);
    const data = { message: `The MCP App starter for ${appName} is ready!` };

    return {
      content: [{ type: "text", text: data.message }],
      structuredContent: data,
    };
  },
);
```

`view.name` must match the folder under `views/`. In this template, `my-view` maps to `views/my-view/view.tsx`.

A view-bound tool must declare an `outputSchema`. The `structuredContent` object becomes view rendering data via `useToolContext()`. The `content` array is the short text result the model can read.

`view.csp` allows the Google Fonts stylesheet that `view.css` imports. See [Content Security Policy](/v2/typescript/mcp-apps/content-security-policy) for the other domain categories.

The `say-hello` tool returns `{ greeting }` in `structuredContent` and has no view. The view calls it with `useCallTool()`.

Both tools are assigned to exported constants. The generated `mcp-env.d.ts` reads these exports to type `useToolContext<"show-app">()` and `useCallTool("say-hello")` in the view.

## Inspect the React view

Open `views/my-view/view.tsx`. The view reads the tool invocation with `useToolContext()` and wraps its content in `ThemeProvider`, which applies the host theme.

The generated view also shows host context, display-mode buttons, external links, a follow-up message button, and a `set-counter` tool that the host can call while the view is mounted. This condensed excerpt keeps the template's data access and the **Update State** and **Say Hello** controls, without styling and decorative markup.

```tsx theme={null}
import {
  ModelContext,
  ThemeProvider,
  useCallTool,
  useToolContext,
} from "mcp-use/react";
import { useState } from "react";

export default function McpApp() {
  const view = useToolContext<"show-app">();
  const sayHello = useCallTool("say-hello");
  const [count, setCount] = useState(0);

  const pending = view.status === "pending";
  const callResult = sayHello.data?.structuredContent?.greeting;

  return (
    <ThemeProvider>
      {view.status === "error" ? (
        <>
          <h1>Failed to load MCP App</h1>
          <p>{view.error.message}</p>
        </>
      ) : (
        <>
          {!pending && <ModelContext content={`MCP App counter: ${count}`} />}
          <h1>{view.toolInput?.appName?.trim() || "MCP App"}</h1>
          <button
            type="button"
            disabled={pending}
            onClick={() => setCount((c) => c + 1)}
          >
            Update State {count}
          </button>
          <button
            type="button"
            disabled={pending || sayHello.isPending}
            onClick={() => {
              // A failure also sets sayHello.error, which renders below.
              sayHello.callTool({ name: "mcp-use" }).catch(() => {});
            }}
          >
            {sayHello.isPending ? "Saying…" : "Say Hello"}
          </button>
          {!pending && callResult !== undefined && (
            <p>Tool Response: {callResult}</p>
          )}
          {!pending && sayHello.error !== undefined && (
            <p role="alert">{sayHello.error.message}</p>
          )}
        </>
      )}
    </ThemeProvider>
  );
}
```

Views can render before the tool finishes. While `status` is `"pending"`, `toolInput` can hold partial arguments, so the heading reads `appName` from it. The starter view does not render the tool output. After you check `status === "ready"`, `view.toolOutput.message` holds the message from `structuredContent`.

`ModelContext` describes the current counter to the model. See [Model context](/v2/typescript/mcp-apps/model-context) for the available channels.

## Verify in the Inspector

Use the Inspector to confirm that the server and view work.

1. Open `http://localhost:3000/mcp/inspector`.
2. Go to the **Tools** tab.
3. Select `show-app`.
4. Run the tool with `{ "appName": "My App" }`. Without `appName`, the result message contains `undefined`.
5. Confirm that the view renders below the tool result and its heading shows "My App". The handler waits 1.5 seconds, so the view can show a loading skeleton first.
6. Select **Say Hello** in the view. Confirm that it shows `Tool Response: Hello, mcp-use!`.

If the tool runs but no view appears, check that `view.name` exactly matches the folder under `views/` and that the handler returns `structuredContent`.

## Next steps

* Use [`useToolContext()`](https://mcp-use-typescript-api-reference.vercel.app/modules/mcp-use.react.html#usetoolcontext) to read tool input, output, and lifecycle status.
* Use [`useCallTool()`](https://mcp-use-typescript-api-reference.vercel.app/modules/mcp-use.react.html#usecalltool) when a view needs to call another MCP tool. See [Interactivity](/v2/typescript/mcp-apps/interactivity) for follow-up messages, external links, display modes, and view tools.
* Configure [Content Security Policy](/v2/typescript/mcp-apps/content-security-policy) before loading external APIs, images, scripts, embeds, or static assets.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.