For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Dashboard
User GuideDeveloper GuidesAPI Reference
User GuideDeveloper GuidesAPI Reference
  • Getting Started
    • Introduction
    • Authentication
    • Quickstart
  • Guides
    • Working with tools
    • Runtime tools
    • FPO templates
    • Importing products
  • Integrations
    • MCP servers
    • Runtype MCP server
Dashboard
LogoLogo
On this page
  • When to Use Runtime Tools
  • Basic Example
  • Tool Types
  • External Tools
  • Custom Tools (Code)
  • Flow Tools
  • Subagent Tools
  • Passing Secrets
  • Variable Substitution
  • Combining with Saved Tools
  • SDK Helper Functions
  • Client-side tools (WebMCP)
  • Restricting which client tools are admitted
  • Persona chat surfaces and WebMCP
  • Resuming from a browser (client-token path)
  • Limits
  • API Format
  • Best Practices
  • Next Steps
Guides

Runtime Tools

Was this page helpful?
Previous

FPO Templates

Next
Built with

Runtime tools let you define tools directly in dispatch requests, without saving them to your account first. This is useful for dynamic tool configurations, testing, and multi-tenant applications.

When to Use Runtime Tools

Use CaseDescription
TestingTry tool configurations before saving
Dynamic configsTool URLs/params vary per request
User-providedUsers supply their own tool definitions
One-off tasksTools needed for a single execution

Basic Example

TypeScript SDK
1import { FlowBuilder, RuntypeClient } from '@runtypelabs/sdk'
2
3const client = new RuntypeClient({
4 apiKey: process.env.RUNTYPE_API_KEY,
5})
6
7const result = await new FlowBuilder()
8 .createFlow({ name: 'Weather Agent' })
9 .prompt({
10 name: 'Agent',
11 model: 'gpt-5.4',
12 userPrompt: 'What is the weather in Tokyo?',
13 tools: {
14 runtimeTools: [
15 {
16 name: 'get_weather',
17 description: 'Get current weather for a city',
18 toolType: 'external',
19 parametersSchema: {
20 type: 'object',
21 properties: {
22 city: { type: 'string', description: 'City name' },
23 },
24 required: ['city'],
25 },
26 config: {
27 url: 'https://api.weather.com/v1/current?city={{city}}',
28 method: 'GET',
29 headers: {
30 Authorization: 'Bearer {{secrets.weather_key}}',
31 },
32 },
33 },
34 ],
35 },
36 })
37 .run(client, { streamResponse: true })
Python SDK
1runtime_tool = {
2 "name": "get_weather",
3 "description": "Get current weather for a city",
4 "toolType": "external",
5 "parametersSchema": {
6 "type": "object",
7 "properties": {
8 "city": {"type": "string", "description": "City name"}
9 },
10 "required": ["city"]
11 },
12 "config": {
13 "url": "https://api.weather.com/v1/current?city={{city}}",
14 "method": "GET",
15 "headers": {
16 "authorization": "Bearer {{secrets.weather_key}}"
17 }
18 }
19}
20
21for event in client.dispatch({
22 "flow": {
23 "steps": [{
24 "type": "prompt",
25 "config": {
26 "model": "gpt-5.4",
27 "userPrompt": "What is the weather in Tokyo?",
28 "tools": {
29 "runtimeTools": [runtime_tool]
30 }
31 }
32 }]
33 },
34 "secrets": {
35 "weather_key": os.environ["WEATHER_API_KEY"]
36 }
37}):
38 print(event)
cURL
$curl https://api.runtype.com/v1/dispatch \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "flow": {
> "steps": [{
> "type": "prompt",
> "config": {
> "model": "gpt-5.4",
> "userPrompt": "What is the weather in Tokyo?",
> "tools": {
> "runtimeTools": [{
> "name": "get_weather",
> "description": "Get current weather for a city",
> "toolType": "external",
> "parametersSchema": {
> "type": "object",
> "properties": {
> "city": {"type": "string"}
> },
> "required": ["city"]
> },
> "config": {
> "url": "https://api.weather.com/v1/current?city={{city}}",
> "method": "GET"
> }
> }]
> }
> }
> }]
> }
> }'

Tool Types

Runtime tools support the same types as saved tools:

