# SDK quickstart example

> Walkthrough of `examples/docs-sdk-quickstart`: in-memory credential provider, OpenAPI `addSpec`, connection creation, tool listing, schema inspection, and shutdown.

- 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

- `examples/docs-sdk-quickstart/src/main.ts`
- `examples/docs-sdk-quickstart/package.json`
- `scripts/generate-doc-snippets.ts`
- `packages/plugins/openapi/src/promise.ts`
- `packages/core/sdk/src/promise.ts`
- `packages/core/sdk/src/connection.ts`

---

---
title: "SDK quickstart example"
description: "Walkthrough of `examples/docs-sdk-quickstart`: in-memory credential provider, OpenAPI `addSpec`, connection creation, tool listing, schema inspection, and shutdown."
---

`examples/docs-sdk-quickstart` is a single-file Promise-mode script that registers an inline OpenAPI integration, stores a credential in a writable in-memory provider, materializes per-connection tools, and reads the resulting catalog. The file is also the source of truth for generated docs snippets under `docs/snippets/sdk/quickstart/`.

## What the example demonstrates

The script exercises the minimum embed path without the CLI daemon, HTTP server, or MCP host:

| Phase | API surface | Outcome |
| --- | --- | --- |
| Bootstrap | `createExecutor` | In-process executor with OpenAPI plugin and writable credential store |
| Register integration | `executor.openapi.addSpec` | Tenant-level `inventory` integration from an inline spec blob |
| Bind credential | `executor.connections.create` | Owner-scoped connection that persists the API key and produces tools |
| Discover tools | `executor.tools.list` | Addressable tool rows for the connection |
| Inspect schema | `executor.tools.schema` | JSON Schema roots and optional TypeScript preview strings |
| Teardown | `executor.close` | Plugin and database cleanup |

Tools are persisted per connection when `connections.create` runs. `tools.list` is a read of that catalog, not a live re-parse of the OpenAPI document on every call.

## Example layout

:::files
examples/docs-sdk-quickstart/
├── package.json          # workspace deps and `bun run start`
├── src/
│   └── main.ts           # runnable example + docs:start/docs:end snippet markers
└── tsconfig.json
:::

The example depends on `@executor-js/sdk`, `@executor-js/plugin-openapi`, and `effect`. It imports Promise-mode entry points from `@executor-js/sdk/promise` and `@executor-js/plugin-openapi/promise`.

## Prerequisites

<Steps>
<Step title="Bootstrap the monorepo">

From the repository root, install workspace packages:

```bash
bun run bootstrap
```

Fresh checkouts need this before workspace imports such as `@executor-js/sdk/promise` resolve.

</Step>
<Step title="Confirm Bun is available">

The example runs with Bun (`"start": "bun run src/main.ts"`). Node is not wired in this package's scripts.

</Step>
</Steps>

## Run the example

```bash
cd examples/docs-sdk-quickstart
bun run start
```

<ResponseExample>

```text
tools.inventory.org.default.listItems: List inventory items
tools.inventory.org.default.getItem: Get an inventory item
No input required
```

</ResponseExample>

The first two lines come from `tools.list`. The third line is `schema?.inputTypeScript` for the first listed tool; `listItems` has no required input, so the fallback `"No input required"` prints when `inputTypeScript` is absent.

<Note>
Tool list order is not guaranteed. The schema line always reflects `tools[0]`, whichever operation the store returns first.
</Note>

## Architecture

```mermaid
sequenceDiagram
  participant App as main.ts
  participant Exec as createExecutor
  participant OAI as openApiPlugin
  participant Prov as memoryProvider
  participant DB as executor store

  App->>Exec: plugins, providers, onElicitation
  Exec->>OAI: register extension
  Exec->>Prov: register writable store
  App->>OAI: addSpec(slug: inventory)
  OAI->>DB: persist integration + auth template
  App->>Exec: connections.create(value)
  Exec->>Prov: set(credential id, api key)
  OAI->>DB: persist tools per connection
  App->>Exec: tools.list / tools.schema
  Exec->>DB: read tool rows
  App->>Exec: close()
```

Three identities matter:

