Construindo Tools
Crie ferramentas MCP em TypeScript com segurança de tipos
Os exemplos seguem o template de MCP App no commit 3fc9bd15. Comece por Estrutura do projeto para configurar essa versão.
Anatomia de uma ferramenta
Toda ferramenta MCP tem estes componentes:
- ID — identificador único em snake_case (por exemplo,
hello_world) - Descrição — informa à IA quando e como usar a ferramenta
- Schemas de entrada e saída — schemas Zod para validação com segurança de tipos
- Função de execução — a função executada quando a ferramenta é chamada
_meta.ui.resourceUri(opcional) — vincula a ferramenta a uma interface interativa- Anotações (opcional) — indicações sobre o comportamento da ferramenta (
readOnlyHint,destructiveHint, etc.)
Exemplo básico
Esta é a ferramenta hello_world do template (api/tools/hello.ts):
import { createTool } from "@decocms/runtime/tools";
import { z } from "zod";
import type { Env } from "../types/env.ts";
export const HELLO_RESOURCE_URI = "ui://mcp-app/hello";
export const helloInputSchema = z.object({
name: z.string().optional().describe("The name to greet"),
});
export type HelloInput = z.infer<typeof helloInputSchema>;
export const helloOutputSchema = z.object({
greeting: z.string(),
timestamp: z.string(),
});
export type HelloOutput = z.infer<typeof helloOutputSchema>;
export const helloTool = (_env: Env) =>
createTool({
id: "hello_world",
description:
"Say hello! Takes a name and returns a friendly greeting.",
inputSchema: helloInputSchema,
outputSchema: helloOutputSchema,
_meta: { ui: { resourceUri: HELLO_RESOURCE_URI } },
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
execute: async ({ context }) => {
const { name } = context;
const displayName = name || "World";
return {
greeting: `Hello, ${displayName}! Welcome to MCP Apps on deco.`,
timestamp: new Date().toISOString(),
};
},
});Pontos principais:
createToolde@decocms/runtime/toolscria ferramentas com segurança de tipos- A função que cria a ferramenta recebe
Env, o ambiente tipado do seu app _meta.ui.resourceUrivincula esta ferramenta a uma interface interativa (veja Criando MCP Apps)annotationsinformam aos clientes MCP o comportamento da ferramenta
Vinculando ferramentas a interfaces
Quando uma ferramenta inclui _meta.ui.resourceUri, os clientes MCP podem renderizar uma interface interativa junto ao resultado da ferramenta:
_meta: { ui: { resourceUri: "ui://mcp-app/hello" } },A URI aponta para um resource de MCP App que serve um bundle HTML. A interface recebe a entrada e o resultado da ferramenta por meio do SDK de MCP Apps e os exibe de forma interativa.
Veja Criando MCP Apps para construir a interface e Resources para disponibilizá-la.
Padrões comuns
Chamadas a APIs externas
export const weatherTool = (env: Env) =>
createTool({
id: "get_weather",
description: "Get current weather for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
}),
outputSchema: z.object({
temperature: z.number(),
condition: z.string(),
}),
annotations: { readOnlyHint: true, openWorldHint: true },
execute: async ({ context }) => {
const response = await fetch(
`https://api.weather.example/v1/current?city=${encodeURIComponent(context.city)}`
);
const data = await response.json();
return {
temperature: data.temp,
condition: data.condition,
};
},
});Usando integrações
Chame outros servidores MCP instalados no mesmo workspace. O env está disponível pelo escopo da função que cria a ferramenta:
export const myTool = (env: Env) =>
createTool({
id: "my_tool",
description: "Call an installed integration",
inputSchema: z.object({ value: z.string() }),
outputSchema: z.object({ result: z.unknown() }),
execute: async ({ context }) => {
const integration = env["my-integration-id"];
const result = await integration.callTool("some_tool", {
param: context.value,
});
return { result };
},
});Registrando ferramentas
Adicione a função que cria sua ferramenta ao array tools em api/tools/index.ts:
import { helloTool } from "./hello.ts";
import { weatherTool } from "./weather.ts";
export const tools = [helloTool, weatherTool];Depois, adicione essas funções à opção tools de withRuntime() em api/app.ts (o template já faz isso por meio do array reexportado).
Testando ferramentas
- Execute
bun run dev. O endpoint MCP local éhttp://localhost:3001/api/mcp; o template rejeita/mcp. - Para testar pelo Studio hospedado, exponha a porta 3001 por um túnel HTTPS. O script
bun run startdo template iniciadeco linke o servidor de dev quando o CLIdecoestá instalado; você também pode usar um túnel HTTPS que já execute. - Abra o Studio e vá a Settings → Connections → Add connection → Custom Connection. Selecione HTTP e cole a URL do túnel com
/api/mcp. - Configure a autenticação se o servidor exigir e associe a conexão e suas ferramentas selecionadas em Settings do agente.
- Chame
hello_worldcom um nome, confira a saudação retornada e confirme que a UI vinculada aparece.
Um client MCP local na sua máquina pode usar diretamente o endpoint localhost. O servidor do Studio hospedado não alcança o localhost da sua máquina. Veja Conexões para o modelo compartilhado.
Boas práticas
- Responsabilidade única — cada ferramenta deve fazer uma coisa bem
- Nomes descritivos — use
snake_case(por exemplo,hello_world,get_weather) - Tipagem forte — defina schemas Zod para entrada e saída
- Anotações — defina
readOnlyHint,destructiveHint,idempotentHinteopenWorldHintpara ajudar os clientes MCP a tomar melhores decisões - Tratamento de erros — lance erros descritivos; o runtime os inclui em respostas de erro MCP
Organizando ferramentas
Agrupe as ferramentas por domínio em api/tools/:
api/tools/
├── index.ts # Exports all tools
├── hello.ts # Greeting tool
├── weather.ts # Weather tools
└── analytics.ts # Analytics tools
Exporte tudo de index.ts:
import { helloTool } from "./hello.ts";
import { weatherTool } from "./weather.ts";
import { analyticsTool } from "./analytics.ts";
export const tools = [helloTool, weatherTool, analyticsTool];