# Embed with the SDK

> Compose `createExecutor` with plugins and credential providers, register integrations, create connections, list and invoke tools, and shut down cleanly in application code.

- 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/sdk/src/executor.ts`
- `packages/core/sdk/src/promise.ts`
- `packages/core/sdk/package.json`
- `examples/docs-sdk-quickstart/src/main.ts`
- `examples/all-plugins/src/main.ts`
- `packages/plugins/openapi/README.md`

---

---
title: "Embed with the SDK"
description: "Compose `createExecutor` with plugins and credential providers, register integrations, create connections, list and invoke tools, and shut down cleanly in application code."
---

`@executor-js/sdk` exposes `createExecutor`, a composable runtime that wires protocol plugins, credential providers, and a FumaDB-backed catalog into one typed `Executor` object. Application code registers integrations through plugin extensions (`executor.openapi.addSpec`, `executor.graphql.addIntegration`, `executor.mcp.addServer`), saves credentials as connections, lists persisted tools per connection, and invokes them with `executor.execute`. The default published import is Promise-based; the Effect entry at `@executor-js/sdk/core` is for plugin authors and hosts that already run on Effect.

## Install

```sh
bun add @executor-js/sdk @executor-js/plugin-openapi
```

Add additional `@executor-js/plugin-*` packages for GraphQL, MCP, keychain, file-secrets, 1Password, Google bundles, or WorkOS Vault as needed. See the [plugin catalog](/plugin-catalog) for the full set.

## Import paths

<Tabs>
<Tab title="Promise (recommended)">

Use the root or `/promise` subpath. Every Effect-returning method is promisified; no Effect dependency required in application code.

```ts
import { createExecutor, ProviderKey, ProviderItemId } from "@executor-js/sdk";
import { openApiPlugin, variable } from "@executor-js/plugin-openapi";
```

`examples/docs-sdk-quickstart` is the canonical snippet source for this surface.

</Tab>
<Tab title="Effect">

Use `@executor-js/sdk/core` (or `@executor-js/sdk` in the monorepo dev tree) when the host already runs on Effect. Plugin factories and credential provider `get`/`set` methods return `Effect`s.

```ts
import { Effect } from "effect";
import { createExecutor, Tenant } from "@executor-js/sdk/core";
import { openApiPlugin } from "@executor-js/plugin-openapi/core";

const program = Effect.gen(function* () {
  const executor = yield* createExecutor({ tenant: Tenant.make("my-tenant"), /* ... */ });
  yield* executor.close();
});
```

`examples/all-plugins` exercises this shape end to end.

</Tab>
</Tabs>

<Note>
Plugin READMEs that reference `executor.tools.invoke`, `executor.scopes`, or `executor.secrets` describe the pre-v2 surface. The v2 SDK invokes tools with `executor.execute(address, args)` and stores credentials as connections routed through credential providers.
</Note>

## Architecture

```mermaid
flowchart TB
  subgraph app [Application]
    CE["createExecutor(config)"]
    EX["Executor"]
  end
  subgraph plugins [Protocol plugins]
    OA["executor.openapi"]
    GQL["executor.graphql"]
    MCP["executor.mcp"]
  end
  subgraph providers [Credential providers]
    MEM["inline memory provider"]
    KC["keychain / file-secrets / 1Password"]
  end
  subgraph store [Persistence]
    DB["FumaDB (default: in-memory)"]
  end
  CE --> EX
  EX --> plugins
  EX --> providers
  EX --> DB
  OA -->|"addSpec"| INT["integrations catalog"]
  INT -->|"connections.create"| CON["connections + tool rows"]
  CON -->|"tools.list"| TOOLS["addressable tools"]
  TOOLS -->|"execute"| INV["plugin.invokeTool"]
  providers --> INV
