# Custom HTTP tools: let the agent call your API mid-conversation

> **You want.** *"crear tool CUSTOM, no se puede por MCP"* · *"crear herramienta personalizada
> custom tool HTTP con token"* · *"CUSTOM_HTTP tool settings url method headers body"* ·
> *"endpointUrl parameters jsonSchema argument substitution"* · *"tool call arguments LLM
> generated vs contact id"* · *"custom tool GET endpoint, response returned to model"*.
>
> **Short answer.** Create it in Studio; the MCP can only edit or toggle an existing one
> (`update_tool`). Check first whether a native integration already does the job.

## Native first

- **GoHighLevel booking:** connect `gohighlevel_booking` (`create_connect_link`), then in Studio
  tick "Permitir que los agentes agenden directamente en el chat". Every agent in the workspace
  gets availability, book, cancel and reschedule tools.
- **Tienda Nube stock and prices:** connect it in Studio → Integraciones (not via MCP). Every agent
  gets `search_tiendanube_products`.

## Sequence

1. Studio → agent → **Herramientas** → Herramientas Personalizadas → **Agregar Herramienta**:
   - Nombre de la Herramienta: ASCII only (see below).
   - Nombre de la Función: `lower_snake`, the name the model calls.
   - Descripción: what the model reads to decide when to call it (10+ chars).
   - URL del Endpoint: HTTPS, public (no localhost or private IPs; checked on every call).
   - Método: POST or GET. Headers Personalizados: put your auth token here.
   - Parámetros: Estructurado *or* JSON Schema, not both. Types: string, number, boolean, object,
     array, date.
   
   **Crear Herramienta** also links it to this agent.
2. In the prompt, name the function exactly as Nombre de la Función, and say when to call it and
   what to do on an error.
3. Optional, MCP `update_tool`: `settings.timeoutMs` (default 30000) or `settings.metadataInBody`.
   Studio has no field for either and drops both on every save, so re-apply after one.

## What your endpoint receives

- **POST:** the model's arguments as a JSON body, plus `_metadata` {agentId, conversationId,
  influencerId, toolId, toolName, functionName}.
- **GET:** arguments in the query string (objects JSON-encoded, URL max 2000 chars); `_metadata`
  in the `X-Tool-Metadata` header. `metadataInBody: false` does the same on POST, for APIs that
  reject unknown fields.
- **Contact data:** `{{contact.whatsappNumber}}`, `{{contact.email}}`, `{{contact.<codename>}}`
  (any custom property; also firstName, lastName, phoneNumber, instagramUsername, ...) are filled
  in server-side, **only inside argument values**, never in the URL or headers. The model must
  send the literal placeholder, so say so in the parameter description. Missing value = empty
  string.
- **Trust:** arguments are model-generated, and `_metadata` is unsigned and carries no contact id.
  Authenticate with your own header; never let an argument alone decide whose data comes back.

## What the model gets back

- 2xx: the raw body as text. Keep it short and data-only; the agent may repeat any `message` field.
- Non-2xx: `{"status":"error","error":"Endpoint returned <code>: <body>"}`.
- Timeout: `Request timed out after <ms>ms`. At most 5 tool rounds per turn.

## Invariant

`list_agent_tools` shows the tool `enabled: true` with the right function name, and a test chat
produces a request in your endpoint's logs.

## How it goes wrong

- **The prompt names a function that isn't loaded** (typo, not linked, disabled): no request is
  made and the agent may invent a result.
- **Non-ASCII tool name with GET or `metadataInBody: false`:** the header can't be built and the
  request never leaves. Use an ASCII name.
- **Agent moved to another workspace:** tools belong to the workspace; it runs without them.
  Re-create them there.

See also: `property-actions` (fire-and-forget HTTP on a property change; nothing returns to the
chat), `connecting-integrations`.
