Studio → MCP Apps e extensões → Criar MCP Apps
Criando MCP Apps
Crie interfaces interativas para tools MCP com o SDK de MCP Apps
O que é um MCP App?
Um MCP App é uma interface interativa exibida dentro de clientes MCP (Claude, Cursor etc.). Ele se conecta ao host pelo SDK @modelcontextprotocol/ext-apps, recebe entradas e resultados de tools e os apresenta em uma interface interativa.
MCP Apps são:
- Compilados como bundles HTML de arquivo único
- Servidos como resources MCP pelo seu servidor
- Conectados às tools por
_meta.ui.resourceUri - Estilizados automaticamente pelo tema do host
Stack
- React 19 com React Compiler (
babel-plugin-react-compiler) - Tailwind v4 + componentes shadcn/ui
- TanStack Router (roteamento por hash, compatível com contextos incorporados)
- SDK @modelcontextprotocol/ext-apps para comunicação com o host
- Vite + vite-plugin-singlefile (compila para um único arquivo HTML)
Como funciona
- Seu servidor MCP expõe tools com
_meta.ui.resourceUriapontando para um resource - Quando uma tool é chamada, o cliente MCP carrega o resource (um bundle HTML)
- O bundle HTML se conecta ao host pelo SDK de MCP Apps
- A interface recebe as entradas e os resultados das tools por handlers de eventos
- A interface apresenta o resultado de forma interativa e pode enviar mensagens de volta à conversa
Estrutura do app
web/app.tsx — Ponto de entrada React
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { McpProvider } from "./context.tsx";
import { AppRouter } from "./router.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<McpProvider>
<AppRouter />
</McpProvider>
</StrictMode>,
);web/context.tsx — Gerenciamento de estado MCP
O McpProvider se conecta ao host MCP e gerencia o estado:
import {
type App,
type McpUiHostContext,
useApp,
useHostStyles,
} from "@modelcontextprotocol/ext-apps/react";
export function McpProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<McpState>(INITIAL_STATE);
const onAppCreated = useCallback((app: App) => {
app.ontoolinput = (params) => {
setState((prev) => ({
...prev,
status: "tool-input",
toolInput: params.arguments,
}));
};
app.ontoolresult = (result) => {
setState((prev) => ({
...prev,
status: "tool-result",
toolResult: result.structuredContent,
}));
};
app.ontoolcancelled = () => {
setState((prev) => ({ ...prev, status: "tool-cancelled" }));
};
}, []);
const { app, isConnected } = useApp({
appInfo: { name: "MCP App", version: "1.0.0" },
capabilities: {},
onAppCreated,
});
useHostStyles(app, app?.getHostContext());
return (
<McpAppContext.Provider value={app}>
<McpStateContext.Provider value={state}>
{children}
</McpStateContext.Provider>
</McpAppContext.Provider>
);
}Exports:
useMcpState<TInput, TResult>()— acesso tipado ao estado MCP atualuseMcpApp()— acesso à instância de App (parasendMessage()etc.)useMcpHostContext()— acesso ao contexto do host (margens da área segura, informações da tool)
web/router.tsx — Roteamento de tools
const TOOL_PAGES: Record<string, React.ComponentType> = {
hello_world: HelloPage,
};
function ToolRouter() {
const { toolName } = useMcpState();
const Page = TOOL_PAGES[toolName];
if (!Page) return <p>Unknown tool: {toolName}</p>;
return <Page />;
}O router lê toolName do contexto do host MCP e renderiza a página correspondente. O RootLayout aplica as margens da área segura fornecidas pelo host.
web/types.ts — Tipos de estado
export type McpStatus =
| "initializing"
| "connected"
| "tool-input"
| "tool-result"
| "tool-cancelled"
| "error";
export interface McpState<TInput = unknown, TResult = unknown> {
status: McpStatus;
toolName?: string;
error?: string;
toolInput?: TInput;
toolResult?: TResult;
}Criando uma interface para uma tool
Passo a passo: adicione uma nova interface de tool
- Crie a tool (
api/tools/my-tool.ts) com_meta.ui.resourceUri - Crie o componente de interface (
web/tools/my-tool/index.tsx) - Registre no router — adicione a entrada em
TOOL_PAGESno arquivoweb/router.tsx - Crie o resource (
api/resources/my-tool.ts) que serve o bundle HTML - Registre o resource em
api/app.ts(arrayresourcesno template revisado)
Exemplo: componente de interface de uma tool
Esta é a interface da tool de saudação (web/tools/hello/index.tsx):
import { Badge } from "@/components/ui/badge.tsx";
import { Button } from "@/components/ui/button.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { useMcpApp, useMcpState } from "@/context.tsx";
import type { HelloInput, HelloOutput } from "../../../api/tools/hello.ts";
export default function HelloPage() {
const state = useMcpState<HelloInput, HelloOutput>();
const app = useMcpApp();
if (state.status === "initializing") {
return <div>Connecting to host...</div>;
}
if (state.status === "connected") {
return (
<Card>
<CardHeader>
<CardTitle>Hello MCP App</CardTitle>
</CardHeader>
<CardContent>
<p>
Connected. Call the <Badge variant="secondary">hello_world</Badge>{" "}
tool to see a greeting here.
</p>
</CardContent>
</Card>
);
}
if (state.status === "tool-input") {
return <div>Greeting {state.toolInput?.name ?? "someone"}...</div>;
}
if (state.status === "error") {
return <p className="text-destructive">{state.error}</p>;
}
if (state.status === "tool-cancelled") {
return <p className="text-destructive">Tool call was cancelled.</p>;
}
// tool-result
return (
<Card>
<CardHeader>
<CardTitle>{state.toolResult?.greeting}</CardTitle>
</CardHeader>
<CardContent>
<Button
onClick={() => {
app?.sendMessage({
role: "user",
content: [{ type: "text", text: "Tell me more!" }],
});
}}
>
Send Message
</Button>
</CardContent>
</Card>
);
}Padrões principais:
- Use
useMcpState<TInput, TResult>()com os tipos de entrada e saída da sua tool - Trate todos os estados (
initializing,connected,tool-input,tool-result,tool-cancelled,error) - Use
app.sendMessage()para enviar mensagens de volta à conversa
Exemplo: definição de resource
Veja Resources para criar o resource que serve esta interface.
Integração com o host
useHostStyles()— herda automaticamente o tema do host (cores, fontes, espaçamento)useMcpHostContext()— acessa as margens da área segura e as informações da tool fornecidas pelo hostapp.sendMessage()— envia mensagens de volta à conversa com a IA
Estilização
- Componentes shadcn/ui disponíveis em
web/components/ui/ - Tailwind v4 para classes utilitárias
- Estilos do host aplicados automaticamente por
useHostStyles()— sua interface acompanha o tema do host - Projete para contextos incorporados: use as margens da área segura e layouts compactos