HTTP tools and connections
Your system is reached live, during a conversation, by an HTTP tool: one configured instance per action, with a schema the model fills in. The tool holds the address and the shape of the call; it never holds the key. What authorizes the call is a connection, created once and reused by every tool, thread and automation in your organization.
One tool per action
Code
input_schemais what the model is shown, so each property'sdescriptionis an instruction to it. Keep the schema to the one action: a job with several steps or a branch is a flow of tools, not one tool that does everything.config.endpointis the address,config.methodthe verb (POSTwhen you send none).GET,HEADandDELETEcarry the call's values in the query string, added to whatever query you wrote into the endpoint; every other method carries them as a JSON body.- The endpoint may name gaps in its path —
.../patients/{patient_id}/visits— each filled from a value of the call and encoded as one path segment. Gaps in the host, the query or the fragment are refused. deadline_secondsis how long a conversation waits for your system: 1 to 300, and 30 if you send none. When it passes, the agent says so and the conversation continues.
Test it by running it. POST /tools/{tool_id}/test really makes the call, with
this instance's own connection and deadline — nothing is simulated — and
records what happened, so a wrong address or a refused key shows up here first
rather than in somebody's conversation.
What your system receives
Three layers, and nothing else:
- what the model asked for, against the schema above;
- what you fixed on the instance (
fixed_parameters), which the model is not shown, cannot ask for and cannot overwrite; - what a flow step mapped onto it by name (
param_overrides), when the tool is a step of a flow.
A value fixed by a person wins over the model's; a value the flow step mapped wins over both. Nothing of ours travels with them: the platform's own organization and conversation ids are not sent to your system, and a flow's inputs reach a step only through a value the step maps or a parameter the instance declares — never by sharing a name with one of them.
Two headers the platform sets itself: Idempotency-Key on every method but
GET, HEAD and OPTIONS — the same value on each retry of one call, so a
retried write is written once — and whichever header the connection's own
authorization needs.
A call is attempted at most three times, always inside the deadline, and only
when the failure was worth retrying — a refusal from your system is an answer
and is handed to the model instead.
Authorizing with a key
A system of your own behind a key is one connection provider, http_key.
Its secret holds the key and where the key goes, so the tool spells none of
it. Create the connection once:
A key in a header
Code
grant_secret is a string: the JSON object above, serialized. prefix is
joined to the key verbatim, which is why "Bearer " carries its trailing
space; leave it out for a bare X-Api-Key-style key. scopes is empty and
that is the rule rather than an omission — your key can do whatever your system
lets it, and the platform can neither see nor narrow that.
A key in a query parameter
Code
The key is added to the query of every attempt, beside whatever the endpoint
already carries, and it outranks a value of the same name from the model or
from your fixed values. There is no prefix for this placement.
A user name and a password
Code
Sent as Authorization: Basic base64(username:password). The platform builds
the header; you supply the two halves.
An omitted placement means header, so a connection you created before the
other two existed keeps working untouched.
Pointing the tool at it
create_connection answers the connection, including its id. Put it on the
instance — the column, never a header:
Code
One connection serves as many instances as you like. A tool whose own
config.headers would carry a credential is refused when it is created or
changed — a header whose name says so (Authorization, anything with key,
token or secret in it) — naming the header and never its value: the key
belongs in a connection, where it is rotated once and where the platform can
tell you what stopped working if it is refused.
Which brings us to the one thing worth knowing about a key that stops working:
when your system answers 401 or 403 to it, the connection is marked
broken and GET /connections says so, with the reason and the tool that hit
it. Every instance behind it is refused until the key is replaced (PATCH /connections/{connection_id} with a new grant_secret, which makes it healthy
again). The refusal your system wrote is also handed to the agent, so it can
say something true to the person it is talking to.
Nothing of the key reaches a record, a refusal or a log, in any placement: a key in the query is as absent from them as one in a header, and so is a basic password — including where your system quotes the request back in its own error message.
Authorizing as an account you consented to
Where your system is behind a provider's consent screen rather than a key — a Google account of your organization's — the connection is authorized in the dashboard, because a consent screen needs a browser and a person who can agree to it. The tool then carries that account's bearer, renewed by the platform as the provider's rules require, and may do what the account consented to and nothing wider.
From the API, that connection is like any other: read it with
GET /connections (provider is google) and put its id on the instance's
connection_id. One difference is worth knowing:
- the provider answering "this account is no longer authorized" (
401) breaks the connection, exactly as a refused key does; - the provider answering "the consent does not cover this request" (
403) refuses that one tool call and breaks nothing — the authorization is intact, so every other instance behind it keeps working, and the agent is told why this call could not be made.
A deployment with no OAuth client with the provider configured refuses such an instance saying so, rather than calling without a credential.
An MCP server behind a connection
If your actions already exist behind an MCP server, import it: one configured
instance per remote tool, each with a name, fixed parameters and a deadline of
its own. Pass connection_id and the imported tools carry no credential of
their own — and the import's own listing call goes out with that connection too.
Code
- An
http_keyconnection whose key goes in a header sends that header, under the name your server asks for, with its prefix; abasicone sendsAuthorization: Basic …; a consented account sends its bearer. - A key the connection places in the query is not a way to reach an MCP server: such an instance is refused before the server is contacted, naming the reason. Give the server's key a header.
name_prefixis the lever for one collision: two servers may each publish asearch, and a person attaching both to one agent needs to tell them apart.- Importing the same server again refreshes what the server owns — each tool's description and parameter schema — and keeps what you changed: the instance's name, its fixed parameters, its deadline. A remote tool that disappeared is left alone rather than deleted.
Connect an MCP server that signs you in
Some MCP servers — Notion's, Linear's and others — ask whoever calls them to sign in on their own screen. Connect one from Connections: pick the product, or An MCP server and give its address, sign in on the server's screen, and you come back connected. No key is pasted and nothing is registered beforehand. Then import the server with that connection: the listing and every imported tool use it, and its token is sent to that server and no other.
- What the agent may do there is what you allowed on the server's screen, in the server's words. A call that needs more is refused saying what is missing; connect the same server again under the same name to widen it.
- It renews itself while the server allows; it breaks only when the server's sign-in service refuses to renew it, or the server still refuses after one renewal.
- A server that asks for no sign-in, or that this door cannot use yet, is connected with a key instead (above).
Import an API description (OpenAPI or Google Discovery)
If your system publishes a description of its interface, you do not write one tool per endpoint: you choose the operations you want and the platform makes each of them an ordinary HTTP tool — editable, testable, attachable to an agent like one made by hand — with one connection for the whole set. Reading the description involves no model and costs nothing.
It takes two calls. The first lists; the second writes.
1. Preview: what could become a tool
Code
Send exactly one of url (fetched by the platform, from the public internet
only, within the fetch deadline) or document (the text of the JSON file, as a string).
Both, or neither, is a 422. name_prefix is optional and shows the names
with a prefix. Nothing is written.
Code
kindisopenapiordiscovery.servers: pick one for the set. A server written with variables comes with the description's defaults filled in. Aurlofnullis a server the file gives relative to itself: type it yourself when you import.operationsare in the description's order.keyis the operation's own identifier, orMETHOD /pathwhere it has none; it is what you send back.name,descriptionandinput_schemaare what the tool would carry, andlocationsis where each value goes (see below); a gap in the path is filled from the endpoint and never listed. The text is the description's own, with markup removed and the tail cut; no model writes it.unimportableare listed with the reason and cannot be chosen.
The example above is abbreviated to two entries. A description the platform
refuses (see below), or an import naming an operation that does not exist or
cannot be imported, answers 400 with the reason; 422 is only for the
shape of the request itself.
2. Import: the tools you chose
Code
Send the same url or document as the preview, plus:
server: the one you chose for the set.operations: thekeyof each operation, at least one and at most 50.connection_idorconnection_group(the label of a group of connections, as oncreate_tool) — not both; or neither, for an interface that asks for no key. Every tool created points at it.
It answers 201:
Code
tools are the tools created or refreshed, in the shape GET /tools returns
(abbreviated above). Like any tool, they never return their config.
Everything is created or nothing is: a set that fails while it is written
leaves no tool behind.
The connection it suggests
The preview's suggestion on each server comes from the description's
securitySchemes. You confirm or change it; the import never creates a
connection itself.
| The description declares | It suggests |
|---|---|
| a key in a header or in the query | an http_key connection with the key placed where the description says (placement, header or parameter) |
| a user name and password | an http_key connection with placement basic |
| a bearer token | an http_key connection in the Authorization header with the prefix Bearer |
| consent through a provider's screen | your consented account with that provider, where the platform holds that provider's consent screen — on a Google server, your Google account |
| anything else | nothing; note says why when the description asked for something the platform cannot hold |
A scheme the platform cannot connect does not stop the import: point the set at
a generic connection or at none. A credential is never a parameter of the tool.
These are left to the connection and kept off the tool: any header or query
parameter the description names as its key; the Authorization,
Proxy-Authorization, Cookie and X-API-Key headers; any other header whose
name contains key, token, secret or auth (an X-Request-Token is dropped
from the schema without a notice); and Idempotency-Key, which the platform sets
itself.
What the model is shown
Each tool's schema holds the operation's own parameters (path, query, headers)
and the properties of its JSON body, cut to what the operation needs: every
required property, the optional ones in the description's order up to the
per-object bound, and nesting down to the depth bound, below which a value of any shape is
accepted. A name used in more than one place is told apart for the model — of
path, query, headers and body, in that order, the first keeps the name and each
later one becomes query_id, header_name, body_name — while the value is
sent under its original name. A tool's name is the operation's identifier (or
its method and path) behind your prefix; two that would share a name get a
number.
What cannot be imported
Such an operation appears in unimportable with its reason:
- a body that is not JSON (a form, a file, several parts), or a JSON body that is not an object;
- a callback or webhook — your system would call it rather than be called;
- a reference the description does not resolve within the chain bound (one that loops back to itself is cut there and accepted as a value of any shape);
- a gap in the path that no parameter names;
- a cookie the operation requires that is not its key;
- parameters that expand past a bound: more than 2,000 parts once references are followed, or a schema of more than 64 KB (see Bounds below).
A Google Discovery document is read the same way: its methods are the
operations, its server is the rootUrl plus servicePath, and Google's global
parameters are left out. A path written {+name} cannot be imported.
A description that is not JSON, is not OpenAPI 3.x or Discovery, is over the size or operation bound, or would take more than 400,000 steps to read in all (see Bounds below) is refused whole, naming what was found or the bound. YAML is not read yet: hand over the JSON form of the file. A client who needs part of a larger interface hands over a description cut to that part. The platform keeps the tools, never the description.
Importing again
Import the same description with the same server and the tools made before
are refreshed rather than duplicated. The platform remembers, on each tool, the
server and the operation it was imported for; a tool is the same one when both
match, and the description's title plays no part. You need do nothing but
import again with the same server.
| Refreshed | Kept |
|---|---|
address, method, locations, parameter schema, description | name, fixed parameters, deadline, connection, the agents and nodes it is attached to |
- A chosen operation with no tool yet is created, pointing at the connection you chose for this import.
- A tool whose operation you did not choose is not touched.
- A tool whose operation the description no longer holds is left exactly as
it is — it may be attached to a published agent — and named in
missing:{"operation": "…", "tool_id": "…", "name": "…"}. Nothing is deleted. - A different
serveris a new set, so a tool never moves to another server while keeping its connection. - A value you fixed on a parameter the operation no longer has stays on the tool and reaches nothing.
Bounds
| Bound | Value |
|---|---|
| A description's size | at most 16 MB |
| Waiting for a description fetched by its address | at most 30 seconds |
| Operations a description may hold to be listed | at most 3,000 |
| Operations created by one import | at most 50 |
| References followed in one chain | at most 32 |
| Optional properties kept per object of a body | at most 20, in the description's order |
| Nesting shown to the model | 5 levels; below them, a value of any shape |
| Steps to read one operation's parameters, references followed | at most 2,000; past it the operation is unimportable |
| One operation's parameter schema | at most 64 KB; past it the operation is unimportable |
| Steps to read the whole description | at most 400,000; past it the description is refused whole |
| An imported tool's name, prefix included | at most 64 characters |
| An imported tool's description | at most 1,024 characters |
| An imported parameter's description | at most 300 characters |
Saying where a parameter goes
Any HTTP tool, imported or written by hand, may say where each of its
parameters goes with config.locations. A tool that says nothing sends them by
its method: in the query for GET, HEAD and DELETE, in a JSON body
otherwise.
Code
A place is "query", "header" or "body"; a gap in the endpoint's path
({clinic} above) is filled from the value of that name and needs no entry. Write
{"in": …, "name": …} when the name on the wire differs from the one the model
sees; the value is sent under name. A header that carries a credential, or
Idempotency-Key, cannot be named: the tool is refused when it is created or
changed — the connection places the credential.
Next
- Sync a lead to the client's system — an HTTP tool and a connection inside a flow and an automation.
- Quote from a price list — one read tool the agent calls mid-conversation.
- API Reference —
create_tool,test_tool,import_mcp_server,preview_openapi_import,import_openapi,create_connection, with every field.