# Tools

> Tool addresses, discovery, schema inspection, invocation paths, and how tools are produced per connection across CLI, HTTP API, and MCP surfaces.

- Repository: RhysSullivan/executor
- GitHub: https://github.com/RhysSullivan/executor
- Human docs: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052
- Complete Markdown: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052/llms-full.txt

## Source Files

- `packages/core/api/src/tools/api.ts`
- `packages/core/api/src/handlers/tools.ts`
- `packages/core/sdk/src/executor.ts`
- `packages/core/sdk/src/ids.ts`
- `apps/cli/src/main.ts`
- `packages/hosts/mcp/src/tool-server.ts`

---

---
title: "Tools"
description: "Tool addresses, discovery, schema inspection, invocation paths, and how tools are produced per connection across CLI, HTTP API, and MCP surfaces."
---

Executor exposes a per-connection tool catalog: each saved [connection](/connections) produces a persisted set of callable tools, keyed by a dotted address. Discovery surfaces (`tools.list`, sandbox `tools.search`, CLI `tools`) return metadata; `tools.schema` returns full JSON Schema and TypeScript previews; invocation routes through `executor.execute` (SDK) or code-mode `POST /executions` (HTTP, CLI, MCP).

## Tool addresses

Every dynamic tool has a branded `ToolAddress` with five logical segments:

```
tools.<integration>.<owner>.<connection>.<tool>
```

| Segment | Type | Meaning |
| --- | --- | --- |
| `tools` | prefix | Fixed namespace root for connection-scoped API tools |
| `<integration>` | `IntegrationSlug` | Catalog slug (e.g. `github`, `vercel`) |
| `<owner>` | `Owner` | `org` (tenant-shared) or `user` (acting member) |
| `<connection>` | `ConnectionName` | Connection name under that integration and owner |
| `<tool>` | `ToolName` | Tool name; may itself contain dots |

The `<tool>` segment is everything after the fourth dot. OpenAPI operations with dotted paths (e.g. `aliases.deleteAlias`) address naturally as `tools.github.org.main.aliases.deleteAlias`.

Connection handles omit the final segment:

```
tools.<integration>.<owner>.<connection>
```

Static tools contributed by plugins use a fully qualified id (`fqid`) instead of the five-segment form. Core configuration tools mount under `executor`, for example `executor.coreTools.connections.list`.

<Note>
Sandbox code strips the leading `tools.` prefix. A call like `tools.github.org.main.getRepo(args)` maps back to the full address `tools.github.org.main.getRepo` at invoke time.
</Note>

## How tools are produced

Tools are **persisted per connection**, not resolved live on every list. Production follows this lifecycle:

```mermaid
flowchart TD
  subgraph triggers [Production triggers]
    A[connections.create]
    B[connections.refresh]
    C[oauth.complete]
    D[Stale catalog sync on tools.list]
  end
  subgraph plugin [Plugin layer]
    E[resolveTools]
  end
  subgraph storage [Storage]
    F[(tool rows)]
    G[(definition rows)]
  end
  A --> E
  B --> E
  C --> E
  D --> E
  E --> F
  E --> G
```

When a plugin implements `resolveTools`, the executor:

1. Calls the plugin with the integration config, connection ref, and credential resolvers.
2. Receives `ToolDef` entries (name, description, schemas, annotations) plus shared `$defs`.
3. Deletes prior rows for that connection, writes new `tool` and `definition` rows, and stamps `tools_synced_at`.

OpenAPI plugins derive tools from the integration spec. MCP plugins dial the connection's upstream server. Connections without bound credentials (non-OAuth, non-`none` template, empty `item_ids`) produce zero tools. Plugins without `resolveTools` clear any existing rows and return an empty catalog.

Static tools register at executor startup via each plugin's `staticSources` and live in an in-memory map keyed by `fqid`. They have no backing connection row but carry owner/connection metadata for UI grouping.

## Discovery surfaces

