HTTP tools
Each spec tool makes one HTTP request. This page covers how the request is built and what limits apply to it.
Where this fits: Build with YAML. How a spec entry becomes an HTTP request; the reference lists every field.
On this page
The request
specVersion: 1
name: tickets
version: 1.0.0
tools:
- name: create_ticket
description: Opens a support ticket and returns its id.
input:
type: object
properties:
project: { type: string, pattern: "^[a-z0-9-]{1,40}$" }
title: { type: string, maxLength: 200 }
urgent: { type: boolean }
required: [project, title]
http:
method: POST
url: https://api.example.com/v1/projects/{{input.project}}/tickets
headers: { Accept: application/json }
body:
title: "{{input.title}}"
priority: "{{input.urgent}}"
source: mcp
output:
select: "{id: id, url: html_url}"| Field | Description |
|---|---|
method | GET (default), POST, PUT, PATCH or DELETE. |
url | The scheme, host and port are literal; templates only in the path. |
query | Query parameters; an array value repeats the parameter, an omitted optional argument leaves it out. |
headers | Literal names; values may hold templates. |
body | A JSON value. Strings may hold templates. |
timeoutMs, maxResponseBytes, followRedirects, allowInsecureHttp | Per-tool limits; defaults.http sets them for every tool. |
Templates
Values are encoded for where they go, so an argument can never change the shape of the request:
Only {{input.field.sub}} and {{secrets.NAME}}. No logic, filters or expressions; anything else
is a load error. \{{ writes a literal {{. Values are encoded for where they go:
| Where | How |
|---|---|
| URL | Scheme, host and port must be literal. Templates only in the path, each value percent-encoded; empty values, ./.., and values that form a dot segment with the literal text around them are rejected. |
query | Set through URLSearchParams; an array value repeats the parameter; a missing optional value leaves it out |
headers | Names are literal; values with CR, LF, NUL or other control characters are rejected |
body | Built as a JSON value, never by string concatenation. A string that is exactly one reference keeps the value’s type. |
In the example above, "{{input.urgent}}" is exactly one reference, so priority stays a JSON
boolean in the body.
Limits
| Limit | Default |
|---|---|
| Scheme | https only; allowInsecureHttp: true allows http |
| Timeout | 10 s |
| Response size | 1 MiB, counted while streaming and again after decompression (gzip, deflate, br) |
| Content type | JSON for select; text or JSON for raw |
| Redirects | Not followed (followRedirects: 1..5 to allow; every hop is checked again) |
| Rate limit | 60 calls per minute and 10 at once, per tool |
| Network | Public unicast addresses only (see below) |
| Upstream errors | Reported as Upstream returned 404 Not Found. (no URL, query, body or upstream text) |
| Spec file | 1 MiB, 200 tools, 50 YAML aliases |
Change them per tool or for every tool:
defaults:
http: { timeoutMs: 8000, maxResponseBytes: 262144, followRedirects: 2 }
rateLimit: { perMinute: 30, concurrency: 5 }Rate limits count calls per tool, per process.
Redirects
Redirects are not followed unless followRedirects allows it (at most 5). Every hop is resolved
and checked again like the first request; https to http downgrades are refused, and when the
origin changes, every header from the spec is dropped except Accept and User-Agent, and the
body is not sent. The security model has the
details.
Plain http
https is required by default. allowInsecureHttp: true allows http:// URLs for a tool (or all
tools), but a tool that sends a secret must still use https; see
secrets.