Skip to content

Pass a customer's value to a tool

The concept behind tools is explained in Tools.

Some values belong neither to the tool nor to the model, but to the request: your customer's account id in your own system, the region you choose per customer, or your customer's own token for your API. Such a value is a call variable. The tool declares which call variables it reads, your application sends their values in tool_variables, and Core fills them in on every call of that tool. The model never sees them and cannot change them.

A tool therefore takes three kinds of value:

Kind Marker in a template Who supplies it When
Argument {name} the model on each call, in its arguments
Install value {$name} the tool's author, or the organisation that installs it from a bundle when the tool is registered or installed (Values you set once)
Call variable {@name} your application on each request, in tool_variables

You declare and change call variables through the API or in the console. The console pages describe the same fields for an API connector, an MCP server, a script, a workflow and an alias; a schedule holds the values it sends, and the console shows the variables an agent's tools need.

A call variable is not the same as an agent's or a prompt's variables. Those values (agent.variables) go into the context the model reads. Never send your customer's token in agent.variables; send it in tool_variables.

Declare call variables on an API connector

An API connector declares its call variables in call_variables: a JSON Schema object with one property per variable. Each property is a string, number, integer or boolean, with an optional description, enum and default. required names the variables a call cannot do without. A name is lower-case letters, digits and _, and starts with a letter or a digit.

This example reads one customer's orders from your API, with that customer's own token:

curl -s -X POST "$CORE_URL/api_connectors" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "orders",
         "http_url": "https://api.your-app.example/v1/accounts/{@account_id}/orders",
         "description": "Open orders of this customer.",
         "parameters": {"type": "object", "properties": {"status": {"type": "string"}}},
         "request": {"method": "GET",
                     "query": {"status": "{status}"},
                     "headers": {"Authorization": "Bearer {@customer_token}"}},
         "call_variables": {
             "type": "object",
             "properties": {
                 "account_id": {"type": "string", "description": "Account id of this customer with you"},
                 "customer_token": {"type": "string", "x-secret": true}},
             "required": ["account_id", "customer_token"]},
         "auth": {"type": "none"}}'

customer_token is a secret (x-secret: true, see Secret values), so it may stand in the header only. auth stays none because the token differs per request; a credential that is the same for everybody belongs in auth (Authenticate to your endpoint).

A call variable may stand where an install value may: in the path and query of http_url, and in a query value, header value or body string of request. In the URL the value is percent-encoded, in a query or header value it is text, and a body string that is exactly one {@name} becomes the value itself, of its own type. An optional variable with no value and no default fills in as empty text, and as null where it is a whole body value. Core fills in the install values first, then the call variables, then the model's arguments.

Registration returns 422 on call_variables for:

  • a {@name} that is not declared, or a declaration no {@name} uses;
  • a {@name} in the scheme, host, port or fragment of the URL, or in a key;
  • a name that is also an argument or an install value;
  • a secret variable in the URL or a query value.

A call variable therefore never chooses the host: a host that differs per request could send your organisation's credential to a host of the caller's choosing.

A PATCH with call_variables replaces the whole declaration and creates a new version of the connector.

Declare call variables on an MCP server

An MCP server takes call variables in the values of headers only. Core reads a server's tool list in the background, without a request, so a URL that needs a call variable could never be read.

curl -s -X POST "$CORE_URL/mcp_servers" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "crm", "url": "https://mcp.your-app.example/{$workspace}/mcp",
         "headers": {"X-Api-Key": "{$api_key}", "Authorization": "Bearer {@customer_token}"},
         "install_values": {
             "workspace": {"type": "string", "value": "acme"},
             "api_key": {"type": "secret", "value": "fake-api-key-for-docs-0001"}},
         "call_variables": {
             "type": "object",
             "properties": {"customer_token": {"type": "string", "x-secret": true}},
             "required": ["customer_token"]},
         "auth": {"type": "none"}}'
  • headers are the headers every request to the server carries. A value may hold {$name} install values and {@name} call variables.
  • install_values work as on an API connector. A plain install value may stand in the path and query of the URL and in a header value; a secret in a header value only.
  • Core leaves a header that holds a call variable off when it reads the tool list, because there is no request there to take a value from. Your server must therefore list its tools without that header.
  • Authorization may be in headers only while the server's auth is none, as above (The Authorization header). Proxy-Authorization, Cookie, a header Core or the transport sets, and the header the server's own auth is sent in are always refused (422 on headers).
  • A {@name} in the URL returns 422 on call_variables.

