toolnexus 0.24.0 → 0.26.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.
Files changed (63) hide show
  1. package/README.md +151 -1
  2. package/dist/acp.d.ts +37 -6
  3. package/dist/acp.js +239 -52
  4. package/dist/classifier.d.ts +189 -31
  5. package/dist/classifier.js +589 -30
  6. package/dist/client.d.ts +38 -8
  7. package/dist/client.js +102 -23
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/judge.d.ts +6 -3
  11. package/dist/judge.js +13 -1
  12. package/dist/mcp.d.ts +32 -7
  13. package/dist/mcp.js +125 -36
  14. package/dist/mcp2026/auth.d.ts +137 -0
  15. package/dist/mcp2026/auth.js +446 -0
  16. package/dist/mcp2026/client.d.ts +48 -0
  17. package/dist/mcp2026/client.js +160 -0
  18. package/dist/mcp2026/protocol.d.ts +54 -0
  19. package/dist/mcp2026/protocol.js +113 -0
  20. package/dist/mcp2026/skills.d.ts +5 -0
  21. package/dist/mcp2026/skills.js +25 -0
  22. package/dist/mcp2026/source.d.ts +86 -0
  23. package/dist/mcp2026/source.js +372 -0
  24. package/dist/mcp2026/stdio.d.ts +24 -0
  25. package/dist/mcp2026/stdio.js +99 -0
  26. package/dist/mcp2026/toolsearch.d.ts +5 -0
  27. package/dist/mcp2026/toolsearch.js +58 -0
  28. package/dist/mcphandler.d.ts +169 -0
  29. package/dist/mcphandler.js +1145 -0
  30. package/dist/mcpheader.d.ts +5 -0
  31. package/dist/mcpheader.js +18 -0
  32. package/dist/mcpserve.d.ts +24 -15
  33. package/dist/mcpserve.js +105 -2
  34. package/dist/mcpserverauth.d.ts +48 -0
  35. package/dist/mcpserverauth.js +203 -0
  36. package/dist/serve.js +7 -0
  37. package/dist/skill.d.ts +21 -0
  38. package/dist/skill.js +44 -0
  39. package/dist/toolkit.d.ts +20 -1
  40. package/dist/toolkit.js +25 -3
  41. package/dist/types.d.ts +22 -0
  42. package/package.json +4 -3
  43. package/src/acp.ts +258 -55
  44. package/src/classifier.ts +677 -46
  45. package/src/client.ts +130 -27
  46. package/src/index.ts +2 -0
  47. package/src/judge.ts +19 -4
  48. package/src/mcp.ts +162 -43
  49. package/src/mcp2026/auth.ts +473 -0
  50. package/src/mcp2026/client.ts +161 -0
  51. package/src/mcp2026/protocol.ts +135 -0
  52. package/src/mcp2026/skills.ts +22 -0
  53. package/src/mcp2026/source.ts +407 -0
  54. package/src/mcp2026/stdio.ts +102 -0
  55. package/src/mcp2026/toolsearch.ts +56 -0
  56. package/src/mcphandler.ts +1252 -0
  57. package/src/mcpheader.ts +19 -0
  58. package/src/mcpserve.ts +115 -2
  59. package/src/mcpserverauth.ts +216 -0
  60. package/src/serve.ts +7 -0
  61. package/src/skill.ts +50 -0
  62. package/src/toolkit.ts +31 -4
  63. package/src/types.ts +23 -0
