Ir para o conteúdo
decodecodeveloper docs
Studio → MCP Apps e extensões → Criar MCP Apps

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:

  1. ID — identificador único em snake_case (por exemplo, hello_world)
  2. Descrição — informa à IA quando e como usar a ferramenta
  3. Schemas de entrada e saída — schemas Zod para validação com segurança de tipos
  4. Função de execução — a função executada quando a ferramenta é chamada
  5. _meta.ui.resourceUri (opcional) — vincula a ferramenta a uma interface interativa
  6. 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:

  • createTool de @decocms/runtime/tools cria ferramentas com segurança de tipos
  • A função que cria a ferramenta recebe Env, o ambiente tipado do seu app
  • _meta.ui.resourceUri vincula esta ferramenta a uma interface interativa (veja Criando MCP Apps)
  • annotations informam 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

  1. Execute bun run dev. O endpoint MCP local é http://localhost:3001/api/mcp; o template rejeita /mcp.
  2. Para testar pelo Studio hospedado, exponha a porta 3001 por um túnel HTTPS. O script bun run start do template inicia deco link e o servidor de dev quando o CLI deco está instalado; você também pode usar um túnel HTTPS que já execute.
  3. Abra o Studio e vá a Settings → Connections → Add connection → Custom Connection. Selecione HTTP e cole a URL do túnel com /api/mcp.
  4. Configure a autenticação se o servidor exigir e associe a conexão e suas ferramentas selecionadas em Settings do agente.
  5. Chame hello_world com 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, idempotentHint e openWorldHint para 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];