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

# Funções multi-tool

> Agrupe múltiplas ferramentas em um único deploy com app(): rotas geradas automaticamente, MCP unificado, cron independente e config compartilhada.

<Warning>
  **Preview** — Jelou Functions está em preview. A API e o comportamento podem mudar sem aviso prévio.
</Warning>

## Quando usar `app()` vs `define()`?

| Padrão     | Quando usar                                                          |
| ---------- | -------------------------------------------------------------------- |
| `define()` | Uma única operação com um endpoint HTTP                              |
| `app()`    | Múltiplas ferramentas relacionadas que você quer fazer deploy juntas |

Se o seu projeto tem um único handler, use `define()`. Se você precisa agrupar múltiplas operações — por exemplo, enviar e ler e-mails do mesmo serviço — use `app()`.

## Exemplo básico

```typescript index.ts theme={null}
import { app, define, z } from "@jelou/functions";

export default app({
  config: { cors: { origin: "*" }, timeout: 30_000 },
  tools: {
    sendEmail: define({
      description: "Sends an email",
      input: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
      config: { timeout: 5_000 },
      handler: async (input) => ({ sent: true }),
    }),
    readInbox: define({
      description: "Reads inbox messages",
      input: z.object({ limit: z.coerce.number().default(10) }),
      handler: async (input) => ({ messages: [] }),
    }),
  },
});
```

## Testes locais

Inicie o servidor com `jelou functions dev` e teste cada ferramenta na sua rota:

<CodeGroup>
  ```bash curl /send-email theme={null}
  curl -X POST http://localhost:3000/send-email \
    -H "Content-Type: application/json" \
    -d '{"to": "maria@example.com", "subject": "Hello", "body": "Welcome"}'
  ```

  ```json Response 200 theme={null}
  {
    "sent": true
  }
  ```
</CodeGroup>

<CodeGroup>
  ```bash curl /read-inbox theme={null}
  curl http://localhost:3000/read-inbox?limit=5
  ```

  ```json Response 200 theme={null}
  {
    "messages": []
  }
  ```
</CodeGroup>

Se você enviar um campo inválido, recebe um `400` com detalhes:

<CodeGroup>
  ```bash curl with invalid input theme={null}
  curl -X POST http://localhost:3000/send-email \
    -H "Content-Type: application/json" \
    -d '{"to": ""}'
  ```

  ```json Response 400 theme={null}
  {
    "error": "Validation failed",
    "details": [
      { "path": ["subject"], "message": "Required", "code": "invalid_type" },
      { "path": ["body"], "message": "Required", "code": "invalid_type" }
    ]
  }
  ```
</CodeGroup>

## API

```typescript theme={null}
import { app } from "@jelou/functions";

const edgeApp = app({
  config: { cors: { origin: "*" }, timeout: 30_000 },
  tools: {
    /* ... your define() here ... */
  },
});

export default edgeApp;
```

Assinatura de tipo:

```typescript theme={null}
function app(options: {
  config?: AppConfig;
  tools: Record<string, AnyEdgeFunction>;
}): EdgeApp;
```

### `AppConfig`

```typescript theme={null}
interface AppConfig {
  cors?: {
    origin?: string | string[];
    methods?: string[];
    headers?: string[];
    credentials?: boolean;
    maxAge?: number;
  };
  timeout?: number;
  methods?: string[];
  mcp?: boolean;
}
```

| Campo     | Tipo       | Padrão                                  | Descrição                               |
| --------- | ---------- | --------------------------------------- | --------------------------------------- |
| `cors`    | `object`   | `{ origin: "*" }`                       | Configuração global de CORS             |
| `timeout` | `number`   | `30000`                                 | Timeout em ms para todas as ferramentas |
| `methods` | `string[]` | `["GET","POST","PUT","PATCH","DELETE"]` | Métodos HTTP permitidos                 |
| `mcp`     | `boolean`  | `true`                                  | Registrar ferramentas no servidor MCP   |

## Rotas geradas automaticamente

As chaves do seu objeto `tools` são automaticamente convertidas para kebab-case para gerar rotas HTTP:

| Chave       | Rota          |
| ----------- | ------------- |
| `sendEmail` | `/send-email` |
| `readInbox` | `/read-inbox` |
| `MyTool`    | `/my-tool`    |

Para personalizar uma rota, use `config.path` no `define()` individual:

```typescript theme={null}
tools: {
  sendEmail: define({
    config: { path: "/emails/send" },
    handler: async (input) => ({ sent: true }),
  }),
}
```

## Combinação de config

A config global do `app()` se aplica a todas as ferramentas. Cada `define()` pode sobrescrever valores específicos:

| Campo     | Comportamento                   |
| --------- | ------------------------------- |
| `timeout` | Ferramenta sobrescreve o global |
| `methods` | Ferramenta sobrescreve o global |
| `cors`    | Ferramenta sobrescreve o global |
| `mcp`     | Ferramenta sobrescreve o global |
| `path`    | Sempre por ferramenta           |
| `cron`    | Sempre por ferramenta           |

```typescript theme={null}
export default app({
  config: { timeout: 30_000, cors: { origin: "*" } },
  tools: {
    fast: define({
      config: { timeout: 5_000 },
      handler: async () => ({ ok: true }),
    }),
    slow: define({
      handler: async () => ({ ok: true }),
    }),
  },
});
```

Neste exemplo, `fast` tem um timeout de 5 segundos e `slow` herda os 30 segundos globais. Ambas compartilham a configuração de CORS.