- **Integration** (`inventory`): the API surface and auth template. Registered by `addSpec`.
- **Connection** (`org` / `default`): the saved credential for one integration and one auth method. Identity is `(owner, integration, name)`.
- **Tool**: a persisted, addressable operation owned by a connection. Address shape is `tools.<integration>.<owner>.<connection>.<operationId>`.

## Step 1: Create the executor and in-memory provider

A writable credential provider is required before `connections.create({ value })` can store an inline secret. The example uses a `Map`-backed provider whose `get` and `set` methods return `Effect.sync` values. Production hosts swap this for durable providers (keychain, 1Password, encrypted stores).

<CodeGroup>

```ts title="examples/docs-sdk-quickstart/src/main.ts"
import { Effect } from "effect";
import {
  createExecutor,
  ProviderItemId,
  ProviderKey,
  type CredentialProvider,
} from "@executor-js/sdk/promise";
import { openApiPlugin, variable } from "@executor-js/plugin-openapi/promise";

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

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

</CodeGroup>

<ParamField body="plugins" type="AnyPlugin[]">
Protocol plugins to load. Here, `openApiPlugin()` exposes `executor.openapi.*`.
</ParamField>

<ParamField body="providers" type="CredentialProvider[]" required>
Writable providers registered at construction. Config providers register first; the first writable provider becomes the default store for inline `value` credentials.
</ParamField>

<ParamField body="onElicitation" type='"accept-all" | handler' required>
How to answer mid-invocation elicitation prompts. `"accept-all"` is appropriate for scripts and tests.
</ParamField>

## Step 2: Register the OpenAPI integration

The script embeds a minimal Inventory API spec (operations `listItems` and `getItem`) and passes it as a blob. `addSpec` registers the integration, stores the spec pointer, and records the auth template.

<CodeGroup>

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

</CodeGroup>

<ParamField body="slug" type="string" required>
Integration catalog slug. Becomes the `<integration>` segment in tool addresses.
</ParamField>

<ParamField body="spec" type="OpenApiSpecInput" required>
Spec source. The example uses `{ kind: "blob", value: string }`. URL and file inputs are also supported by the plugin.
</ParamField>

<ParamField body="authenticationTemplate" type="AuthenticationInput[]">
Declares where connection credentials render on outbound requests. `variable("token")` is the slot the resolved credential fills. Omit to derive methods from the spec's `securitySchemes`; pass `[]` to declare no auth.
</ParamField>

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

<ResponseField name="toolCount" type="number">
Operation count extracted from the spec. The inventory example yields `2`.
</ResponseField>

<Warning>
Re-adding an existing slug throws `IntegrationAlreadyExistsError`. Use `updateSpec` to refresh an integration in place.
</Warning>

## Step 3: Create a connection

Connections bind a credential to one integration and one auth template. Inline `value` writes through the default writable provider and triggers tool materialization for that connection.

<CodeGroup>

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

</CodeGroup>

<ParamField body="owner" type="Owner" required>
Scope owner. `"org"` rows file under the executor tenant (default `"default-tenant"`).
</ParamField>

<ParamField body="name" type="ConnectionName" required>
Connection name within `(owner, integration)`.
</ParamField>

<ParamField body="integration" type="IntegrationSlug" required>
Must match the slug passed to `addSpec`.
</ParamField>

<ParamField body="template" type="AuthTemplateSlug" required>
Which auth method from `authenticationTemplate` applies this credential.
</ParamField>

<ParamField body="value" type="string">
Sugar for a single `token` input. Written to the default writable provider. Alternative shapes include `from` (external provider reference), `values`, and `inputs`.
</ParamField>

The returned `Connection` includes `provider` (here `"memory"`), `address` (`tools.inventory.org.default`), and metadata fields. Credentials are applied lazily per invocation through the template, not pre-baked into stored tool definitions.

## Step 4: List tools

Filter the catalog to one integration:

<CodeGroup>

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

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

</CodeGroup>

Each `Tool` row includes:

| Field | Example value |
| --- | --- |
| `address` | `tools.inventory.org.default.listItems` |
| `owner` | `org` |
| `integration` | `inventory` |
| `connection` | `default` |
| `name` | `listItems` |
| `pluginId` | `openapi` |
| `description` | `List inventory items` |

Optional `ToolListFilter` fields include `owner`, `connection`, `query`, `includeAnnotations`, and `includeBlocked` (defaults to omitting blocked tools).

## Step 5: Inspect a tool schema

`tools.schema` returns a `ToolSchemaView` with JSON Schema roots, shared `schemaDefinitions`, and optional TypeScript preview strings. List rows carry lightweight schema hints; this call is the schema-bearing surface.

<CodeGroup>

```ts title="tools.schema"
const firstAddress = tools[0]?.address;
const schema = firstAddress ? await executor.tools.schema(firstAddress) : null;

