This package owns the local host: PGlite, retained builds, credential encryption,
configuration and API access. Reusable account/app/tool operations live in
packages/sdk and are shared with the in-process SDK.
The server imports createExecutor and Executor from @executor-js/sdk/core.
Its HTTP, MCP, dashboard, and app UI handlers call that public Effect API.
Read these files in order:
src/bin.tsruns the CLI;src/main.tsis the headless supervisor entry. Both serve throughsrc/implementation/server-process.ts, which usessrc/node.tsfor one scoped local server.src/implementation/server.tsassembles storage, credentials, runtime and HTTP routes.src/implementation/storage.tsopens the native Effect SQL PGlite driver and initializes the fumadb-effect schema.aesGcmCredentialsfrom@executor-js/sdk/coreencrypts account fields with AES-GCM.packages/mcpdeclares and implements execute, skills, search and the transport;src/implementation/mcp.tssupplies local SDK access and documentation I/O.src/implementation/executor-app.tsdeploys the bundled management app and links its local API account.src/contracts/config.tsdefines local paths, port, explicit secrets and MCP limits.src/contracts/dashboard.tsdefines the typed dashboard API;src/implementation/dashboard.tsprojects SDK data andweb.tsserves the UI.
The exported localApi(config, crypto, auth, options) is an Effect layer containing the SDK, MCP and dashboard routes.
The caller supplies Web Crypto and the HTTP platform and owns its scope. Closing the scope
closes the database. The executable entry point binds only to 127.0.0.1.
Effect's Node runtime handles signals and shuts down the listener and database.
Use Node 22.23 or newer. Run from the repository root:
bun run serverThe repository scripts need no configuration. scripts/local-dev.ts keeps data
and a development key pair in the checkout's ignored .local/dev/. bun run server
and bun run dev listen behind the shared Portless proxy at
https://local.executor.localhost:5394, or https://local.<checkout>.executor.localhost:5394
in another checkout. bun run executor starts or joins the
background service for .local/dev/local on
http://127.0.0.1:4312 and exits; that server keeps running until
bun run executor service stop. Variables that you set win over these defaults.
For example:
curl -H "Authorization: Bearer $(node -p 'require("./.local/dev/keys.json").apiKey')" \
https://local.executor.localhost:5394/v1/appsPress Ctrl+C to stop bun run server or bun run dev. Package-level
bun run start reads its existing process environment only. Relative data paths
are resolved from the process working directory. bun run with:local … loads
1Password values instead; see
configuration.
| Variable | Meaning |
|---|---|
EXECUTOR_DATA_DIR |
Directory containing executor.pglite/, browser-auth.pglite/, builds/ and the service files; defaults to .local/executor (~/.executor/v2/cli in releases). |
EXECUTOR_PORT |
Loopback listener port; wins over the background service's port setting, then defaults to 4312. 0 chooses any free port. |
EXECUTOR_API_KEY |
Optional supplied bearer token; set with the encryption key. At least 32 characters. |
EXECUTOR_ENCRYPTION_KEY |
Optional supplied AES key; set with the API key. Exactly 64 hexadecimal characters. |
EXECUTOR_KEY_STORAGE |
Optional key storage for a new directory: file for keys.json, os for the OS credential store without the key file fallback. See Key storage. |
EXECUTOR_MCP_TIMEOUT_MS |
Catalog discovery plus program timeout; defaults to 300000. |
EXECUTOR_MCP_MAX_TOOL_CALLS |
Admitted calls per execute, including search; defaults to 100. |
EXECUTOR_TOOL_LISTING_FRESH_SECONDS |
Reuse an app's evaluated tool list this long before refreshing it in the background; defaults to 30. |
EXECUTOR_TOOL_LISTING_RENEW_SECONDS |
Renew the tool lists search serves in the background once they are this old; defaults to 86400. |
EXECUTOR_TOOL_LISTING_MAX_AGE_SECONDS |
Never serve an evaluated tool list older than this; 0 evaluates every list. Defaults to 604800. |
EXECUTOR_TOOL_LISTING_LOAD_SECONDS |
Stop evaluating an app's tool list after this long when no request is waiting for it; defaults to 45. |
EXECUTOR_EVALUATION_MEMORY_MB |
Memory for kept tool lists and app declarations; defaults to 256. |
EXECUTOR_APP_WORKERS |
Most app and data Workers kept loaded; defaults to 64. Idle ones above it unload and reload on their next call. An app with a database runs its calls in a data Worker, which counts too. |
EXECUTOR_APP_WORKER_IDLE_SECONDS |
Unload an app or data Worker not called for this long, even below EXECUTOR_APP_WORKERS; 0 turns this off. Defaults to 300. |
First launch generates the API and encryption keys, saves them in the OS
credential store and records the installation ID in installation.json. Linux
uses a persistent Secret Service; the kernel keyring is never used because it
loses keys on reboot.
When there is no store on a directory's first start, Executor writes the keys
to keys.json in the data directory instead, as Executor 1 did with
auth.json. "No store" means the native keyring module cannot load, Linux has
no D-Bus session or Secret Service, or a Windows logon session has no
credential vault. When a store exists but access is denied or cancelled, or it
is locked, startup stops with a message saying so. Nothing is written except
the pending record, so the next start prompts again, and can still fall back
if the store turns out to be absent. Failures that cannot be identified as a
missing store count as denied. The directory is created with mode 0700 and the file with 0600.
The record then says file, and startup prints one line to stderr naming the
file. This applies to every platform, including desktop. A first start is a
directory with no record, or a pending record and no databases. A first start
interrupted after writing keys.json finishes with that file instead of
replacing it.
When a store exists but no prompt can appear, such as over SSH, the denied
message includes the platform error text and says how to unlock the store first
(gnome-keyring-daemon --unlock on Linux, security unlock-keychain on macOS).
On a first start it also points to the opt-in below.
EXECUTOR_KEY_STORAGE=file chooses keys.json on a first start even when a
store works. It writes the file exactly as the fallback does, records file,
never touches the store and prints EXECUTOR_KEY_STORAGE=file; keys saved to <path> on stderr. EXECUTOR_KEY_STORAGE=os requires the store: an absent
store on a first start fails instead of falling back. Either value is a no-op on
a directory already using that storage and fails without changes on one using
the other; keys are never moved. Any other value, or combining it with supplied
keys, fails before anything is written.
A directory keeps its chosen key source; Executor never switches it silently.
A ready directory whose store is missing or denied stops instead of falling back.
A file directory with a missing or invalid keys.json stops with a restore
instruction. Existing data without a record stops too. No replacement keys are
generated in any of these cases.
Explicit environment keys stay supported and are never persisted. A directory
that saves its own keys, in the OS store or keys.json, refuses them. Back up
the installation record with the data, together with either the OS credential or
keys.json, or retain both supplied keys. Anyone who can read keys.json can
use the local API and decrypt saved accounts. Changing an encryption key does
not re-encrypt existing accounts. Database rows contain encrypted bytes, and
each ciphertext is bound to its account ID.
A refused key setup reports one reason: credential-unavailable (the
directory needs an OS store and there is none), credential-denied,
credential-missing, invalid (a damaged record or key), misconfigured
(EXECUTOR_KEY_STORAGE or supplied keys cannot apply to this directory),
locked or io. The desktop receives that reason too, so it can choose
recovery actions.
executor rotate-key replaces the saved API key in place, in the OS credential
store or keys.json, and keeps the encryption key, so saved accounts and MCP
sign-ins keep working. The record is unchanged. A running server keeps using its
current key until it restarts (executor service restart); the next start also
updates the managed Executor account. Rotation refuses supplied keys, a directory without saved keys, and a
store that is locked or denied, and changes nothing in those cases.
Account-connection links signed with the old key stop working.
SDK routes, including /mcp and /openapi.json, require Authorization: Bearer ….
The running server's server.json password is accepted wherever the API key is.
The local token grants access to the whole local instance. Owner filters do not
represent user authentication. SDK and MCP requests with a browser Origin
header are rejected. Dashboard data requires the same bearer key and permits
only the configured loopback origin when an Origin header is present.
One server serves each data directory, and every local client shares it.
src/contracts/service.ts is the contract, service-client.ts finds, starts
and stops the server, and server-process.ts runs it. Nothing is installed with
the OS: no system service, no login item and no idle exit. The server runs until
something stops it, so after a reboot MCP clients get connection refused until
something runs executor or executor service start.
executorstarts or joins the server, pairs the browser throughPOST /auth/pair, prints the Executor, MCP and Connect lines, opens the link and exits. It replaces a server that runs another version, asexecutor service restartdoes.executor pair,executor appswithout--hostorEXECUTOR_HOST, andexecutor service startuse whatever version runs.executor pairwaits up to 30 seconds for a registered server that is still starting or stopping, or that holds the data directory without answering.- The CLI prints or opens a connection link only when it is an
httporhttpsURL without credentials in it. A link from the CLI's own server, the background or private one, must also be on that server's URL or its HTTPSEXECUTOR_BROWSER_ORIGIN; anything else fails as an unexpected response. executor serveruns the server in the foreground and registers it the same way, so other clients use it while it runs.executor --standaloneruns a private, unregisteredserve --stdiochild on a free port for as long as the command runs. It keeps the child's stderr until the child is ready, so a refusal reports the child's own reason. The command fails, saying how, if the child stops first. When the command ends it closes the child's stdin and gives it five seconds to stop before the child is killed, which on Windows skips its cleanup.executor --server <url>pairs with a server elsewhere, sendingEXECUTOR_API_KEYor the saved API key.executor service start|stop|restart|status|get|set|unsetmanages it.statusprintsstartingbefore the address while the registered server boots,stoppingonce it is asked to stop, andnot respondingwhile its info request times out but its process still runs, as a hung or suspended server's does. The settings areport,env(set env <name> <value>,unset env <name>) anddisabled.disabledmakesexecutorandexecutor appswithout--hostorEXECUTOR_HOSTrun a private server, as--standalonedoes;executor service start,executor pairand the desktop still use the background server. Changingportorenv, or settingdisabled, saves the setting and then stops the server, so whichever client starts it next, even at once, starts it with the new settings. The desktop then shows that it was stopped and starts none itself.executor appswithout--hostreports a local server that cannot start like its other failures: codeLocalServerUnavailable, and JSON on stdout when stdout is not a terminal.executor upgradeand--rollbackstop the registered server before a standalone install switches versions, which removes older version directories, or before the package manager runs, which replaces the installed files. Afterwards they run the new version'sexecutor service start. A target that refusesexecutor service status, as a release from before the background service does, gets no restart; the upgrade says to runexecutor servethere instead. A failed or interrupted upgrade starts the server again with this CLI. When the server does not stop, a standalone switch keeps its version directory, and an npm, pnpm, Bun or Yarn upgrade stops withservice-running. A server from a development build is left running.- A registered server stops itself, removing its registration, once its
executable is no longer the file it started from: every 5 seconds it compares
the device, inode, size and modification time of the release executable, or
of the Node or Bun that runs the CLI, with those at its start.
brew upgrade,npm i -gand the install script remove or replace that file without asking the server to stop, which would leave it serving from deleted runtime files; the next client then starts the installed version. The install scripts also stop the registered server through the installedexecutor service stopbefore they replace or prune a version. On Windows a running executable cannot be replaced or removed, sonpm i -gfails while the server runs.
The port is EXECUTOR_PORT, then the port setting, then 4312. A client
starts detached serve --service processes with its own environment (the
desktop passes its own) plus env and the absolute data directory, and waits up
to two minutes for one to answer. EXECUTOR_API_KEY and
EXECUTOR_ENCRYPTION_KEY reach a server it starts, background or private, only
when both are set; one alone is the client's credential, which it only sends as
its bearer. A starting server that finds the port held by this directory's
registered server exits quietly; one that finds another program there fails
with port-in-use at once, and one that finds another Executor there waits up
to 15 seconds for it to go, then fails saying another Executor holds it. The
message names EXECUTOR_PORT when that chose the port.
Once a client has its server, it sends SIGTERM to every process it started that
did not become the registered server, so none can take the port later. A
foreground or private server whose port this directory's registered server holds
fails with directory-in-use and that server's URL, also when that server does
not answer within a second but its process still runs. A server that cannot take
the data directory's lock names the registered server's URL when that server
proves it answers, or still runs without answering in time; otherwise the
holder is unregistered, such as a
--standalone server or the desktop moving data, which executor service stop
cannot stop, and the message says so.
The first start that writes keys.json prints which file it wrote on stderr,
which nothing reads for a background server. executor and
executor service start repeat that line when the data directory had no
installation record before they started the server and has a file record
after; they read only the record.
Executor 2 servers before the background service wrote server.json as
{ pid, version?, url, command } and took no .server.lock. While the process
such a record names runs and an Executor answers GET /auth/session at its URL
(or does not answer within two seconds), servers and clients refuse the data
directory with directory-in-use, naming that process and its URL, instead of
opening the same databases. A record whose process runs while nothing answers
there, or something else does, was left by a killed server whose pid another
program now has: it is ignored, with a line on stderr, until a server replaces
it.
| File | Contents |
|---|---|
server.json |
The running server's id, version, url, pid, on Linux and macOS started and boot (its process, as .server.lock records it), and per-process password; group when it leads its own process group, as a background server does; browserOrigin when set. Mode 0600, replaced atomically, removed on exit. A server stops once the file no longer names it. |
service.json |
The port, env and disabled settings. Mode 0600. |
.server.lock |
Held for the server's lifetime, before storage opens, and by the desktop while it moves or resets data; owner.json inside names the holder's process by pid, on Linux and macOS with started and boot, on Windows with the pipe it listens on, and its host and, for messages, for a server, its loopback port. A second server fails with directory-in-use. started is the start time the operating system keeps: on Linux the clock ticks from boot, on macOS the second, read with /bin/ps. boot is Linux's boot ID with the reader's pid and time namespaces, and macOS's boot session UUID. A process reads its own as it reads any other process's, so the two are compared for equality. On Windows, Node and Bun read another process's start time only by starting a program such as PowerShell, which takes seconds on a busy machine, so nothing there starts a program to check a lock. The holder listens on a named pipe with a random name, \\.\pipe\executor-lock-<random>, from before it takes the lock until after it releases it. Windows removes the pipe as soon as that process exits, also when it is killed, and keeps it while the process is suspended. Opening it connects, or fails at once with EBUSY once a suspended holder's few pipe instances are taken, and both mean it runs; once it has exited, the pipe is not found. The pipe holds no file in the data directory open, so the desktop can rename the directory while it holds the lock. A lock whose owner can be checked is held, however stale its time, while that process runs, as a suspended or sleeping server does: its pid runs on this boot with that start time, or its pipe exists on the host that recorded it. It is taken over at once when that process has exited, also when its pid now names another program, whatever listens on its port. A lock without an owner, as one being taken, one owned on another boot, machine or time namespace, or recorded without a start time or pipe, and one whose owner's start time, this boot or pipe cannot be read now, is taken over once it is 30 seconds stale. A server that cannot read its own start time says so once, on stderr and in its diagnostics log; one that cannot listen on its pipe says so in its log. A takeover is decided once, under .server.lock.takeover, and renames the abandoned lock aside before removing it. A holder releases the lock only while owner.json still holds the record it wrote, so a host name change does not leave it behind. |
GET /api/service answers before storage opens and needs no credential:
{ id, version, pid, status }, with 503 and starting until every route is
served, then 200 and ready, and 503 and stopping from when it is asked to
stop, or starts closing, until it exits. A client waits for a stopping server
to exit instead of joining it or counting it as lost, then starts one if it
starts servers. Anyone on the machine can read those, so they do
not identify the server. A client sends ?challenge=<random nonce>, and a
server with a password adds proof: the base64url HMAC-SHA256 of the nonce,
keyed with its password. Clients, and a starting server checking who holds its
port, trust server.json only when this route answers with its id, pid,
version and a proof made with the password in that file, so a process that
took the port after the server died cannot pass as it, and the password is
never sent before the proof. POST /api/service/stop with the registration
password as bearer shuts the server down gracefully; a server that does not
exit then gets SIGTERM and SIGKILL. A server that only times out is signalled
without being sent the password. Each signal goes only to a pid that runs on
this boot with the start time server.json records, or on Windows that the data
directory's lock names while its pipe exists, checked again before each;
otherwise the server has exited and its pid may now name another program, or
that cannot be told, so escalation stops and its registration is removed. A
start also removes, without waiting, a registration whose process does not
answer and has exited, or, when that cannot be told, as for one from another
boot, no longer holds the data directory's lock with a refresh in the last 30
seconds. A registration with
group is signalled with its process group on POSIX, so its telemetry
collector and app runtimes stop with it. Both routes refuse requests with an
Origin header. The password changes with every server process, so do not give
it to MCP clients.
Known limitation: another machine's boot looks like an earlier boot of this one. With the data directory on a home directory shared by two machines, a client on the machine that does not run the server finds nothing at the registered loopback URL, so it removes the registration, and the server on the other machine then stops within five seconds. A lock owned on another boot stays held while it is refreshed, so the two never open the databases at once.
For UI development, run bun run dev from the repository root. It starts the
same local server behind the Portless origin above, with Vite middleware and
React Fast Refresh. UI changes update without rebuilding or refreshing the page.
The HMR WebSocket listens separately on a free loopback port. API requests,
session cookies, pairing and OAuth callbacks use the Portless origin, which stays
the same across restarts, so the browser stays paired. Server code changes still
require a restart.
Build the dashboard from the repository root, then start the server:
bun run web:build
bun run serverbun run executor starts or joins the background server, opens a one-use
connection link and exits. bun run executor serve (or bun run server) runs
the server in the foreground and prints the link without opening a browser.
bun run executor pair prints a fresh link for the registered server, sent the
password from its server.json. bun run dev runs a private server that does
not register, so executor pair cannot reach it; it holds .local/dev/local,
so stop it before running bun run executor.
A paired dashboard can also issue a new link through POST /auth/pair, with its
session cookie and a valid local Origin. Executor desktop uses this for File →
Open in browser. A bearer key alone still cannot mint a link from browser requests.
Repository commands run TypeScript source through Node. Released builds use the
same commands with data in ~/.executor/v2/cli. Build one with
bun run release:cli; see release packaging.
The link expires after five minutes and is consumed once. Its token is carried
in the URL fragment, removed from browser history before exchange, and replaced
by a seven-day HttpOnly, SameSite=Strict cookie. Refresh retains the session.
Sessions survive server restarts when the same data directory and address are
used. Only token digests and expiry dates are saved in browser-auth.pglite/.
Disconnect revokes the session persistently; restarting does not extend its
seven-day expiry. Outstanding pairing links remain process-local. No reusable API key enters the browser.
Bearer authentication for SDK/MCP remains unchanged. Requests must use the exact
http://127.0.0.1:<port> origin and Host; no remote access or CORS is enabled.
The public HTML and bundled assets contain no key or account data.
Inspect apps, their linked accounts, live tools, retained versions and source files. The Accounts page shows provider metadata and the apps that use each account. It never returns saved credentials. This first dashboard is also supports catalog imports. Accounts are connected and their credentials replaced from an app's Accounts tab, which also links and unlinks saved accounts. The SDK and MCP expose the underlying operations to other clients.
The React UI lives in apps/local/web. Effect Atom reads the shared HTTP API
contract, including response schemas and typed errors. Dashboard reads use live
SSE subscriptions driven by committed storage writes, including SDK and MCP writes.
See reactive storage for runtime limits. Development and built
previews both use the local server origin. src/dev.ts owns Vite's lifetime;
the production entry points do not load Vite.
@executor-js/local-server/node is the composition edge for the CLI's
foreground, background and private servers. listenLocal binds 127.0.0.1 and
serves the service routes at once, lockDataDirectory holds .server.lock, and
only then does composeLocalServer open storage and serve the product.
startLocalServer does all three for one private, unregistered server, as
bun run dev does, and stops, like every server, if it loses the lock. Its
Effect scope owns HTTP, persistence, and auth; closing it interrupts requests
and closes retained connections. EXECUTOR_PORT=0 chooses an available
loopback port.
Executor desktop shares the same background service as the CLI. The
desktop README describes how it starts, pairs and
recovers it. Run bun run desktop:dev from the repository root.
- Transport: Streamable HTTP
- URL:
http://127.0.0.1:4312/mcp, or the portexecutor service statusprints - Header:
Authorization: Bearer <API key>, the saved local API key orEXECUTOR_API_KEY - Tools:
skills, for app documents;execute, with acodestring
Do not configure a client with the server.json password, which changes every
time the server starts. The client reaches the background server only while it
runs; after a reboot, run executor or executor service start first.
The server sends the Executor app's executor skill as its MCP instructions.
Discover the Executor app with skills({}), then read that skill with
{ app: "executor", name: "executor" } using its current slug. It links to
code-mode, which covers calling tools from execute, and app-authoring,
which has runnable source, schema helpers, provider accounts and the deploy
flow. Read app-authoring before writing an app. These are ordinary skill files
in the Executor app's current version.
Start discovery inside execute with this code:
return await tools.search({ query: "Executor" });Search returns exact callable paths, one-line descriptions and input types;
tools.search.describe({ paths }) returns full signatures. The Executor app's
tools are generated from this server's management API, such as apps.deploy,
apps.list, accounts.list, appAccounts.userConnect and tools.list.
It calls this server's API through its linked account. The host uses
the existing configured bearer key; it does not create a separate admin key.
The persisted host-owned app is named Executor, with owner executor-local.
Startup restores its bundled source and updates its local account's port/key
while retaining the account identity. Leave that connection under host control.
Each app appears once, under tools[appSlug]. Calls never choose accounts: each
uses the accounts linked to the app, the newest one for each plain requirement and
every one for a many() requirement. Linking or connecting another account for a
plain requirement replaces the one linked before; it stays saved. Links are kept
in local_app_account_links. Copies have independent source and versions. The catalog is evaluated at the start of each execute,
and tool calls resolve their accounts again.
Run a new execute to discover apps added or configured during a previous one.
Incomplete or failing apps appear in unavailableApps with a safe error tag.
Programs support await, Promise.all, loops and data transformation. There
are no direct imports, fetch, process or filesystem globals. App tools still run
as trusted Node code with the selected credentials. Interpreter limits are
cooperative; they do not isolate arbitrary app code or bound process memory.
App code can pass context.signal to fetch to observe cancellation. Effects
already completed are not rolled back when an execution fails or is cancelled.
- Deploy source, derive provider IDs and retain immutable versions.
- Add API-key accounts and read account metadata without returning credentials.
- Create configured app copies.
- Validate account existence, provider compatibility, cardinality and duplicates.
- List live tools and invoke them with the accounts linked to the app.
- Activate retained versions in the same code lineage.
- Serve MCP execute for app calls and management through the bundled Executor app.
- Serve a read-only skills tool for deployed app documents, including the Executor app’s
executor,code-modeandapp-authoringskills. - Handle July 2026 and November 2025 MCP requests, with authentication on every request.
- Serve a local dashboard over the same persisted apps and accounts, with catalog imports and account setup.
SDK writes use database transactions. Builds and app execution run outside transactions. A failed activation leaves the saved app unchanged. Unused retained builds can remain after a later database failure; build garbage collection is deferred.
The remote SDK, workspace folders, app-authored UI and an executor dev
command remain separate work. Account creation validates the serialized provider
schema; native app validation also runs on invocation. Serialized schemas retain
the known Effect beta limitation around optional undefined/null values.
For a private HTTPS proxy such as Tailscale Serve, set EXECUTOR_BROWSER_ORIGIN
to its exact origin. Pairing links and remote OAuth callbacks use that origin;
loopback access remains available. No wildcard origins or forwarded headers are
trusted. HTTPS browser sessions receive Secure cookies.
The account form resolves an OAuth client automatically through saved configuration, CIMD or DCR. If the provider needs a supplied client, the form shows its callback URL and client fields. Client secrets, authorization attempts and refresh tokens are encrypted on the host. Apps receive only their declared access-token fields.
Set EXECUTOR_OAUTH_CLIENT_METADATA_URL only when you have a public HTTPS client
metadata document. There is no hosted Executor OAuth broker in the local app.
See OAuth notes for the protocol library and limits.
Apps → Add app lists Executor's official apps from the feed at
EXECUTOR_CATALOG_URL (the feed's index.json; the default is the published feed at
https://catalog.executor.sh/index.json).
Choosing one installs the app's own source as a copy, the way a published app is
installed, and then asks for an account through the app's own providers. Choose
an unused app name; this flow never replaces an existing app. See
catalog imports.
Apps → Add app → Custom app also accepts MCP, GraphQL, and OpenAPI URLs. These templates generate source and deploy through the same SDK. API keys are connected afterward. GraphQL uses live account-specific introspection; OpenAPI supports an optional API base URL override and relative server URLs.
MCP entries instead generate index.ts and, when needed, provider.ts. Select
Automatic, OAuth, API key, or No authentication. Tools load live using the app's
selected account. Streamable HTTP and legacy SSE servers are supported. Each
discovery or tool call owns its connection; session state is not retained across
separate calls. Remote prompts/resources, sampling, and elicitation are
not included. See catalog imports.
Automatic import checks the server's Bearer OAuth metadata advertisement when the catalog does not already include OAuth. This supplements incomplete API-key entries with OAuth while keeping their API-key method. The account form prefers OAuth when available. An authentication error alone does not imply OAuth support.
After import, connect an account from the app's Accounts tab. Every saved account whose provider one of the app's requirements uses is linked to it. Account responses never include credentials. If a network failure leaves saving uncertain, inspect Accounts before submitting a new account.
On an app page, Delete app opens a full-page confirmation. It removes only
that configured copy. Saved accounts, other app
copies, and retained source/builds stay available. The built-in Executor app is
managed by the local server and cannot be deleted. The same operation is exposed
as executor.apps.remove({ app }) and the management app's removeApp tool.
OpenAPI 3.0/3.1 imports support header/query API keys, bearer tokens, combined API-key requirements, JSON bodies, and base64-encoded binary uploads. Generated OAuth declarations retain authorization-code metadata when available. GraphQL and CLI catalog entries are labeled unavailable; Swagger 2, external references, server URL templates, multipart bodies, and custom signing need further importer work. Unsupported features fail explicitly before deploying.
Local uses disk-persisted PGlite with the PostgreSQL schema and an in-process
live coordinator. The host owns each database until shutdown. Only one server
process may open a data directory; .server.lock enforces this. fumadb-effect/pglite configures the native
Effect driver to preserve UTC dates on machines outside UTC.
Existing executor.sqlite and browser-auth.sqlite files are untouched and are
not imported automatically. The new directories start empty, so old browser
cookies require a new pairing. To retain records, stop the host and plan a
separate one-shot transfer that preserves IDs, selections, ciphertexts, keys,
and session digests. See storage.