package/dist/client.d.ts CHANGED
@@ -208,6 +208,12 @@ export type MetricEvent = {
208
208
  promptTokens: number;
209
209
  completionTokens: number;
210
210
  error?: string;
211
+ /** L1: how many questions were asked. */
212
+ questions: number;
213
+ /** L1: sha256 hex of `canonicalRequest(model, questions)` ("" when unencodable). */
214
+ questionsHash: string;
215
+ /** True when the decision came from the classifier cache and no request was sent. */
216
+ cached: boolean;
211
217
  } | {
212
218
  event: "classifier.warning";
213
219
  question: string;
@@ -358,12 +364,21 @@ export interface RunResult {
358
364
  export type StreamEvent = {
359
365
  type: "text";
360
366
  delta: string;
361
- } | {
367
+ }
368
+ /** Model reasoning narration (e.g. an ACP agent's `agent_thought_chunk`). Never part of
369
+ * the answer text or the RunResult. */
370
+ | {
371
+ type: "thought";
372
+ delta: string;
373
+ via?: "acp";
374
+ }
375
+ /** `via: "acp"` marks a tool the ACP agent ran itself: reported, never executed by the loop. */
376
+ | {
362
377
  type: "tool_call";
363
378
  id: string;
364
379
  name: string;
365
380
  args: Record<string, unknown>;
366
- via?: "call_tool";
381
+ via?: "call_tool" | "acp";
367
382
  } | {
368
383
  type: "tool_result";
369
384
  id: string;
@@ -371,7 +386,7 @@ export type StreamEvent = {
371
386
  output: string;
372
387
  isError: boolean;
373
388
  metadata?: Record<string, unknown>;
374
- via?: "call_tool";
389
+ via?: "call_tool" | "acp";
375
390
  } | {
376
391
  type: "usage";
377
392
  usage: Usage;
@@ -582,11 +597,22 @@ export interface InProcessResponse {
582
597
  totalTokens?: number;
583
598
  };
584
599
  }
600
+ /** A streaming in-process model: `emit` each event as it happens — `text` deltas become
601
+ * the streamed answer as they arrive; any other event (thought, an agent's own tool_call /
602
+ * tool_result) goes straight to the stream consumer and is never executed by the loop.
603
+ * Return the whole message when done (its `toolCalls`, if any, are what the loop runs). */
604
+ export type InProcessGenerateStream = (request: InProcessRequest, emit: (event: StreamEvent) => void) => InProcessResponse | Promise<InProcessResponse>;
605
+ type InProcessGenerate = (request: InProcessRequest) => InProcessResponse | Promise<InProcessResponse>;
585
606
  /** Options for {@link createInProcessClient}: every client option EXCEPT the three
586
- * that describe a wire — `baseUrl`, `apiKey` and `style` — plus `generate`. */
607
+ * that describe a wire — `baseUrl`, `apiKey` and `style` — plus `generate` and/or
608
+ * `generateStream` (at least one). */
587
609
  export type InProcessOptions = Omit<ClientOptions, "baseUrl" | "apiKey" | "style" | "fetch"> & {
588
610
  /** Your model. Receives the assembled request, returns one assistant message. */
589
- generate: (request: InProcessRequest) => InProcessResponse | Promise<InProcessResponse>;
611
+ generate?: InProcessGenerate;
612
+ /** Your model, streaming for real. With it, `stream()` is incremental; without it a
613
+ * `stream()` on a plain `generate` is refused. When both are set, `generate` serves
614
+ * non-streaming calls; alone, it serves them too (with a no-op `emit`). */
615
+ generateStream?: InProcessGenerateStream;
590
616
  };
591
617
  /**
592
618
  * Build a `fetch`-shaped adapter over a semantic `generate` function (ADR 0030):
@@ -596,9 +622,11 @@ export type InProcessOptions = Omit<ClientOptions, "baseUrl" | "apiKey" | "style
596
622
  * or the agent runtime's `inProcess` option) goes through this ONE function, so there
597
623
  * is no second copy of the request/response assembly to drift.
598
624
  *
599
- * Streaming is refused rather than faked — see the thrown error.
625
+ * Streaming with only a plain `generate` is refused rather than faked — see the thrown
626
+ * error. With `generateStream` it streams for real: each text event is written to the
627
+ * SSE body the moment it is emitted.
600
628
  */
601
- export declare function createInProcessFetch(generate: (request: InProcessRequest) => InProcessResponse | Promise<InProcessResponse>): typeof fetch;
629
+ export declare function createInProcessFetch(generate?: InProcessGenerate, generateStream?: InProcessGenerateStream): typeof fetch;
602
630
  /**
603
631
  * A client backed by a model running IN THIS PROCESS — no server, no socket, and no
604
632
  * HTTP types to construct.
@@ -609,7 +637,8 @@ export declare function createInProcessFetch(generate: (request: InProcessReques
609
637
  * The `fetch`-shaped adapter itself is {@link createInProcessFetch} — this function
610
638
  * is just that adapter wired into a `Client`.
611
639
  *
612
- * Streaming is refused rather than faked — see {@link createInProcessFetch}.
640
+ * Streaming is refused rather than faked for a plain `generate`; with `generateStream`
641
+ * it streams for real — see {@link createInProcessFetch}.
613
642
  */
614
643
  export declare function createInProcessClient(opts: InProcessOptions): Client;
615
644
  /** Stateful multi-turn conversation: each send() continues the same transcript (memory). */
@@ -625,3 +654,4 @@ export declare class Conversation {
625
654
  /** Reset the conversation memory. */
626
655
  reset(): void;
627
656
  }
657
+ export {};
package/dist/client.js CHANGED
@@ -1132,6 +1132,13 @@ export class Client {
1132
1132
  if (payload === "[DONE]")
1133
1133
  break;
1134
1134
  const j = safeParse(payload);
1135
+ // A non-text event from an in-process streaming generate rides in-band so it keeps
1136
+ // its order and arrives as it happens; it goes straight to the consumer and is never
1137
+ // executed. Honoured only on the in-process sentinel URL, never from a real server.
1138
+ if (j && j[IN_PROCESS_SIDE_KEY] && this.opts.baseUrl === IN_PROCESS_BASE_URL) {
1139
+ yield j[IN_PROCESS_SIDE_KEY];
1140
+ continue;
1141
+ }
1135
1142
  if (j?.usage)
1136
1143
  addUsage(usage, j.usage, "openai");
1137
1144
  const choice = j?.choices?.[0];
@@ -1336,6 +1343,9 @@ export function createClient(opts) {
1336
1343
  * it must be syntactically valid. `.invalid` is reserved by RFC 2606 precisely so a
1337
1344
  * name can never resolve. */
1338
1345
  const IN_PROCESS_BASE_URL = "http://in-process.invalid/v1";
1346
+ /** The in-band SSE key that carries a non-text stream event from an in-process
1347
+ * streaming generate to the consumer (honoured only on {@link IN_PROCESS_BASE_URL}). */
1348
+ const IN_PROCESS_SIDE_KEY = "toolnexus_event";
1339
1349
  /**
1340
1350
  * Build a `fetch`-shaped adapter over a semantic `generate` function (ADR 0030):
1341
1351
  * the reusable piece of {@link createInProcessClient} — assembles the wire request,
@@ -1344,14 +1354,19 @@ const IN_PROCESS_BASE_URL = "http://in-process.invalid/v1";
1344
1354
  * or the agent runtime's `inProcess` option) goes through this ONE function, so there
1345
1355
  * is no second copy of the request/response assembly to drift.
1346
1356
  *
1347
- * Streaming is refused rather than faked — see the thrown error.
1357
+ * Streaming with only a plain `generate` is refused rather than faked — see the thrown
1358
+ * error. With `generateStream` it streams for real: each text event is written to the
1359
+ * SSE body the moment it is emitted.
1348
1360
  */
1349
- export function createInProcessFetch(generate) {
1350
- if (typeof generate !== "function") {
1351
- throw new Error("toolnexus: createInProcessFetch requires a `generate` function");
1361
+ export function createInProcessFetch(generate, generateStream) {
1362
+ if (typeof generate !== "function" && typeof generateStream !== "function") {
1363
+ throw new Error("toolnexus: createInProcessFetch requires a `generate` (or `generateStream`) function");
1352
1364
  }
1353
1365
  const inProcessFetch = async (_url, init) => {
1354
1366
  const body = JSON.parse(String(init?.body ?? "{}"));
1367
+ const request = { messages: body.messages ?? [], tools: body.tools, model: body.model, body };
1368
+ if (body.stream && generateStream)
1369
+ return inProcessStream(generateStream, request);
1355
1370
  // A generate() returns a whole answer, so a stream would be one chunk pretending
1356
1371
  // to be many. Content and delta-count assertions cannot tell that from a real
1357
1372
  // stream, so refuse instead of degrading silently.
@@ -1359,24 +1374,10 @@ export function createInProcessFetch(generate) {
1359
1374
  throw new Error("toolnexus: createInProcessClient does not support streaming — `generate` returns a " +
1360
1375
  "complete answer. Use the non-streaming run, or supply a `fetch`/transport that streams.");
1361
1376
  }
1362
- const answer = await generate({
1363
- messages: body.messages ?? [],
1364
- tools: body.tools,
1365
- model: body.model,
1366
- body,
1367
- });
1377
+ const answer = generate ? await generate(request) : await generateStream(request, () => { });
1368
1378
  const message = { role: "assistant" };
1369
1379
  if (answer.toolCalls && answer.toolCalls.length > 0) {
1370
- message.tool_calls = answer.toolCalls.map((c, i) => ({
1371
- id: c.id ?? `call_${i}`,
1372
- type: "function",
1373
- function: {
1374
- name: c.name,
1375
- // A string is already encoded; anything else is encoded here so a model
1376
- // author never has to think about the wire.
1377
- arguments: typeof c.arguments === "string" ? c.arguments : JSON.stringify(c.arguments ?? {}),
1378
- },
1379
- }));
1380
+ message.tool_calls = wireToolCalls(answer.toolCalls, false);
1380
1381
  }
1381
1382
  else {
1382
1383
  message.content = answer.content ?? "";
@@ -1395,6 +1396,80 @@ export function createInProcessFetch(generate) {
1395
1396
  };
1396
1397
  return inProcessFetch;
1397
1398
  }
1399
+ function wireToolCalls(calls, indexed) {
1400
+ return calls.map((c, i) => ({
1401
+ ...(indexed ? { index: i } : {}),
1402
+ id: c.id ?? `call_${i}`,
1403
+ type: "function",
1404
+ function: {
1405
+ name: c.name,
1406
+ // A string is already encoded; anything else is encoded here so a model
1407
+ // author never has to think about the wire.
1408
+ arguments: typeof c.arguments === "string" ? c.arguments : JSON.stringify(c.arguments ?? {}),
1409
+ },
1410
+ }));
1411
+ }
1412
+ /** Runs a streaming generate and answers with an OpenAI SSE body written AS EVENTS ARRIVE:
1413
+ * text → a content delta; any other event → an in-band side chunk for the consumer; then
1414
+ * (tool calls as one delta | the content as one delta when no text was emitted), the
1415
+ * finish_reason chunk, usage, and [DONE]. */
1416
+ function inProcessStream(generateStream, request) {
1417
+ const enc = new TextEncoder();
1418
+ let open = true;
1419
+ const sse = new ReadableStream({
1420
+ start(controller) {
1421
+ const chunk = (v) => {
1422
+ if (open)
1423
+ controller.enqueue(enc.encode(`data: ${JSON.stringify(v)}\n\n`));
1424
+ };
1425
+ let sawText = false;
1426
+ void (async () => {
1427
+ try {
1428
+ const answer = await generateStream(request, (ev) => {
1429
+ if (!ev || typeof ev !== "object")
1430
+ return;
1431
+ if (ev.type === "text") {
1432
+ if (ev.delta) {
1433
+ sawText = true;
1434
+ chunk({ choices: [{ index: 0, delta: { content: ev.delta } }] });
1435
+ }
1436
+ return;
1437
+ }
1438
+ chunk({ [IN_PROCESS_SIDE_KEY]: ev });
1439
+ });
1440
+ let finish = "stop";
1441
+ if (answer.toolCalls && answer.toolCalls.length > 0) {
1442
+ chunk({ choices: [{ index: 0, delta: { tool_calls: wireToolCalls(answer.toolCalls, true) } }] });
1443
+ finish = "tool_calls";
1444
+ }
1445
+ else if (!sawText && answer.content) {
1446
+ chunk({ choices: [{ index: 0, delta: { content: answer.content } }] });
1447
+ }
1448
+ chunk({ choices: [{ index: 0, delta: {}, finish_reason: finish }] });
1449
+ const u = answer.usage;
1450
+ if (u) {
1451
+ const p = u.promptTokens ?? 0, c = u.completionTokens ?? 0;
1452
+ chunk({ choices: [], usage: { prompt_tokens: p, completion_tokens: c, total_tokens: u.totalTokens || p + c } });
1453
+ }
1454
+ if (open)
1455
+ controller.enqueue(enc.encode("data: [DONE]\n\n"));
1456
+ open = false;
1457
+ controller.close();
1458
+ }
1459
+ catch (e) {
1460
+ if (open) {
1461
+ open = false;
1462
+ controller.error(e);
1463
+ }
1464
+ }
1465
+ })();
1466
+ },
1467
+ cancel() {
1468
+ open = false;
1469
+ },
1470
+ });
1471
+ return new Response(sse, { status: 200, headers: { "content-type": "text/event-stream" } });
1472
+ }
1398
1473
  /**
1399
1474
  * A client backed by a model running IN THIS PROCESS — no server, no socket, and no
1400
1475
  * HTTP types to construct.
@@ -1405,11 +1480,15 @@ export function createInProcessFetch(generate) {
1405
1480
  * The `fetch`-shaped adapter itself is {@link createInProcessFetch} — this function
1406
1481
  * is just that adapter wired into a `Client`.
1407
1482
  *
1408
- * Streaming is refused rather than faked — see {@link createInProcessFetch}.
1483
+ * Streaming is refused rather than faked for a plain `generate`; with `generateStream`
1484
+ * it streams for real — see {@link createInProcessFetch}.
1409
1485
  */
1410
1486
  export function createInProcessClient(opts) {
1411
- const { generate, ...rest } = opts;
1412
- const inProcessFetch = createInProcessFetch(generate);
1487
+ const { generate, generateStream, ...rest } = opts;
1488
+ if (typeof generate !== "function" && typeof generateStream !== "function") {
1489
+ throw new Error("toolnexus: createInProcessClient requires a `generate` (or `generateStream`) function");
1490
+ }
1491
+ const inProcessFetch = createInProcessFetch(generate, generateStream);
1413
1492
  return new Client({
1414
1493
  // Retries default to ZERO here, unlike a network client. There is no wire, so
1415
1494
  // there is no transient failure to ride out: whatever `generate` throws will
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ export * from "./a2a.js";
10
10
  export * from "./acp.js";
11
11
  export * from "./serve.js";
12
12
  export * from "./mcpserve.js";
13
+ export * from "./mcphandler.js";
14
+ export * from "./mcpserverauth.js";
13
15
  export * from "./adapters.js";
14
16
  export * from "./translate.js";
15
17
  export * from "./toolkit.js";
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ export * from "./a2a.js";
10
10
  export * from "./acp.js";
11
11
  export * from "./serve.js";
12
12
  export * from "./mcpserve.js";
13
+ export * from "./mcphandler.js";
14
+ export * from "./mcpserverauth.js";
13
15
  export * from "./adapters.js";
14
16
  export * from "./translate.js";
15
17
  export * from "./toolkit.js";
package/dist/judge.d.ts CHANGED
@@ -7,12 +7,12 @@
7
7
  *
8
8
  * A gate never authorises; it only declines to decide.
9
9
  */
10
- import { Classifier, Decision, type DecisionAnswer, type NoulCriteria, type Question, type RecordedDecision } from "./classifier.js";
10
+ import { Classifier, Decision, type DecisionAnswer, type NoulCriteria, type AnyQuestion, type RecordedDecision } from "./classifier.js";
11
11
  import type { Request } from "./types.js";
12
12
  /** One named question of an ordered question list. */
13
13
  export interface NamedQuestion {
14
14
  name: string;
15
- question: Question;
15
+ question: AnyQuestion;
16
16
  }
17
17
  /** `noul` question with a name. Name the state field it judges in `instructions`. */
18
18
  export declare function noul(name: string, instructions: string, criteria?: NoulCriteria): NamedQuestion;
@@ -20,8 +20,11 @@ export declare function noul(name: string, instructions: string, criteria?: Noul
20
20
  export declare function choice(name: string, instructions: string, options: Record<string, string>): NamedQuestion;
21
21
  /** `score` question with a name; `levels` is the ordered rubric. */
22
22
  export declare function score(name: string, instructions: string, levels: string[]): NamedQuestion;
23
+ /** A raw question with a name: a System One type the library does not model (e.g. `box`), sent
24
+ * as written. Its answer decodes to a raw answer `{type, value}`. */
25
+ export declare function raw(name: string, type: string, instructions: string, criteria?: unknown): NamedQuestion;
23
26
  /** Ordered list -> the §8B questions map. A duplicate name is an error naming it. */
24
- export declare function questionMap(questions: readonly NamedQuestion[]): Record<string, Question>;
27
+ export declare function questionMap(questions: readonly NamedQuestion[]): Record<string, AnyQuestion>;
25
28
  /** `role` next to the data's fields; a non-object value goes under `data`. */
26
29
  export declare function State(role: string, data: unknown): Record<string, unknown>;
27
30
  /** Sugar: `{context, message, ...extra}`. */
package/dist/judge.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * A gate never authorises; it only declines to decide.
9
9
  */
10
- import { Classifier, noul as noulQ, choice as choiceQ, score as scoreQ, } from "./classifier.js";
10
+ import { Classifier, isRawAnswer, raw as rawQ, noul as noulQ, choice as choiceQ, score as scoreQ, } from "./classifier.js";
11
11
  /** `noul` question with a name. Name the state field it judges in `instructions`. */
12
12
  export function noul(name, instructions, criteria) {
13
13
  return { name, question: noulQ(instructions, criteria) };
@@ -20,6 +20,11 @@ export function choice(name, instructions, options) {
20
20
  export function score(name, instructions, levels) {
21
21
  return { name, question: scoreQ(instructions, levels) };
22
22
  }
23
+ /** A raw question with a name: a System One type the library does not model (e.g. `box`), sent
24
+ * as written. Its answer decodes to a raw answer `{type, value}`. */
25
+ export function raw(name, type, instructions, criteria) {
26
+ return { name, question: rawQ(type, instructions, criteria) };
27
+ }
23
28
  /** Ordered list -> the §8B questions map. A duplicate name is an error naming it. */
24
29
  export function questionMap(questions) {
25
30
  const out = {};
@@ -47,6 +52,13 @@ function noulBand(p, b) {
47
52
  return p < b.low ? "no" : p > b.high ? "yes" : "uncertain";
48
53
  }
49
54
  function wrap(a, b) {
55
+ if (isRawAnswer(a)) {
56
+ // An unmodelled answer type is never sure: there is no number to band.
57
+ const out = { ...a, sure: false };
58
+ Object.defineProperty(out, "value", { value: () => 0, enumerable: false });
59
+ Object.defineProperty(out, "pick", { value: () => "", enumerable: false });
60
+ return out;
61
+ }
50
62
  const extra = a.type === "noul"
51
63
  ? { band: noulBand(a.noul, b) }
52
64
  : { sure: a.confidence > b.high && !(a.type === "choice" && a.nearUniform) };
package/dist/mcp.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import type { ElicitRequest, ElicitResult } from "@modelcontextprotocol/sdk/types.js";
2
- import { type JSONSchema, type Tool, type ToolHints, type McpStatus, type Request, type Answer } from "./types.js";
2
+ import { type JSONSchema, type Tool, type ToolHints, type ToolResult, type McpStatus, type Request, type Answer } from "./types.js";
3
3
  import { type ContentPart } from "./content.js";
4
+ import type { SkillDef } from "./skill.js";
5
+ import { type AuthConfig } from "./mcp2026/source.js";
6
+ import { type TokenStore } from "./mcp2026/auth.js";
7
+ export { Token, MemoryTokenStore, type TokenStore } from "./mcp2026/auth.js";
8
+ export type { AuthConfig } from "./mcp2026/source.js";
4
9
  /** Map an MCP `elicitation/create` request onto a §10 `Request`. */
5
10
  export declare function elicitationToRequest(params: ElicitRequest["params"]): Request;
6
11
  /** Map a resolved §10 `Answer` back onto an MCP `ElicitResult`. ok→accept; declined→decline; else→cancel. */
@@ -18,6 +23,10 @@ export interface LocalServer {
18
23
  * builtins/skills filter (nil/empty ⇒ all; ≥1 true ⇒ allowlist; only-false ⇒ drop-list; unknown
19
24
  * ⇒ ignore+warn). Applied to the listed defs before sanitize/prefix. */
20
25
  tools?: Record<string, boolean>;
26
+ /** §2.8 lazy tool search: expose `<server>_search_tools` + `<server>_call_tool` instead of every tool. */
27
+ toolSearch?: boolean;
28
+ /** §2.8 what this server is for (shown by search_tools). */
29
+ description?: string;
21
30
  }
22
31
  export interface RemoteServer {
23
32
  type?: "remote";
@@ -28,6 +37,11 @@ export interface RemoteServer {
28
37
  timeout?: number;
29
38
  /** §2 Gap 7. Per-server tool allowlist (see LocalServer.tools). */
30
39
  tools?: Record<string, boolean>;
40
+ /** §2.8 lazy tool search (see LocalServer.toolSearch). */
41
+ toolSearch?: boolean;
42
+ description?: string;
43
+ /** §2A OAuth 2.1 client settings. Absent ⇒ a 401 still triggers the flow (CIMD if the AS supports it). */
44
+ auth?: AuthConfig;
31
45
  }
32
46
  export type ServerConfig = LocalServer | RemoteServer;
33
47
  export type McpConfig = Record<string, ServerConfig>;
@@ -46,6 +60,8 @@ export declare function expandEnvHeaders(headers?: Record<string, string>): Reco
46
60
  * takes it there.
47
61
  */
48
62
  export declare function mcpContentToParts(content: unknown): ContentPart[] | undefined;
63
+ /** §2 `tools/call` result → ToolResult (shared by the 2026 path and the frozen fallback). */
64
+ export declare function mapCallToolResult(result: any, server: string): ToolResult;
49
65
  export interface McpSource {
50
66
  tools: Tool[];
51
67
  status: Record<string, McpStatus>;
@@ -56,6 +72,8 @@ export interface McpSource {
56
72
  * source is closed.
57
73
  */
58
74
  resourceTools(): Tool[];
75
+ /** §2.7 skills served by 2026 servers (skills extension); the toolkit adds them to the `skill` tool. */
76
+ skills: SkillDef[];
59
77
  }
60
78
  /** One server's outcome, reported by `loadMcp`'s `onServer` the moment that server finishes. */
61
79
  export interface McpServerResult {
@@ -75,12 +93,21 @@ export declare function mcpHints(a: {
75
93
  destructiveHint?: boolean;
76
94
  openWorldHint?: boolean;
77
95
  } | undefined): ToolHints | undefined;
96
+ /** Options shared by loadMcp / listMcpTools for the 2026 path and OAuth (§2A.1a). */
97
+ export interface McpLoadOptions {
98
+ waitFor?: (request: Request) => Promise<Answer>;
99
+ signal?: AbortSignal;
100
+ /** Where OAuth tokens live (default: in memory, per load). */
101
+ tokenStore?: TokenStore;
102
+ /** How `auto` OAuth mode opens the authorization URL (default: the OS opener). */
103
+ openUrl?: (url: string) => unknown | Promise<unknown>;
104
+ /** Final exposed names to drop; on a tool-search server they are also hidden from search/call. */
105
+ disableTools?: string[];
106
+ }
78
107
  /** Connect to every enabled server, list + convert tools. Failures are isolated.
79
108
  * §2 Gap 3: an optional `signal` bounds each connect/list by the server timeout and aborts the
80
109
  * whole load promptly on parent cancellation. */
81
- export declare function loadMcp(input: string | object, opts?: {
82
- waitFor?: (request: Request) => Promise<Answer>;
83
- signal?: AbortSignal;
110
+ export declare function loadMcp(input: string | object, opts?: McpLoadOptions & {
84
111
  /**
85
112
  * Called once per server the moment it is connected, failed or found disabled (SPEC §4C),
86
113
  * so a host can register a fast server's tools while a slow one is still connecting.
@@ -105,6 +132,4 @@ export interface McpInventory {
105
132
  * §2 Gap 6. Connect to every enabled server, list tool definitions, and DISCONNECT before
106
133
  * returning — no toolkit, no execute wiring, nothing left running. Failure isolation as in loadMcp.
107
134
  */
108
- export declare function listMcpTools(input: string | object, opts?: {
109
- signal?: AbortSignal;
110
- }): Promise<McpInventory>;
135
+ export declare function listMcpTools(input: string | object, opts?: McpLoadOptions): Promise<McpInventory>;