## Endpoint de health

No modo app, `/__health` retorna informações sobre todas as ferramentas:

```json theme={null}
{
  "mode": "app",
  "tools": [
    {
      "key": "sendEmail",
      "name": "Send Email",
      "description": "Sends an email",
      "path": "/send-email",
      "cron": []
    },
    {
      "key": "readInbox",
      "name": "Read Inbox",
      "description": "Reads inbox messages",
      "path": "/read-inbox",
      "cron": []
    }
  ]
}
```

## Type guards

Use `isEdgeApp()` e `isEdgeFunction()` para identificar o tipo de export em tempo de execução:

```typescript theme={null}
import { isEdgeApp, isEdgeFunction } from "@jelou/functions";

isEdgeApp(module);      // true if created with app()
isEdgeFunction(module);  // true if created with define()
```

## Cron multi-tool

Cada ferramenta dentro de `app()` pode ter seus próprios schedules de cron independentes. As requisições de cron são enviadas para a rota específica de cada ferramenta.

```typescript theme={null}
export default app({
  tools: {
    dailyCleanup: define({
      description: "Cleans up stale records",
      input: z.object({}),
      config: { cron: [{ expression: "0 3 * * *", timezone: "UTC" }] },
      handler: async (_input, ctx) => {
        if (!ctx.isCron) return { skipped: true };
        return { cleaned: true };
      },
    }),
    hourlySync: define({
      description: "Syncs external data",
      input: z.object({}),
      config: { cron: [{ expression: "0 * * * *" }] },
      handler: async (_input, ctx) => {
        if (!ctx.isCron) return { skipped: true };
        return { synced: true };
      },
    }),
  },
});
```

<Warning>
  O limite de **10** schedules de cron é **agregado** entre todas as ferramentas de um `app()`. Se `dailyCleanup` tiver 3 e `hourlySync` tiver 4, só restam 3 disponíveis.
</Warning>

## MCP multi-tool

Um único servidor MCP em `/mcp` registra automaticamente todas as ferramentas do `app()`. Para excluir uma ferramenta do registro MCP, use `mcp: false` na sua config:

```typescript theme={null}
export default app({
  tools: {
    publicTool: define({
      description: "Visible to MCP",
      input: z.object({}),
      handler: async () => ({}),
    }),
    internalTool: define({
      description: "HTTP only",
      input: z.object({}),
      config: { mcp: false },
      handler: async () => ({}),
    }),
  },
});
```

Neste exemplo, os agentes de IA só descobrem `publicTool` via MCP. `internalTool` só é acessível via HTTP direto na sua rota `/internal-tool`.

## Rotas expostas (modo app)

| Rota          | Descrição                                                            |
| ------------- | -------------------------------------------------------------------- |
| `/__health`   | Health check com lista de ferramentas                                |
| `/mcp`        | Servidor MCP unificado (a menos que `config.mcp: false` globalmente) |
| `/<tool-key>` | Rota de cada ferramenta (kebab-case ou `config.path` personalizado)  |

## Problemas comuns

<Tabs>
  <Tab title="404 na rota da ferramenta">
    As chaves de `tools` são convertidas para **kebab-case**. Se sua chave é `sendEmail`, a rota é `/send-email`, não `/sendEmail`.

    Verifique as rotas reais com o health check:

    <CodeGroup>
      ```bash curl theme={null}
      curl http://localhost:3000/__health
      ```

      ```json Response theme={null}
      {
        "mode": "app",
        "tools": [
          { "key": "sendEmail", "path": "/send-email" },
          { "key": "readInbox", "path": "/read-inbox" }
        ]
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Validação falha com 400">
    A validação do schema é executada antes do handler. Se você receber um `400`, verifique o array `details`:

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST http://localhost:3000/send-email \
        -H "Content-Type: application/json" \
        -d '{"to": 12345}'
      ```

      ```json Response 400 theme={null}
      {
        "error": "Validation failed",
        "details": [
          { "path": ["to"], "message": "Expected string, received number", "code": "invalid_type" },
          { "path": ["subject"], "message": "Required", "code": "invalid_type" },
          { "path": ["body"], "message": "Required", "code": "invalid_type" }
        ]
      }
      ```
    </CodeGroup>

    Cada entrada em `details` indica o campo (`path`), o que era esperado (`message`) e o código Zod (`code`).
  </Tab>

  <Tab title="Ferramenta não aparece no MCP">
    Se uma ferramenta tem `config: { mcp: false }`, ela não é registrada no servidor MCP. Verifique quais ferramentas estão expostas:

    <CodeGroup>
      ```bash curl theme={null}
      curl http://localhost:3000/mcp
      ```

      ```json Response theme={null}
      {
        "tools": [
          { "name": "send-email", "description": "Sends an email", "inputSchema": { "..." } }
        ]
      }
      ```
    </CodeGroup>

    Se uma ferramenta estiver faltando, verifique se ela não tem `mcp: false` na sua `config`.
  </Tab>
</Tabs>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Testing" icon="flask-vial" href="/pt/guias/funcoes/testing">
    Aprenda a testar funções multi-tool com `createMockApp()`.
  </Card>

  <Card title="MCP" icon="plug" href="/pt/guias/funcoes/mcp">
    Configure o servidor MCP unificado para seus agentes de IA.
  </Card>

  <Card title="Cron" icon="clock" href="/pt/guias/funcoes/cron">
    Schedules independentes por ferramenta dentro de `app()`.
  </Card>
</CardGroup>
