@electric-ax/agents 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoint.js +541 -810
- package/dist/index.cjs +537 -804
- package/dist/index.d.cts +84 -45
- package/dist/index.d.ts +86 -47
- package/dist/index.js +558 -824
- package/docs/entities/agents/horton.md +2 -5
- package/docs/index.md +4 -2
- package/docs/quickstart.md +2 -2
- package/docs/reference/handler-context.md +0 -35
- package/docs/reference/mcp-registry.md +189 -0
- package/docs/reference/mcp-server-config.md +226 -0
- package/docs/usage/clients-and-react.md +0 -4
- package/docs/usage/embedded-builtins.md +26 -16
- package/docs/usage/mcp-servers.md +354 -0
- package/docs/usage/overview.md +1 -3
- package/docs/usage/programmatic-runtime-client.md +1 -1
- package/docs/usage/writing-handlers.md +0 -5
- package/package.json +7 -5
- package/docs/entities/agents/coder.md +0 -99
|
@@ -8,7 +8,7 @@ outline: [2, 3]
|
|
|
8
8
|
|
|
9
9
|
# Horton agent
|
|
10
10
|
|
|
11
|
-
The built-in assistant registered by the Electric Agents dev server. Horton can chat conversationally, search the web, read and edit files, run shell commands, dispatch workers for isolated subtasks
|
|
11
|
+
The built-in assistant registered by the Electric Agents dev server. Horton can chat conversationally, search the web, read and edit files, run shell commands, and dispatch workers for isolated subtasks.
|
|
12
12
|
|
|
13
13
|
**Source:** [`packages/agents/src/agents/horton.ts`](https://github.com/electric-sql/electric/blob/main/packages/agents/src/agents/horton.ts)
|
|
14
14
|
|
|
@@ -35,8 +35,6 @@ Horton is configured with `ctx.electricTools` plus the base Horton tool set:
|
|
|
35
35
|
| `brave_search` | Web search via the Brave Search API. |
|
|
36
36
|
| `fetch_url` | Fetch a URL and return it as markdown. |
|
|
37
37
|
| `spawn_worker` | Dispatch a subagent for an isolated subtask. |
|
|
38
|
-
| `spawn_coder` | Spawn a long-lived `coder` entity backed by Claude Code or Codex. |
|
|
39
|
-
| `prompt_coder` | Send a follow-up prompt to an existing coder. |
|
|
40
38
|
|
|
41
39
|
`brave_search` requires `BRAVE_SEARCH_API_KEY` in the environment; without it the tool errors at call time.
|
|
42
40
|
|
|
@@ -53,7 +51,7 @@ After the first agent run completes, Horton calls `generateTitle()` (Haiku) to s
|
|
|
53
51
|
| Type name | `horton` |
|
|
54
52
|
| Model | `HORTON_MODEL` (`claude-sonnet-4-5-20250929`) |
|
|
55
53
|
| Title model | `claude-haiku-4-5-20251001` |
|
|
56
|
-
| Tools | `ctx.electricTools` + base Horton tool set,
|
|
54
|
+
| Tools | `ctx.electricTools` + base Horton tool set, plus docs/skill tools when configured |
|
|
57
55
|
| Working directory | Passed at bootstrap (defaults to `process.cwd()`) |
|
|
58
56
|
| Title generation | Yes, after the first run if no title tag exists |
|
|
59
57
|
|
|
@@ -89,4 +87,3 @@ registry.define("my-assistant", {
|
|
|
89
87
|
## Related
|
|
90
88
|
|
|
91
89
|
- [Worker](./worker) — the subagent type Horton dispatches via `spawn_worker`.
|
|
92
|
-
- [Coder](./coder) — the coding-session entity Horton dispatches via `spawn_coder`.
|
package/docs/index.md
CHANGED
|
@@ -22,7 +22,7 @@ Every step — runs, tool calls, text deltas, state changes — is appended to t
|
|
|
22
22
|
|
|
23
23
|
<EntityOverviewDiagram />
|
|
24
24
|
|
|
25
|
-
Start with the [Quickstart](/docs/agents/quickstart) to run the built-in `horton
|
|
25
|
+
Start with the [Quickstart](/docs/agents/quickstart) to run the built-in `horton` and `worker` entities and connect your own app in a few minutes. The [Usage overview](/docs/agents/usage/overview) summarises the full developer surface in a single page.
|
|
26
26
|
|
|
27
27
|
## How it works
|
|
28
28
|
|
|
@@ -135,6 +135,8 @@ await ctx.agent.run()
|
|
|
135
135
|
|
|
136
136
|
Functions the LLM can call during the agent loop. Each tool has a name, description, parameters (defined with [TypeBox](https://github.com/sinclairzx81/typebox) or any [Standard Schema](https://standardschema.dev) validator), and an execute function. Tools run in the handler's context and have access to the entity's state and coordination primitives. See [Defining tools](/docs/agents/usage/defining-tools) and the [`AgentTool` reference](/docs/agents/reference/agent-tool).
|
|
137
137
|
|
|
138
|
+
External tools, resources, and prompts can also be loaded from [Model Context Protocol](https://modelcontextprotocol.io) servers — declared in `mcp.json`, the desktop app's `settings.json`, or programmatically. See [MCP servers](/docs/agents/usage/mcp-servers).
|
|
139
|
+
|
|
138
140
|
```ts
|
|
139
141
|
const searchKbTool: AgentTool = {
|
|
140
142
|
name: "search_kb",
|
|
@@ -198,7 +200,7 @@ console.log(db.collections.texts.toArray)
|
|
|
198
200
|
|
|
199
201
|
## Next steps
|
|
200
202
|
|
|
201
|
-
- [Quickstart](/docs/agents/quickstart) — run the built-in `horton
|
|
203
|
+
- [Quickstart](/docs/agents/quickstart) — run the built-in `horton` and `worker` entities and connect your own app.
|
|
202
204
|
- [Usage overview](/docs/agents/usage/overview) — the full developer surface on one page.
|
|
203
205
|
- [Defining entities](/docs/agents/usage/defining-entities) — entity types, schemas, and configuration.
|
|
204
206
|
- [Writing handlers](/docs/agents/usage/writing-handlers) — handler lifecycle and the `ctx` API.
|
package/docs/quickstart.md
CHANGED
|
@@ -49,7 +49,7 @@ npx electric-ax agents quickstart
|
|
|
49
49
|
This:
|
|
50
50
|
|
|
51
51
|
1. Starts Postgres, Electric, and the Electric Agents runtime server in Docker (the runtime serves both the API and the web UI on `http://localhost:4437`).
|
|
52
|
-
2. Starts a built-in **Horton** runtime in the foreground that registers the `horton
|
|
52
|
+
2. Starts a built-in **Horton** runtime in the foreground that registers the `horton` and `worker` entity types.
|
|
53
53
|
3. Prints onboarding commands you can copy into a second terminal.
|
|
54
54
|
|
|
55
55
|
Leave this terminal running. Press `Ctrl-C` to stop the built-in Horton runtime — the runtime server containers keep running in the background until you call [`electric agents stop`](#stop-the-dev-environment).
|
|
@@ -186,7 +186,7 @@ npx electric-ax agents stop --remove-volumes # stop containers and wipe data
|
|
|
186
186
|
|
|
187
187
|
```sh
|
|
188
188
|
npx electric-ax agents start # runtime server + UI (background, Docker)
|
|
189
|
-
npx electric-ax agents start-builtin # built-in Horton
|
|
189
|
+
npx electric-ax agents start-builtin # built-in Horton and worker (foreground)
|
|
190
190
|
```
|
|
191
191
|
|
|
192
192
|
See the [CLI reference](./reference/cli#start) for the full set of commands.
|
|
@@ -59,10 +59,6 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
|
|
|
59
59
|
id: string,
|
|
60
60
|
schema: T
|
|
61
61
|
): SharedStateHandle<T>
|
|
62
|
-
useCodingAgent(
|
|
63
|
-
sessionId: string,
|
|
64
|
-
opts: UseCodingAgentOptions
|
|
65
|
-
): Promise<CodingSessionHandle>
|
|
66
62
|
send(
|
|
67
63
|
entityUrl: string,
|
|
68
64
|
payload: unknown,
|
|
@@ -107,43 +103,12 @@ interface HandlerContext<TState extends StateProxy = StateProxy> {
|
|
|
107
103
|
| `spawn(type, id, args?, opts?)` | `Promise<EntityHandle>` | Spawn a child entity. `opts` accepts `tags`, `observe`, `initialMessage`, and `wake`. See [`EntityHandle`](./entity-handle). |
|
|
108
104
|
| `observe(source, opts?)` | `Promise<EntityHandle \| SharedStateHandle \| ObservationHandle>` | Observe a source. Return type depends on source type: `EntityHandle` for entities, `SharedStateHandle & ObservationHandle` for db, `ObservationHandle` otherwise. Use `entity()`, `cron()`, `entities()`, `db()` helpers to build sources. |
|
|
109
105
|
| `mkdb(id, schema)` | `SharedStateHandle<T>` | Create a new shared state stream. See [`SharedStateHandle`](./shared-state-handle). |
|
|
110
|
-
| `useCodingAgent(sessionId, opts)` | `Promise<CodingSessionHandle>` | Spawn or attach to the built-in `coder` entity for a Claude Code or Codex CLI session. Requires the `coder` type to be registered. |
|
|
111
106
|
| `send(entityUrl, payload, opts?)` | `void` | Send a message to another entity. `opts` accepts `type` and `afterMs` (delay in milliseconds). |
|
|
112
107
|
| `recordRun()` | `RunHandle` | Record a non-LLM run in the built-in `runs` collection, so observers using `wake: "runFinished"` are notified when external work completes. |
|
|
113
108
|
| `setTag(key, value)` | `Promise<void>` | Set a tag on this entity. |
|
|
114
109
|
| `removeTag(key)` | `Promise<void>` | Remove a tag from this entity. |
|
|
115
110
|
| `sleep()` | `void` | End the handler without running an agent. The entity remains idle until the next wake. |
|
|
116
111
|
|
|
117
|
-
## Coding sessions
|
|
118
|
-
|
|
119
|
-
`useCodingAgent()` is a convenience wrapper around the built-in `coder` entity type.
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
type CodingAgentType = "claude" | "codex"
|
|
123
|
-
|
|
124
|
-
interface UseCodingAgentOptions {
|
|
125
|
-
agent: CodingAgentType
|
|
126
|
-
nativeSessionId?: string
|
|
127
|
-
importFrom?: { agent: CodingAgentType; sessionId: string }
|
|
128
|
-
cwd?: string
|
|
129
|
-
wake?: Wake
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface CodingSessionHandle {
|
|
133
|
-
readonly entityUrl: string
|
|
134
|
-
readonly sessionId: string
|
|
135
|
-
readonly agent: CodingAgentType
|
|
136
|
-
meta(): CodingSessionMeta | undefined
|
|
137
|
-
status(): "initializing" | "idle" | "running" | "error" | undefined
|
|
138
|
-
run: Promise<void>
|
|
139
|
-
send(prompt: string): void
|
|
140
|
-
readonly events: ReadonlyArray<CodingSessionEventRow>
|
|
141
|
-
readonly messages: ReadonlyArray<CodingSessionEventRow>
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
By default, `useCodingAgent()` observes the coder with `wake: "runFinished"`, so the caller wakes when a prompt finishes. Pass a change wake if you need per-event updates.
|
|
146
|
-
|
|
147
112
|
## RunHandle
|
|
148
113
|
|
|
149
114
|
`recordRun()` is for handlers that perform work outside `ctx.agent.run()` but still want to expose run lifecycle events.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: McpRegistry
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
|
+
description: >-
|
|
5
|
+
API reference for the MCP Registry — addServer, applyConfig,
|
|
6
|
+
subscribe, reauthorize, and the lifecycle of an MCP server entry
|
|
7
|
+
inside the runtime.
|
|
8
|
+
outline: [2, 3]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# McpRegistry
|
|
12
|
+
|
|
13
|
+
The MCP registry owns the live set of [Model Context Protocol](https://modelcontextprotocol.io) servers a runtime is connected to. It manages stdio subprocesses and HTTP clients, drives the OAuth state machine, and emits push-based snapshots so embedders can render UI without polling.
|
|
14
|
+
|
|
15
|
+
**Source:** `@electric-ax/agents-mcp` (re-exported as `McpRegistry` from `@electric-ax/agents`)
|
|
16
|
+
|
|
17
|
+
`BuiltinAgentsServer.mcpRegistry` exposes the instance that the embedded runtime owns; the [usage guide](/docs/agents/usage/mcp-servers) walks through registering servers from agent-host code.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface Registry {
|
|
21
|
+
addServer(cfg: McpServerConfig): Promise<AddServerResult>
|
|
22
|
+
applyConfig(cfg: McpConfig): Promise<AddServerResult[]>
|
|
23
|
+
removeServer(name: string): Promise<void>
|
|
24
|
+
list(): ReadonlyArray<ListedEntry>
|
|
25
|
+
get(name: string): Entry | undefined
|
|
26
|
+
finishAuth(server: string, code: string, state?: string): Promise<AddServerResult>
|
|
27
|
+
reauthorize(name: string): Promise<void>
|
|
28
|
+
disable(name: string): Promise<void>
|
|
29
|
+
enable(name: string): Promise<AddServerResult>
|
|
30
|
+
subscribe(handler: RegistrySubscriber): () => void
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Methods
|
|
35
|
+
|
|
36
|
+
| Method | Description |
|
|
37
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
38
|
+
| `addServer(cfg)` | Register or reconfigure a single server. Idempotent on unchanged `(name, url, transport, authMode, scopes, command, args)`. |
|
|
39
|
+
| `applyConfig(cfg)` | Replace the full set of registered servers. Adds new ones, reconfigures changed ones, and removes anything not in `cfg`. |
|
|
40
|
+
| `removeServer(name)` | Tear down a single server: close the transport, drop tokens from the in-memory cache, remove the entry. |
|
|
41
|
+
| `list()` | Returns the current snapshot as a plain array — same shape as the `servers` field of [`RegistrySnapshot`](#registrysnapshot). |
|
|
42
|
+
| `get(name)` | Internal lookup of a single entry, with the live `transport` handle and the resolved `provider`. Used by IPC handlers. |
|
|
43
|
+
| `finishAuth(name, code, state?)` | Complete the OAuth authorization-code flow for an `authenticating` server. Called by the embedder after intercepting the redirect URI. |
|
|
44
|
+
| `reauthorize(name)` | Force a fresh OAuth flow without removing the entry. Closes the transport, drops cached tokens (hooks remain registered), and rebuilds in place. |
|
|
45
|
+
| `disable(name)` | Pause a server. Closes the transport but keeps the entry; tokens stay in the cache. |
|
|
46
|
+
| `enable(name)` | Re-add a previously-disabled server using its last-known config. |
|
|
47
|
+
| `subscribe(handler)` | Push-based view of registry state. The handler fires synchronously with a sentinel snapshot, then on every mutation. Returns an unsubscribe function. |
|
|
48
|
+
|
|
49
|
+
## `addServer` vs `applyConfig`
|
|
50
|
+
|
|
51
|
+
Both feed the same internal pipeline; pick by what you have:
|
|
52
|
+
|
|
53
|
+
- **`addServer(cfg)`** — register one server. Use when you're adding an entry in response to a user action, a per-session tool, or a one-off integration.
|
|
54
|
+
- **`applyConfig({ servers })`** — replace the full set. Anything in the registry that isn't in `cfg.servers` is removed; existing entries with unchanged config are left alone (no transport churn). This is what the file-based loaders for `mcp.json` and the desktop `settings.json` compile down to.
|
|
55
|
+
|
|
56
|
+
Idempotency is the load-bearing property: editors save spuriously, file watchers fire double events on macOS, and most apps re-apply the same baseline on every restart. Calling `applyConfig` with the same shape twice does nothing the second time, so it's safe to wire to noisy upstreams.
|
|
57
|
+
|
|
58
|
+
## `AddServerResult`
|
|
59
|
+
|
|
60
|
+
`addServer` and `finishAuth` return a discriminated union so the caller can react without inspecting the registry afterwards:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
type AddServerResult =
|
|
64
|
+
| { state: "ready"; id: string; toolCount: number }
|
|
65
|
+
| { state: "authenticating"; id: string; authUrl: string }
|
|
66
|
+
| { state: "error"; id: string; error: McpToolError }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| State | Meaning |
|
|
70
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `ready` | Connected and tools listed; calls available at the next agent wake. |
|
|
72
|
+
| `authenticating` | OAuth required. `authUrl` is the URL to send the user to. The desktop's `openAuthorizeUrl` hook opens it in a sandboxed BrowserWindow automatically. |
|
|
73
|
+
| `error` | Connect, transport, or auth-config failure. `error.kind` and `error.message` describe what went wrong. |
|
|
74
|
+
|
|
75
|
+
`applyConfig` returns one `AddServerResult` per server in the supplied config (in the same order).
|
|
76
|
+
|
|
77
|
+
## Lifecycle of an entry
|
|
78
|
+
|
|
79
|
+
Every entry transitions through one of five statuses, surfaced on the snapshot. The states that matter to UI:
|
|
80
|
+
|
|
81
|
+
| Status | Meaning |
|
|
82
|
+
| ---------------- | ------------------------------------------------------------------------------------------------ |
|
|
83
|
+
| `connecting` | Transport is being built (DCR, HTTPS discovery) or reconnecting. |
|
|
84
|
+
| `authenticating` | OAuth flow needed; `authUrl` is set, browser window is open. |
|
|
85
|
+
| `ready` | Transport connected; tools listed and callable. |
|
|
86
|
+
| `error` | Transport or auth-config failure. The entry stays in `list()` so the UI can surface the failure. |
|
|
87
|
+
| `disabled` | Operator paused the server via `disable(name)`. Recoverable through `enable(name)`. |
|
|
88
|
+
|
|
89
|
+
Transitions are atomic with respect to subscribers: every state change fires a single snapshot in which the entry shows its new status. `reauthorize` mutates entries in place — the row never disappears from `list()`, even mid-rebuild, so renderers don't see a flicker.
|
|
90
|
+
|
|
91
|
+
## `subscribe(handler)` and `RegistrySnapshot`
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
type RegistrySubscriber = (snapshot: RegistrySnapshot) => void
|
|
95
|
+
|
|
96
|
+
interface RegistrySnapshot {
|
|
97
|
+
seq: number
|
|
98
|
+
servers: ReadonlyArray<ListedEntry>
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Subscribing is the primary way to drive a UI off the registry. The first invocation is synchronous and carries `seq: 0` as a sentinel — embedders treat it as the bootstrap snapshot, not part of the event stream. After that, every mutation increments `seq` (1, 2, 3, …) and broadcasts the full snapshot. A late subscriber still sees `seq: 0` on its first delivery; emitted events continue from the registry's current counter.
|
|
103
|
+
|
|
104
|
+
Handlers must not throw. The registry catches exceptions per subscriber so a misbehaving consumer can't break the others, but the catch is a safety net, not a feature — log and swallow inside your handler.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const off = registry.subscribe((snap) => {
|
|
108
|
+
if (snap.seq === 0) {
|
|
109
|
+
// bootstrap — render the initial list
|
|
110
|
+
} else {
|
|
111
|
+
// diff against the previous snapshot, or just re-render
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
// ...
|
|
115
|
+
off()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## `ListedEntry`
|
|
119
|
+
|
|
120
|
+
The shape of each `servers[]` entry inside a snapshot:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
interface ListedEntry {
|
|
124
|
+
name: string
|
|
125
|
+
status: McpServerStatus
|
|
126
|
+
toolCount: number
|
|
127
|
+
transport?: "http" | "stdio"
|
|
128
|
+
authMode?: "none" | "apiKey" | "clientCredentials" | "authorizationCode"
|
|
129
|
+
authUrl?: string
|
|
130
|
+
error?: McpToolError
|
|
131
|
+
tools: Array<{ name: string; description?: string; inputSchema: unknown }>
|
|
132
|
+
capabilities?: unknown
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Field | Type | Description |
|
|
137
|
+
| -------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
138
|
+
| `name` | `string` | The server's stable identifier. |
|
|
139
|
+
| `status` | `McpServerStatus` | Current lifecycle state — see the table above. |
|
|
140
|
+
| `toolCount` | `number` | Number of tools the server advertises. `0` until `status === "ready"`. |
|
|
141
|
+
| `transport` | `"http" \| "stdio"` | The transport variant in use. |
|
|
142
|
+
| `authMode` | `string` | `none` / `apiKey` / `clientCredentials` / `authorizationCode`. UI badges + "show Authorize" check use this. |
|
|
143
|
+
| `authUrl` | `string` | Set while `status === "authenticating"`. The URL to open for OAuth consent. |
|
|
144
|
+
| `error` | [`McpToolError`](#mcptoolerror) | Set while `status === "error"`. |
|
|
145
|
+
| `tools` | `Array<{ name; description?; inputSchema }>` | Tool metadata as advertised by the server. Each becomes `mcp__<server>__<tool>` for the LLM. |
|
|
146
|
+
| `capabilities` | `unknown` | Server-declared MCP capabilities object (resources, prompts, etc.). |
|
|
147
|
+
|
|
148
|
+
## `McpToolError`
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
interface McpToolError {
|
|
152
|
+
kind: McpToolErrorKind
|
|
153
|
+
message: string
|
|
154
|
+
details?: unknown
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type McpToolErrorKind =
|
|
158
|
+
| "auth_unavailable"
|
|
159
|
+
| "transport_error"
|
|
160
|
+
| "timeout"
|
|
161
|
+
| "server_error"
|
|
162
|
+
| "tool_not_found"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The same shape surfaces on entry-level `error` (when `addServer` fails to connect) and on individual tool calls. See the [usage guide's failure modes table](/docs/agents/usage/mcp-servers#failure-modes).
|
|
166
|
+
|
|
167
|
+
## `RegistryOpts`
|
|
168
|
+
|
|
169
|
+
`BuiltinAgentsServer` constructs the registry on your behalf. You only see this shape if you instantiate `agents-mcp` directly (e.g. from a custom embedder):
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
interface RegistryOpts {
|
|
173
|
+
publicUrl?: string
|
|
174
|
+
openAuthorizeUrl?: (url: string, server: string) => void
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createRegistry(opts: RegistryOpts): Registry
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
| Field | Description |
|
|
181
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
182
|
+
| `publicUrl` | Base URL used to construct the OAuth `redirect_uri` (full URI is `<publicUrl>/oauth/callback/<server-name>`). MUST be stable across restarts — DCR registers it with the auth server and persists it in the keychain, so a value that drifts forces re-authorization on every launch. Embedders that listen on an ephemeral port should pass a fixed loopback literal (the desktop uses `http://127.0.0.1:53117`); nothing actually listens at the URL — the embedder's BrowserWindow intercepts the redirect by prefix. |
|
|
183
|
+
| `openAuthorizeUrl` | Hook invoked when an `authorizationCode` server first needs consent. Receives the SDK-generated authorize URL. The desktop opens it in a sandboxed `BrowserWindow`; headless embedders can read the URL from the `authenticating` envelope of `addServer` and surface it themselves. |
|
|
184
|
+
|
|
185
|
+
## See also
|
|
186
|
+
|
|
187
|
+
- [MCP servers usage guide](/docs/agents/usage/mcp-servers) — the practical walkthrough of registering servers, OAuth, persistence, and the per-agent allowlist.
|
|
188
|
+
- [`McpServerConfig`](/docs/agents/reference/mcp-server-config) — schema for the `cfg` argument to `addServer` / `applyConfig`.
|
|
189
|
+
- [`BuiltinAgentsServer`](/docs/agents/usage/embedded-builtins) — host options that affect MCP, including `extraMcpServers` and `openAuthorizeUrl`.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: McpServerConfig
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
|
+
description: >-
|
|
5
|
+
Schema reference for MCP server entries — transports, auth modes, and
|
|
6
|
+
persistence callbacks accepted by Registry.addServer, applyConfig, and
|
|
7
|
+
the mcp.json / settings.json layers.
|
|
8
|
+
outline: [2, 3]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# McpServerConfig
|
|
12
|
+
|
|
13
|
+
Shape of a server entry in the MCP registry. Identical between declarative (`mcp.json`, desktop `settings.json`) and programmatic (`Registry.addServer`) creation paths.
|
|
14
|
+
|
|
15
|
+
**Source:** `@electric-ax/agents` (re-exported from `@electric-ax/agents-mcp`)
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
type McpServerConfig = McpHttpServerConfig | McpStdioServerConfig
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The variant is selected by the `transport` field.
|
|
22
|
+
|
|
23
|
+
## McpHttpServerConfig
|
|
24
|
+
|
|
25
|
+
Streamable HTTP transport per the current MCP spec.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
interface McpHttpServerConfig {
|
|
29
|
+
name: string
|
|
30
|
+
transport: "http"
|
|
31
|
+
url: string
|
|
32
|
+
auth: McpAuthConfig
|
|
33
|
+
timeoutMs?: number
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Field | Type | Required | Description |
|
|
38
|
+
| ----------- | --------------- | -------- | ---------------------------------------------------------------------------- |
|
|
39
|
+
| `name` | `string` | Yes | Stable identifier. Used as the keychain account, the IPC verb argument, and the `mcp__<name>__<tool>` tool prefix. |
|
|
40
|
+
| `transport` | `"http"` | Yes | Discriminator selecting the HTTP variant. |
|
|
41
|
+
| `url` | `string` | Yes | The MCP server's HTTPS endpoint. Streamable transport with SSE inside. |
|
|
42
|
+
| `auth` | `McpAuthConfig` | Yes | One of the auth modes below. Use `{ mode: "none" }` for unauthenticated servers. |
|
|
43
|
+
| `timeoutMs` | `number` | No | Per-call timeout override. Default `30000` (30 seconds). |
|
|
44
|
+
|
|
45
|
+
## McpStdioServerConfig
|
|
46
|
+
|
|
47
|
+
Locally-spawned subprocess speaking the MCP stdio protocol.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
interface McpStdioServerConfig {
|
|
51
|
+
name: string
|
|
52
|
+
transport: "stdio"
|
|
53
|
+
command: string
|
|
54
|
+
args?: string[]
|
|
55
|
+
env?: Record<string, string>
|
|
56
|
+
auth?: McpAuthConfig
|
|
57
|
+
timeoutMs?: number
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Field | Type | Required | Description |
|
|
62
|
+
| ----------- | ------------------------ | -------- | ------------------------------------------------------------------------ |
|
|
63
|
+
| `name` | `string` | Yes | Stable identifier (see HTTP variant). |
|
|
64
|
+
| `transport` | `"stdio"` | Yes | Discriminator selecting the stdio variant. |
|
|
65
|
+
| `command` | `string` | Yes | Executable name or absolute path. Resolved against `PATH`. |
|
|
66
|
+
| `args` | `string[]` | No | CLI arguments. `${workspaceRoot}` is the only built-in expansion. |
|
|
67
|
+
| `env` | `Record<string, string>` | No | Environment variables for the subprocess. Inherits the parent's `PATH`. |
|
|
68
|
+
| `auth` | `McpAuthConfig` | No | Typically `{ mode: "none" }`. Defaults to `none` when omitted. |
|
|
69
|
+
| `timeoutMs` | `number` | No | Per-call timeout override. Default `30000`. |
|
|
70
|
+
|
|
71
|
+
The subprocess is spawned lazily on the first tool call, kept alive across calls, and restarted on crash. One process per server, multiplexed via JSON-RPC `id`.
|
|
72
|
+
|
|
73
|
+
## McpAuthConfig
|
|
74
|
+
|
|
75
|
+
Discriminated union covering the four supported credential modes.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type McpAuthConfig =
|
|
79
|
+
| { mode: "none" }
|
|
80
|
+
| ApiKeyAuth
|
|
81
|
+
| ClientCredentialsAuth
|
|
82
|
+
| AuthorizationCodeAuth
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `none`
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
{ mode: "none" }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
No authentication. Required for stdio servers that don't need credentials; rare for HTTP servers.
|
|
92
|
+
|
|
93
|
+
### `apiKey`
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
interface ApiKeyAuth {
|
|
97
|
+
mode: "apiKey"
|
|
98
|
+
key: string
|
|
99
|
+
headerName?: string // default "Authorization"
|
|
100
|
+
valuePrefix?: string // e.g. "Bearer "
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Field | Type | Required | Description |
|
|
105
|
+
| ------------- | -------- | -------- | ---------------------------------------------------------------------------- |
|
|
106
|
+
| `key` | `string` | Yes | Raw secret. Inline at the call site (e.g. `process.env.X_API_KEY`). |
|
|
107
|
+
| `headerName` | `string` | No | HTTP header name. Defaults to `Authorization`. |
|
|
108
|
+
| `valuePrefix` | `string` | No | Prepended to the key. Use `"Bearer "` for bearer tokens; empty for raw keys. |
|
|
109
|
+
|
|
110
|
+
### `clientCredentials`
|
|
111
|
+
|
|
112
|
+
OAuth 2.1 client-credentials grant. Unattended; no user interaction.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
interface ClientCredentialsAuth {
|
|
116
|
+
mode: "clientCredentials"
|
|
117
|
+
tokenUrl: string
|
|
118
|
+
clientId: string
|
|
119
|
+
clientSecret: string
|
|
120
|
+
scopes?: string[]
|
|
121
|
+
audience?: string
|
|
122
|
+
resource?: string
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Field | Type | Required | Description |
|
|
127
|
+
| -------------- | ---------- | -------- | --------------------------------------------------------------------------- |
|
|
128
|
+
| `tokenUrl` | `string` | Yes | OAuth token endpoint. |
|
|
129
|
+
| `clientId` | `string` | Yes | Inline at the call site (e.g. `process.env.X_CLIENT_ID`). |
|
|
130
|
+
| `clientSecret` | `string` | Yes | Inline at the call site (e.g. `process.env.X_CLIENT_SECRET`). |
|
|
131
|
+
| `scopes` | `string[]` | No | Requested OAuth scopes. |
|
|
132
|
+
| `audience` | `string` | No | Auth0/OIDC `audience` claim, when the auth server requires it. |
|
|
133
|
+
| `resource` | `string` | No | RFC 8707 resource indicator. |
|
|
134
|
+
|
|
135
|
+
The runtime exchanges the credentials for a short-lived access token, retries the exchange on 401, and never surfaces user-facing errors during steady state.
|
|
136
|
+
|
|
137
|
+
### `authorizationCode`
|
|
138
|
+
|
|
139
|
+
OAuth 2.1 authorization-code grant with PKCE. Requires a one-time browser flow per user.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
interface AuthorizationCodeAuth {
|
|
143
|
+
mode: "authorizationCode"
|
|
144
|
+
scopes?: string[]
|
|
145
|
+
resource?: string
|
|
146
|
+
redirectUri?: string
|
|
147
|
+
client?: OAuthClientInfo
|
|
148
|
+
tokens?: OAuthTokens
|
|
149
|
+
onTokensChanged?: (tokens: OAuthTokens) => void | Promise<void>
|
|
150
|
+
onClientRegistered?: (client: OAuthClientInfo) => void | Promise<void>
|
|
151
|
+
oauthProviderRef?: string
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Field | Type | Required | Description |
|
|
156
|
+
| -------------------- | --------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `scopes` | `string[]` | No | Requested OAuth scopes. |
|
|
158
|
+
| `resource` | `string` | No | RFC 8707 resource indicator. |
|
|
159
|
+
| `redirectUri` | `string` | No | Override the default `${publicUrl}/oauth/callback/<server>` sentinel. Most embedders don't need this. |
|
|
160
|
+
| `client` | `OAuthClientInfo` | No | Pre-registered OAuth client (`client_id`, optional `client_secret`). When present, RFC 7591 Dynamic Client Registration is skipped. |
|
|
161
|
+
| `tokens` | `OAuthTokens` | No | Pre-existing tokens to seed the in-process cache. The OAuth flow is skipped on boot; refresh-token rotation still happens transparently. |
|
|
162
|
+
| `onTokensChanged` | `(tokens) => void \| Promise<void>` | No | Fires after initial auth and on every refresh. Wire to a persistence layer if tokens should survive process restarts. |
|
|
163
|
+
| `onClientRegistered` | `(client) => void \| Promise<void>` | No | Fires once after RFC 7591 DCR completes. Pair with `client` on the next boot to skip DCR. |
|
|
164
|
+
| `oauthProviderRef` | `string` | No | Reference into a per-process map of pre-built `OAuthClientProvider` instances. Escape hatch for mTLS, OIDC quirks, etc. |
|
|
165
|
+
|
|
166
|
+
### `OAuthTokens`
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
interface OAuthTokens {
|
|
170
|
+
accessToken: string
|
|
171
|
+
refreshToken?: string
|
|
172
|
+
expiresAt?: number // Unix seconds
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `OAuthClientInfo`
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
interface OAuthClientInfo {
|
|
180
|
+
clientId: string
|
|
181
|
+
clientSecret?: string
|
|
182
|
+
// RFC 7591 metadata (issued_at, expires_at, redirect_uris, …)
|
|
183
|
+
// — preserved verbatim from the DCR response.
|
|
184
|
+
[key: string]: unknown
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Validation
|
|
189
|
+
|
|
190
|
+
`mcp.json` and programmatic configs are validated when first applied:
|
|
191
|
+
|
|
192
|
+
- `transport` must be `"http"` or `"stdio"`.
|
|
193
|
+
- `auth.mode` must be one of `none` / `apiKey` / `clientCredentials` / `authorizationCode`.
|
|
194
|
+
- `mcp.json` rejects forbidden reference keys (`clientIdRef`, `clientSecretRef`, `valueRef`, …) — secrets must be passed inline at the call site, not declared as references.
|
|
195
|
+
- Inline `${VAR}` placeholders in `mcp.json` are expanded against `process.env` before validation.
|
|
196
|
+
|
|
197
|
+
Invalid entries surface as `error`-state entries in `Registry.list()` with a structured `McpToolError`. The rest of the registry continues to operate.
|
|
198
|
+
|
|
199
|
+
## Persistence helpers
|
|
200
|
+
|
|
201
|
+
Two opt-in helpers from `@electric-ax/agents-mcp` produce auth-config slices that satisfy the persistence callback contract:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { keychainPersistence, filePersistence } from "@electric-ax/agents-mcp"
|
|
205
|
+
|
|
206
|
+
const tokens = await keychainPersistence({ server: "honeycomb" })
|
|
207
|
+
// → { tokens?, client?, onTokensChanged, onClientRegistered }
|
|
208
|
+
|
|
209
|
+
await registry.addServer({
|
|
210
|
+
name: "honeycomb",
|
|
211
|
+
transport: "http",
|
|
212
|
+
url: "https://mcp.honeycomb.io/mcp",
|
|
213
|
+
auth: { mode: "authorizationCode", scopes: ["mcp:read"], ...tokens },
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
| Helper | Backing store | Notes |
|
|
218
|
+
| ----------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
219
|
+
| `keychainPersistence({ server })` | macOS `security`, Linux `secret-tool`. Throws on Windows. | Service `electric-agents`, accounts `tokens:<server>` / `client:<server>`. |
|
|
220
|
+
| `filePersistence({ path, server })` | Mode-`0600` JSON file. Refuses to read files with looser permissions. | Use for CI / containers without an OS keychain. |
|
|
221
|
+
|
|
222
|
+
## See also
|
|
223
|
+
|
|
224
|
+
- [MCP servers usage guide](/docs/agents/usage/mcp-servers) — programmatic, file-based, and desktop-settings paths end-to-end.
|
|
225
|
+
- [`McpRegistry`](/docs/agents/reference/mcp-registry) — the API that consumes this config (`addServer` / `applyConfig` / lifecycle).
|
|
226
|
+
- [`BuiltinAgentsServer`](/docs/agents/usage/embedded-builtins) — host options that affect MCP, including `extraMcpServers` and `openAuthorizeUrl`.
|
|
@@ -35,9 +35,6 @@ const membersDb = await client.observe(
|
|
|
35
35
|
)
|
|
36
36
|
console.log(membersDb.collections.members.toArray)
|
|
37
37
|
|
|
38
|
-
// Observe a built-in coder session by id.
|
|
39
|
-
const coderDb = await client.observe(codingSession("feature-work"))
|
|
40
|
-
console.log(coderDb.collections.events.toArray)
|
|
41
38
|
```
|
|
42
39
|
|
|
43
40
|
### Types
|
|
@@ -68,7 +65,6 @@ The same source helpers used by `ctx.observe()` can be used with `AgentsClient`.
|
|
|
68
65
|
| Helper | Use case |
|
|
69
66
|
| ------------------- | ---------------------------------------------------- |
|
|
70
67
|
| `entity(url)` | Observe one entity by URL. |
|
|
71
|
-
| `codingSession(id)` | Observe a built-in `coder` entity by session id. |
|
|
72
68
|
| `entities({ tags })` | Observe the entity membership stream matching tags. |
|
|
73
69
|
| `db(id, schema)` | Observe a shared-state stream. |
|
|
74
70
|
| `cron(expression)` | Build a cron source for wake subscriptions. |
|