@forgewisp/mcp 0.5.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.
@@ -0,0 +1,230 @@
1
+ import { FunctionDefinition, RiskTier } from '@forgewisp/core';
2
+ export { FunctionDefinition, JSONSchema, JSONSchemaProperty, RiskTier, ToolContext } from '@forgewisp/core';
3
+ import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
4
+ export { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
5
+ export { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
6
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
7
+
8
+ /**
9
+ * Structural view of a Forgewisp agent: the two methods the MCP integration needs. The real
10
+ * `ForgewispAgent` (from `createAgent`) satisfies this structurally, so consumers pass their
11
+ * agent instance directly: `await registerMcpServer(agent, { ... })`. Declaring a structural
12
+ * subset (rather than importing the not-exported `ForgewispAgent` class) keeps this package
13
+ * decoupled from core's internals — same technique `apps/bundled-demo` uses for its `ToolMeta`.
14
+ */
15
+ interface AgentLike {
16
+ /** Registers a tool. Throws at registration time for `write`/`destructive` tools when the
17
+ * agent's `onConfirmRequired` is unset (core enforces this — see `packages/core/src/agent.ts`). */
18
+ registerFunction(def: FunctionDefinition): void;
19
+ /** Deregisters a tool by name. No-op if unknown. */
20
+ deregisterFunction(name: string): void;
21
+ }
22
+ /**
23
+ * Configuration for connecting an MCP server and adapting its tools into Forgewisp
24
+ * `FunctionDefinition`s.
25
+ */
26
+ interface McpServerConfig {
27
+ /**
28
+ * Logical server name. Used as the default `toolNamePrefix` and as the audit/UI attribution
29
+ * suffix on each tool's description. Also identifies the server in error messages.
30
+ */
31
+ name: string;
32
+ /** Streamable HTTP endpoint URL of the MCP server. */
33
+ url: string;
34
+ /**
35
+ * Optional bearer token sent to the MCP endpoint as `Authorization: Bearer <apiKey>`. The
36
+ * header is omitted entirely when unset, so no-auth/proxy endpoints work — mirrors core's
37
+ * `HttpClient` (which only sends `Authorization` when an `apiKey` is configured). Mutually
38
+ * exclusive with `authProvider`; when both are set, `authProvider` wins and `apiKey` is ignored.
39
+ * Use this when you already hold an access token; use `authProvider` when the server requires a
40
+ * full OAuth flow to *obtain* one.
41
+ */
42
+ apiKey?: string;
43
+ /**
44
+ * OAuth client provider. When set, takes precedence over `apiKey` and drives the full OAuth 2.1
45
+ * authorization-code + PKCE flow via the SDK: RFC 9728 protected-resource discovery, RFC 8414
46
+ * authorization-server metadata discovery, dynamic client registration (RFC 7591) when the
47
+ * server supports it, token exchange, and refresh. The consumer owns browser-redirect plumbing
48
+ * and token storage by implementing `OAuthClientProvider`.
49
+ *
50
+ * On the first connect, if the server requires auth and no usable token exists, the SDK calls
51
+ * `authProvider.redirectToAuthorization(url)` and the connect returns `authState: 'pending'`
52
+ * with an empty `tools` array (instead of throwing). The consumer completes the redirect-back and
53
+ * calls `finishAuth(code)` on the handle (or, after a page reload, calls
54
+ * `createMcpTools`/`registerMcpServer` again with `options.authorizationCode`). See `mcp.ts` for
55
+ * why a fresh transport is built on resume (the SDK cannot re-`connect` a spent transport).
56
+ */
57
+ authProvider?: OAuthClientProvider;
58
+ /** Per-call timeout for MCP `listTools`/`callTool` requests, in milliseconds. Default 60_000. */
59
+ requestTimeoutMs?: number;
60
+ /** Prefix for the registered (prefixed) tool names. Default: `config.name`. */
61
+ toolNamePrefix?: string;
62
+ /** Default risk tier for tools not listed in `tierOverrides`. Default: `'read'`. */
63
+ defaultTier?: RiskTier;
64
+ /** Map of original MCP tool name → risk tier (overrides `defaultTier` for that tool). */
65
+ tierOverrides?: Record<string, RiskTier>;
66
+ /** Allowlist of original MCP tool names to register; all others are skipped. */
67
+ onlyTools?: string[];
68
+ /** Blocklist of original MCP tool names to skip. */
69
+ excludeTools?: string[];
70
+ /**
71
+ * Whether the agent has `onConfirmRequired` configured. Required to be `true` when any
72
+ * resolved tool tier is `write` or `destructive`; `registerMcpServer` throws a clear error
73
+ * up front (before registering anything) otherwise — so a tier mismatch never leaves a
74
+ * partially-registered server. Core's own registration-time invariant still fires as a
75
+ * backstop inside `agent.registerFunction`.
76
+ */
77
+ hasConfirmation?: boolean;
78
+ }
79
+ /** Handle returned by `registerMcpServer` for managing a server's lifecycle. */
80
+ interface McpServerHandle {
81
+ /** The prefixed tool names that were registered on the agent. Empty while `authState` is
82
+ * `'pending'` (tools are not listed until auth completes). */
83
+ toolNames: string[];
84
+ /** Authorization state. `'pending'` means the server requires OAuth and the consumer must complete
85
+ * the redirect-back via `finishAuth` (or resume with `options.authorizationCode`). */
86
+ authState: McpAuthState;
87
+ /**
88
+ * Complete the OAuth redirect-back: exchange `authorizationCode` for tokens (via the provider),
89
+ * build a fresh transport, reconnect, list the server's tools, run the `hasConfirmation`
90
+ * preflight, and register the tools on the agent. Mutates `toolNames`/`authState` in place.
91
+ *
92
+ * Rejects if already authorized or closed, or if the post-auth preflight fails (in which case
93
+ * nothing remains registered). Idempotent guard: a second `finishAuth` is a no-op.
94
+ */
95
+ finishAuth(authorizationCode: string): Promise<void>;
96
+ /** Deregisters all of this server's tools from the agent and disconnects from the MCP server. */
97
+ close(): Promise<void>;
98
+ }
99
+ /** Authorization state of a connected MCP server. */
100
+ type McpAuthState = 'authorized' | 'pending';
101
+ /**
102
+ * Options for `createMcpTools` / `registerMcpServer`.
103
+ */
104
+ interface McpConnectOptions {
105
+ /**
106
+ * Resume path: an authorization code obtained from the redirect-back after a page reload (or a
107
+ * blocked-popup same-tab navigation). When set alongside `config.authProvider`, the code is
108
+ * exchanged for tokens on a fresh transport *before* connecting, so the (re)connect reads the
109
+ * saved token and returns `authState: 'authorized'` directly — skipping the pending state.
110
+ * Ignored when `authProvider` is not set.
111
+ */
112
+ authorizationCode?: string;
113
+ /**
114
+ * @internal Test seam to inject a non-HTTP transport (e.g. `InMemoryTransport` or an OAuth-gated
115
+ * fake), so tests never dynamic-import `StreamableHTTPClientTransport`. May be a single
116
+ * `Transport` (used once for the initial connect — fine for non-OAuth tests, where `finishAuth`
117
+ * is never called) or a factory `() => Transport | Promise<Transport>` invoked each time a
118
+ * transport is needed (initial connect AND each `finishAuth` reconnect), which is how OAuth
119
+ * tests supply a fresh gated transport that shares one in-memory pipe. Not part of the stable
120
+ * public contract.
121
+ */
122
+ transport?: Transport | (() => Transport | Promise<Transport>);
123
+ }
124
+
125
+ /**
126
+ * MCP → Forgewisp adapter.
127
+ *
128
+ * Connects to an MCP server over the Streamable HTTP transport, lists its tools, and adapts
129
+ * each into a Forgewisp `FunctionDefinition` that is registered through the agent's existing
130
+ * `registerFunction` path. Because registration goes through core unchanged, the registry,
131
+ * Ajv validation, the two-phase executor, the `onConfirmRequired` confirmation invariant, the
132
+ * audit log, and `runToolLoop` all apply to MCP tools for free — MCP becomes just another source
133
+ * of `FunctionDefinition`s.
134
+ *
135
+ * The adapter is layered so the mapping is unit-testable without a network:
136
+ * - `adaptMcpTool` / `adaptMcpTools` — pure given a `McpClient` (the test seam; a plain fake
137
+ * object implementing `callTool` is enough).
138
+ * - `connectMcpServer` — builds the SDK `Client` + `StreamableHTTPClientTransport`.
139
+ * - `createMcpTools` / `registerMcpServer` — the public wiring.
140
+ *
141
+ * ── Schema handling ──────────────────────────────────────────────────────────
142
+ * MCP `inputSchema` is full draft-07 JSON Schema. Forgewisp's `JSONSchema` type (in core) is a
143
+ * deliberately narrow subset enforcing strict hand-authored schemas in `@forgewisp/bundled-tools`;
144
+ * we do NOT widen it. The adapter casts the raw `inputSchema` to `JSONSchema` at the boundary.
145
+ * Runtime is correct: core's Ajv is `strict: false` and validates draft-07 (`$ref`/`enum`/`oneOf`
146
+ * etc.), the compiled validator is cached per schema-object identity, and the wire payload sent
147
+ * to the LLM carries the full schema, which OpenAI-compatible models accept. Limitation: any
148
+ * external `$ref` (not present in well-formed MCP schemas) won't resolve.
149
+ *
150
+ * ── Risk tiers ────────────────────────────────────────────────────────────────
151
+ * MCP has no risk-tier concept. Tiers come entirely from `McpServerConfig` (`defaultTier` +
152
+ * `tierOverrides`); MCP `annotations.readOnlyHint`/`destructiveHint` are informational only and
153
+ * deliberately NOT auto-mapped — the consumer owns the security boundary.
154
+ *
155
+ * ── Abort ─────────────────────────────────────────────────────────────────────
156
+ * The parent run's `AbortSignal` is threaded into handlers by core's executor via `ToolContext`.
157
+ * The handler forwards it to `client.callTool` (merged with a per-server timeout), so aborting
158
+ * the parent run aborts in-flight MCP calls.
159
+ *
160
+ * ── OAuth ─────────────────────────────────────────────────────────────────────
161
+ * `McpServerConfig.authProvider` (an SDK `OAuthClientProvider`) takes precedence over `apiKey` and
162
+ * drives the full OAuth 2.1 authorization-code + PKCE flow (RFC 9728 / 8414 discovery, dynamic client
163
+ * registration, token exchange, refresh) via the SDK transport — the consumer only implements the
164
+ * provider (browser-redirect plumbing + token storage). `client/auth.js` is dynamically imported
165
+ * only on the OAuth path; non-OAuth consumers never load it.
166
+ *
167
+ * State machine: the first `connect` that needs auth has the SDK call
168
+ * `authProvider.redirectToAuthorization(url)` and then throw `UnauthorizedError` from `connect`.
169
+ * `Client.connect` catches that, calls `void this.close()`, and rethrows. The transport is now
170
+ * *spent* — `close()` aborts its `AbortController` but does not null it, so a second `start()`
171
+ * throws "already started". **The same transport cannot be re-`connect`ed.** So this adapter
172
+ * catches `UnauthorizedError` (via `instanceof`, dynamically importing the class only on this path —
173
+ * the SDK's error doesn't set a custom `name`, so a name check won't work) and returns
174
+ * `authState: 'pending'` (empty tools) instead of throwing, keeping the spent transport+client only
175
+ * long enough to call `transport.finishAuth(code)` (which just calls `auth()` to exchange the code
176
+ * and save tokens into the provider — it does not require `start()`). `finishAuth` then builds a
177
+ * **fresh** transport + client whose `connect` reads the saved token via `provider.tokens()` and
178
+ * succeeds. The same "finishAuth-then-fresh-connect" pattern powers the resume path
179
+ * (`options.authorizationCode`), which exchanges the code on a single fresh transport *before*
180
+ * connecting.
181
+ *
182
+ * For `registerMcpServer`, the `hasConfirmation` preflight runs only once tools are listed — i.e.
183
+ * after auth completes (deferred when `authState` is `'pending'`), mirroring the non-OAuth path.
184
+ */
185
+
186
+ /** Shape returned by `createMcpTools`: the adapted tools, a closer, and (for OAuth) the auth flow. */
187
+ interface McpToolsResult {
188
+ /** The adapted `FunctionDefinition`s. Empty while `authState` is `'pending'`; mutated in place by
189
+ * `finishAuth` once auth completes, so the caller's reference stays valid. */
190
+ tools: FunctionDefinition[];
191
+ /** Authorization state. `'pending'` means the server requires OAuth and `finishAuth` (or a
192
+ * resume via `options.authorizationCode`) is needed before tools are available. */
193
+ authState: McpAuthState;
194
+ /** Disconnects the underlying client. Idempotent. */
195
+ close: () => Promise<void>;
196
+ /** Completes the OAuth redirect-back. See `McpServerHandle.finishAuth`. */
197
+ finishAuth: (authorizationCode: string) => Promise<void>;
198
+ }
199
+ /**
200
+ * Connect to an MCP server, list its tools, and adapt them into `FunctionDefinition`s without
201
+ * touching any agent. Useful for custom registration or `ToolSet` composition. The returned
202
+ * `close()` disconnects the underlying client.
203
+ *
204
+ * OAuth: when `config.authProvider` is set and the server requires auth, the first connect returns
205
+ * `authState: 'pending'` with an empty `tools` array (the SDK already redirected the user agent).
206
+ * Call `finishAuth(code)` after the redirect-back to exchange the code and populate `tools`, or pass
207
+ * `options.authorizationCode` to resume in one shot after a page reload.
208
+ *
209
+ * @internal `options.transport` is a test seam to inject a non-HTTP transport; not part of the
210
+ * stable public contract.
211
+ */
212
+ declare function createMcpTools(config: McpServerConfig, options?: McpConnectOptions): Promise<McpToolsResult>;
213
+ /**
214
+ * Connect to an MCP server and register all of its (adapted) tools on the given agent. Returns an
215
+ * `McpServerHandle` whose `close()` deregisters every tool and disconnects.
216
+ *
217
+ * Confirmation preflight: before registering anything, if any adapted tool resolves to
218
+ * `write`/`destructive` and `config.hasConfirmation` is not `true`, the client is closed and a
219
+ * clear error is thrown — so a tier mismatch never leaves a partially-registered server. Core's
220
+ * own registration-time invariant still fires as a backstop inside `agent.registerFunction`.
221
+ *
222
+ * OAuth: when the first connect returns `authState: 'pending'`, no tools are registered yet and the
223
+ * preflight is deferred until `handle.finishAuth(code)` lists the tools. If the post-auth preflight
224
+ * fails, `finishAuth` deregisters anything registered, disconnects, and throws the same error.
225
+ *
226
+ * @internal `options.transport` is a test seam to inject a non-HTTP transport.
227
+ */
228
+ declare function registerMcpServer(agent: AgentLike, config: McpServerConfig, options?: McpConnectOptions): Promise<McpServerHandle>;
229
+
230
+ export { type AgentLike, type McpAuthState, type McpConnectOptions, type McpServerConfig, type McpServerHandle, type McpToolsResult, createMcpTools, registerMcpServer };
@@ -0,0 +1,230 @@
1
+ import { FunctionDefinition, RiskTier } from '@forgewisp/core';
2
+ export { FunctionDefinition, JSONSchema, JSONSchemaProperty, RiskTier, ToolContext } from '@forgewisp/core';
3
+ import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
4
+ export { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
5
+ export { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
6
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
7
+
8
+ /**
9
+ * Structural view of a Forgewisp agent: the two methods the MCP integration needs. The real
10
+ * `ForgewispAgent` (from `createAgent`) satisfies this structurally, so consumers pass their
11
+ * agent instance directly: `await registerMcpServer(agent, { ... })`. Declaring a structural
12
+ * subset (rather than importing the not-exported `ForgewispAgent` class) keeps this package
13
+ * decoupled from core's internals — same technique `apps/bundled-demo` uses for its `ToolMeta`.
14
+ */
15
+ interface AgentLike {
16
+ /** Registers a tool. Throws at registration time for `write`/`destructive` tools when the
17
+ * agent's `onConfirmRequired` is unset (core enforces this — see `packages/core/src/agent.ts`). */
18
+ registerFunction(def: FunctionDefinition): void;
19
+ /** Deregisters a tool by name. No-op if unknown. */
20
+ deregisterFunction(name: string): void;
21
+ }
22
+ /**
23
+ * Configuration for connecting an MCP server and adapting its tools into Forgewisp
24
+ * `FunctionDefinition`s.
25
+ */
26
+ interface McpServerConfig {
27
+ /**
28
+ * Logical server name. Used as the default `toolNamePrefix` and as the audit/UI attribution
29
+ * suffix on each tool's description. Also identifies the server in error messages.
30
+ */
31
+ name: string;
32
+ /** Streamable HTTP endpoint URL of the MCP server. */
33
+ url: string;
34
+ /**
35
+ * Optional bearer token sent to the MCP endpoint as `Authorization: Bearer <apiKey>`. The
36
+ * header is omitted entirely when unset, so no-auth/proxy endpoints work — mirrors core's
37
+ * `HttpClient` (which only sends `Authorization` when an `apiKey` is configured). Mutually
38
+ * exclusive with `authProvider`; when both are set, `authProvider` wins and `apiKey` is ignored.
39
+ * Use this when you already hold an access token; use `authProvider` when the server requires a
40
+ * full OAuth flow to *obtain* one.
41
+ */
42
+ apiKey?: string;
43
+ /**
44
+ * OAuth client provider. When set, takes precedence over `apiKey` and drives the full OAuth 2.1
45
+ * authorization-code + PKCE flow via the SDK: RFC 9728 protected-resource discovery, RFC 8414
46
+ * authorization-server metadata discovery, dynamic client registration (RFC 7591) when the
47
+ * server supports it, token exchange, and refresh. The consumer owns browser-redirect plumbing
48
+ * and token storage by implementing `OAuthClientProvider`.
49
+ *
50
+ * On the first connect, if the server requires auth and no usable token exists, the SDK calls
51
+ * `authProvider.redirectToAuthorization(url)` and the connect returns `authState: 'pending'`
52
+ * with an empty `tools` array (instead of throwing). The consumer completes the redirect-back and
53
+ * calls `finishAuth(code)` on the handle (or, after a page reload, calls
54
+ * `createMcpTools`/`registerMcpServer` again with `options.authorizationCode`). See `mcp.ts` for
55
+ * why a fresh transport is built on resume (the SDK cannot re-`connect` a spent transport).
56
+ */
57
+ authProvider?: OAuthClientProvider;
58
+ /** Per-call timeout for MCP `listTools`/`callTool` requests, in milliseconds. Default 60_000. */
59
+ requestTimeoutMs?: number;
60
+ /** Prefix for the registered (prefixed) tool names. Default: `config.name`. */
61
+ toolNamePrefix?: string;
62
+ /** Default risk tier for tools not listed in `tierOverrides`. Default: `'read'`. */
63
+ defaultTier?: RiskTier;
64
+ /** Map of original MCP tool name → risk tier (overrides `defaultTier` for that tool). */
65
+ tierOverrides?: Record<string, RiskTier>;
66
+ /** Allowlist of original MCP tool names to register; all others are skipped. */
67
+ onlyTools?: string[];
68
+ /** Blocklist of original MCP tool names to skip. */
69
+ excludeTools?: string[];
70
+ /**
71
+ * Whether the agent has `onConfirmRequired` configured. Required to be `true` when any
72
+ * resolved tool tier is `write` or `destructive`; `registerMcpServer` throws a clear error
73
+ * up front (before registering anything) otherwise — so a tier mismatch never leaves a
74
+ * partially-registered server. Core's own registration-time invariant still fires as a
75
+ * backstop inside `agent.registerFunction`.
76
+ */
77
+ hasConfirmation?: boolean;
78
+ }
79
+ /** Handle returned by `registerMcpServer` for managing a server's lifecycle. */
80
+ interface McpServerHandle {
81
+ /** The prefixed tool names that were registered on the agent. Empty while `authState` is
82
+ * `'pending'` (tools are not listed until auth completes). */
83
+ toolNames: string[];
84
+ /** Authorization state. `'pending'` means the server requires OAuth and the consumer must complete
85
+ * the redirect-back via `finishAuth` (or resume with `options.authorizationCode`). */
86
+ authState: McpAuthState;
87
+ /**
88
+ * Complete the OAuth redirect-back: exchange `authorizationCode` for tokens (via the provider),
89
+ * build a fresh transport, reconnect, list the server's tools, run the `hasConfirmation`
90
+ * preflight, and register the tools on the agent. Mutates `toolNames`/`authState` in place.
91
+ *
92
+ * Rejects if already authorized or closed, or if the post-auth preflight fails (in which case
93
+ * nothing remains registered). Idempotent guard: a second `finishAuth` is a no-op.
94
+ */
95
+ finishAuth(authorizationCode: string): Promise<void>;
96
+ /** Deregisters all of this server's tools from the agent and disconnects from the MCP server. */
97
+ close(): Promise<void>;
98
+ }
99
+ /** Authorization state of a connected MCP server. */
100
+ type McpAuthState = 'authorized' | 'pending';
101
+ /**
102
+ * Options for `createMcpTools` / `registerMcpServer`.
103
+ */
104
+ interface McpConnectOptions {
105
+ /**
106
+ * Resume path: an authorization code obtained from the redirect-back after a page reload (or a
107
+ * blocked-popup same-tab navigation). When set alongside `config.authProvider`, the code is
108
+ * exchanged for tokens on a fresh transport *before* connecting, so the (re)connect reads the
109
+ * saved token and returns `authState: 'authorized'` directly — skipping the pending state.
110
+ * Ignored when `authProvider` is not set.
111
+ */
112
+ authorizationCode?: string;
113
+ /**
114
+ * @internal Test seam to inject a non-HTTP transport (e.g. `InMemoryTransport` or an OAuth-gated
115
+ * fake), so tests never dynamic-import `StreamableHTTPClientTransport`. May be a single
116
+ * `Transport` (used once for the initial connect — fine for non-OAuth tests, where `finishAuth`
117
+ * is never called) or a factory `() => Transport | Promise<Transport>` invoked each time a
118
+ * transport is needed (initial connect AND each `finishAuth` reconnect), which is how OAuth
119
+ * tests supply a fresh gated transport that shares one in-memory pipe. Not part of the stable
120
+ * public contract.
121
+ */
122
+ transport?: Transport | (() => Transport | Promise<Transport>);
123
+ }
124
+
125
+ /**
126
+ * MCP → Forgewisp adapter.
127
+ *
128
+ * Connects to an MCP server over the Streamable HTTP transport, lists its tools, and adapts
129
+ * each into a Forgewisp `FunctionDefinition` that is registered through the agent's existing
130
+ * `registerFunction` path. Because registration goes through core unchanged, the registry,
131
+ * Ajv validation, the two-phase executor, the `onConfirmRequired` confirmation invariant, the
132
+ * audit log, and `runToolLoop` all apply to MCP tools for free — MCP becomes just another source
133
+ * of `FunctionDefinition`s.
134
+ *
135
+ * The adapter is layered so the mapping is unit-testable without a network:
136
+ * - `adaptMcpTool` / `adaptMcpTools` — pure given a `McpClient` (the test seam; a plain fake
137
+ * object implementing `callTool` is enough).
138
+ * - `connectMcpServer` — builds the SDK `Client` + `StreamableHTTPClientTransport`.
139
+ * - `createMcpTools` / `registerMcpServer` — the public wiring.
140
+ *
141
+ * ── Schema handling ──────────────────────────────────────────────────────────
142
+ * MCP `inputSchema` is full draft-07 JSON Schema. Forgewisp's `JSONSchema` type (in core) is a
143
+ * deliberately narrow subset enforcing strict hand-authored schemas in `@forgewisp/bundled-tools`;
144
+ * we do NOT widen it. The adapter casts the raw `inputSchema` to `JSONSchema` at the boundary.
145
+ * Runtime is correct: core's Ajv is `strict: false` and validates draft-07 (`$ref`/`enum`/`oneOf`
146
+ * etc.), the compiled validator is cached per schema-object identity, and the wire payload sent
147
+ * to the LLM carries the full schema, which OpenAI-compatible models accept. Limitation: any
148
+ * external `$ref` (not present in well-formed MCP schemas) won't resolve.
149
+ *
150
+ * ── Risk tiers ────────────────────────────────────────────────────────────────
151
+ * MCP has no risk-tier concept. Tiers come entirely from `McpServerConfig` (`defaultTier` +
152
+ * `tierOverrides`); MCP `annotations.readOnlyHint`/`destructiveHint` are informational only and
153
+ * deliberately NOT auto-mapped — the consumer owns the security boundary.
154
+ *
155
+ * ── Abort ─────────────────────────────────────────────────────────────────────
156
+ * The parent run's `AbortSignal` is threaded into handlers by core's executor via `ToolContext`.
157
+ * The handler forwards it to `client.callTool` (merged with a per-server timeout), so aborting
158
+ * the parent run aborts in-flight MCP calls.
159
+ *
160
+ * ── OAuth ─────────────────────────────────────────────────────────────────────
161
+ * `McpServerConfig.authProvider` (an SDK `OAuthClientProvider`) takes precedence over `apiKey` and
162
+ * drives the full OAuth 2.1 authorization-code + PKCE flow (RFC 9728 / 8414 discovery, dynamic client
163
+ * registration, token exchange, refresh) via the SDK transport — the consumer only implements the
164
+ * provider (browser-redirect plumbing + token storage). `client/auth.js` is dynamically imported
165
+ * only on the OAuth path; non-OAuth consumers never load it.
166
+ *
167
+ * State machine: the first `connect` that needs auth has the SDK call
168
+ * `authProvider.redirectToAuthorization(url)` and then throw `UnauthorizedError` from `connect`.
169
+ * `Client.connect` catches that, calls `void this.close()`, and rethrows. The transport is now
170
+ * *spent* — `close()` aborts its `AbortController` but does not null it, so a second `start()`
171
+ * throws "already started". **The same transport cannot be re-`connect`ed.** So this adapter
172
+ * catches `UnauthorizedError` (via `instanceof`, dynamically importing the class only on this path —
173
+ * the SDK's error doesn't set a custom `name`, so a name check won't work) and returns
174
+ * `authState: 'pending'` (empty tools) instead of throwing, keeping the spent transport+client only
175
+ * long enough to call `transport.finishAuth(code)` (which just calls `auth()` to exchange the code
176
+ * and save tokens into the provider — it does not require `start()`). `finishAuth` then builds a
177
+ * **fresh** transport + client whose `connect` reads the saved token via `provider.tokens()` and
178
+ * succeeds. The same "finishAuth-then-fresh-connect" pattern powers the resume path
179
+ * (`options.authorizationCode`), which exchanges the code on a single fresh transport *before*
180
+ * connecting.
181
+ *
182
+ * For `registerMcpServer`, the `hasConfirmation` preflight runs only once tools are listed — i.e.
183
+ * after auth completes (deferred when `authState` is `'pending'`), mirroring the non-OAuth path.
184
+ */
185
+
186
+ /** Shape returned by `createMcpTools`: the adapted tools, a closer, and (for OAuth) the auth flow. */
187
+ interface McpToolsResult {
188
+ /** The adapted `FunctionDefinition`s. Empty while `authState` is `'pending'`; mutated in place by
189
+ * `finishAuth` once auth completes, so the caller's reference stays valid. */
190
+ tools: FunctionDefinition[];
191
+ /** Authorization state. `'pending'` means the server requires OAuth and `finishAuth` (or a
192
+ * resume via `options.authorizationCode`) is needed before tools are available. */
193
+ authState: McpAuthState;
194
+ /** Disconnects the underlying client. Idempotent. */
195
+ close: () => Promise<void>;
196
+ /** Completes the OAuth redirect-back. See `McpServerHandle.finishAuth`. */
197
+ finishAuth: (authorizationCode: string) => Promise<void>;
198
+ }
199
+ /**
200
+ * Connect to an MCP server, list its tools, and adapt them into `FunctionDefinition`s without
201
+ * touching any agent. Useful for custom registration or `ToolSet` composition. The returned
202
+ * `close()` disconnects the underlying client.
203
+ *
204
+ * OAuth: when `config.authProvider` is set and the server requires auth, the first connect returns
205
+ * `authState: 'pending'` with an empty `tools` array (the SDK already redirected the user agent).
206
+ * Call `finishAuth(code)` after the redirect-back to exchange the code and populate `tools`, or pass
207
+ * `options.authorizationCode` to resume in one shot after a page reload.
208
+ *
209
+ * @internal `options.transport` is a test seam to inject a non-HTTP transport; not part of the
210
+ * stable public contract.
211
+ */
212
+ declare function createMcpTools(config: McpServerConfig, options?: McpConnectOptions): Promise<McpToolsResult>;
213
+ /**
214
+ * Connect to an MCP server and register all of its (adapted) tools on the given agent. Returns an
215
+ * `McpServerHandle` whose `close()` deregisters every tool and disconnects.
216
+ *
217
+ * Confirmation preflight: before registering anything, if any adapted tool resolves to
218
+ * `write`/`destructive` and `config.hasConfirmation` is not `true`, the client is closed and a
219
+ * clear error is thrown — so a tier mismatch never leaves a partially-registered server. Core's
220
+ * own registration-time invariant still fires as a backstop inside `agent.registerFunction`.
221
+ *
222
+ * OAuth: when the first connect returns `authState: 'pending'`, no tools are registered yet and the
223
+ * preflight is deferred until `handle.finishAuth(code)` lists the tools. If the post-auth preflight
224
+ * fails, `finishAuth` deregisters anything registered, disconnects, and throws the same error.
225
+ *
226
+ * @internal `options.transport` is a test seam to inject a non-HTTP transport.
227
+ */
228
+ declare function registerMcpServer(agent: AgentLike, config: McpServerConfig, options?: McpConnectOptions): Promise<McpServerHandle>;
229
+
230
+ export { type AgentLike, type McpAuthState, type McpConnectOptions, type McpServerConfig, type McpServerHandle, type McpToolsResult, createMcpTools, registerMcpServer };