External Tools

Call any HTTP API:

1{
2 name: 'fetch_user',
3 description: 'Fetch user details by ID',
4 toolType: 'external',
5 parametersSchema: {
6 type: 'object',
7 properties: {
8 userId: { type: 'string' }
9 },
10 required: ['userId']
11 },
12 config: {
13 url: 'https://api.example.com/users/{{userId}}',
14 method: 'GET',
15 headers: {
16 'Authorization': 'Bearer {{secrets.api_token}}'
17 }
18 }
19}

Custom Tools (Code)

Execute JavaScript in a secure sandbox:

1{
2 name: 'calculate_tax',
3 description: 'Calculate sales tax',
4 toolType: 'custom',
5 parametersSchema: {
6 type: 'object',
7 properties: {
8 amount: { type: 'number' },
9 rate: { type: 'number' }
10 },
11 required: ['amount', 'rate']
12 },
13 config: {
14 code: `
15 const tax = amount * (rate / 100);
16 return {
17 subtotal: amount,
18 tax: tax.toFixed(2),
19 total: (amount + tax).toFixed(2)
20 };
21 `,
22 timeout: 5000
23 }
24}

Flow Tools

Execute another Runtype flow as a tool:

1{
2 name: 'analyze_sentiment',
3 description: 'Run sentiment analysis on text',
4 toolType: 'flow',
5 parametersSchema: {
6 type: 'object',
7 properties: {
8 text: { type: 'string' }
9 },
10 required: ['text']
11 },
12 config: {
13 flowId: 'flow_abc123',
14 parameterMapping: { 'input_text': 'text' },
15 outputMapping: 'sentiment'
16 }
17}

Subagent Tools

Delegate a self-contained sub-task to a focused child agent. The child runs in its own context window — it cannot see the parent’s conversation — and returns only its final answer, which keeps the parent’s context clean for work that needs it. The child’s allowedTools are always intersected with the parent’s resolved tools, so a subagent can never use a tool the parent lacks.

1{
2 name: 'research_topic',
3 description: 'Research a topic and return a summary',
4 toolType: 'subagent',
5 parametersSchema: {
6 type: 'object',
7 properties: {
8 task: { type: 'string', description: 'What to research' }
9 },
10 required: ['task']
11 },
12 config: {
13 // Either point at a saved agent...
14 agentId: 'agent_abc123',
15 // ...or define the child inline with `agent: { ... }` (set exactly one).
16 allowedTools: ['builtin:exa', 'mcp:linear:*'],
17 maxTurns: 5, // default 5
18 timeoutMs: 300000, // default 5 minutes
19 outputFormat: 'text' // 'text' | 'json' | 'last_message'
20 }
21}

To let an agent decide what to delegate at runtime, add a subagentConfig to its tools configuration. Runtype synthesizes a spawn_subagent tool the model can call, choosing the task, the tools to grant (from toolPool), and an optional system prompt:

1tools: {
2 toolIds: ['builtin:exa', 'mcp:linear:create_issue'],
3 subagentConfig: {
4 toolPool: ['builtin:exa', 'mcp:linear:*'], // subset of parent's tools
5 maxSpawnsPerRun: 5, // default 5
6 maxTurnsLimit: 10, // hard cap, default 10
7 allowNesting: false // default false
8 }
9}

The parent emits a single tool bubble per subagent call — the child’s internal turns and tool calls do not leak into the parent’s stream. Each subagent call counts as one tool call against the parent’s maxToolCalls, and the child’s cost rolls up into the parent’s total.

Passing Secrets

Use the secrets field for sensitive values:

1const result = await client.dispatch({
2 flow: { ... },
3 secrets: {
4 api_token: process.env.EXTERNAL_API_KEY,
5 db_password: process.env.DB_PASSWORD
6 }
7})

Reference in tool config: {{secrets.api_token}}

Secrets are never logged, stored, or returned in API responses.

Variable Substitution

Tool configurations support template variables:

VariableSource
{{paramName}}Tool call parameter
{{secrets.keyName}}Dispatch secrets field
{{_record.field}}Current record data
{{_flow.id}}Flow metadata

Example:

