Estrutura do Projeto
Entenda a arquitetura TypeScript full-stack dos MCP Apps na deco
Este guia segue o template de MCP App no commit 3fc9bd15. O servidor MCP e a interface React são separados do framework Blocks para websites.
Para usar o template revisado:
git clone https://github.com/decocms/mcp-app.git my-mcp-app
cd my-mcp-app
git checkout 3fc9bd15cb28a66361d5c99730962307d3fd77e0
bun install --frozen-lockfile
bun run devEstrutura de Diretórios
├── api/ # Servidor MCP (Bun)
│ ├── app.ts # Runtime, tools, resources e middleware
│ ├── main.bun.ts # Entrada do servidor HTTP Bun
│ ├── tools/
│ │ ├── index.ts # Registro de tools
│ │ └── hello.ts # Tool de exemplo (hello_world)
│ ├── resources/
│ │ └── hello.ts # Resource de MCP App (serve HTML)
│ └── types/
│ └── env.ts # StateSchema + tipo Env
├── web/ # UI React (MCP App)
│ ├── app.tsx # Ponto de entrada do React
│ ├── context.tsx # Gerenciamento de estado do MCP
│ ├── router.tsx # TanStack Router (baseado em hash)
│ ├── types.ts # Tipos McpState, McpStatus
│ ├── tools/ # Uma pasta por UI de tool
│ │ └── hello/
│ │ └── index.tsx # UI do tool hello_world
│ ├── components/ui/ # Componentes shadcn/ui
│ ├── lib/utils.ts # Helper cn()
│ └── globals.css # Estilos base do Tailwind
├── index.html # HTML de entrada do Vite
├── package.json
├── tsconfig.json
├── biome.json
├── vite.config.ts # Vite + React Compiler + singlefile
├── components.json # Configuração shadcn/ui
└── app.json # Identidade do app e conexão implantada
Backend (api/)
app.ts
O núcleo da aplicação configura o runtime MCP. Registre tools e resources aqui:
import { withRuntime } from "@decocms/runtime";
import { helloAppResource } from "./resources/hello.ts";
import { tools } from "./tools/index.ts";
import { type Env, StateSchema } from "./types/env.ts";
const runtime = withRuntime<Env, typeof StateSchema>({
configuration: {
state: StateSchema,
},
tools,
resources: [helloAppResource],
});
O app factory existente envolve runtime.fetch com logs e withMcpApiRoute(). Preserve esse adaptador: ele serve o endpoint público em /api/mcp e rejeita /mcp.
main.bun.ts
A entrada Bun importa o núcleo da aplicação e inicia o servidor HTTP:
import { app } from "./app.ts";
Bun.serve({
hostname: "0.0.0.0",
port: Number(process.env.PORT) || 3001,
fetch: app.fetch,
});Outras plataformas precisam de uma entrada própria e armazenamento de resources compatível; a entrada do template revisado roda no Bun.
tools/
Definições de tools organizadas por domínio. Cada arquivo exporta uma função criadora de tool que recebe o environment.
tools/index.tsexporta o arraytools— todas as tool creators registradas aqui- Veja Construindo Tools para detalhes
resources/
Resources de MCP App que servem bundles HTML de arquivo único como UIs ricas para resultados de tools.
- Cada resource mapeia uma URI (ex:
ui://mcp-app/hello) para o arquivo HTML buildado - Veja Resources para detalhes
types/
import type { DefaultEnv } from "@decocms/runtime";
import { z } from "zod";
export const StateSchema = z.object({});
export type Env = DefaultEnv<typeof StateSchema>;StateSchema— schema Zod que define a configuração do seu app (mostrada a quem instala)Env— environment tipado que estendeDefaultEnv, disponível nos handlers de tools
Frontend (web/)
Ponto de entrada (web/app.tsx)
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>,
);O app envolve tudo em McpProvider (conecta ao host MCP) e AppRouter (roteia para a UI de tool correta).
Contexto MCP (web/context.tsx)
Gerencia a conexão com o host MCP usando @modelcontextprotocol/ext-apps:
McpProvider— conecta viauseApp(), trata eventos (ontoolinput,ontoolresult,ontoolcancelled,onerror,onhostcontextchanged), aplica estilos do hostuseMcpState<TInput, TResult>()— acessa o estado MCP atual (status, input/result do tool)useMcpApp()— acessa a instância do MCP App (ex: paraapp.sendMessage())useMcpHostContext()— acessa o contexto do host (safe area insets, informações do tool)
Router (web/router.tsx)
Usa TanStack Router com histórico baseado em hash:
const TOOL_PAGES: Record<string, React.ComponentType> = {
hello_world: HelloPage,
};O componente ToolRouter lê toolName do estado MCP e renderiza o componente de página correspondente. RootLayout aplica os safe area insets do contexto do host.
UIs de tools (web/tools/<nome>/)
A UI de cada tool vive em sua própria pasta. O componente usa useMcpState<TInput, TResult>() para tratar todos os estados do ciclo de vida: initializing, connected, tool-input, tool-result, tool-cancelled e error.
Build system
- Vite com
vite-plugin-singlefile— builda em um únicodist/client/index.html - React 19 com React Compiler (
babel-plugin-react-compiler) - Todo o CSS e JS são inlineados em um único HTML para servir como resource de MCP App
Configuração
app.json
{
"scopeName": "deco",
"name": "mcp-app",
"friendlyName": "MCP App Template",
"description": "Starter template for building MCP Apps on deco.",
"connection": {
"type": "HTTP",
"url": "https://mcp.example.com/api/mcp",
"configSchema": {
"type": "object",
"properties": {},
"required": []
}
}
}scopeName+name— identificador único do seu app na plataforma decofriendlyName— nome de exibição mostrado aos usuáriosconnection.url— seu endpoint MCP implantadoconfigSchema— JSON Schema que define as opções de configuração mostradas a quem instala
.mcp.json
Para um client MCP local, crie esta configuração no client que suporte esse formato:
{
"mcpServers": {
"mcp-app": {
"type": "http",
"url": "http://localhost:3001/api/mcp"
}
}
}O Studio hospedado precisa de uma URL HTTPS acessível, e não do localhost da sua máquina. Veja Testando ferramentas para o fluxo de conexão.
Fluxo de Desenvolvimento
- Crie um tool em
api/tools/my-tool.ts - Registre-o em
api/tools/index.ts - Crie um componente de UI em
web/tools/my-tool/index.tsx - Registre a página em
web/router.tsx(mapTOOL_PAGES) - Crie um resource em
api/resources/my-tool.ts - Registre o resource em
api/app.ts(arrayresources) - Rode
bun run deve teste