```

Integrations are tenant-level catalog entries. Connections bind a credential to one integration and produce that integration's tools. Tool addresses follow `tools.<integration>.<owner>.<connection>.<tool>`; the tool segment may contain dots (for example `tools.inventory.org.default.items__list`).

## Compose `createExecutor`

<Steps>
<Step title="Pick plugins and providers">

Pass protocol plugins in `plugins` and optional inline credential providers in `providers`. Plugins may also contribute providers through `plugin.credentialProviders`; config-level providers register first and win as the default writable store.

</Step>
<Step title="Set elicitation policy">

`onElicitation` is required. Pass `"accept-all"` for scripts, tests, and non-interactive hosts. Pass a handler function for interactive hosts that must answer approval prompts and mid-call form or URL elicitations.

</Step>
<Step title="Await the executor">

`createExecutor` returns a Promise (Promise surface) or `Effect<Executor, StorageFailure>` (Effect surface). Plugin extension methods are merged onto the returned object as `executor[pluginId]` (for example `executor.openapi`).

</Step>
</Steps>

### `ExecutorConfig` fields

| Field | Default | Role |
| --- | --- | --- |
| `tenant` | `"default-tenant"` | Org/workspace partition for `owner: "org"` rows |
| `subject` | omitted | Acting member; required to target `owner: "user"` |
| `plugins` | `[]` | Protocol and provider plugins to load |
| `providers` | `[]` | Inline credential providers (registered before plugin providers) |
| `db` | in-memory FumaDB | Persistent store factory or handle; omit for ephemeral scripts |
| `onElicitation` | required | `"accept-all"` or `(ctx) => ElicitationResponse` |
| `redirectUri` | none | OAuth callback URL; required for interactive OAuth (no localhost default) |
| `coreTools` | disabled | Built-in agent-facing static tools over integrations/connections/policies |
| `blobs` | FumaDB blob table | Object-store backend for large plugin blobs |
| `httpClientLayer` / `fetch` | platform default | Outbound HTTP for plugins |

<ParamField body="tenant" type="string">
Org or workspace id. Branded as `Tenant` on the Effect surface.
</ParamField>

<ParamField body="subject" type="string">
Optional member id. Omit for a pure-org executor with no `owner: "user"` rows.
</ParamField>

<ParamField body="plugins" type="readonly AnyPlugin[]" required>
Plugin factories such as `openApiPlugin()`, `graphqlPlugin()`, `mcpPlugin()`, `keychainPlugin()`, `fileSecretsPlugin()`.
</ParamField>

<ParamField body="providers" type="readonly CredentialProvider[]">
Writable stores for inline credential values. A writable provider is required before `connections.create({ value })` can persist a pasted secret.
</ParamField>

<ParamField body="onElicitation" type='"accept-all" | ElicitationHandler' required>
How the executor answers approval prompts and tool elicitations. Overridable per call through `execute` options.
</ParamField>

<ParamField body="db" type="FumaDb | ExecutorDb | factory">
Supply a SQLite/LibSQL handle for durable hosts (as `apps/local` does). Omit to use the SDK's ephemeral in-memory adapter.
</ParamField>

<ParamField body="redirectUri" type="string">
OAuth redirect such as `${webBaseUrl}/oauth/callback`. Omit only when the executor never runs interactive OAuth.
</ParamField>

<ParamField body="coreTools" type="object">
Enable `core-tools` static tools. Set `webBaseUrl`, optional `orgSlug`, and `includeProviders` for agent-facing configuration helpers.
</ParamField>

### Minimal composition

<CodeGroup>
```ts title="Promise — in-memory script"
import { createExecutor, ProviderKey, ProviderItemId, type CredentialProvider } from "@executor-js/sdk";
import { Effect } from "effect";
import { openApiPlugin } from "@executor-js/plugin-openapi";

const memory = new Map<string, string>();
const memoryProvider: CredentialProvider = {
  key: ProviderKey.make("memory"),
  writable: true,
  get: (id) => Effect.sync(() => memory.get(String(id)) ?? null),
  set: (id, value) => Effect.sync(() => { memory.set(String(id), value); }),
};

const executor = await createExecutor({
  plugins: [openApiPlugin()],
  providers: [memoryProvider],
  onElicitation: "accept-all",
});
```

```ts title="Effect — with tenant"
import { Effect } from "effect";
import { createExecutor, Tenant } from "@executor-js/sdk/core";
import { openApiPlugin } from "@executor-js/plugin-openapi/core";