1config: {
2 url: 'https://api.example.com/{{_record.type}}/{{id}}',
3 headers: {
4 'Authorization': 'Bearer {{secrets.token}}',
5 'X-User-Id': '{{_user.id}}'
6 }
7}

Combining with Saved Tools

Mix runtime tools with saved tools:

1tools: {
2 // Saved tools by ID
3 toolIds: [
4 'tool_abc123',
5 'mcp:notion:create_page'
6 ],
7 // Runtime tools
8 runtimeTools: [
9 { name: 'dynamic_tool', ... }
10 ],
11 maxToolCalls: 10
12}

SDK Helper Functions

The TypeScript SDK provides helper functions:

1import { createExternalTool } from '@runtypelabs/sdk'
2
3const weatherTool = createExternalTool({
4 name: 'get_weather',
5 description: 'Get weather for a city',
6 parametersSchema: {
7 type: 'object',
8 properties: {
9 city: { type: 'string' },
10 },
11 },
12 url: 'https://api.weather.com/current?city={{city}}',
13 method: 'GET',
14 headers: {
15 Authorization: 'Bearer {{secrets.key}}',
16 },
17})
18
19// Use in flow
20tools: {
21 runtimeTools: [weatherTool]
22}

Client-side tools (WebMCP)

Runtime tools (above) execute server-side — Runtype calls the URL, runs the sandboxed code, or invokes the nested flow. Client tools are the opposite: they execute in your own client (a browser page or your SDK process). You declare them per-dispatch in the top-level clientTools[] field; when the model calls one, Runtype pauses the run and streams an await event, you run the tool locally, and you resume with the result.

Each entry has a name, description, parametersSchema, and an origin:

  • origin: 'sdk' — a tool your SDK process executes. Admitted automatically.
  • origin: 'webmcp' — a tool registered by a browser page via WebMCP (document.modelContext). On the API-key /v1/dispatch path the caller holds a secret key and controls the page, so webmcp tools are admitted by default — their presence in clientTools[] is the opt-in.

Client tools merge into the model’s tool set with the precedence saved < runtimeTools < clientTools, so a later turn can override a saved tool of the same name without editing the flow.

The API-key /v1/dispatch examples below are the lower-level path. Prefer the Persona widget for browser-embedded chat. Use direct dispatch when you are building a non-Persona chat UI, running local tools from an SDK/native process, or proxying browser tool calls through your own server that safely holds the Runtype API key. Do not expose a Runtype API key in an untrusted browser page.

TypeScript SDK
1import { RuntypeClient } from '@runtypelabs/sdk'
2
3const client = new RuntypeClient({ apiKey: process.env.RUNTYPE_API_KEY })
4
5// scope: 'turn' snapshots your local tools into the dispatch envelope's
6// clientTools[] and drives the dispatch + resume loop for you, executing
7// each tool call locally.
8await client.runWithLocalTools(
9 {
10 flow: { id: 'flow_abc123' },
11 messages: [{ role: 'user', content: 'What time is it?' }],
12 },
13 {
14 get_time: {
15 description: 'Return the current ISO time',
16 parametersSchema: { type: 'object', properties: {} },
17 execute: async () => new Date().toISOString(),
18 },
19 },
20 { scope: 'turn' }
21)
cURL
$curl -X POST https://api.runtype.com/v1/dispatch \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "agent": { "id": "agent_abc123" },
> "messages": [{ "role": "user", "content": "Search the catalog" }],
> "clientTools": [
> {
> "name": "search",
> "description": "Search the catalog",
> "parametersSchema": { "type": "object", "properties": {} },
> "origin": "webmcp"
> }
> ]
> }'

Restricting which client tools are admitted

By default every submitted client tool is admitted. To narrow which origin: 'webmcp' tools are accepted, pass an optional clientToolsPolicy with an allowlist of glob patterns (an exact name, or a single trailing *). SDK-origin tools are never gated by the allowlist.

1{
2 "agent": { "id": "agent_abc123" },
3 "clientTools": [
4 {
5 "name": "search_products",
6 "description": "...",
7 "parametersSchema": { "type": "object" },
8 "origin": "webmcp"
9 },
10 {
11 "name": "delete_account",
12 "description": "...",
13 "parametersSchema": { "type": "object" },
14 "origin": "webmcp"
15 }
16 ],
17 "clientToolsPolicy": { "allowlist": ["search_*"] }
18}

