mcp-tool-bridge 1.1.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,668 @@
1
+ import { P as Principal, J as JsonSchemaObject, S as Sensitivity, A as ArgsSchema, T as ToolOutput, a as JsonObject, b as ArgIssue, c as JsonValue, d as ToolResult } from './types-tss7dg3j.cjs';
2
+ export { e as AudioContent, C as ContentBlock, I as ImageContent, f as JsonPrimitive, g as ParseResult, h as SENSITIVITY_LEVELS, i as TextContent, j as isSensitivity, s as sensitivityRank } from './types-tss7dg3j.cjs';
3
+ import { JSONSchema, FromSchema } from 'json-schema-to-ts';
4
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
+
6
+ /**
7
+ * `auto` lets the bridge's policy decide from `sensitivity` and `reversible`.
8
+ * `always` forces a confirmation whatever the policy. There is deliberately
9
+ * no way to opt a tool *out* of the policy.
10
+ */
11
+ type ConfirmMode = 'auto' | 'always';
12
+ /** What a handler knows about the call it serves. */
13
+ interface CallContext<TContext> {
14
+ readonly principal: Principal;
15
+ /** Whatever the host built for this principal (database client, tenant…). */
16
+ readonly context: TContext;
17
+ /** Aborted when the call times out or the client cancels it. */
18
+ readonly signal: AbortSignal;
19
+ /** Unique per call; the same id appears in every audit event of the call. */
20
+ readonly callId: string;
21
+ }
22
+ type ToolHandler<TArgs, TContext> = (args: TArgs, call: CallContext<TContext>) => Promise<ToolOutput>;
23
+ /**
24
+ * A tool declaration. The governance fields — `sensitivity`, `reversible`,
25
+ * `roles` — are required and have no default: a tool nobody classified is a
26
+ * startup error, not a tool open to everyone.
27
+ */
28
+ interface ToolDefinition<TArgs, TContext> {
29
+ /** Unique within a registry. MCP allows `A-Z a-z 0-9 _ - .`, 1 to 128 characters. */
30
+ readonly name: string;
31
+ readonly title?: string;
32
+ /** What the model reads to decide whether and how to call the tool. */
33
+ readonly description: string;
34
+ readonly args: ArgsSchema<TArgs>;
35
+ readonly sensitivity: Sensitivity;
36
+ /** Can the effect be undone? An irreversible tool is confirmed one level earlier. */
37
+ readonly reversible: boolean;
38
+ /** A principal needs at least one of these roles to see or call the tool. */
39
+ readonly roles: readonly [string, ...string[]];
40
+ readonly confirm?: ConfirmMode;
41
+ /** One sentence describing this specific call, shown to whoever confirms it. */
42
+ readonly summarize?: (args: NoInfer<TArgs>) => string;
43
+ /**
44
+ * What the audit log may keep of this tool's calls. By default, nothing but
45
+ * metadata (tool, principal, argument digest, verdict, duration). Keeping
46
+ * content is a written decision: JSON Pointers into the arguments
47
+ * (`args`) and into the structured result (`result`); `*` matches every
48
+ * element. A `read_email` tool would keep `{ args: ['/messageId'] }` and nothing else.
49
+ */
50
+ readonly audit?: AuditRetention;
51
+ readonly timeoutMs?: number;
52
+ readonly handler: ToolHandler<NoInfer<TArgs>, TContext>;
53
+ }
54
+ /** JSON Pointers of the fields the audit log may keep. Empty by default. */
55
+ interface AuditRetention {
56
+ readonly args?: readonly string[];
57
+ readonly result?: readonly string[];
58
+ }
59
+ declare const contextType: unique symbol;
60
+ /**
61
+ * A declared tool: an immutable descriptor. It carries no way to execute the
62
+ * handler; only the bridge can, after its checks. `TContext` is the context
63
+ * the handler expects; a tool that needs none fits any registry.
64
+ */
65
+ interface Tool<TContext = unknown> {
66
+ readonly name: string;
67
+ readonly title: string | undefined;
68
+ readonly description: string;
69
+ readonly inputSchema: JsonSchemaObject;
70
+ readonly sensitivity: Sensitivity;
71
+ readonly reversible: boolean;
72
+ readonly roles: readonly string[];
73
+ readonly confirm: ConfirmMode;
74
+ /** What the audit may keep; both lists are empty unless the tool declared otherwise. */
75
+ readonly audit: {
76
+ readonly args: readonly string[];
77
+ readonly result: readonly string[];
78
+ };
79
+ readonly timeoutMs: number | undefined;
80
+ /** Type-level only: keeps a tool needing `{ db }` out of a registry that cannot provide it. */
81
+ readonly [contextType]?: (context: TContext) => void;
82
+ }
83
+ declare function defineTool<TArgs, TContext = unknown>(definition: ToolDefinition<TArgs, TContext>): Tool<TContext>;
84
+ /** True for descriptors produced by `defineTool`, and only those. */
85
+ declare function isTool(value: unknown): value is Tool;
86
+ /** MCP behaviour hints, derived from the declaration. */
87
+ interface ToolAnnotations {
88
+ readonly title?: string;
89
+ /** `sensitivity: "none"` means the tool has no side effect. */
90
+ readonly readOnlyHint: boolean;
91
+ /** Irreversible tools are flagged as destructive. */
92
+ readonly destructiveHint: boolean;
93
+ }
94
+ /** What the model is shown of a tool. Roles and sensitivity levels stay server-side. */
95
+ interface ExposedTool {
96
+ readonly name: string;
97
+ readonly title?: string;
98
+ readonly description: string;
99
+ readonly inputSchema: JsonSchemaObject;
100
+ readonly annotations: ToolAnnotations;
101
+ }
102
+ declare function exposeTool(tool: Tool<never>): ExposedTool;
103
+
104
+ /**
105
+ * The single access rule, used both when listing tools and when executing
106
+ * one: the principal must hold at least one of the tool's roles. Role names
107
+ * are compared exactly; there is no wildcard and no hierarchy.
108
+ */
109
+ declare function canAccess(tool: Pick<Tool<never>, 'roles'>, principal: Principal): boolean;
110
+ /** The tools this principal may see, in registry order. */
111
+ declare function visibleTools<TContext>(tools: Iterable<Tool<TContext>>, principal: Principal): Tool<TContext>[];
112
+ /**
113
+ * Checks a principal coming from outside the type system (configuration,
114
+ * environment, an authentication hook) and returns a frozen copy, so that
115
+ * mutating the original between two checks changes nothing.
116
+ */
117
+ declare function parsePrincipal(value: unknown): Principal;
118
+
119
+ /** How to talk to an existing function: what to give it, and how to read its answer. */
120
+ interface AdaptSpec<TArgs, TContext, TInput, TResult> {
121
+ /** Builds the function's input from the validated arguments and the call. */
122
+ readonly input: (args: TArgs, call: CallContext<TContext>) => TInput;
123
+ /** Turns the function's result into the tool output; `envelope()` fits here. */
124
+ readonly output: (result: TResult) => ToolOutput;
125
+ }
126
+ /**
127
+ * Turns an existing function into a handler, without rewriting it: the
128
+ * bridge validates the arguments, `input` maps them to what the function
129
+ * expects, and `output` maps its result back.
130
+ *
131
+ * ```ts
132
+ * handler: adapt((action: LegacyAction) => legacy.execute(action), {
133
+ * input: (args, call) => ({ userId: call.principal.id, data: JSON.stringify(args) }),
134
+ * output: fromLegacy, // an envelope()
135
+ * }),
136
+ * ```
137
+ *
138
+ * Pass methods bound (`legacy.execute.bind(legacy)`) or wrapped in an arrow
139
+ * function: `adapt` calls `fn` without a receiver.
140
+ */
141
+ declare function adapt<TArgs, TContext, TInput, TResult>(fn: (input: TInput) => TResult | Promise<TResult>, spec: AdaptSpec<TArgs, TContext, TInput, TResult>): ToolHandler<TArgs, TContext>;
142
+
143
+ /**
144
+ * What a foreign tool definition does not say: how much harm the tool can do
145
+ * and who may use it. Declared per tool, never defaulted.
146
+ */
147
+ interface Governance {
148
+ readonly sensitivity: Sensitivity;
149
+ readonly reversible: boolean;
150
+ readonly roles: readonly [string, ...string[]];
151
+ readonly title?: string;
152
+ readonly confirm?: ConfirmMode;
153
+ /** What the audit log may keep; nothing but metadata by default. */
154
+ readonly audit?: AuditRetention;
155
+ readonly timeoutMs?: number;
156
+ readonly summarize?: (args: JsonObject) => string;
157
+ }
158
+ /** Runs any of the imported tools, by name. Arguments are already validated. */
159
+ type Dispatcher<TContext> = (name: string, args: JsonObject, call: CallContext<TContext>) => Promise<ToolOutput>;
160
+ /**
161
+ * Turns tool definitions written for an LLM API into bridge tools, with one
162
+ * dispatcher for all of them. Accepted shapes:
163
+ *
164
+ * - Anthropic: `{ name, description, input_schema }`
165
+ * - MCP: `{ name, description, inputSchema }`
166
+ * - OpenAI: `{ type: 'function', function: { name, description, parameters } }`,
167
+ * or `{ type: 'function', name, description, parameters }`
168
+ *
169
+ * Every definition needs an entry in `governance`, and every entry a
170
+ * definition: a tool nobody classified, or a classification for a tool that
171
+ * does not exist (a typo), fails at startup, all names listed at once.
172
+ * Schemas are compiled in the same strict mode as `jsonSchema()`.
173
+ */
174
+ declare function importDefinitions<TContext = unknown>(definitions: readonly unknown[], dispatch: Dispatcher<TContext>, governance: Readonly<Record<string, Governance>>): Tool<TContext>[];
175
+
176
+ /** How to read the result envelope an existing function returns. */
177
+ interface EnvelopeSpec<R> {
178
+ /** Did the call succeed? */
179
+ readonly ok: (result: R) => boolean;
180
+ /** What the model gets on success: a string, or `text()` / `json()`. */
181
+ readonly value: (result: R) => ToolOutput;
182
+ /**
183
+ * What the model reads on failure. It is shown as is: map raw errors
184
+ * (stack traces, hostnames, SQL) to something the model may read.
185
+ */
186
+ readonly message: (result: R) => string;
187
+ }
188
+ /**
189
+ * Plugs an existing implementation that reports failure in its return value
190
+ * (`{ success, data, error }`, `{ ok, detail }`…) into a handler, without
191
+ * rewriting it. A failed envelope becomes a `ToolError`, so the bridge treats
192
+ * it as an expected failure the model may read; a successful one becomes the
193
+ * tool's output.
194
+ *
195
+ * ```ts
196
+ * const fromLegacy = envelope<{ success: boolean; data?: Invoice; error?: string }>({
197
+ * ok: (r) => r.success,
198
+ * value: (r) => json({ invoiceId: r.data?.id ?? null }),
199
+ * message: (r) => r.error ?? 'The invoice could not be sent.',
200
+ * })
201
+ * handler: async (args) => fromLegacy(await legacy.sendInvoice(args.invoiceId)),
202
+ * ```
203
+ */
204
+ declare function envelope<R>(spec: EnvelopeSpec<R>): (result: R) => ToolOutput;
205
+
206
+ type AuditTransport = 'stdio' | 'http' | 'direct';
207
+ /**
208
+ * Why a call did not run. The model is told less: `forbidden` reaches it as
209
+ * `unknown_tool`, so that probing tool names reveals nothing.
210
+ */
211
+ type RejectionReason = 'unknown_tool' | 'forbidden' | 'invalid_arguments' | 'confirmation_invalid' | 'confirmation_expired';
212
+ /**
213
+ * Which check refused a confirmation token. Recorded in the audit log only:
214
+ * the model gets a generic refusal and never learns which check stopped it.
215
+ * These are the lines that show whether something is trying to force its way.
216
+ */
217
+ type ConfirmationFailure = 'unknown' | 'consumed' | 'expired' | 'principal_mismatch' | 'tool_mismatch' | 'arguments_mismatch';
218
+ /**
219
+ * - `tool_error`: the handler threw a ToolError, its message reached the model.
220
+ * - `exception`: anything else went wrong; the model only got a generic message.
221
+ * - `timeout`, `aborted`: the bridge stopped waiting. The handler may still be running.
222
+ * - `audit_unavailable`: the call was not executed because the audit log could not record it.
223
+ */
224
+ type FailureKind = 'tool_error' | 'exception' | 'timeout' | 'aborted' | 'audit_unavailable';
225
+ /**
226
+ * What the audit keeps of a result: whether it was an error, and the fields
227
+ * the tool declared it may keep (`audit.result`). Nothing else, no text.
228
+ */
229
+ interface AuditedResult {
230
+ readonly isError: boolean;
231
+ /** Declared fields only, keyed by their JSON Pointer. Absent when none is declared. */
232
+ readonly kept?: JsonObject;
233
+ }
234
+ /** Fields every event carries. */
235
+ interface AuditBase {
236
+ /** Unique per event. */
237
+ readonly id: string;
238
+ /** ISO 8601. */
239
+ readonly at: string;
240
+ /** Shared by every event of one call, including its confirmation and redemption. */
241
+ readonly callId: string;
242
+ readonly transport: AuditTransport;
243
+ readonly principal: {
244
+ readonly id: string;
245
+ readonly roles: readonly string[];
246
+ };
247
+ readonly tool: string;
248
+ /**
249
+ * SHA-256 of the canonical arguments: enough to tell two calls apart, or to
250
+ * match a call with its confirmation, without keeping what was sent.
251
+ * Absent when the arguments were not JSON.
252
+ */
253
+ readonly argsDigest?: string;
254
+ /** The argument fields the tool declared it may keep (`audit.args`), keyed by pointer. */
255
+ readonly args?: JsonObject;
256
+ }
257
+ /** What distinguishes one kind of event from another. */
258
+ type AuditEventBody = {
259
+ readonly type: 'call.rejected';
260
+ readonly reason: RejectionReason;
261
+ readonly detail?: ConfirmationFailure;
262
+ readonly issues?: readonly ArgIssue[];
263
+ } | {
264
+ readonly type: 'confirmation.issued';
265
+ readonly expiresAt: string;
266
+ } | {
267
+ readonly type: 'confirmation.declined';
268
+ } | {
269
+ readonly type: 'call.started';
270
+ readonly confirmed: boolean;
271
+ } | {
272
+ readonly type: 'call.succeeded';
273
+ readonly confirmed: boolean;
274
+ readonly durationMs: number;
275
+ readonly result: AuditedResult;
276
+ } | {
277
+ readonly type: 'call.failed';
278
+ readonly confirmed: boolean;
279
+ readonly durationMs: number;
280
+ readonly error: {
281
+ readonly kind: FailureKind;
282
+ readonly message: string;
283
+ };
284
+ };
285
+ /**
286
+ * One line of the audit log. A call that runs produces `call.started` then
287
+ * `call.succeeded` or `call.failed`; a confirmed call is preceded by
288
+ * `confirmation.issued`, under the same `callId`.
289
+ */
290
+ type AuditEvent = AuditBase & AuditEventBody;
291
+ type AuditEventType = AuditEvent['type'];
292
+ /**
293
+ * Where audit events go. `write` may be synchronous or return a promise; the
294
+ * bridge waits for it before going on, so events are recorded in order.
295
+ */
296
+ interface AuditSink {
297
+ write(event: AuditEvent): void | Promise<void>;
298
+ }
299
+
300
+ declare const REDACTED = "[REDACTED]";
301
+ declare function isSecretKey(key: string): boolean;
302
+
303
+ /**
304
+ * One JSON object per line on stderr. The default sink: under stdio, stdout
305
+ * carries the protocol and must never receive anything else.
306
+ */
307
+ declare function stderrJsonSink(): AuditSink;
308
+ interface MemorySink extends AuditSink {
309
+ readonly events: readonly AuditEvent[];
310
+ clear(): void;
311
+ }
312
+ /** Keeps events in memory. For tests, or to inspect what a host recorded. */
313
+ declare function memorySink(): MemorySink;
314
+
315
+ /** `none` is not a threshold: a tool with no side effect has nothing to confirm. */
316
+ type ConfirmationThreshold = Exclude<Sensitivity, 'none'>;
317
+ interface ConfirmationPolicy {
318
+ /** Tools at or above this level are confirmed. Default `high`. */
319
+ readonly threshold: ConfirmationThreshold;
320
+ /** Confirm irreversible tools one level below the threshold. Default true. */
321
+ readonly irreversibleLowersThreshold: boolean;
322
+ }
323
+ /**
324
+ * Whether a call to this tool must be confirmed before it runs. Decided from
325
+ * the declaration alone, never from the arguments or from anything the model
326
+ * says: the same tool is always confirmed, or never.
327
+ */
328
+ declare function requiresConfirmation(tool: Pick<Tool<never>, 'sensitivity' | 'reversible' | 'confirm'>, policy?: ConfirmationPolicy): boolean;
329
+
330
+ /** A call held back until someone confirms it. */
331
+ interface ConfirmationRecord {
332
+ /** SHA-256 of the token. The token itself is never stored. */
333
+ readonly tokenHash: string;
334
+ /** The call this confirmation belongs to; its audit events share this id. */
335
+ readonly callId: string;
336
+ readonly principalId: string;
337
+ readonly tool: string;
338
+ /** The arguments as received, already validated once. They are what will run. */
339
+ readonly arguments: JsonValue;
340
+ readonly argumentsDigest: string;
341
+ readonly summary: string;
342
+ /** Epoch milliseconds. */
343
+ readonly createdAt: number;
344
+ /**
345
+ * Computed once, when the confirmation is issued, and stored with it. Never
346
+ * recomputed from a configuration that may have changed since.
347
+ */
348
+ readonly expiresAt: number;
349
+ }
350
+ /**
351
+ * What a redemption attempt found. Presenting a token consumes it, whatever
352
+ * the outcome: a second presentation finds it `consumed`. `expired` carries
353
+ * the record while it is still stored; once a store has evicted it, only the
354
+ * fact that it expired is remembered.
355
+ */
356
+ type TakeResult = {
357
+ readonly status: 'taken';
358
+ readonly record: ConfirmationRecord;
359
+ } | {
360
+ readonly status: 'expired';
361
+ readonly record?: ConfirmationRecord;
362
+ } | {
363
+ readonly status: 'consumed';
364
+ } | {
365
+ readonly status: 'unknown';
366
+ };
367
+ /**
368
+ * Where pending confirmations wait.
369
+ *
370
+ * `take` must, in ONE atomic step, read the record, check its expiry against
371
+ * `now`, and mark it consumed: that is what makes a token single-use when two
372
+ * redemptions race, and what keeps an expired token from passing. Expiry is
373
+ * decided there, never by a cleanup job; cleanup only frees space.
374
+ *
375
+ * In SQL: `UPDATE confirmations SET consumed_at = now() WHERE token_hash = $1
376
+ * AND consumed_at IS NULL RETURNING *, expires_at <= now() AS expired`, then,
377
+ * when no row comes back, one read to tell `consumed` from `unknown`.
378
+ */
379
+ interface ConfirmationStore {
380
+ put(record: ConfirmationRecord): Promise<void>;
381
+ take(tokenHash: string, now: number): Promise<TakeResult>;
382
+ }
383
+ interface MemoryConfirmationStoreOptions {
384
+ /** Upper bound on pending confirmations. Default 10 000. */
385
+ readonly maxEntries?: number;
386
+ /** Upper bound on remembered consumed tokens. Default 10 000. */
387
+ readonly maxConsumed?: number;
388
+ /**
389
+ * How long a consumed or expired token is remembered after its own expiry,
390
+ * so that a replay is reported as such rather than as unknown. Default 24 h.
391
+ */
392
+ readonly consumedRetentionMs?: number;
393
+ readonly now?: () => number;
394
+ }
395
+ /**
396
+ * In-process store, for a single process.
397
+ *
398
+ * Both maps are bounded, so a process that runs for months does not grow:
399
+ * pending records by `maxEntries`, consumed tokens by `maxConsumed` and by
400
+ * their retention. When a map is full, expired entries go first, then the
401
+ * oldest. A model that floods sensitive calls cannot make memory grow.
402
+ */
403
+ declare class MemoryConfirmationStore implements ConfirmationStore {
404
+ #private;
405
+ constructor(options?: MemoryConfirmationStoreOptions);
406
+ /** Pending confirmations. */
407
+ get size(): number;
408
+ /** Remembered consumed or expired tokens. */
409
+ get consumedSize(): number;
410
+ put(record: ConfirmationRecord): Promise<void>;
411
+ take(tokenHash: string, now: number): Promise<TakeResult>;
412
+ }
413
+
414
+ /**
415
+ * The catalog of tools a bridge may expose. It only accepts descriptors
416
+ * built by `defineTool`, so every entry has passed the declaration checks.
417
+ */
418
+ declare class ToolRegistry<TContext = unknown> {
419
+ #private;
420
+ /**
421
+ * Adds tools, all or none: if one of them is invalid or its name is taken
422
+ * (by an existing tool or by another tool in the same call), the registry
423
+ * is left unchanged.
424
+ */
425
+ register(...tools: readonly Tool<TContext>[]): this;
426
+ /** Removes a tool. Returns false if there was none by that name. */
427
+ unregister(name: string): boolean;
428
+ get(name: string): Tool<TContext> | undefined;
429
+ has(name: string): boolean;
430
+ /** Every tool, in registration order, regardless of who is asking. */
431
+ list(): readonly Tool<TContext>[];
432
+ get size(): number;
433
+ /**
434
+ * Called after every change. The MCP server uses it to send
435
+ * `notifications/tools/list_changed`. Returns an unsubscribe function.
436
+ */
437
+ onChange(listener: () => void): () => void;
438
+ }
439
+
440
+ /** Builds what handlers receive as `call.context`, once per executed call. */
441
+ type ContextFactory<TContext> = (principal: Principal) => TContext | Promise<TContext>;
442
+ interface ConfirmationOptions {
443
+ /** Default `high`. */
444
+ readonly threshold?: ConfirmationThreshold;
445
+ /** Default true: irreversible tools are confirmed one level below the threshold. */
446
+ readonly irreversibleLowersThreshold?: boolean;
447
+ /**
448
+ * How long a confirmation stays redeemable: one duration for every tool, or
449
+ * a table per sensitivity and reversibility. The expiry is computed when the
450
+ * confirmation is issued and stored with it; changing this later does not
451
+ * move confirmations already issued. Default 5 minutes.
452
+ */
453
+ readonly ttlMs?: number | ConfirmationTtlTable;
454
+ /** Default: in memory, for a single process. */
455
+ readonly store?: ConfirmationStore;
456
+ }
457
+ /**
458
+ * Lifetimes, in milliseconds, per sensitivity and reversibility. An
459
+ * irreversible action deserves a shorter window: past it, the context of the
460
+ * decision is not the one in which it was taken. A level or a case left out
461
+ * falls back to 5 minutes.
462
+ *
463
+ * ```ts
464
+ * const HOUR = 3_600_000
465
+ * ttlMs: {
466
+ * medium: { reversible: 24 * HOUR, irreversible: 12 * HOUR },
467
+ * high: { reversible: 12 * HOUR, irreversible: 4 * HOUR },
468
+ * critical: { reversible: 4 * HOUR, irreversible: HOUR },
469
+ * }
470
+ * ```
471
+ */
472
+ type ConfirmationTtlTable = Readonly<Partial<Record<Sensitivity, {
473
+ readonly reversible?: number;
474
+ readonly irreversible?: number;
475
+ }>>>;
476
+ interface BridgeOptions<TContext> {
477
+ readonly registry: ToolRegistry<TContext>;
478
+ readonly context: ContextFactory<TContext>;
479
+ readonly confirmation?: ConfirmationOptions;
480
+ /** Default: JSON lines on stderr. */
481
+ readonly audit?: AuditSink | readonly AuditSink[];
482
+ /**
483
+ * What to do when a sink fails. `continue` (default) warns on stderr and
484
+ * goes on. `block` refuses to start a call, or to issue a confirmation,
485
+ * that the audit log could not record.
486
+ */
487
+ readonly auditFailure?: 'continue' | 'block';
488
+ /** Clock, in epoch milliseconds. For tests. */
489
+ readonly now?: () => number;
490
+ }
491
+ interface CallRequest {
492
+ readonly name: string;
493
+ /** Omitted arguments are treated as `{}`, as MCP does. */
494
+ readonly arguments?: unknown;
495
+ /**
496
+ * A token from a previous `confirmation_required` outcome. Set by the host
497
+ * once a human has confirmed — never taken from the model's output.
498
+ */
499
+ readonly confirmationToken?: string;
500
+ }
501
+ interface CallOptions {
502
+ /** Cancels the call. The handler receives a signal that aborts with it. */
503
+ readonly signal?: AbortSignal;
504
+ /** Recorded in the audit log. The bundled servers set it; default `direct`. */
505
+ readonly transport?: AuditTransport;
506
+ }
507
+ /** For the host, not for the model: the token is what lets the call run. */
508
+ interface PendingConfirmation {
509
+ readonly token: string;
510
+ /** ISO 8601. */
511
+ readonly expiresAt: string;
512
+ readonly tool: string;
513
+ readonly summary: string;
514
+ }
515
+ /** Every outcome carries the `callId` found in the audit log. */
516
+ type CallOutcome = {
517
+ readonly status: 'ok';
518
+ readonly callId: string;
519
+ readonly result: ToolResult;
520
+ } | {
521
+ readonly status: 'tool_error';
522
+ readonly callId: string;
523
+ readonly message: string;
524
+ } | {
525
+ readonly status: 'invalid_arguments';
526
+ readonly callId: string;
527
+ readonly issues: readonly ArgIssue[];
528
+ } | {
529
+ readonly status: 'confirmation_required';
530
+ readonly callId: string;
531
+ readonly confirmation: PendingConfirmation;
532
+ } | {
533
+ readonly status: 'rejected';
534
+ readonly callId: string;
535
+ readonly reason: 'unknown_tool' | 'confirmation_invalid' | 'confirmation_expired';
536
+ };
537
+ interface Bridge<TContext> {
538
+ readonly registry: ToolRegistry<TContext>;
539
+ /** The tools this principal may see, as the model will see them. First filter. */
540
+ listTools(principal: Principal): readonly ExposedTool[];
541
+ /** Second filter, then validation, then the confirmation guard, then execution. */
542
+ callTool(principal: Principal, request: CallRequest, options?: CallOptions): Promise<CallOutcome>;
543
+ /**
544
+ * Runs a call held back for confirmation, for the principal it was issued
545
+ * to. For confirmations given later, outside the conversation.
546
+ */
547
+ executeConfirmed(principal: Principal, token: string, options?: CallOptions): Promise<CallOutcome>;
548
+ /**
549
+ * Withdraws a pending confirmation: the human said no. Returns false if
550
+ * there was nothing to withdraw for this principal. A token presented by
551
+ * another principal is destroyed all the same.
552
+ */
553
+ revokeConfirmation(principal: Principal, token: string, options?: CallOptions): Promise<boolean>;
554
+ }
555
+ declare function createBridge<TContext>(options: BridgeOptions<TContext>): Bridge<TContext>;
556
+
557
+ type ToolDefinitionErrorCode = 'invalid_definition' | 'invalid_name' | 'invalid_title' | 'invalid_description' | 'invalid_schema' | 'invalid_sensitivity' | 'invalid_reversible' | 'invalid_roles' | 'invalid_confirm' | 'invalid_summarize' | 'invalid_audit' | 'invalid_timeout' | 'invalid_handler' | 'duplicate_name' | 'missing_governance' | 'unknown_governance' | 'not_a_tool';
558
+ /**
559
+ * A tool declaration is wrong. Thrown at startup — by `defineTool`, a schema
560
+ * adapter or the registry — never while serving a call.
561
+ */
562
+ declare class ToolDefinitionError extends Error {
563
+ readonly name = "ToolDefinitionError";
564
+ readonly code: ToolDefinitionErrorCode;
565
+ /** The offending tool, when its name is known. */
566
+ readonly tool: string | undefined;
567
+ constructor(code: ToolDefinitionErrorCode, message: string, options?: {
568
+ readonly tool?: string | undefined;
569
+ readonly cause?: unknown;
570
+ });
571
+ }
572
+ /**
573
+ * An expected failure that the model is allowed to read: "no invoice with
574
+ * this number", "the slot is already booked". Its message is returned as the
575
+ * tool result. Any other exception thrown by a handler is reported to the
576
+ * model as a generic failure, and its details go to the audit log only.
577
+ */
578
+ declare class ToolError extends Error {
579
+ readonly name = "ToolError";
580
+ }
581
+
582
+ /** A result made of a single text block. */
583
+ declare function text(value: string): ToolResult;
584
+ /**
585
+ * A result carrying JSON. Objects also go into `structuredContent`, which
586
+ * MCP reserves for objects; every value is serialized as text too, for
587
+ * clients that only read `content`.
588
+ */
589
+ declare function json(value: JsonValue): ToolResult;
590
+
591
+ /** A JSON Schema literal whose root is an object. */
592
+ type ObjectJsonSchema = Exclude<JSONSchema, boolean> & {
593
+ readonly type: 'object';
594
+ };
595
+ /**
596
+ * The argument type a schema describes. A property with a `default` stays
597
+ * optional: defaults are not applied (see `jsonSchema`), so the handler must
598
+ * not be told the property is always there.
599
+ */
600
+ type ArgsOf<S> = S extends JSONSchema ? FromSchema<S, {
601
+ keepDefaultedPropertiesOptional: true;
602
+ }> : never;
603
+ /** `unknown` for a valid schema; otherwise the full JSON Schema type, to report the faulty keyword. */
604
+ type ValidSchema<S> = S extends JSONSchema ? unknown : JSONSchema;
605
+ /**
606
+ * Wraps a JSON Schema (draft 2020-12) literal. Declare it inline or `as const`
607
+ * and the handler's argument type is inferred from it.
608
+ *
609
+ * The schema is compiled once, here, in Ajv's strict mode: an unknown
610
+ * keyword, an unknown format or a `required` property missing from
611
+ * `properties` fails at startup instead of silently accepting bad input.
612
+ * Defaults are documentation for the model; they are not applied, and no
613
+ * type coercion takes place: `"3"` is not a number.
614
+ */
615
+ declare function jsonSchema<const S extends {
616
+ readonly type: 'object';
617
+ }>(schema: S & ValidSchema<NoInfer<S>>): ArgsSchema<ArgsOf<S>>;
618
+
619
+ /**
620
+ * The `_meta` key carrying a pending confirmation: in a `confirmation_required`
621
+ * result, the server puts `{ token, expiresAt, callId }` there; to redeem it,
622
+ * the host repeats the same call with `{ token }` under the same key in the
623
+ * request's `_meta`. `_meta` is for the host: it is not meant to reach the model.
624
+ */
625
+ declare const CONFIRMATION_META_KEY = "mcp-tool-bridge/confirmation";
626
+ interface ServerInfo {
627
+ readonly name: string;
628
+ readonly version: string;
629
+ readonly title?: string;
630
+ /** Sent to the client at initialization; often shown to the model. */
631
+ readonly instructions?: string;
632
+ }
633
+ interface McpServerOptions {
634
+ readonly info: ServerInfo;
635
+ /**
636
+ * Who this server acts for, for its whole lifetime: under stdio, the
637
+ * identity is fixed when the process starts.
638
+ */
639
+ readonly principal: Principal;
640
+ /**
641
+ * When a call needs confirmation and the client supports MCP elicitation,
642
+ * ask the user directly and run the call on a yes. Default true. Without
643
+ * elicitation, the result carries the token in `_meta` for the host.
644
+ */
645
+ readonly elicitConfirmations?: boolean;
646
+ /** Recorded in the audit log. `serveStdio` sets `stdio`; default `direct`. */
647
+ readonly transport?: AuditTransport;
648
+ }
649
+ /**
650
+ * An MCP server, built on the SDK's low-level `Server`, that serves one
651
+ * principal from a bridge. Connect it to any transport; `serveStdio` does it
652
+ * for stdio.
653
+ */
654
+ declare function createMcpServer<TContext>(bridge: Bridge<TContext>, options: McpServerOptions): Server;
655
+
656
+ type StdioServerOptions = Omit<McpServerOptions, 'transport'>;
657
+ interface StdioHandle {
658
+ readonly server: Server;
659
+ close(): Promise<void>;
660
+ }
661
+ /**
662
+ * Serves the bridge over stdin/stdout, for one principal fixed at launch.
663
+ * stdout carries the protocol and nothing else: the audit log and warnings
664
+ * go to stderr.
665
+ */
666
+ declare function serveStdio<TContext>(bridge: Bridge<TContext>, options: StdioServerOptions): Promise<StdioHandle>;
667
+
668
+ export { type AdaptSpec, ArgIssue, type ArgsOf, ArgsSchema, type AuditBase, type AuditEvent, type AuditEventBody, type AuditEventType, type AuditRetention, type AuditSink, type AuditTransport, type AuditedResult, type Bridge, type BridgeOptions, CONFIRMATION_META_KEY, type CallContext, type CallOptions, type CallOutcome, type CallRequest, type ConfirmMode, type ConfirmationFailure, type ConfirmationOptions, type ConfirmationPolicy, type ConfirmationRecord, type ConfirmationStore, type ConfirmationThreshold, type ContextFactory, type Dispatcher, type EnvelopeSpec, type ExposedTool, type FailureKind, type Governance, JsonObject, JsonSchemaObject, JsonValue, type McpServerOptions, MemoryConfirmationStore, type MemoryConfirmationStoreOptions, type MemorySink, type ObjectJsonSchema, type PendingConfirmation, Principal, REDACTED, type RejectionReason, Sensitivity, type ServerInfo, type StdioHandle, type StdioServerOptions, type TakeResult, type Tool, type ToolAnnotations, type ToolDefinition, ToolDefinitionError, type ToolDefinitionErrorCode, ToolError, type ToolHandler, ToolOutput, ToolRegistry, ToolResult, adapt, canAccess, createBridge, createMcpServer, defineTool, envelope, exposeTool, importDefinitions, isSecretKey, isTool, json, jsonSchema, memorySink, parsePrincipal, requiresConfirmation, serveStdio, stderrJsonSink, text, visibleTools };