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

yaml
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}"
FieldDescription
methodGET (default), POST, PUT, PATCH or DELETE.
urlThe scheme, host and port are literal; templates only in the path.
queryQuery parameters; an array value repeats the parameter, an omitted optional argument leaves it out.
headersLiteral names; values may hold templates.
bodyA JSON value. Strings may hold templates.
timeoutMs, maxResponseBytes, followRedirects, allowInsecureHttpPer-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:

WhereHow
URLScheme, 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.
querySet through URLSearchParams; an array value repeats the parameter; a missing optional value leaves it out
headersNames are literal; values with CR, LF, NUL or other control characters are rejected
bodyBuilt 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

LimitDefault
Schemehttps only; allowInsecureHttp: true allows http
Timeout10 s
Response size1 MiB, counted while streaming and again after decompression (gzip, deflate, br)
Content typeJSON for select; text or JSON for raw
RedirectsNot followed (followRedirects: 1..5 to allow; every hop is checked again)
Rate limit60 calls per minute and 10 at once, per tool
NetworkPublic unicast addresses only (see below)
Upstream errorsReported as Upstream returned 404 Not Found. (no URL, query, body or upstream text)
Spec file1 MiB, 200 tools, 50 YAML aliases

Change them per tool or for every tool:

yaml
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.