const executor = yield* createExecutor({
  tenant: Tenant.make("example-tenant"),
  plugins: [openApiPlugin()],
  onElicitation: "accept-all",
});
```
</CodeGroup>

## Credential providers

A connection's value lives in a registered `CredentialProvider`, not a separate secret store. Providers expose `key`, `writable`, `get`, and optional `set`, `delete`, and `list`. Plugin-provided providers (keychain, file-secrets, 1Password, WorkOS Vault) register at startup; inline providers in `config.providers` register first and become the default writable store.

```ts
const keys = await executor.providers.list();
const entries = await executor.providers.items(ProviderKey.make("memory"));
```

For production hosts, swap the in-memory map for a durable plugin provider. See [Configure credentials](/configure-credentials).

## Register integrations

Each protocol plugin exposes an extension object on the executor. Registration methods write to the integrations catalog and declare auth templates; they do not create callable tools until a connection exists.

| Plugin | Extension | Registration method |
| --- | --- | --- |
| OpenAPI | `executor.openapi` | `addSpec({ slug, spec, baseUrl, authenticationTemplate, ... })` |
| GraphQL | `executor.graphql` | `addIntegration({ slug, endpoint, introspectionJson, ... })` |
| MCP | `executor.mcp` | `addServer({ slug, transport, endpoint, ... })` |
| Google | `executor.google` | `addBundle({ slug, urls, ... })` |

OpenAPI auth templates declare where credentials render on each request. Use `variable("token")` as the slot the resolved credential fills:

```ts
import { variable } from "@executor-js/plugin-openapi";

await executor.openapi.addSpec({
  slug: "inventory",
  description: "Inventory API",
  baseUrl: "https://inventory.example.com",
  spec: { kind: "blob", value: JSON.stringify(openApiDocument) },
  authenticationTemplate: [
    {
      slug: "apiKey",
      type: "apiKey",
      headers: { "X-API-Key": [variable("token")] },
    },
  ],
});
```

`executor.integrations.list()`, `executor.integrations.get(slug)`, `executor.integrations.detect(url)`, and `executor.integrations.remove(slug)` operate on the shared catalog regardless of which plugin registered an entry.

## Create connections

A connection is the saved credential for one integration, scoped by `(owner, integration, name)`. Creating a connection with an inline `value` writes to the default writable provider and produces the integration's per-connection tool rows.

<ParamField body="owner" type='"org" | "user"' required>
`"org"` for workspace-scoped credentials; `"user"` requires `subject` on the executor.
</ParamField>

<ParamField body="name" type="ConnectionName" required>
Connection name within the owner and integration (commonly `"default"`).
</ParamField>

<ParamField body="integration" type="IntegrationSlug" required>
Slug of the registered integration.
</ParamField>

<ParamField body="template" type="AuthTemplateSlug" required>
Which auth method from the integration's `authenticationTemplate` to apply.
</ParamField>

<ParamField body="value" type="string">
Inline credential for single-input templates. Written to the default writable provider.
</ParamField>

```ts
const connection = await executor.connections.create({
  owner: "org",
  name: "default",
  integration: "inventory",
  template: "apiKey",
  value: "inventory-api-key",
});
```

<ResponseField name="address" type="ConnectionAddress">
Callable prefix `tools.<integration>.<owner>.<connection>`. Append `.<tool>` for a specific tool address.
</ResponseField>

For OAuth-backed integrations, use `executor.oauth.start` and `executor.oauth.complete` instead of inline `value`. Hosts that serve OAuth must pass `redirectUri` to `createExecutor`.

External provider references use `from: { provider, id }` or multi-input `values` / `inputs` maps. See [Connections](/connections) for the full identity model.

## List and inspect tools

Tools are persisted per connection at create/refresh time. `tools.list` reads stored rows; it does not dial upstream APIs on every call.

```ts
const tools = await executor.tools.list({ integration: "inventory" });