Here only search_products is admitted; delete_account is dropped. Omit clientToolsPolicy entirely to admit every webmcp tool.

clientToolsPolicy applies only to the raw API-key dispatch request. It is a global self-restriction over bare tool names, not an origin-scoped surface policy. Persona and other client-token chat surfaces use behavior.webmcp.allowlist instead.

Persona chat surfaces and WebMCP

The examples above use the API-key /v1/dispatch path. Persona chat widgets use the public client-token path (/v1/client/chat), so WebMCP admission is controlled by the chat surface’s behavior.webmcp policy instead of only the request payload.

1{
2 "type": "chat",
3 "webmcp": {
4 "enabled": true,
5 "allowlist": [
6 { "origin": "https://store.example.com", "tools": ["search_*", "get_cart"] },
7 { "origin": "*", "tools": ["read_page"] }
8 ]
9 }
10}

On a Persona surface:

  • The embedding page registers tools with document.modelContext.registerTool(...).
  • Persona snapshots those tools per turn and forwards them as clientTools[] with origin: 'webmcp'.
  • Runtype applies a server-owned webmcp: prefix before the model sees the tool.
  • behavior.webmcp.enabled must be true, and any allowlist rules must match the validated request origin and the bare tool name.
  • The client token’s allowedOrigins remains the enforced CORS boundary for which browser origins may call the surface.

The dashboard WebMCP tab shows page tools and origins observed on the chat surface over the trailing 7 days. You can promote a discovered tool into an origin-scoped allowlist rule from there.

Resuming from a browser (client-token path)

The examples above use the API-key /v1/dispatch path, where you complete a paused tool call by posting the result back to /v1/dispatch/resume. That route requires a secret API key with DISPATCH:* scope, so a browser page (the embedded Persona widget, or your own client-token integration) cannot use it.

Browsers authenticate with a client token (ct_live_…) against the /v1/client/* routes instead. To complete a paused local-tool turn, resume via /v1/client/resume — the session-authenticated sibling of /v1/dispatch/resume:

cURL
$curl -X POST https://api.runtype.com/v1/client/resume \
> -H "Content-Type: application/json" \
> -d '{
> "sessionId": "cs_...",
> "executionId": "exec_...",
> "toolOutputs": {
> "call_abc123": { "result": "..." }
> },
> "streamResponse": true
> }'
  • sessionId is the session from /v1/client/init; it authenticates the request (active session, active client token, matching Origin).
  • executionId comes from the await event of the paused run. It scopes the resume to your session’s user — a client token can only resume its own user’s executions.
  • toolOutputs is keyed by the per-call toolCallId from the await event (preferred — this is what makes parallel calls of the same tool addressable), or by tool name (legacy, single-call only).
  • Resume does not consume additional execution quota — the turn was already counted when it started.

You do not re-send the clientTools[] definitions on resume; Runtype already has them from the originating /v1/client/chat dispatch.

The Persona chat widget drives this /v1/client/resume round-trip for you when it runs in client-token mode — you only implement the local tools themselves. The endpoint is documented here for custom client-token integrations.

Limits

LimitValue
Total runtime tools per request50
Client tools per dispatch50
Client tools payload64 KB
MCP servers per step5
Custom tool timeout30 seconds

API Format

The API uses camelCase for all field names:

1{
2 "tools": {
3 "runtimeTools": [{
4 "name": "myTool",
5 "toolType": "external",
6 "parametersSchema": { ... },
7 "config": { ... }
8 }]
9 }
10}

Best Practices

Save frequently-used tools

If you use the same runtime tool repeatedly, save it to your account via the API or dashboard for cleaner code.

Use lowercase headers

Use lowercase header names (e.g., authorization not Authorization) to avoid issues with automatic case conversion.

Test before production

Use runtime tools to test configurations, then save working tools for production use.

Validate schemas

Ensure parametersSchema is valid JSON Schema. Invalid schemas cause tool calls to fail.

Next Steps

Working with Tools

Complete tools overview

MCP Servers

Runtime MCP server configuration

Tools API

Tools endpoint reference

Building Flows

Create flows with tools