A PATCH that names headers, install_values or call_variables replaces that field whole.

Send the values

Send the values in tool_variables: a flat object of names to strings, numbers or booleans.

curl -s -X POST "$CORE_URL/responses" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"model": "<a model id>",
         "input": "Which orders are still open?",
         "tools": [{"type": "tool", "name": "api__orders"}],
         "tool_variables": {"account_id": "A-10442",
                            "customer_token": "fake-token-for-docs-0001"}}'

The same field exists on:

Where How
POST /responses and POST /messages tool_variables beside the rest of the request, a background turn's included
POST /tools/{name}/call {"arguments": {...}, "tool_variables": {...}}, checked against that one tool (Call a tool directly)
A schedule tool_variables in the stored request; every fire sends them
An evaluation scenario tool_variables on the scenario, sent on every step; non-secret values only
A toolset served over MCP in each call's params._meta (below)

A workflow run takes its values in variables when it starts; see Scripts, workflows and aliases.

What happens to a value

  • The model never sees it. The values are not in the context, not in a tool's description and not in its arguments.
  • Each tool receives only what it declares. A tool that declares no call variables receives none, and no tool sees the request's whole set.
  • A name no tool declares is ignored. You can send the same set even when your key's policy or a toolset leaves a tool out of the turn. The trace names the ignored names in interpretation.tool_variables_ignored, and on each tool call the names that tool received. A value is never in a trace, a log line or the audit trail.
  • The response does not repeat them. The stored response and GET /responses/{id} carry no tool_variables.

A value that is missing or does not fit

Core checks tool_variables against the declarations of every tool the turn offers before any model is called. Nothing goes to the model and nothing is billed when a value is wrong. A tool your key's policy withholds does not count.

Core refuses:

  • a required variable with no value and no default;
  • a value its declaration does not admit: the wrong type, or not in its enum;
  • a value that cannot stand where it goes: a { or }, a control character in a header, or . or .. in a URL path.

The message names the variable and the tool. On POST /responses it is a 400 in OpenAI's shape:

{
  "error": {
    "message": "api__orders requires customer_token.",
    "type": "invalid_request_error",
    "param": "tool_variables.customer_token",
    "code": "invalid_request"
  }
}

POST /messages answers 400 in Anthropic's shape, with tool_variables.customer_token: api__orders requires customer_token. as the message. POST /tools/{name}/call answers 422 with tool_variables.customer_token as the field in its errors list (Error format).

A background turn checks its values when it starts: when one is missing, the run fails before any model is called, with the same sentence as its error.

Nested agents, tasks and workflows

A turn passes its values on to what it starts, because the caller stays the same:

  • an agent called as a tool and an agent started as a task receive the whole set and check it against their own tools;
  • a tool running as a task in the background keeps the share it declares;
  • a workflow started from a turn as a task keeps the values it declares itself;
  • core__call_tool, with which a turn calls another tool, carries the turn's values;
  • a tool behind the tool router receives its own share, like any other tool.

The model cannot set call variables itself. A request a core__ tool sends to Core on the model's behalf may not carry tool_variables; Core refuses it. builtin__subagent offers no tools, so it has nothing to pass on.

A toolset over MCP

When you offer a toolset as an MCP server, the client has no field for tool_variables. It sends the values with each call in params._meta["artific/tool_variables"]:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "api__orders",
    "arguments": {"status": "open"},
    "_meta": {"artific/tool_variables": {"account_id": "A-10442",
                                         "customer_token": "fake-token-for-docs-0001"}}
  }
}