console.log(schema?.inputTypeScript ?? "No input required");
```

</CodeGroup>

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

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

<ResponseField name="inputTypeScript" type="string">
Generated TypeScript type preview for the input shape. Useful for agent prompts and codegen.
</ResponseField>

<ResponseField name="schemaDefinitions" type="Record<string, unknown>">
Shared component definitions reachable from the input/output roots.
</ResponseField>

For `getItem`, expect an input shape that includes the `id` path parameter. For `listItems`, `inputTypeScript` is typically absent.

## Step 6: Shut down

Always close embedded executors so plugins and any configured database factory can release resources:

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

The Promise-mode `close` wraps the Effect executor's teardown, including per-plugin `close` hooks and optional `db.close`.

## Docs snippet generation

`main.ts` is annotated with `// docs:start <name>` and `// docs:end <name>` markers. Editing those blocks and running the root script regenerates MDX snippet files:

```bash
bun run docs:snippets
```

Output lands in `docs/snippets/sdk/quickstart/` (one `.mdx` file per marker name: `create-executor`, `add-integration`, `create-connection`, `list-tools`, `inspect-schema`, `close-executor`). The generator dedents captured lines and prefixes each file with a generated-file notice.

<Tip>
Change snippet content in `examples/docs-sdk-quickstart/src/main.ts`, not in the generated `docs/snippets/` tree.
</Tip>

## Common failure modes

<AccordionGroup>
<Accordion title="Cannot find module '@executor-js/sdk/promise'">

Run `bun run bootstrap` from the repository root so workspace packages link. The example uses monorepo `workspace:*` dependencies, not standalone npm installs.

</Accordion>

<Accordion title="connections.create rejects inline value">

`createExecutor` needs at least one writable provider in `providers` (or from a plugin's `credentialProviders`). Without one, inline `value` credentials cannot be stored.

</Accordion>

<Accordion title="tools.list returns an empty array">

Confirm `connections.create` ran for the same `integration` slug as `addSpec`, and that the connection's `template` matches a slug in `authenticationTemplate`. Tools materialize at connection creation, not at `addSpec`.

</Accordion>

<Accordion title="IntegrationAlreadyExistsError on second run">

`addSpec` blocks duplicate slugs. Use a fresh in-memory database (default for scripts without a custom `db` factory) or call `executor.openapi.removeSpec("inventory")` before re-adding.

</Accordion>
</AccordionGroup>

## Beyond this example

This script stops before `tools.invoke`. Invocation uses the same addresses and applies the connection credential through the auth template on each outbound OpenAPI request. For multi-plugin setups, OAuth flows, and production credential backends, see the broader embed and configuration docs.

## Related pages

<CardGroup>
<Card title="Embed with the SDK" href="/embed-sdk">
Compose `createExecutor` in application code with plugins, providers, integrations, connections, and tool invocation.
</Card>
<Card title="SDK reference" href="/sdk-reference">
Promise and Effect entry points, typed IDs, error tags, and executor surface area.
</Card>
<Card title="Connections" href="/connections">
Owner-scoped credential model, provider resolution, and connection identity.
</Card>
<Card title="Integrations" href="/integrations">
Tenant-level catalog identities and auth method descriptors.
</Card>
<Card title="Configure credentials" href="/configure-credentials">
Durable credential providers, OAuth minting, and placement-based auth templates.
</Card>
<Card title="Tools" href="/tools">
Tool addresses, discovery filters, schema inspection, and invocation paths.
</Card>
<Card title="Plugin catalog" href="/plugin-catalog">
Published protocol and provider plugins beyond OpenAPI.
</Card>
<Card title="Develop locally" href="/develop-locally">
Monorepo bootstrap, targeted tests, and contributor workflows.
</Card>
</CardGroup>