Executor separates lightweight catalog reads from schema-heavy inspection.

| Surface | Path | Returns | Schema bytes |
| --- | --- | --- | --- |
| `tools.list` | SDK / `GET /tools` | Metadata per tool | Omitted (projected columns) |
| `tools.schema` | SDK / `GET /tools/schema` | Full `ToolSchemaView` | Included |
| `tools.search` | Sandbox builtin | Ranked, paginated paths | Via `describe.tool` |
| `tools.describe.tool` | Sandbox builtin | Compact TypeScript shapes | Preview strings |
| `tools.executor.sources.list` | Sandbox builtin | Integrations with `toolCount` | No |

### `tools.list` filters

<ParamField query="integration" type="IntegrationSlug">
Filter to one integration slug.
</ParamField>

<ParamField query="owner" type="Owner">
Filter to `org` or `user`. Omit to merge both owner partitions.
</ParamField>

<ParamField query="connection" type="ConnectionName">
Filter to one connection name.
</ParamField>

<ParamField query="query" type="string">
Case-insensitive substring match against `name` or `description`.
</ParamField>

<ParamField query="includeAnnotations" type="string">
Pass `"true"` to resolve plugin-derived annotations. HTTP query param; handler coerces from string.
</ParamField>

<ParamField query="includeBlocked" type="string">
Pass `"false"` to omit blocked tools. Defaults to including blocked tools on the HTTP surface; SDK defaults to `false`.
</ParamField>

<ResponseField name="address" type="ToolAddress">
Full callable address.
</ResponseField>

<ResponseField name="owner" type="Owner">
`org` or `user`.
</ResponseField>

<ResponseField name="integration" type="IntegrationSlug">
Integration slug.
</ResponseField>

<ResponseField name="connection" type="ConnectionName">
Connection name.
</ResponseField>

<ResponseField name="name" type="string">
Final tool name segment.
</ResponseField>

<ResponseField name="pluginId" type="string">
Owning plugin id.
</ResponseField>

<ResponseField name="description" type="string">
Human-readable description.
</ResponseField>

<ResponseField name="mayElicit" type="boolean">
Plugin annotation: tool may trigger elicitation during invoke.
</ResponseField>

<ResponseField name="requiresApproval" type="boolean">
Plugin-derived default approval annotation. Surfaces as the default policy when no user `tool_policy` rule matches.
</ResponseField>

<ResponseField name="approvalDescription" type="string">
Custom approval prompt text from the plugin.
</ResponseField>

<ResponseField name="static" type="boolean">
True for plugin-contributed static tools (e.g. core configuration tools).
</ResponseField>

On each list read, the executor also runs a best-effort stale-catalog sync: connections whose `tools_synced_at` predates the integration's `config_revised_at` are re-produced.

### `tools.schema`

Returns a `ToolSchemaView` for one address:

<ResponseField name="inputSchema" type="object">
JSON Schema root for tool input.
</ResponseField>

<ResponseField name="outputSchema" type="object">
JSON Schema root for tool output.
</ResponseField>

<ResponseField name="schemaDefinitions" type="object">
Shared `$defs` referenced by the tool's schemas.
</ResponseField>

<ResponseField name="inputTypeScript" type="string">
Compact TypeScript type string for input.
</ResponseField>

<ResponseField name="outputTypeScript" type="string">
Compact TypeScript type string for output.
</ResponseField>

<ResponseField name="typeScriptDefinitions" type="object">
Named TypeScript definitions referenced by the preview strings.
</ResponseField>

Returns `null` (HTTP 404 `ToolNotFoundError`) when the address does not resolve.

## HTTP API

:::endpoint GET /tools
List persisted tool metadata. Mirrors `executor.tools.list`. Query params carry the branded address as plain strings because dotted addresses are not path segments.
:::

:::endpoint GET /tools/schema
Return the full schema view for one tool. Requires `address` query param (`ToolAddress`).
:::