for (const tool of tools) {
  console.log(`${tool.address}: ${tool.description}`);
}
```

### `ToolListFilter`

| Field | Purpose |
| --- | --- |
| `integration` | Narrow to one integration slug |
| `owner` | Filter by `org` or `user` |
| `connection` | Filter by connection name |
| `query` | Case-insensitive substring on name or description |
| `includeAnnotations` | Resolve plugin-derived annotations (default `true`) |
| `includeBlocked` | Include tools with effective `block` policy (default `false`) |

Inspect input schemas and generated TypeScript previews:

```ts
const schema = await executor.tools.schema(tools[0].address);
console.log(schema?.inputTypeScript ?? "No input required");
```

`ToolSchemaView` includes `inputSchema`, `outputSchema`, `schemaDefinitions` for `$ref` resolution, and optional `inputTypeScript` / `outputTypeScript` strings.

## Invoke tools

Call `executor.execute` with a `ToolAddress` and arguments matching the tool's input schema. This is the v2 invoke path: the executor resolves the connection credential, enforces effective policy and approval elicitation, then dispatches to the owning plugin's `invokeTool`.

```ts
const result = await executor.execute(
  "tools.inventory.org.default.items__list",
  {},
);
```

<ParamField body="address" type="ToolAddress" required>
Full five-segment address from `tools.list`, or a static tool fqid for core-tools entries.
</ParamField>

<ParamField body="args" type="unknown" required>
JSON-shaped arguments matching the tool input schema.
</ParamField>

<ParamField body="options.onElicitation" type="OnElicitation">
Per-call override of the executor-level elicitation handler.
</ParamField>

### Invoke-time behavior

| Stage | Behavior |
| --- | --- |
| Policy resolution | `block` raises `ToolBlockedError`; `require_approval` or `annotations.requiresApproval` triggers an approval elicitation |
| Credential resolution | Connection value loaded from the registered provider and rendered through the auth template |
| Plugin dispatch | Owning plugin's `invokeTool` builds and sends the upstream request |
| Elicitation | Mid-call form or URL requests route through `onElicitation`; `"accept-all"` auto-accepts |

Typed failures on the `execute` channel include `ToolNotFoundError`, `ToolInvocationError`, `ToolBlockedError`, `ConnectionNotFoundError`, `CredentialResolutionError`, and `ElicitationDeclinedError`. Promise callers receive these as rejected Promise values.

<Warning>
POST and DELETE OpenAPI operations commonly carry `requiresApproval: true` in default annotations. With `onElicitation: "accept-all"`, approval prompts are auto-accepted. Interactive hosts should supply a handler that surfaces approval UI.
</Warning>

## Policies

Owner-scoped tool policies are available directly on the embedded executor:

```ts
await executor.policies.create({ owner: "org", pattern: "tools.inventory.*", action: "require_approval" });
const effective = await executor.policies.resolve(toolAddress);
```

See [Policies](/policies) and [Manage policies](/manage-policies) for precedence and approval flows.

## Persist across restarts

Omitting `db` uses an in-memory FumaDB adapter suitable for scripts and tests. Durable hosts pass a database factory or handle with the executor-owned table set from `collectTables()`. The local daemon (`apps/local`) wires SQLite, runs data migrations, sets `redirectUri` from `EXECUTOR_WEB_BASE_URL`, and enables `coreTools`.

```ts
const executor = await createExecutor({
  tenant: "my-workspace",
  subject: "user-123",
  db: sqliteHandle,
  plugins,
  onElicitation: interactiveHandler,
  redirectUri: new URL("/api/oauth/callback", webBaseUrl).toString(),
  coreTools: { webBaseUrl },
});
```

## Shut down

Call `executor.close()` when the process or request scope ends. `close` runs every plugin's `close` hook (for example MCP connection pool teardown) and invokes the database `close` callback when supplied.

```ts
await executor.close();
```

Hosts that wrap the executor in a managed runtime should close on dispose, as `apps/local` does in `createExecutorHandle`.

## End-to-end example

The `examples/docs-sdk-quickstart` package walks the full embed flow in under 150 lines: in-memory provider, `openApiPlugin`, `addSpec`, `connections.create`, `tools.list`, `tools.schema`, and `close`. Run it with:

```sh
cd examples/docs-sdk-quickstart && bun run start
```

For every published plugin wired together, see `examples/all-plugins` and the [SDK quickstart example](/sdk-quickstart-example) page.

## Related pages

<CardGroup>
<Card title="SDK reference" href="/sdk-reference">
`createExecutor` exports, typed IDs, plugin extension typing, promise-mode entry points, and error tags.
</Card>
<Card title="SDK quickstart example" href="/sdk-quickstart-example">
Line-by-line walkthrough of `examples/docs-sdk-quickstart`.
</Card>
<Card title="Plugin catalog" href="/plugin-catalog">
Published protocol and provider plugins and how `examples/all-plugins` composes them.
</Card>
<Card title="Connections" href="/connections">
Owner-scoped credential identity, provider routing, and OAuth minting.
</Card>
<Card title="Tools" href="/tools">
Tool addresses, discovery, schema inspection, and invocation across surfaces.
</Card>
<Card title="Configure credentials" href="/configure-credentials">
Durable credential providers, auth templates, and connection payloads.
</Card>
</CardGroup>