Core checks the call against that one tool, as POST /tools/{name}/call does. The tool list also shows the tools that require a variable. When a required variable is missing, a value does not fit, or _meta is not a flat object, the call answers as MCP reports a failed tool: the refusal's sentence with isError, before anything runs or is billed.

Secret values

An API connector and an MCP server can keep both kinds of value secret.

A secret call variable is a property with x-secret: true: a string of at most 8,192 characters, with no default and no enum, because everyone who can read the tool reads those.

A secret install value is {"type": "secret", "value": "..."}, for an endpoint that needs another credential besides auth. For an API connector it is described under Values you set once; on an MCP server it works the same way:

  • you write it once and never read it back: GET answers {"type": "secret", "has_value": true, "description": "..."};
  • a PATCH that replaces install_values gives each secret again, or {"$keep": true} to keep the stored one;
  • a deployment without a sealing key answers 503 to a body carrying a secret.

For both kinds:

  • Only in a header value or a body string, never in the URL or a query value, because those end up in proxy and server logs. On an MCP server, in a header value only.
  • Core masks them in results. Before the model or the trace sees a result, Core replaces each exact echo of a secret value of 8 characters or more with [secret]. A shorter value, or one the endpoint transformed (encoded in base64, hashed, split), is not recognised. Do not send a secret back in your answer.
  • Stored sealed. A background run and a schedule keep a secret value encrypted; the run drops it when it ends. A schedule answers the names in tool_variable_secrets as {"has_value": true} and leaves them out of request.tool_variables; an update that leaves such a name out keeps the stored value. A background request carrying a secret on a deployment without a sealing key is a 503.
  • Not in an evaluation scenario. A scenario is test data everyone with access to the suite reads. A value for a secret name returns 422 on tool_variables.<name>. Core counts a name as secret as soon as an API connector or MCP server of your organisation declares it x-secret.
  • Scripts, workflows and aliases take no secrets. Their registration refuses an x-secret call variable and a secret install value with 422. A tool behind a workflow or an alias that needs a secret receives it with the request that reaches that tool.

The Authorization header

An API connector's template and an MCP server's headers may write the Authorization header only when the tool's own credential, auth, is none. That is how both examples above work: "Bearer {@customer_token}" with a secret call variable. Beside a credential it would be a second one, and that returns 422, on request for a connector and on headers for an MCP server. A connector or server shared through a publication whose credential travels with it or is filled in by the installer counts as having a credential.

Cookie and Proxy-Authorization are always refused, and so is the header auth is sent in. There is no way to replace auth per request: a per-request credential is a secret call variable, placed where the tool's author put it in the template.

Scripts, workflows and aliases

These kinds declare install_values and call_variables too, without secrets:

Kind Install value Call variable
Script the file ARTIFIC_INSTALL names, read-only the file ARTIFIC_VARIABLES names, holding only the variables the version declares
Workflow install.<name> in an expression run.variables.<name> in an expression; a tool step passes them on to its tool
Alias a fixed argument that is exactly "{$name}" a fixed argument that is exactly "{@name}"

A workflow run takes the values in variables when it starts (POST /workflows/{id}/runs), not in tool_variables; a value that does not fit returns 422 on variables.<name>. A tool step whose tool requires a variable the run lacks ends with the code variable_missing:<name>.

An alias gives any other tool a value per request without the model seeing it: a fixed argument "{@customer_id}" gives a search tool a filter per customer, for example.

Limits

What Limit
Names in tool_variables 64
One string 4,096 characters; 8,192 for a secret
The whole object, as compact JSON 64 KiB
call_variables on one version 64

A request with too many names, too many bytes in all, or a string over 8,192 characters is refused on tool_variables before any tool is looked at. A plain string between 4,097 and 8,192 characters is refused when the turn's tools are checked, on tool_variables.<name> naming the tool that declares it; for a name no tool declares, it is ignored. Your deployment may have set other limits.