Tool invocation has no direct HTTP route. Callers post JavaScript/TypeScript to the executions group:

:::endpoint POST /executions
Execute code in the QuickJS sandbox. Payload: `{ "code": "..." }`. Returns `completed` or `paused` status with text and structured fields.
:::

:::endpoint POST /executions/:executionId/resume
Resume a paused execution. Payload: `{ "action": "accept" | "decline" | "cancel", "content?: unknown }`.
:::

:::endpoint GET /executions/:executionId
Fetch paused execution info for browser approval flows.
:::

<RequestExample>
```bash title="List GitHub org tools"
curl "$EXECUTOR_ORIGIN/tools?integration=github&owner=org"
```
</RequestExample>

<RequestExample>
```bash title="Inspect schema"
curl "$EXECUTOR_ORIGIN/tools/schema?address=tools.github.org.main.getRepo"
```
</RequestExample>

## Invocation paths

All surfaces converge on `executor.execute(address, args)` inside the SDK. External clients reach it through code-mode execution.

```mermaid
sequenceDiagram
  participant Client as CLI / HTTP / MCP
  participant Exec as Execution engine
  participant SDK as executor.execute
  participant Plugin as plugin.invokeTool
  participant Upstream as Upstream API

  Client->>Exec: POST /executions { code }
  Exec->>Exec: QuickJS sandbox
  Exec->>SDK: tools.<path>(args)
  SDK->>SDK: Policy + approval gate
  SDK->>Plugin: invokeTool(toolRow, credential, args)
  Plugin->>Upstream: Authenticated request
  Upstream-->>Plugin: Response
  Plugin-->>SDK: ToolResult
  SDK-->>Exec: Result envelope
  Exec-->>Client: completed / paused
```

### SDK

```typescript title="Direct invoke"
const result = await executor.execute(
  ToolAddress.make("tools.github.org.main.getRepo"),
  { owner: "octocat", repo: "hello-world" },
);
```

```typescript title="List and inspect"
const tools = await executor.tools.list({ integration: "github", owner: "org" });
const schema = await executor.tools.schema(tools[0].address);
```

The `Executor` surface exposes `tools.list` and `tools.schema` as read methods. Invoke uses top-level `execute`.

### CLI

| Command | Behavior |
| --- | --- |
| `executor call <path> [args]` | Builds sandbox code calling `tools.<path>(args)`, posts to `/executions` |
| `executor call --help <prefix>` | Browse namespace children and print schema help |
| `executor tools search <query>` | Runs `tools.search({ query, limit })` in the sandbox |
| `executor tools describe <path>` | Runs `tools.describe.tool({ path })` |
| `executor tools sources` | Runs `tools.executor.sources.list({})` with optional query filter |
| `executor resume --execution-id <id>` | Posts to `/executions/:id/resume` |

<CodeGroup>
```bash title="Invoke a tool"
executor call github.org.main.getRepo '{"owner":"octocat","repo":"hello-world"}'
```

```bash title="Search the catalog"
executor tools search "github issues" --namespace github --limit 12
```

```bash title="Describe schema"
executor tools describe github.org.main.getRepo
```
</CodeGroup>

### MCP

The MCP host (`executor mcp`) registers two protocol tools:

| MCP tool | Purpose |
| --- | --- |
| `execute` | Run sandbox code with access to the `tools` proxy |
| `resume` | Resume paused executions (model-managed or browser-approval mode) |

Upstream integration tools are **not** registered as individual MCP tools. Agents discover and call them through code inside `execute`, using the lazy `tools` proxy (`tools.search`, `tools.describe.tool`, then `tools.<path>(args)`). The `execute` tool description is built dynamically from the connection inventory.

<Info>
MCP elicitation modes (`model`, `browser`, `native`) control how approval pauses surface to the client. See [Executions](/executions) and [MCP proxy](/mcp-proxy) for the full pause/resume flow.
</Info>

### Sandbox builtins

