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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1328 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ var _chunkXMYSMKGRcjs = require('./chunk-XMYSMKGR.cjs');
14
+
15
+ // src/access.ts
16
+ function canAccess(tool, principal) {
17
+ return tool.roles.some((role) => principal.roles.includes(role));
18
+ }
19
+ function visibleTools(tools, principal) {
20
+ const visible = [];
21
+ for (const tool of tools) if (canAccess(tool, principal)) visible.push(tool);
22
+ return visible;
23
+ }
24
+ function parsePrincipal(value) {
25
+ if (typeof value !== "object" || value === null) {
26
+ throw new TypeError("a principal must be an object with an id and roles");
27
+ }
28
+ const id = Reflect.get(value, "id");
29
+ if (typeof id !== "string" || id.trim() === "") {
30
+ throw new TypeError("a principal needs a non-empty string id");
31
+ }
32
+ const roles = Reflect.get(value, "roles");
33
+ if (!Array.isArray(roles)) throw new TypeError(`principal "${id}": roles must be an array`);
34
+ const copy = [];
35
+ for (const role of roles) {
36
+ if (typeof role !== "string" || role === "") {
37
+ throw new TypeError(`principal "${id}": every role must be a non-empty string`);
38
+ }
39
+ copy.push(role);
40
+ }
41
+ return Object.freeze({ id, roles: Object.freeze(copy) });
42
+ }
43
+
44
+ // src/adapters/adapt.ts
45
+ function adapt(fn, spec) {
46
+ return async (args, call) => spec.output(await fn(spec.input(args, call)));
47
+ }
48
+
49
+ // src/schema/jsonSchema.ts
50
+ var _2020js = require('ajv/dist/2020.js');
51
+ var _ajvformats = require('ajv-formats'); var _ajvformats2 = _interopRequireDefault(_ajvformats);
52
+ function jsonSchema(schema) {
53
+ const compiled = compileJsonSchema(schema);
54
+ const parse = (input) => {
55
+ const result = compiled.parse(input);
56
+ if (!result.ok) return result;
57
+ const typed = result.value;
58
+ return { ok: true, value: typed };
59
+ };
60
+ return Object.freeze({ jsonSchema: compiled.jsonSchema, parse });
61
+ }
62
+ function compileJsonSchema(schema, tool) {
63
+ const root = _chunkXMYSMKGRcjs.freezeRootSchema.call(void 0, schema, tool);
64
+ const ajv = new (0, _2020js.Ajv2020)({
65
+ allErrors: true,
66
+ strict: true,
67
+ coerceTypes: false,
68
+ useDefaults: false,
69
+ removeAdditional: false
70
+ });
71
+ _ajvformats2.default.call(void 0, ajv);
72
+ let validate;
73
+ try {
74
+ validate = ajv.compile(root);
75
+ } catch (error) {
76
+ const prefix = tool === void 0 ? "" : `tool "${tool}": `;
77
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
78
+ "invalid_schema",
79
+ `${prefix}invalid JSON Schema: ${messageOf(error)}`,
80
+ {
81
+ tool,
82
+ cause: error
83
+ }
84
+ );
85
+ }
86
+ const parse = (input) => {
87
+ const copy = _chunkXMYSMKGRcjs.cloneJson.call(void 0, input);
88
+ if (!copy.ok) return { ok: false, issues: [_chunkXMYSMKGRcjs.NOT_JSON_ISSUE] };
89
+ const value = copy.value;
90
+ if (!validate(value)) return { ok: false, issues: toIssues(validate.errors) };
91
+ if (!_chunkXMYSMKGRcjs.isJsonObject.call(void 0, value))
92
+ return { ok: false, issues: [{ path: "", message: "must be object" }] };
93
+ return { ok: true, value };
94
+ };
95
+ return Object.freeze({ jsonSchema: root, parse });
96
+ }
97
+ function toIssues(errors) {
98
+ if (!errors || errors.length === 0) return [{ path: "", message: "is invalid" }];
99
+ return errors.slice(0, _chunkXMYSMKGRcjs.MAX_ISSUES).map((error) => {
100
+ const params = error.params;
101
+ let path = error.instancePath;
102
+ if (error.keyword === "required" && typeof params.missingProperty === "string") {
103
+ path += `/${_chunkXMYSMKGRcjs.pointerSegment.call(void 0, params.missingProperty)}`;
104
+ }
105
+ if (error.keyword === "additionalProperties" && typeof params.additionalProperty === "string") {
106
+ path += `/${_chunkXMYSMKGRcjs.pointerSegment.call(void 0, params.additionalProperty)}`;
107
+ }
108
+ return { path, message: _nullishCoalesce(error.message, () => ( "is invalid")) };
109
+ });
110
+ }
111
+ function messageOf(error) {
112
+ return error instanceof Error ? error.message : String(error);
113
+ }
114
+
115
+ // src/types.ts
116
+ var SENSITIVITY_LEVELS = ["none", "low", "medium", "high", "critical"];
117
+ function isSensitivity(value) {
118
+ return typeof value === "string" && SENSITIVITY_LEVELS.includes(value);
119
+ }
120
+ function sensitivityRank(level) {
121
+ return SENSITIVITY_LEVELS.indexOf(level);
122
+ }
123
+
124
+ // src/tool.ts
125
+ var internals = /* @__PURE__ */ new WeakMap();
126
+ var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;
127
+ var MAX_TIMEOUT_MS = 2 ** 31 - 1;
128
+ function defineTool(definition) {
129
+ const descriptor = checkDefinition(definition);
130
+ const { args, handler, summarize } = definition;
131
+ const { name, title } = descriptor;
132
+ const tool = Object.freeze({
133
+ ...descriptor,
134
+ inputSchema: _chunkXMYSMKGRcjs.freezeRootSchema.call(void 0, args.jsonSchema, name)
135
+ });
136
+ const prepare = (input) => {
137
+ const parsed = args.parse(input);
138
+ if (!parsed.ok) return { ok: false, issues: parsed.issues };
139
+ const value = parsed.value;
140
+ return {
141
+ ok: true,
142
+ summary: summarize ? summarize(value) : _nullishCoalesce(title, () => ( name)),
143
+ run: (call) => handler(value, call)
144
+ };
145
+ };
146
+ internals.set(tool, { prepare });
147
+ return tool;
148
+ }
149
+ function isTool(value) {
150
+ return typeof value === "object" && value !== null && internals.has(value);
151
+ }
152
+ function prepareCall(tool, input) {
153
+ const found = internals.get(tool);
154
+ if (!found) {
155
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)("not_a_tool", "this object was not created by defineTool()");
156
+ }
157
+ return found.prepare(input);
158
+ }
159
+ function exposeTool(tool) {
160
+ const readOnly = tool.sensitivity === "none";
161
+ const destructive = !readOnly && !tool.reversible;
162
+ return tool.title === void 0 ? {
163
+ name: tool.name,
164
+ description: tool.description,
165
+ inputSchema: tool.inputSchema,
166
+ annotations: { readOnlyHint: readOnly, destructiveHint: destructive }
167
+ } : {
168
+ name: tool.name,
169
+ title: tool.title,
170
+ description: tool.description,
171
+ inputSchema: tool.inputSchema,
172
+ annotations: { title: tool.title, readOnlyHint: readOnly, destructiveHint: destructive }
173
+ };
174
+ }
175
+ function checkDefinition(definition) {
176
+ if (typeof definition !== "object" || definition === null) {
177
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)("invalid_definition", "a tool definition must be an object");
178
+ }
179
+ const name = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "name");
180
+ if (typeof name !== "string" || !TOOL_NAME.test(name)) {
181
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
182
+ "invalid_name",
183
+ `invalid tool name ${JSON.stringify(name)}: use 1 to 128 characters among A-Z a-z 0-9 _ - .`
184
+ );
185
+ }
186
+ const fail = (code, message) => new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(code, `tool "${name}": ${message}`, { tool: name });
187
+ const title = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "title");
188
+ if (!(title === void 0 || isNonEmptyString(title))) {
189
+ throw fail("invalid_title", "`title` must be a non-empty string when present");
190
+ }
191
+ const description = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "description");
192
+ if (!isNonEmptyString(description)) {
193
+ throw fail("invalid_description", "`description` is required: it is all the model reads");
194
+ }
195
+ if (!isArgsSchema(_chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "args"))) {
196
+ throw fail(
197
+ "invalid_schema",
198
+ "`args` must come from jsonSchema(), zodSchema() or match ArgsSchema"
199
+ );
200
+ }
201
+ const sensitivity = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "sensitivity");
202
+ if (!isSensitivity(sensitivity)) {
203
+ throw fail("invalid_sensitivity", "expected none, low, medium, high or critical");
204
+ }
205
+ const reversible = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "reversible");
206
+ if (typeof reversible !== "boolean") {
207
+ throw fail("invalid_reversible", "`reversible` must be declared, true or false");
208
+ }
209
+ const roles = checkRoles(_chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "roles"), fail);
210
+ const confirm = _nullishCoalesce(_chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "confirm"), () => ( "auto"));
211
+ if (confirm !== "auto" && confirm !== "always") {
212
+ throw fail("invalid_confirm", '`confirm` must be "auto" or "always"');
213
+ }
214
+ const summarize = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "summarize");
215
+ if (summarize !== void 0 && typeof summarize !== "function") {
216
+ throw fail("invalid_summarize", "`summarize` must be a function");
217
+ }
218
+ const audit = checkAudit(_chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "audit"), fail);
219
+ const timeoutMs = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "timeoutMs");
220
+ if (!(timeoutMs === void 0 || isTimeout(timeoutMs))) {
221
+ throw fail("invalid_timeout", "`timeoutMs` must be a positive integer of milliseconds");
222
+ }
223
+ if (typeof _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "handler") !== "function") {
224
+ throw fail("invalid_handler", "`handler` must be a function");
225
+ }
226
+ return { name, title, description, sensitivity, reversible, roles, confirm, audit, timeoutMs };
227
+ }
228
+ function isNonEmptyString(value) {
229
+ return typeof value === "string" && value.trim() !== "";
230
+ }
231
+ function isTimeout(value) {
232
+ return Number.isInteger(value) && typeof value === "number" && value > 0 && value <= MAX_TIMEOUT_MS;
233
+ }
234
+ function isArgsSchema(value) {
235
+ if (typeof value !== "object" || value === null) return false;
236
+ return "parse" in value && typeof value.parse === "function" && "jsonSchema" in value;
237
+ }
238
+ function checkRoles(value, fail) {
239
+ if (!Array.isArray(value) || value.length === 0) {
240
+ throw fail("invalid_roles", "`roles` must list at least one role");
241
+ }
242
+ const roles = /* @__PURE__ */ new Set();
243
+ for (const role of value) {
244
+ if (typeof role !== "string" || role === "" || role.trim() !== role) {
245
+ throw fail("invalid_roles", `invalid role ${JSON.stringify(role)}`);
246
+ }
247
+ roles.add(role);
248
+ }
249
+ return Object.freeze([...roles]);
250
+ }
251
+ function checkAudit(value, fail) {
252
+ if (value === void 0)
253
+ return Object.freeze({ args: Object.freeze([]), result: Object.freeze([]) });
254
+ if (typeof value !== "object" || value === null) {
255
+ throw fail("invalid_audit", '`audit` must be an object such as { args: ["/messageId"] }');
256
+ }
257
+ for (const key of Object.keys(value)) {
258
+ if (key !== "args" && key !== "result") {
259
+ throw fail("invalid_audit", `unknown audit field ${JSON.stringify(key)}: use args or result`);
260
+ }
261
+ }
262
+ return Object.freeze({
263
+ args: checkPointers(_chunkXMYSMKGRcjs.readProperty.call(void 0, value, "args"), "audit.args", fail),
264
+ result: checkPointers(_chunkXMYSMKGRcjs.readProperty.call(void 0, value, "result"), "audit.result", fail)
265
+ });
266
+ }
267
+ function checkPointers(value, field, fail) {
268
+ if (value === void 0) return Object.freeze([]);
269
+ if (!Array.isArray(value))
270
+ throw fail("invalid_audit", `\`${field}\` must be an array of JSON Pointers`);
271
+ const pointers = [];
272
+ for (const pointer of value) {
273
+ if (typeof pointer !== "string" || !pointer.startsWith("/") || pointer === "/") {
274
+ throw fail(
275
+ "invalid_audit",
276
+ `${JSON.stringify(pointer)} in ${field} is not a JSON Pointer such as "/messageId"`
277
+ );
278
+ }
279
+ pointers.push(pointer);
280
+ }
281
+ return Object.freeze(pointers);
282
+ }
283
+
284
+ // src/adapters/definitions.ts
285
+ var NO_PARAMETERS = Object.freeze({
286
+ type: "object",
287
+ properties: Object.freeze({}),
288
+ additionalProperties: false
289
+ });
290
+ function importDefinitions(definitions, dispatch, governance) {
291
+ const parsed = definitions.map(normalize);
292
+ const seen = /* @__PURE__ */ new Set();
293
+ for (const { name } of parsed) {
294
+ if (seen.has(name)) {
295
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)("duplicate_name", `two definitions are named "${name}"`, {
296
+ tool: name
297
+ });
298
+ }
299
+ seen.add(name);
300
+ }
301
+ const missing = parsed.map(({ name }) => name).filter((name) => !Object.hasOwn(governance, name));
302
+ if (missing.length > 0) {
303
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
304
+ "missing_governance",
305
+ `no governance declared for: ${missing.join(", ")}. Declare sensitivity, reversible and roles for each.`
306
+ );
307
+ }
308
+ const unknown = Object.keys(governance).filter((name) => !seen.has(name));
309
+ if (unknown.length > 0) {
310
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
311
+ "unknown_governance",
312
+ `governance declared for tools that have no definition: ${unknown.join(", ")}`
313
+ );
314
+ }
315
+ return parsed.map(({ name, description, schema }) => {
316
+ const rules = governance[name];
317
+ if (rules === void 0) throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)("missing_governance", name);
318
+ return defineTool({
319
+ ...rules,
320
+ name,
321
+ description,
322
+ args: compileJsonSchema(_nullishCoalesce(schema, () => ( NO_PARAMETERS)), name),
323
+ handler: (args, call) => dispatch(name, args, call)
324
+ });
325
+ });
326
+ }
327
+ function normalize(definition, index) {
328
+ if (typeof definition !== "object" || definition === null) {
329
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
330
+ "invalid_definition",
331
+ `definition #${String(index)} is not an object`
332
+ );
333
+ }
334
+ const nested = _chunkXMYSMKGRcjs.readProperty.call(void 0, definition, "function");
335
+ const source = typeof nested === "object" && nested !== null ? nested : definition;
336
+ const name = _chunkXMYSMKGRcjs.readProperty.call(void 0, source, "name");
337
+ if (typeof name !== "string") {
338
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)("invalid_name", `definition #${String(index)} has no name`);
339
+ }
340
+ const description = _chunkXMYSMKGRcjs.readProperty.call(void 0, source, "description");
341
+ if (typeof description !== "string" || description.trim() === "") {
342
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
343
+ "invalid_description",
344
+ `tool "${name}": a description is required, it is all the model reads`,
345
+ { tool: name }
346
+ );
347
+ }
348
+ const schema = _nullishCoalesce(_nullishCoalesce(_chunkXMYSMKGRcjs.readProperty.call(void 0, source, "input_schema"), () => ( _chunkXMYSMKGRcjs.readProperty.call(void 0, source, "inputSchema"))), () => ( _chunkXMYSMKGRcjs.readProperty.call(void 0, source, "parameters")));
349
+ return { name, description, schema };
350
+ }
351
+
352
+ // src/adapters/envelope.ts
353
+ function envelope(spec) {
354
+ return (result) => {
355
+ if (!spec.ok(result)) throw new (0, _chunkXMYSMKGRcjs.ToolError)(spec.message(result));
356
+ return spec.value(result);
357
+ };
358
+ }
359
+
360
+ // src/audit/redact.ts
361
+ var REDACTED = "[REDACTED]";
362
+ var SECRET_KEY_PARTS = [
363
+ "password",
364
+ "passwd",
365
+ "secret",
366
+ "token",
367
+ "apikey",
368
+ "authorization",
369
+ "cookie",
370
+ "privatekey"
371
+ ];
372
+ var MAX_KEPT_STRING = 500;
373
+ var MAX_KEPT_ITEMS = 100;
374
+ function isSecretKey(key) {
375
+ const normalized = key.toLowerCase().replaceAll(/[-_]/g, "");
376
+ return SECRET_KEY_PARTS.some((part) => normalized.includes(part));
377
+ }
378
+ function keepDeclared(value, pointers) {
379
+ if (pointers.length === 0) return void 0;
380
+ const kept = {};
381
+ for (const pointer of pointers) {
382
+ const segments = parsePointer(pointer);
383
+ const last = segments.at(-1);
384
+ const secret = last !== void 0 && last !== "*" && isSecretKey(last);
385
+ const matches = resolve(value, segments).slice(0, MAX_KEPT_ITEMS);
386
+ if (matches.length === 0) continue;
387
+ const copies = matches.map((match) => secret ? REDACTED : bound(match));
388
+ kept[pointer] = segments.includes("*") ? copies : _nullishCoalesce(copies[0], () => ( null));
389
+ }
390
+ return kept;
391
+ }
392
+ function auditResult(result, pointers) {
393
+ const isError = result.isError === true;
394
+ const kept = result.structuredContent === void 0 ? void 0 : keepDeclared(result.structuredContent, pointers);
395
+ return kept === void 0 ? { isError } : { isError, kept };
396
+ }
397
+ function resolve(value, segments) {
398
+ const [head, ...rest] = segments;
399
+ if (head === void 0) return [value];
400
+ if (head === "*") {
401
+ const children = _chunkXMYSMKGRcjs.isJsonArray.call(void 0, value) ? [...value] : _chunkXMYSMKGRcjs.isJsonObject.call(void 0, value) ? Object.values(value) : [];
402
+ return children.flatMap((child) => resolve(child, rest));
403
+ }
404
+ if (_chunkXMYSMKGRcjs.isJsonArray.call(void 0, value)) {
405
+ if (!/^(0|[1-9][0-9]*)$/.test(head)) return [];
406
+ const item2 = value[Number(head)];
407
+ return item2 === void 0 ? [] : resolve(item2, rest);
408
+ }
409
+ if (!_chunkXMYSMKGRcjs.isJsonObject.call(void 0, value) || !Object.hasOwn(value, head)) return [];
410
+ const item = value[head];
411
+ return item === void 0 ? [] : resolve(item, rest);
412
+ }
413
+ function bound(value) {
414
+ if (typeof value === "string") {
415
+ return value.length > MAX_KEPT_STRING ? `${value.slice(0, MAX_KEPT_STRING)}\u2026` : value;
416
+ }
417
+ if (_chunkXMYSMKGRcjs.isJsonArray.call(void 0, value)) return value.slice(0, MAX_KEPT_ITEMS).map(bound);
418
+ if (!_chunkXMYSMKGRcjs.isJsonObject.call(void 0, value)) return value;
419
+ const out = {};
420
+ for (const [key, item] of Object.entries(value))
421
+ out[key] = isSecretKey(key) ? REDACTED : bound(item);
422
+ return out;
423
+ }
424
+ function parsePointer(pointer) {
425
+ return pointer.split("/").slice(1).map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
426
+ }
427
+
428
+ // src/audit/sinks.ts
429
+ function stderrJsonSink() {
430
+ return {
431
+ write(event) {
432
+ process.stderr.write(`${JSON.stringify(event)}
433
+ `);
434
+ }
435
+ };
436
+ }
437
+ function memorySink() {
438
+ const events = [];
439
+ return {
440
+ events,
441
+ write(event) {
442
+ events.push(event);
443
+ },
444
+ clear() {
445
+ events.length = 0;
446
+ }
447
+ };
448
+ }
449
+
450
+ // src/bridge.ts
451
+ var _crypto = require('crypto');
452
+
453
+ // src/confirmation/policy.ts
454
+ var DEFAULT_POLICY = Object.freeze({
455
+ threshold: "high",
456
+ irreversibleLowersThreshold: true
457
+ });
458
+ function isConfirmationThreshold(value) {
459
+ return isSensitivity(value) && value !== "none";
460
+ }
461
+ function requiresConfirmation(tool, policy = DEFAULT_POLICY) {
462
+ if (tool.confirm === "always") return true;
463
+ if (tool.sensitivity === "none") return false;
464
+ const lowered = policy.irreversibleLowersThreshold && !tool.reversible ? 1 : 0;
465
+ return sensitivityRank(tool.sensitivity) >= sensitivityRank(policy.threshold) - lowered;
466
+ }
467
+
468
+ // src/confirmation/store.ts
469
+ var DAY_MS = 24 * 60 * 60 * 1e3;
470
+ var MemoryConfirmationStore = class {
471
+ #records = /* @__PURE__ */ new Map();
472
+ #consumed = /* @__PURE__ */ new Map();
473
+ #maxEntries;
474
+ #maxConsumed;
475
+ #retentionMs;
476
+ #now;
477
+ constructor(options = {}) {
478
+ this.#maxEntries = positiveInteger(_nullishCoalesce(options.maxEntries, () => ( 1e4)), "maxEntries");
479
+ this.#maxConsumed = positiveInteger(_nullishCoalesce(options.maxConsumed, () => ( 1e4)), "maxConsumed");
480
+ this.#retentionMs = positiveInteger(
481
+ _nullishCoalesce(options.consumedRetentionMs, () => ( DAY_MS)),
482
+ "consumedRetentionMs"
483
+ );
484
+ this.#now = _nullishCoalesce(options.now, () => ( Date.now));
485
+ }
486
+ /** Pending confirmations. */
487
+ get size() {
488
+ return this.#records.size;
489
+ }
490
+ /** Remembered consumed or expired tokens. */
491
+ get consumedSize() {
492
+ return this.#consumed.size;
493
+ }
494
+ put(record) {
495
+ const now = this.#now();
496
+ this.#forgetExpiredTombstones(now);
497
+ if (this.#records.size >= this.#maxEntries) this.#evictRecords(now);
498
+ this.#records.set(record.tokenHash, record);
499
+ return Promise.resolve();
500
+ }
501
+ take(tokenHash, now) {
502
+ const record = this.#records.get(tokenHash);
503
+ if (record) {
504
+ this.#records.delete(tokenHash);
505
+ this.#remember(tokenHash, "consumed", record.expiresAt, now);
506
+ return Promise.resolve(
507
+ record.expiresAt <= now ? { status: "expired", record } : { status: "taken", record }
508
+ );
509
+ }
510
+ const tombstone = this.#consumed.get(tokenHash);
511
+ if (!tombstone || tombstone.until <= now) return Promise.resolve({ status: "unknown" });
512
+ if (tombstone.outcome === "expired") {
513
+ this.#consumed.set(tokenHash, { ...tombstone, outcome: "consumed" });
514
+ return Promise.resolve({ status: "expired" });
515
+ }
516
+ return Promise.resolve({ status: "consumed" });
517
+ }
518
+ #remember(tokenHash, outcome, expiresAt, now) {
519
+ this.#forgetExpiredTombstones(now);
520
+ if (this.#consumed.size >= this.#maxConsumed) {
521
+ const oldest = this.#consumed.keys().next();
522
+ if (!oldest.done) this.#consumed.delete(oldest.value);
523
+ }
524
+ this.#consumed.set(tokenHash, { outcome, until: Math.max(expiresAt, now) + this.#retentionMs });
525
+ }
526
+ #forgetExpiredTombstones(now) {
527
+ for (const [hash, tombstone] of this.#consumed) {
528
+ if (tombstone.until <= now) this.#consumed.delete(hash);
529
+ }
530
+ }
531
+ #evictRecords(now) {
532
+ for (const [hash, record] of this.#records) {
533
+ if (record.expiresAt <= now) {
534
+ this.#records.delete(hash);
535
+ this.#remember(hash, "expired", record.expiresAt, now);
536
+ }
537
+ }
538
+ for (const hash of this.#records.keys()) {
539
+ if (this.#records.size < this.#maxEntries) break;
540
+ this.#records.delete(hash);
541
+ }
542
+ }
543
+ };
544
+ function positiveInteger(value, name) {
545
+ if (!Number.isInteger(value) || value < 1)
546
+ throw new TypeError(`${name} must be a positive integer`);
547
+ return value;
548
+ }
549
+
550
+ // src/confirmation/token.ts
551
+
552
+ var TOKEN_PREFIX = "mtb_";
553
+ function newToken() {
554
+ return TOKEN_PREFIX + _crypto.randomBytes.call(void 0, 32).toString("base64url");
555
+ }
556
+ function hashToken(token) {
557
+ return _crypto.createHash.call(void 0, "sha256").update(token, "utf8").digest("hex");
558
+ }
559
+ function digestArguments(args) {
560
+ return _crypto.createHash.call(void 0, "sha256").update(canonicalJson(args), "utf8").digest("hex");
561
+ }
562
+ function canonicalJson(value) {
563
+ if (_chunkXMYSMKGRcjs.isJsonArray.call(void 0, value)) return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
564
+ if (_chunkXMYSMKGRcjs.isJsonObject.call(void 0, value)) {
565
+ const keys = Object.keys(value).sort();
566
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(_nullishCoalesce(value[key], () => ( null)))}`).join(",")}}`;
567
+ }
568
+ return JSON.stringify(value);
569
+ }
570
+
571
+ // src/registry.ts
572
+ var ToolRegistry = class {
573
+ #tools = /* @__PURE__ */ new Map();
574
+ #listeners = /* @__PURE__ */ new Set();
575
+ /**
576
+ * Adds tools, all or none: if one of them is invalid or its name is taken
577
+ * (by an existing tool or by another tool in the same call), the registry
578
+ * is left unchanged.
579
+ */
580
+ register(...tools) {
581
+ const incoming = /* @__PURE__ */ new Set();
582
+ for (const tool of tools) {
583
+ if (!isTool(tool)) {
584
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
585
+ "not_a_tool",
586
+ "only tools created by defineTool() can be registered"
587
+ );
588
+ }
589
+ if (this.#tools.has(tool.name) || incoming.has(tool.name)) {
590
+ throw new (0, _chunkXMYSMKGRcjs.ToolDefinitionError)(
591
+ "duplicate_name",
592
+ `a tool named "${tool.name}" already exists`,
593
+ {
594
+ tool: tool.name
595
+ }
596
+ );
597
+ }
598
+ incoming.add(tool.name);
599
+ }
600
+ if (tools.length === 0) return this;
601
+ for (const tool of tools) this.#tools.set(tool.name, tool);
602
+ this.#notify();
603
+ return this;
604
+ }
605
+ /** Removes a tool. Returns false if there was none by that name. */
606
+ unregister(name) {
607
+ const removed = this.#tools.delete(name);
608
+ if (removed) this.#notify();
609
+ return removed;
610
+ }
611
+ get(name) {
612
+ return this.#tools.get(name);
613
+ }
614
+ has(name) {
615
+ return this.#tools.has(name);
616
+ }
617
+ /** Every tool, in registration order, regardless of who is asking. */
618
+ list() {
619
+ return Object.freeze([...this.#tools.values()]);
620
+ }
621
+ get size() {
622
+ return this.#tools.size;
623
+ }
624
+ /**
625
+ * Called after every change. The MCP server uses it to send
626
+ * `notifications/tools/list_changed`. Returns an unsubscribe function.
627
+ */
628
+ onChange(listener) {
629
+ this.#listeners.add(listener);
630
+ return () => {
631
+ this.#listeners.delete(listener);
632
+ };
633
+ }
634
+ #notify() {
635
+ for (const listener of [...this.#listeners]) {
636
+ try {
637
+ listener();
638
+ } catch (error) {
639
+ process.emitWarning(
640
+ `mcp-tool-bridge: a registry onChange listener threw: ${error instanceof Error ? error.message : String(error)}`
641
+ );
642
+ }
643
+ }
644
+ }
645
+ };
646
+
647
+ // src/result.ts
648
+ function text(value) {
649
+ return Object.freeze({ content: Object.freeze([Object.freeze({ type: "text", text: value })]) });
650
+ }
651
+ function json(value) {
652
+ const copy = _chunkXMYSMKGRcjs.cloneJson.call(void 0, value);
653
+ if (!copy.ok) throw new TypeError("json(): value is not plain JSON data");
654
+ const frozen = _chunkXMYSMKGRcjs.deepFreeze.call(void 0, copy.value);
655
+ const content = Object.freeze([Object.freeze({ type: "text", text: JSON.stringify(frozen) })]);
656
+ return _chunkXMYSMKGRcjs.isJsonObject.call(void 0, frozen) ? Object.freeze({ content, structuredContent: frozen }) : Object.freeze({ content });
657
+ }
658
+ function normalizeOutput(output) {
659
+ if (typeof output === "string") return text(output);
660
+ const copy = _chunkXMYSMKGRcjs.cloneJson.call(void 0, output);
661
+ if (!copy.ok || !_chunkXMYSMKGRcjs.isJsonObject.call(void 0, copy.value)) return void 0;
662
+ const { content, structuredContent, isError } = copy.value;
663
+ if (!Array.isArray(content)) return void 0;
664
+ const blocks = [];
665
+ for (const item of content) {
666
+ const block = toBlock(item);
667
+ if (!block) return void 0;
668
+ blocks.push(block);
669
+ }
670
+ if (structuredContent !== void 0 && !_chunkXMYSMKGRcjs.isJsonObject.call(void 0, structuredContent)) return void 0;
671
+ if (isError !== void 0 && typeof isError !== "boolean") return void 0;
672
+ return Object.freeze({
673
+ content: Object.freeze(blocks),
674
+ ...structuredContent === void 0 ? {} : { structuredContent: _chunkXMYSMKGRcjs.deepFreeze.call(void 0, structuredContent) },
675
+ ...isError === void 0 ? {} : { isError }
676
+ });
677
+ }
678
+ function toBlock(item) {
679
+ if (!_chunkXMYSMKGRcjs.isJsonObject.call(void 0, item)) return void 0;
680
+ const { type, text: value, data, mimeType } = item;
681
+ if (type === "text")
682
+ return typeof value === "string" ? Object.freeze({ type, text: value }) : void 0;
683
+ if ((type === "image" || type === "audio") && typeof data === "string" && typeof mimeType === "string") {
684
+ return Object.freeze({ type, data, mimeType });
685
+ }
686
+ return void 0;
687
+ }
688
+
689
+ // src/bridge.ts
690
+ var DEFAULT_TTL_MS = 5 * 60 * 1e3;
691
+ var MESSAGES = {
692
+ exception: "The tool failed unexpectedly. The failure was recorded.",
693
+ aborted: "The call was cancelled.",
694
+ auditUnavailable: "The call was not executed: the audit log is unavailable.",
695
+ timeout: (ms) => `The tool did not answer within ${String(ms)} ms.`
696
+ };
697
+ function createBridge(options) {
698
+ return new ToolBridge(checkOptions(options));
699
+ }
700
+ function scope(callId, transport, principal, name, tool, args) {
701
+ const keepArgs = _nullishCoalesce(_optionalChain([tool, 'optionalAccess', _ => _.audit, 'access', _2 => _2.args]), () => ( []));
702
+ return {
703
+ callId,
704
+ transport,
705
+ principal,
706
+ tool: name,
707
+ argsDigest: args === void 0 ? void 0 : digestArguments(args),
708
+ keptArgs: args === void 0 || !_chunkXMYSMKGRcjs.isJsonObject.call(void 0, args) ? void 0 : keepDeclared(args, keepArgs),
709
+ keepResult: _nullishCoalesce(_optionalChain([tool, 'optionalAccess', _3 => _3.audit, 'access', _4 => _4.result]), () => ( []))
710
+ };
711
+ }
712
+ var MAX_AUDITED_ERROR = 500;
713
+ var ToolBridge = class {
714
+
715
+ #settings;
716
+ constructor(settings) {
717
+ this.registry = settings.registry;
718
+ this.#settings = settings;
719
+ }
720
+ listTools(principal) {
721
+ const caller = parsePrincipal(principal);
722
+ return Object.freeze(visibleTools(this.registry.list(), caller).map(exposeTool));
723
+ }
724
+ async callTool(principal, request, options = {}) {
725
+ const caller = parsePrincipal(principal);
726
+ const copy = _chunkXMYSMKGRcjs.cloneJson.call(void 0, _nullishCoalesce(request.arguments, () => ( {})));
727
+ const args = copy.ok ? copy.value : void 0;
728
+ if (request.confirmationToken !== void 0) {
729
+ return this.#redeem(caller, request.confirmationToken, options, { name: request.name, args });
730
+ }
731
+ const callId = _crypto.randomUUID.call(void 0, );
732
+ const transport = _nullishCoalesce(options.transport, () => ( "direct"));
733
+ const tool = this.registry.get(request.name);
734
+ if (!tool) {
735
+ return this.#reject(
736
+ scope(callId, transport, caller, request.name, void 0, args),
737
+ "unknown_tool"
738
+ );
739
+ }
740
+ const inScope = scope(callId, transport, caller, tool.name, tool, args);
741
+ if (!canAccess(tool, caller)) return this.#reject(inScope, "forbidden");
742
+ if (args === void 0) return this.#rejectArguments(inScope, [_chunkXMYSMKGRcjs.NOT_JSON_ISSUE]);
743
+ let prepared;
744
+ try {
745
+ prepared = prepareCall(tool, args);
746
+ } catch (error) {
747
+ return this.#fail(inScope, false, 0, "exception", error);
748
+ }
749
+ if (!prepared.ok) return this.#rejectArguments(inScope, prepared.issues);
750
+ if (requiresConfirmation(tool, this.#settings.policy)) {
751
+ return this.#issue(inScope, tool, args, prepared.summary);
752
+ }
753
+ return this.#execute(inScope, tool, prepared, false, options.signal);
754
+ }
755
+ executeConfirmed(principal, token, options = {}) {
756
+ return this.#redeem(parsePrincipal(principal), token, options, void 0);
757
+ }
758
+ async revokeConfirmation(principal, token, options = {}) {
759
+ const caller = parsePrincipal(principal);
760
+ const transport = _nullishCoalesce(options.transport, () => ( "direct"));
761
+ const taken = await this.#settings.store.take(hashToken(token), this.#settings.now());
762
+ const record = "record" in taken ? taken.record : void 0;
763
+ const inScope = this.#recordScope(record, transport, caller, void 0);
764
+ if (taken.status === "unknown" || taken.status === "consumed") {
765
+ await this.#reject(inScope, "confirmation_invalid", taken.status);
766
+ return false;
767
+ }
768
+ if (record && record.principalId !== caller.id) {
769
+ await this.#reject(inScope, "confirmation_invalid", "principal_mismatch");
770
+ return false;
771
+ }
772
+ await this.#record(inScope, { type: "confirmation.declined" });
773
+ return true;
774
+ }
775
+ /**
776
+ * Redeems a token: from `executeConfirmed` (`expected` absent) or from a
777
+ * repeated `callTool` (`expected` is that request, which must match).
778
+ * Everything is checked again, as if the call were new: the tool may have
779
+ * been removed and the principal's roles may have changed since.
780
+ */
781
+ async #redeem(caller, token, options, presented) {
782
+ const transport = _nullishCoalesce(options.transport, () => ( "direct"));
783
+ const now = this.#settings.now();
784
+ const taken = await this.#settings.store.take(hashToken(token), now);
785
+ const record = "record" in taken ? taken.record : void 0;
786
+ const inScope = this.#recordScope(record, transport, caller, presented);
787
+ if (taken.status === "unknown" || taken.status === "consumed") {
788
+ return this.#reject(inScope, "confirmation_invalid", taken.status);
789
+ }
790
+ if (record && record.principalId !== caller.id) {
791
+ return this.#reject(inScope, "confirmation_invalid", "principal_mismatch");
792
+ }
793
+ if (taken.status === "expired" || !record || now >= record.expiresAt) {
794
+ return this.#reject(inScope, "confirmation_expired", "expired");
795
+ }
796
+ if (presented) {
797
+ if (presented.name !== record.tool) {
798
+ return this.#reject(inScope, "confirmation_invalid", "tool_mismatch");
799
+ }
800
+ if (presented.args === void 0 || digestArguments(presented.args) !== record.argumentsDigest) {
801
+ return this.#reject(inScope, "confirmation_invalid", "arguments_mismatch");
802
+ }
803
+ }
804
+ const tool = this.registry.get(record.tool);
805
+ if (!tool) return this.#reject(inScope, "unknown_tool");
806
+ if (!canAccess(tool, caller)) return this.#reject(inScope, "forbidden");
807
+ let prepared;
808
+ try {
809
+ prepared = prepareCall(tool, record.arguments);
810
+ } catch (error) {
811
+ return this.#fail(inScope, true, 0, "exception", error);
812
+ }
813
+ if (!prepared.ok) return this.#rejectArguments(inScope, prepared.issues);
814
+ return this.#execute(inScope, tool, prepared, true, options.signal);
815
+ }
816
+ /** The audit scope of a redemption: the confirmed call when known, else what was presented. */
817
+ #recordScope(record, transport, caller, presented) {
818
+ const name = _nullishCoalesce(_nullishCoalesce(_optionalChain([record, 'optionalAccess', _5 => _5.tool]), () => ( _optionalChain([presented, 'optionalAccess', _6 => _6.name]))), () => ( ""));
819
+ const tool = this.registry.get(name);
820
+ if (record) {
821
+ return {
822
+ ...scope(record.callId, transport, caller, name, tool, record.arguments),
823
+ argsDigest: record.argumentsDigest
824
+ };
825
+ }
826
+ return scope(_crypto.randomUUID.call(void 0, ), transport, caller, name, tool, _optionalChain([presented, 'optionalAccess', _7 => _7.args]));
827
+ }
828
+ async #issue(scope2, tool, args, summary) {
829
+ const { store, ttlFor, now } = this.#settings;
830
+ const token = newToken();
831
+ const createdAt = now();
832
+ const record = Object.freeze({
833
+ tokenHash: hashToken(token),
834
+ callId: scope2.callId,
835
+ principalId: scope2.principal.id,
836
+ tool: tool.name,
837
+ arguments: _chunkXMYSMKGRcjs.deepFreeze.call(void 0, args),
838
+ argumentsDigest: digestArguments(args),
839
+ summary,
840
+ createdAt,
841
+ // Computed once, now, from this tool's class; stored with the record.
842
+ expiresAt: createdAt + ttlFor(tool)
843
+ });
844
+ try {
845
+ await store.put(record);
846
+ } catch (error) {
847
+ return this.#fail(scope2, false, 0, "exception", error);
848
+ }
849
+ const expiresAt = new Date(record.expiresAt).toISOString();
850
+ const recorded = await this.#record(scope2, { type: "confirmation.issued", expiresAt });
851
+ if (!recorded && this.#settings.blockOnAuditFailure) {
852
+ await store.take(record.tokenHash, now());
853
+ return { status: "tool_error", callId: scope2.callId, message: MESSAGES.auditUnavailable };
854
+ }
855
+ return {
856
+ status: "confirmation_required",
857
+ callId: scope2.callId,
858
+ confirmation: Object.freeze({ token, expiresAt, tool: tool.name, summary })
859
+ };
860
+ }
861
+ async #execute(scope2, tool, prepared, confirmed, signal) {
862
+ let context;
863
+ try {
864
+ context = await this.#settings.context(scope2.principal);
865
+ } catch (error) {
866
+ return this.#fail(scope2, confirmed, 0, "exception", error);
867
+ }
868
+ const recorded = await this.#record(scope2, { type: "call.started", confirmed });
869
+ if (!recorded && this.#settings.blockOnAuditFailure) {
870
+ return this.#fail(scope2, confirmed, 0, "audit_unavailable", void 0);
871
+ }
872
+ const startedAt = this.#settings.now();
873
+ const elapsed = () => Math.max(0, this.#settings.now() - startedAt);
874
+ let result;
875
+ try {
876
+ const output = await runWithLimits(
877
+ (abort) => prepared.run({
878
+ principal: scope2.principal,
879
+ context,
880
+ signal: abort,
881
+ callId: scope2.callId
882
+ }),
883
+ tool.timeoutMs,
884
+ signal
885
+ );
886
+ result = normalizeOutput(output);
887
+ } catch (error) {
888
+ if (error instanceof Timeout) return this.#fail(scope2, confirmed, elapsed(), "timeout", error);
889
+ if (error instanceof Cancelled)
890
+ return this.#fail(scope2, confirmed, elapsed(), "aborted", error);
891
+ if (error instanceof _chunkXMYSMKGRcjs.ToolError) {
892
+ return this.#fail(scope2, confirmed, elapsed(), "tool_error", error);
893
+ }
894
+ return this.#fail(scope2, confirmed, elapsed(), "exception", error);
895
+ }
896
+ if (!result) {
897
+ const invalid = new Error("the handler returned neither a string nor a valid ToolResult");
898
+ return this.#fail(scope2, confirmed, elapsed(), "exception", invalid);
899
+ }
900
+ await this.#record(scope2, {
901
+ type: "call.succeeded",
902
+ confirmed,
903
+ durationMs: elapsed(),
904
+ result: auditResult(result, scope2.keepResult)
905
+ });
906
+ return { status: "ok", callId: scope2.callId, result };
907
+ }
908
+ async #reject(scope2, reason, detail) {
909
+ await this.#record(
910
+ scope2,
911
+ detail === void 0 ? { type: "call.rejected", reason } : { type: "call.rejected", reason, detail }
912
+ );
913
+ const visible = reason === "forbidden" ? "unknown_tool" : reason;
914
+ return { status: "rejected", callId: scope2.callId, reason: visible };
915
+ }
916
+ async #rejectArguments(scope2, issues) {
917
+ await this.#record(scope2, { type: "call.rejected", reason: "invalid_arguments", issues });
918
+ return { status: "invalid_arguments", callId: scope2.callId, issues };
919
+ }
920
+ async #fail(scope2, confirmed, durationMs, kind, error) {
921
+ const full = kind === "audit_unavailable" ? MESSAGES.auditUnavailable : error instanceof Error ? error.message : String(error);
922
+ const detail = full.length > MAX_AUDITED_ERROR ? `${full.slice(0, MAX_AUDITED_ERROR)}\u2026` : full;
923
+ await this.#record(scope2, {
924
+ type: "call.failed",
925
+ confirmed,
926
+ durationMs,
927
+ error: { kind, message: detail }
928
+ });
929
+ return { status: "tool_error", callId: scope2.callId, message: publicMessage(kind, error) };
930
+ }
931
+ /** Writes to every sink. Returns false if any of them failed. */
932
+ async #record(scope2, body) {
933
+ const event = {
934
+ id: _crypto.randomUUID.call(void 0, ),
935
+ at: new Date(this.#settings.now()).toISOString(),
936
+ callId: scope2.callId,
937
+ transport: scope2.transport,
938
+ principal: { id: scope2.principal.id, roles: scope2.principal.roles },
939
+ tool: scope2.tool,
940
+ ...scope2.argsDigest === void 0 ? {} : { argsDigest: scope2.argsDigest },
941
+ ...scope2.keptArgs === void 0 ? {} : { args: scope2.keptArgs },
942
+ ...body
943
+ };
944
+ let ok = true;
945
+ for (const sink of this.#settings.sinks) {
946
+ try {
947
+ await sink.write(event);
948
+ } catch (error) {
949
+ ok = false;
950
+ process.emitWarning(
951
+ `mcp-tool-bridge: an audit sink failed on ${event.type}: ${error instanceof Error ? error.message : String(error)}`
952
+ );
953
+ }
954
+ }
955
+ return ok;
956
+ }
957
+ };
958
+ function publicMessage(kind, error) {
959
+ switch (kind) {
960
+ case "tool_error":
961
+ return error instanceof Error ? error.message : MESSAGES.exception;
962
+ case "timeout":
963
+ return error instanceof Timeout ? MESSAGES.timeout(error.ms) : MESSAGES.exception;
964
+ case "aborted":
965
+ return MESSAGES.aborted;
966
+ case "audit_unavailable":
967
+ return MESSAGES.auditUnavailable;
968
+ case "exception":
969
+ return MESSAGES.exception;
970
+ }
971
+ }
972
+ var Timeout = class extends Error {
973
+
974
+ constructor(ms) {
975
+ super(`no answer within ${String(ms)} ms`);
976
+ this.ms = ms;
977
+ }
978
+ };
979
+ var Cancelled = class extends Error {
980
+ constructor() {
981
+ super("cancelled by the caller");
982
+ }
983
+ };
984
+ async function runWithLimits(run, timeoutMs, outer) {
985
+ const controller = new AbortController();
986
+ const cancel = () => {
987
+ controller.abort(new Cancelled());
988
+ };
989
+ if (_optionalChain([outer, 'optionalAccess', _8 => _8.aborted])) cancel();
990
+ else _optionalChain([outer, 'optionalAccess', _9 => _9.addEventListener, 'call', _10 => _10("abort", cancel, { once: true })]);
991
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
992
+ controller.abort(new Timeout(timeoutMs));
993
+ }, timeoutMs);
994
+ const stopped = new Promise((_resolve, reject) => {
995
+ const stop = () => {
996
+ const reason = controller.signal.reason;
997
+ reject(reason instanceof Error ? reason : new Cancelled());
998
+ };
999
+ if (controller.signal.aborted) stop();
1000
+ else controller.signal.addEventListener("abort", stop, { once: true });
1001
+ });
1002
+ try {
1003
+ if (controller.signal.aborted) return await stopped;
1004
+ return await Promise.race([run(controller.signal), stopped]);
1005
+ } finally {
1006
+ clearTimeout(timer);
1007
+ _optionalChain([outer, 'optionalAccess', _11 => _11.removeEventListener, 'call', _12 => _12("abort", cancel)]);
1008
+ }
1009
+ }
1010
+ function checkOptions(options) {
1011
+ const raw = options;
1012
+ if (typeof raw !== "object" || raw === null)
1013
+ throw new TypeError("createBridge: options are required");
1014
+ const { registry, context } = options;
1015
+ if (!(_chunkXMYSMKGRcjs.readProperty.call(void 0, raw, "registry") instanceof ToolRegistry)) {
1016
+ throw new TypeError("createBridge: `registry` must be a ToolRegistry");
1017
+ }
1018
+ if (typeof _chunkXMYSMKGRcjs.readProperty.call(void 0, raw, "context") !== "function") {
1019
+ throw new TypeError(
1020
+ "createBridge: `context` must be a function (use `() => undefined` if tools need none)"
1021
+ );
1022
+ }
1023
+ const confirmation = _nullishCoalesce(_chunkXMYSMKGRcjs.readProperty.call(void 0, raw, "confirmation"), () => ( {}));
1024
+ if (typeof confirmation !== "object" || confirmation === null) {
1025
+ throw new TypeError("createBridge: `confirmation` must be an object");
1026
+ }
1027
+ const threshold = _nullishCoalesce(_chunkXMYSMKGRcjs.readProperty.call(void 0, confirmation, "threshold"), () => ( DEFAULT_POLICY.threshold));
1028
+ if (!isConfirmationThreshold(threshold)) {
1029
+ throw new TypeError(
1030
+ "createBridge: `confirmation.threshold` must be low, medium, high or critical"
1031
+ );
1032
+ }
1033
+ const lowers = _nullishCoalesce(_chunkXMYSMKGRcjs.readProperty.call(void 0, confirmation, "irreversibleLowersThreshold"), () => ( DEFAULT_POLICY.irreversibleLowersThreshold));
1034
+ if (typeof lowers !== "boolean") {
1035
+ throw new TypeError(
1036
+ "createBridge: `confirmation.irreversibleLowersThreshold` must be a boolean"
1037
+ );
1038
+ }
1039
+ const ttlFor = checkTtl(_chunkXMYSMKGRcjs.readProperty.call(void 0, confirmation, "ttlMs"));
1040
+ const now = _chunkXMYSMKGRcjs.readProperty.call(void 0, raw, "now");
1041
+ if (now !== void 0 && typeof now !== "function") {
1042
+ throw new TypeError("createBridge: `now` must be a function");
1043
+ }
1044
+ const clock = _nullishCoalesce(options.now, () => ( Date.now));
1045
+ const store = _nullishCoalesce(_optionalChain([options, 'access', _13 => _13.confirmation, 'optionalAccess', _14 => _14.store]), () => ( new MemoryConfirmationStore({ now: clock })));
1046
+ if (!hasMethods(store, ["put", "take"])) {
1047
+ throw new TypeError("createBridge: `confirmation.store` must implement put() and take()");
1048
+ }
1049
+ const audit = _chunkXMYSMKGRcjs.readProperty.call(void 0, raw, "audit");
1050
+ const sinks = audit === void 0 ? [stderrJsonSink()] : Array.isArray(audit) ? audit : [audit];
1051
+ const checkedSinks = [];
1052
+ for (const sink of sinks) {
1053
+ if (!isSink(sink))
1054
+ throw new TypeError("createBridge: every audit sink must have a write() method");
1055
+ checkedSinks.push(sink);
1056
+ }
1057
+ const auditFailure = _nullishCoalesce(_chunkXMYSMKGRcjs.readProperty.call(void 0, raw, "auditFailure"), () => ( "continue"));
1058
+ if (auditFailure !== "continue" && auditFailure !== "block") {
1059
+ throw new TypeError('createBridge: `auditFailure` must be "continue" or "block"');
1060
+ }
1061
+ return {
1062
+ registry,
1063
+ context,
1064
+ policy: Object.freeze({ threshold, irreversibleLowersThreshold: lowers }),
1065
+ ttlFor,
1066
+ store,
1067
+ sinks: Object.freeze(checkedSinks),
1068
+ blockOnAuditFailure: auditFailure === "block",
1069
+ now: clock
1070
+ };
1071
+ }
1072
+ function checkTtl(value) {
1073
+ const duration = (v, where) => {
1074
+ if (typeof v !== "number" || !Number.isSafeInteger(v) || v <= 0) {
1075
+ throw new TypeError(
1076
+ `createBridge: \`confirmation.ttlMs${where}\` must be a positive integer of milliseconds`
1077
+ );
1078
+ }
1079
+ return v;
1080
+ };
1081
+ if (value === void 0) return () => DEFAULT_TTL_MS;
1082
+ if (typeof value === "number") {
1083
+ const ms = duration(value, "");
1084
+ return () => ms;
1085
+ }
1086
+ if (typeof value !== "object" || value === null) {
1087
+ throw new TypeError(
1088
+ "createBridge: `confirmation.ttlMs` must be a number or a table per sensitivity"
1089
+ );
1090
+ }
1091
+ const table = /* @__PURE__ */ new Map();
1092
+ for (const level of Object.keys(value)) {
1093
+ if (!isSensitivity(level)) {
1094
+ throw new TypeError(
1095
+ `createBridge: \`confirmation.ttlMs\` has an unknown sensitivity ${JSON.stringify(level)}`
1096
+ );
1097
+ }
1098
+ const row = _chunkXMYSMKGRcjs.readProperty.call(void 0, value, level);
1099
+ if (typeof row !== "object" || row === null) {
1100
+ throw new TypeError(
1101
+ `createBridge: \`confirmation.ttlMs.${level}\` must be { reversible?, irreversible? }`
1102
+ );
1103
+ }
1104
+ for (const kind of Object.keys(row)) {
1105
+ if (kind !== "reversible" && kind !== "irreversible") {
1106
+ throw new TypeError(
1107
+ `createBridge: \`confirmation.ttlMs.${level}.${kind}\` is not reversible or irreversible`
1108
+ );
1109
+ }
1110
+ table.set(`${level}:${kind}`, duration(_chunkXMYSMKGRcjs.readProperty.call(void 0, row, kind), `.${level}.${kind}`));
1111
+ }
1112
+ }
1113
+ return (tool) => _nullishCoalesce(table.get(`${tool.sensitivity}:${tool.reversible ? "reversible" : "irreversible"}`), () => ( DEFAULT_TTL_MS));
1114
+ }
1115
+ function hasMethods(value, methods) {
1116
+ if (typeof value !== "object" || value === null) return false;
1117
+ return methods.every((method) => typeof _chunkXMYSMKGRcjs.readProperty.call(void 0, value, method) === "function");
1118
+ }
1119
+ function isSink(value) {
1120
+ return hasMethods(value, ["write"]);
1121
+ }
1122
+
1123
+ // src/server/mcpServer.ts
1124
+ var _indexjs = require('@modelcontextprotocol/sdk/server/index.js');
1125
+
1126
+
1127
+
1128
+
1129
+
1130
+ var _typesjs = require('@modelcontextprotocol/sdk/types.js');
1131
+ var CONFIRMATION_META_KEY = "mcp-tool-bridge/confirmation";
1132
+ function createMcpServer(bridge, options) {
1133
+ const principal = parsePrincipal(options.principal);
1134
+ const transport = _nullishCoalesce(options.transport, () => ( "direct"));
1135
+ const elicit = _nullishCoalesce(options.elicitConfirmations, () => ( true));
1136
+ const { name, version, title, instructions } = options.info;
1137
+ const server = new (0, _indexjs.Server)(title === void 0 ? { name, version } : { name, version, title }, {
1138
+ capabilities: { tools: { listChanged: true } },
1139
+ ...instructions === void 0 ? {} : { instructions }
1140
+ });
1141
+ server.setRequestHandler(_typesjs.ListToolsRequestSchema, () => ({
1142
+ tools: bridge.listTools(principal).map(toMcpTool)
1143
+ }));
1144
+ server.setRequestHandler(_typesjs.CallToolRequestSchema, async (request, extra) => {
1145
+ const { name: tool, arguments: args, _meta } = request.params;
1146
+ const token = confirmationTokenOf(_meta);
1147
+ const callOptions = { signal: extra.signal, transport };
1148
+ const outcome = await bridge.callTool(
1149
+ principal,
1150
+ token === void 0 ? { name: tool, arguments: args } : { name: tool, arguments: args, confirmationToken: token },
1151
+ callOptions
1152
+ );
1153
+ if (outcome.status === "confirmation_required" && elicit && canElicit(server)) {
1154
+ return confirmWithUser(server, bridge, principal, outcome, callOptions);
1155
+ }
1156
+ return toCallToolResult(outcome, tool);
1157
+ });
1158
+ const unsubscribe = bridge.registry.onChange(() => {
1159
+ server.sendToolListChanged().catch(() => void 0);
1160
+ });
1161
+ const previousOnClose = server.onclose;
1162
+ server.onclose = () => {
1163
+ unsubscribe();
1164
+ _optionalChain([previousOnClose, 'optionalCall', _15 => _15()]);
1165
+ };
1166
+ return server;
1167
+ }
1168
+ async function confirmWithUser(server, bridge, principal, outcome, options) {
1169
+ const { confirmation } = outcome;
1170
+ let answer;
1171
+ try {
1172
+ answer = await server.elicitInput(
1173
+ {
1174
+ message: `Allow this action? ${confirmation.summary}`,
1175
+ requestedSchema: {
1176
+ type: "object",
1177
+ properties: {
1178
+ confirm: {
1179
+ type: "boolean",
1180
+ title: "Allow",
1181
+ description: confirmation.summary,
1182
+ default: false
1183
+ }
1184
+ },
1185
+ required: ["confirm"]
1186
+ }
1187
+ },
1188
+ {
1189
+ // A person may take a while: wait as long as the confirmation lives.
1190
+ timeout: Math.max(1, Date.parse(confirmation.expiresAt) - Date.now()),
1191
+ ...options.signal === void 0 ? {} : { signal: options.signal }
1192
+ }
1193
+ );
1194
+ } catch (e) {
1195
+ return pendingResult(outcome.callId, confirmation);
1196
+ }
1197
+ if (answer.action === "accept" && _optionalChain([answer, 'access', _16 => _16.content, 'optionalAccess', _17 => _17.confirm]) === true) {
1198
+ const executed = await bridge.executeConfirmed(principal, confirmation.token, options);
1199
+ return toCallToolResult(executed, confirmation.tool);
1200
+ }
1201
+ await bridge.revokeConfirmation(principal, confirmation.token, options);
1202
+ return {
1203
+ content: [
1204
+ {
1205
+ type: "text",
1206
+ text: `The user declined: ${confirmation.summary}. Nothing was done. Do not try again unless they ask.`
1207
+ }
1208
+ ],
1209
+ structuredContent: { status: "declined", tool: confirmation.tool }
1210
+ };
1211
+ }
1212
+ function canElicit(server) {
1213
+ const elicitation = _optionalChain([server, 'access', _18 => _18.getClientCapabilities, 'call', _19 => _19(), 'optionalAccess', _20 => _20.elicitation]);
1214
+ if (elicitation === void 0) return false;
1215
+ return elicitation.form !== void 0 || elicitation.url === void 0;
1216
+ }
1217
+ function confirmationTokenOf(meta) {
1218
+ if (typeof meta !== "object" || meta === null) return void 0;
1219
+ const entry = _chunkXMYSMKGRcjs.readProperty.call(void 0, meta, CONFIRMATION_META_KEY);
1220
+ if (typeof entry !== "object" || entry === null) return void 0;
1221
+ const token = _chunkXMYSMKGRcjs.readProperty.call(void 0, entry, "token");
1222
+ return typeof token === "string" ? token : void 0;
1223
+ }
1224
+ function toCallToolResult(outcome, tool) {
1225
+ switch (outcome.status) {
1226
+ case "ok":
1227
+ return fromToolResult(outcome.result);
1228
+ case "tool_error":
1229
+ return errorResult(outcome.message);
1230
+ case "invalid_arguments":
1231
+ return errorResult(describeIssues(tool, outcome.issues));
1232
+ case "confirmation_required":
1233
+ return pendingResult(outcome.callId, outcome.confirmation);
1234
+ case "rejected":
1235
+ switch (outcome.reason) {
1236
+ case "unknown_tool":
1237
+ throw new (0, _typesjs.McpError)(_typesjs.ErrorCode.InvalidParams, `Unknown tool: ${tool}`);
1238
+ case "confirmation_expired":
1239
+ case "confirmation_invalid":
1240
+ return errorResult(CONFIRMATION_REFUSED);
1241
+ }
1242
+ }
1243
+ }
1244
+ function pendingResult(callId, confirmation) {
1245
+ const { token, expiresAt, tool, summary } = confirmation;
1246
+ return {
1247
+ content: [
1248
+ {
1249
+ type: "text",
1250
+ text: `Confirmation required: ${summary}. Nothing has been done yet. The user must confirm this action in the application before ${expiresAt}; calling the tool again does not confirm it.`
1251
+ }
1252
+ ],
1253
+ structuredContent: { status: "confirmation_required", tool, summary, expiresAt },
1254
+ _meta: { [CONFIRMATION_META_KEY]: { token, expiresAt, callId } }
1255
+ };
1256
+ }
1257
+ var CONFIRMATION_REFUSED = "This confirmation cannot be used. Nothing was done.";
1258
+ function errorResult(message) {
1259
+ return { content: [{ type: "text", text: message }], isError: true };
1260
+ }
1261
+ function describeIssues(tool, issues) {
1262
+ const lines = issues.map(
1263
+ (issue) => `- ${issue.path === "" ? "(root)" : issue.path}: ${issue.message}`
1264
+ );
1265
+ return [`Invalid arguments for ${tool}:`, ...lines].join("\n");
1266
+ }
1267
+ function fromToolResult(result) {
1268
+ return {
1269
+ content: result.content.map((block) => ({ ...block })),
1270
+ ...result.structuredContent === void 0 ? {} : { structuredContent: { ...result.structuredContent } },
1271
+ ...result.isError === void 0 ? {} : { isError: result.isError }
1272
+ };
1273
+ }
1274
+ function toMcpTool(tool) {
1275
+ const { readOnlyHint, destructiveHint } = tool.annotations;
1276
+ return {
1277
+ name: tool.name,
1278
+ ...tool.title === void 0 ? {} : { title: tool.title },
1279
+ description: tool.description,
1280
+ inputSchema: tool.inputSchema,
1281
+ annotations: {
1282
+ ...tool.title === void 0 ? {} : { title: tool.title },
1283
+ readOnlyHint,
1284
+ destructiveHint
1285
+ }
1286
+ };
1287
+ }
1288
+
1289
+ // src/server/stdio.ts
1290
+ var _stdiojs = require('@modelcontextprotocol/sdk/server/stdio.js');
1291
+ async function serveStdio(bridge, options) {
1292
+ const server = createMcpServer(bridge, { ...options, transport: "stdio" });
1293
+ await server.connect(new (0, _stdiojs.StdioServerTransport)());
1294
+ return {
1295
+ server,
1296
+ close: () => server.close()
1297
+ };
1298
+ }
1299
+
1300
+
1301
+
1302
+
1303
+
1304
+
1305
+
1306
+
1307
+
1308
+
1309
+
1310
+
1311
+
1312
+
1313
+
1314
+
1315
+
1316
+
1317
+
1318
+
1319
+
1320
+
1321
+
1322
+
1323
+
1324
+
1325
+
1326
+
1327
+
1328
+ exports.CONFIRMATION_META_KEY = CONFIRMATION_META_KEY; exports.MemoryConfirmationStore = MemoryConfirmationStore; exports.REDACTED = REDACTED; exports.SENSITIVITY_LEVELS = SENSITIVITY_LEVELS; exports.ToolDefinitionError = _chunkXMYSMKGRcjs.ToolDefinitionError; exports.ToolError = _chunkXMYSMKGRcjs.ToolError; exports.ToolRegistry = ToolRegistry; exports.adapt = adapt; exports.canAccess = canAccess; exports.createBridge = createBridge; exports.createMcpServer = createMcpServer; exports.defineTool = defineTool; exports.envelope = envelope; exports.exposeTool = exposeTool; exports.importDefinitions = importDefinitions; exports.isSecretKey = isSecretKey; exports.isSensitivity = isSensitivity; exports.isTool = isTool; exports.json = json; exports.jsonSchema = jsonSchema; exports.memorySink = memorySink; exports.parsePrincipal = parsePrincipal; exports.requiresConfirmation = requiresConfirmation; exports.sensitivityRank = sensitivityRank; exports.serveStdio = serveStdio; exports.stderrJsonSink = stderrJsonSink; exports.text = text; exports.visibleTools = visibleTools;