Built-in discovery tools live outside the per-connection catalog:

| Path | Input | Output |
| --- | --- | --- |
| `search` | `{ query, namespace?, limit?, offset? }` | `{ items, total, hasMore, nextOffset }` |
| `describe.tool` | `{ path }` | TypeScript shapes, or `error: { code: "tool_not_found", suggestions }` |
| `executor.sources.list` | `{ query?, limit?, offset? }` | Integrations with `toolCount` |
| `executor.coreTools.*` | Varies | Configuration operations (connections, integrations, policies, OAuth) |

The `tools` object is a lazy proxy. `Object.keys(tools)` does not enumerate the catalog; use `tools.search()` or `tools.executor.coreTools.connections.list({})`.

## Policy effects

Before invoke, the executor resolves the effective [policy](/policies) for the tool:

1. Match owner-scoped `tool_policy` rows by pattern (`<integration>.<owner>.<connection>.<tool>`).
2. Fall back to plugin `requiresApproval` annotation as the default.

| Policy action | List behavior | Invoke behavior |
| --- | --- | --- |
| `allow` | Visible (unless `includeBlocked: false` and overridden) | Proceeds |
| `require_approval` | Visible | Pauses for approval elicitation |
| `block` | Omitted by default (`includeBlocked: false`) | `ToolBlockedError` |

Agent-facing surfaces (MCP, default `tools.list`) omit blocked tools silently.

## Invoke result shape

Sandbox tool calls return a uniform envelope:

```typescript
{ ok: true; data: T; http?: { status: number; headers: Record<string, string> } }
| { ok: false; error: { code: string; message: string; status?: number; details?: unknown; retryable?: boolean } }
```

HTTP-backed OpenAPI tools attach transport metadata in `http` beside `data`. File outputs use `ToolFile` values (`{ _tag: "ToolFile", name?, mimeType, encoding: "base64", data, byteLength }`).

## End-to-end workflow

<Steps>
<Step title="Add integration and connection">
Register an [integration](/integrations), then create a [connection](/connections) with credentials. Tool production runs automatically on create.
</Step>

<Step title="Verify the catalog">
```bash
executor tools sources
executor tools search "your intent" --limit 12
```
Confirm the integration appears with a non-zero `toolCount` and search returns ranked paths.
</Step>

<Step title="Inspect schema">
```bash
executor tools describe <integration>.<owner>.<connection>.<tool>
```
Or `GET /tools/schema?address=tools.<integration>.<owner>.<connection>.<tool>`.
</Step>

<Step title="Call the tool">
```bash
executor call <integration>.<owner>.<connection>.<tool> '{"key":"value"}'
```
If execution pauses for approval or OAuth, use `executor resume --execution-id <id>`.
</Step>
</Steps>

<Warning>
A `ToolNotFoundError` includes up to five suggestions from the same connection. Use the suggested path rather than retrying a guessed address.
</Warning>

## Related pages

<CardGroup>
<Card title="Connections" href="/connections">
Owner-scoped credentials that produce per-connection tool catalogs.
</Card>

<Card title="Integrations" href="/integrations">
Tenant-level catalog identities and plugin `resolveTools` sources.
</Card>

<Card title="Policies" href="/policies">
Per-tool allow, require-approval, and block rules that gate list and invoke.
</Card>

<Card title="Executions" href="/executions">
Code-mode sandbox, pause/resume states, and elicitation handling.
</Card>

<Card title="MCP proxy" href="/mcp-proxy">
Single MCP endpoint exposing the unified catalog through `execute`.
</Card>

<Card title="HTTP API reference" href="/http-api-reference">
Full route groups including `tools` and `executions`.
</Card>

<Card title="CLI reference" href="/cli-reference">
`call`, `tools`, `resume`, and server profile flags.
</Card>

<Card title="SDK reference" href="/sdk-reference">
`createExecutor`, `tools.list`, `tools.schema`, and `execute`.
</Card>
</CardGroup>
