torus-ai 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -144,6 +144,82 @@ A weekly GitHub Action ([model-watch.yml](./.github/workflows/model-watch.yml))
144
144
  pulls NVIDIA's live `/v1/models`, flags new free endpoints as candidates, and opens
145
145
  a PR for human review against the policy. Run it locally with `npm run model-watch`.
146
146
 
147
+ ## Connecting to MCP servers (Torus is an MCP host)
148
+
149
+ Torus speaks two flavors of [MCP](https://modelcontextprotocol.io):
150
+
151
+ - **In-process** — `tool()` + `createSdkMcpServer()` define tools that run in your
152
+ process (the toolkit, the catalog/billing servers).
153
+ - **External** — connect to the wider MCP ecosystem (GitHub, Postgres, Slack, …)
154
+ over **stdio** (a local subprocess) or **HTTP**. Their tools are discovered and
155
+ registered under the same `mcp__<server>__<tool>` namespace, so they work in the
156
+ loop, the cascade, and packs, gated by the same permission allowlist.
157
+
158
+ ```ts
159
+ import { query, connectMcpServers } from "torus-ai";
160
+
161
+ const { servers, close } = await connectMcpServers({
162
+ github: { command: "npx", args: ["-y", "@modelcontextprotocol/server-github"],
163
+ env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! } }, // stdio
164
+ docs: { type: "http", url: "https://example.com/mcp" }, // remote
165
+ });
166
+
167
+ for await (const ev of query("List my 3 newest GitHub issues", {
168
+ mcpServers: servers,
169
+ permissions: { allowedTools: ["mcp__github__*"] }, // allowlist the external tools
170
+ })) { /* ... */ }
171
+
172
+ await close(); // tear down the subprocess / connection
173
+ ```
174
+
175
+ Needs the optional `@modelcontextprotocol/sdk` package (`npm i @modelcontextprotocol/sdk`).
176
+
177
+ ## Specializing for a product (packs)
178
+
179
+ Don't fork the SDK per product — load a **pack**. A pack is an adapter that turns
180
+ the generic engine into a vertical specialist (a bridal consultant, a mortgage
181
+ advisor, a support agent): persona + sales playbook + policy + domain tools +
182
+ catalog grounding + guardrails.
183
+
184
+ ```ts
185
+ import { createSpecializedAgent, createCatalogServer, createInvoiceServer,
186
+ createHandoffServer } from "torus-ai";
187
+
188
+ const agent = createSpecializedAgent({
189
+ name: "bridal",
190
+ persona: "You are a warm bridal consultant for Aurora Bridal.",
191
+ playbook: "discover needs → recommend → handle objections → close → settle → confirm",
192
+ knowledge: { catalog: dresses, faqs: "Alterations take 3 weeks. ..." }, // → search_catalog
193
+ tools: [createInvoiceServer(), createHandoffServer()],
194
+ guardrails: {
195
+ policy: "Never invent a price or availability. Max 10% discount. Escalate over $5,000.",
196
+ confirm: ["mcp__billing__create_invoice"], // money step needs a yes
197
+ },
198
+ }, {
199
+ onConfirm: async (tool, input) => askHuman(tool, input), // your confirmation UI
200
+ });
201
+
202
+ for await (const ev of agent.query("Anything under $2k for an August wedding?")) { /* ... */ }
203
+ ```
204
+
205
+ What the pack gives you, mapped to the engine:
206
+
207
+ | Pack part | Effect |
208
+ |---|---|
209
+ | `persona` + `playbook` + `policy` | assembled into the system prompt |
210
+ | `knowledge.catalog` | auto-wired `search_catalog` tool + a "never guess prices" instruction |
211
+ | `tools` | your domain actions (compose the [toolkit](./src/packkit.ts): catalog, lead memory, invoice, handoff) |
212
+ | `guardrails.allowedTools` / `confirm` / `canUseTool` | the safety gate — irreversible steps (billing) pause for confirmation |
213
+ | `model` | defaults to the free-first cascade |
214
+
215
+ Edit content as files (`persona.md`, `playbook.md`, `policy.md`, `catalog.json`,
216
+ `faqs.md`) and `loadPack("packs/bridal", { tools })` assembles the pack — so a shop
217
+ owner edits the catalog and tone while devs write the few action tools.
218
+
219
+ The reusable toolkit ([`src/packkit.ts`](./src/packkit.ts)): `createCatalogServer`,
220
+ `createLeadMemoryServer`, `createInvoiceServer` (generic settle stub),
221
+ `createHandoffServer`.
222
+
147
223
  ## The stage contract (Layer 2)
148
224
 
149
225
  Each `stages/NN_verb/CONTEXT.md` is both the agent's instructions and human docs:
package/dist/index.d.ts CHANGED
@@ -395,6 +395,133 @@ interface DefaultProviderOptions {
395
395
  */
396
396
  declare function createDefaultProvider(opts?: DefaultProviderOptions): CascadeProvider;
397
397
 
398
+ type CatalogItem = Record<string, unknown> & {
399
+ id?: string;
400
+ name?: string;
401
+ price?: number;
402
+ tags?: string[];
403
+ available?: boolean;
404
+ };
405
+ /** A `search_catalog` tool over an in-memory product list (text + price + tags). */
406
+ declare function createCatalogServer(items: CatalogItem[], opts?: {
407
+ serverName?: string;
408
+ }): SdkMcpServer;
409
+ /** `get_lead` / `update_lead` over an in-memory customer profile (the funnel state). */
410
+ declare function createLeadMemoryServer(initial?: Record<string, unknown>): SdkMcpServer & {
411
+ lead: Record<string, unknown>;
412
+ };
413
+ interface Invoice {
414
+ id: string;
415
+ amount: number;
416
+ currency: string;
417
+ items?: unknown;
418
+ customer?: unknown;
419
+ status: "pending";
420
+ }
421
+ /**
422
+ * A generic `create_invoice` settle tool: records an order + amount as pending
423
+ * and returns an invoice id. Provider-agnostic — wire your processor via
424
+ * `onCreate` (e.g. create a real payment link, then confirm via webhook).
425
+ */
426
+ declare function createInvoiceServer(opts?: {
427
+ onCreate?: (inv: Invoice) => void;
428
+ }): SdkMcpServer & {
429
+ invoices: Invoice[];
430
+ };
431
+ /** A `handoff_human` escalation tool. Wire `onHandoff` to notify a real agent. */
432
+ declare function createHandoffServer(opts?: {
433
+ onHandoff?: (info: {
434
+ reason: string;
435
+ summary: string;
436
+ }) => void;
437
+ }): SdkMcpServer;
438
+
439
+ interface PackKnowledge {
440
+ /** Product catalog — auto-wired into a `search_catalog` tool for grounding. */
441
+ catalog?: CatalogItem[];
442
+ /** Short reference text (policies, FAQs) appended to the system prompt. */
443
+ faqs?: string;
444
+ }
445
+ interface PackGuardrails {
446
+ /** Allowlist of tool names the agent may call (namespaced, wildcards ok). */
447
+ allowedTools?: string[];
448
+ /** Tools that require explicit confirmation before running (namespaced names). */
449
+ confirm?: string[];
450
+ /** Extra custom gate, evaluated after allow/confirm. */
451
+ canUseTool?: CanUseTool;
452
+ /** Rules text (discount authority, no-overpromise, escalation) added to the prompt. */
453
+ policy?: string;
454
+ }
455
+ interface AgentPack {
456
+ name: string;
457
+ persona: string;
458
+ playbook?: string;
459
+ tools?: SdkMcpServer[];
460
+ knowledge?: PackKnowledge;
461
+ guardrails?: PackGuardrails;
462
+ model?: ModelProvider;
463
+ }
464
+ interface SpecializeOptions {
465
+ provider?: ModelProvider;
466
+ /** Called when a `confirm` tool wants to run; return true to allow. */
467
+ onConfirm?: (toolName: string, input: Record<string, unknown>) => boolean | Promise<boolean>;
468
+ /** Allow built-in file tools (read/write/list). Off by default for packs. */
469
+ includeBuiltins?: boolean;
470
+ maxTurns?: number;
471
+ }
472
+ interface SpecializedAgent {
473
+ pack: AgentPack;
474
+ system: string;
475
+ servers: SdkMcpServer[];
476
+ query(prompt: string | ContentBlock[], extra?: {
477
+ maxTurns?: number;
478
+ }): AsyncGenerator<AgentEvent>;
479
+ }
480
+ /** Build a ready-to-run specialized agent from a pack. */
481
+ declare function createSpecializedAgent(pack: AgentPack, opts?: SpecializeOptions): SpecializedAgent;
482
+ /**
483
+ * Load a pack's content from a folder (so non-devs can edit it):
484
+ * persona.md · playbook.md · policy.md · catalog.json · faqs.md
485
+ * Code tools (quote/reserve/invoice/...) are passed via `opts.tools`.
486
+ */
487
+ declare function loadPack(dir: string, opts?: {
488
+ tools?: SdkMcpServer[];
489
+ }): Promise<AgentPack>;
490
+
491
+ type McpServerConfig = {
492
+ type?: "stdio";
493
+ command: string;
494
+ args?: string[];
495
+ env?: Record<string, string>;
496
+ } | {
497
+ type: "http";
498
+ url: string;
499
+ headers?: Record<string, string>;
500
+ };
501
+ interface McpConnection {
502
+ /** Namespaced server — its tools become mcp__<name>__<tool>. */
503
+ server: SdkMcpServer;
504
+ /** Tear down the MCP client + transport. */
505
+ close(): Promise<void>;
506
+ }
507
+ /** Connect to one external MCP server and expose its tools as an SdkMcpServer. */
508
+ declare function connectMcpServer(name: string, config: McpServerConfig): Promise<McpConnection>;
509
+ /**
510
+ * Connect to several external MCP servers at once.
511
+ *
512
+ * const { servers, close } = await connectMcpServers({
513
+ * github: { command: "npx", args: ["-y", "@modelcontextprotocol/server-github"], env: { GITHUB_TOKEN } },
514
+ * docs: { type: "http", url: "https://example.com/mcp" },
515
+ * });
516
+ * for await (const ev of query("...", { mcpServers: servers,
517
+ * permissions: { allowedTools: ["mcp__github__*"] } })) { ... }
518
+ * await close();
519
+ */
520
+ declare function connectMcpServers(configs: Record<string, McpServerConfig>): Promise<{
521
+ servers: SdkMcpServer[];
522
+ close(): Promise<void>;
523
+ }>;
524
+
398
525
  declare const CHEAP_MODEL = "claude-haiku-4-5";
399
526
  declare const EXPENSIVE_MODEL = "claude-sonnet-4-6";
400
527
  declare const GEMINI_CHEAP_MODEL = "gemini-2.5-flash-lite";
@@ -457,4 +584,4 @@ interface QueryOptions {
457
584
  */
458
585
  declare function query(prompt: string | ContentBlock[], options?: QueryOptions): AsyncGenerator<AgentEvent>;
459
586
 
460
- export { type AgentEvent, type AnthropicOptions, AnthropicProvider, CHEAP_MODEL, type CanUseTool, type CascadeOptions, CascadeProvider, type CascadeStep, type Complexity, type ContentBlock, DEEPSEEK_V4_FLASH, DEEPSEEK_V4_PRO, type DefaultProviderOptions, EXPENSIVE_MODEL, GEMINI_CHEAP_MODEL, GEMINI_EXPENSIVE_MODEL, type GeminiOptions, GeminiProvider, type JSONSchema, KIMI_K2_6, LLAMA_VISION, type LoadedContext, type LoopOptions, type LoopResult, type MediaBlock, type Message, type MockOptions, MockProvider, type ModelProvider, type ModelRequest, type ModelResponse, NVIDIA_BASE_URL, type NvidiaOptions, NvidiaProvider, type PermissionConfig, type PermissionDecision, PermissionEngine, type PipelineOptions, type QueryOptions, type RegisteredTool, type Role, type RouterOptions, type RoutingStats, type SdkMcpServer, type StageContract, type StageInput, type StopReason, type TextBlock, type ToolContext, type ToolDefinition, ToolRegistry, type ToolResultBlock, type ToolResultPayload, type ToolSchema, type ToolUseBlock, builtinTools, classifyComplexity, classifyComplexityGemini, createDefaultProvider, createSdkMcpServer, fastHeuristic, getRoutingStats, hasMedia, judgeComplexity, judgeComplexityGemini, latestUserText, listDirTool, loadStageContext, loadStages, matchesAllow, parseContract, query, readFileTool, runLoop, runPipeline, selectGeminiModel, selectModel, tool, writeFileTool };
587
+ export { type AgentEvent, type AgentPack, type AnthropicOptions, AnthropicProvider, CHEAP_MODEL, type CanUseTool, type CascadeOptions, CascadeProvider, type CascadeStep, type CatalogItem, type Complexity, type ContentBlock, DEEPSEEK_V4_FLASH, DEEPSEEK_V4_PRO, type DefaultProviderOptions, EXPENSIVE_MODEL, GEMINI_CHEAP_MODEL, GEMINI_EXPENSIVE_MODEL, type GeminiOptions, GeminiProvider, type Invoice, type JSONSchema, KIMI_K2_6, LLAMA_VISION, type LoadedContext, type LoopOptions, type LoopResult, type McpConnection, type McpServerConfig, type MediaBlock, type Message, type MockOptions, MockProvider, type ModelProvider, type ModelRequest, type ModelResponse, NVIDIA_BASE_URL, type NvidiaOptions, NvidiaProvider, type PackGuardrails, type PackKnowledge, type PermissionConfig, type PermissionDecision, PermissionEngine, type PipelineOptions, type QueryOptions, type RegisteredTool, type Role, type RouterOptions, type RoutingStats, type SdkMcpServer, type SpecializeOptions, type SpecializedAgent, type StageContract, type StageInput, type StopReason, type TextBlock, type ToolContext, type ToolDefinition, ToolRegistry, type ToolResultBlock, type ToolResultPayload, type ToolSchema, type ToolUseBlock, builtinTools, classifyComplexity, classifyComplexityGemini, connectMcpServer, connectMcpServers, createCatalogServer, createDefaultProvider, createHandoffServer, createInvoiceServer, createLeadMemoryServer, createSdkMcpServer, createSpecializedAgent, fastHeuristic, getRoutingStats, hasMedia, judgeComplexity, judgeComplexityGemini, latestUserText, listDirTool, loadPack, loadStageContext, loadStages, matchesAllow, parseContract, query, readFileTool, runLoop, runPipeline, selectGeminiModel, selectModel, tool, writeFileTool };
package/dist/index.js CHANGED
@@ -879,6 +879,253 @@ function createDefaultProvider(opts = {}) {
879
879
  });
880
880
  }
881
881
 
882
+ // src/pack.ts
883
+ import { existsSync as existsSync2 } from "fs";
884
+ import { readFile as readFile4 } from "fs/promises";
885
+ import { join as join4 } from "path";
886
+
887
+ // src/packkit.ts
888
+ function createCatalogServer(items, opts = {}) {
889
+ const search = tool(
890
+ "search_catalog",
891
+ "Search the product catalog by text, max price, and tags. Returns matching items with prices and availability. Use this for every product/price/availability question \u2014 never guess.",
892
+ {
893
+ type: "object",
894
+ properties: {
895
+ query: { type: "string" },
896
+ maxPrice: { type: "number" },
897
+ tags: { type: "array", items: { type: "string" } },
898
+ limit: { type: "number" }
899
+ }
900
+ },
901
+ (input) => {
902
+ let res = items.filter((it) => it.available !== false);
903
+ if (input.query) {
904
+ const words = input.query.toLowerCase().split(/\s+/).filter(Boolean);
905
+ res = res.filter((it) => {
906
+ const hay = JSON.stringify(it).toLowerCase();
907
+ return words.every((w) => hay.includes(w));
908
+ });
909
+ }
910
+ if (typeof input.maxPrice === "number") {
911
+ res = res.filter((it) => typeof it.price !== "number" || it.price <= input.maxPrice);
912
+ }
913
+ if (Array.isArray(input.tags) && input.tags.length) {
914
+ res = res.filter((it) => Array.isArray(it.tags) && input.tags.some((t) => it.tags.includes(t)));
915
+ }
916
+ const out = res.slice(0, input.limit ?? 5);
917
+ return { content: out.length ? JSON.stringify(out, null, 2) : "No matching items." };
918
+ }
919
+ );
920
+ return createSdkMcpServer({ name: opts.serverName ?? "catalog", tools: [search] });
921
+ }
922
+ function createLeadMemoryServer(initial = {}) {
923
+ const lead = { ...initial };
924
+ const get = tool(
925
+ "get_lead",
926
+ "Get what we know about the current customer (name, date, budget, stage, items seen).",
927
+ { type: "object", properties: {} },
928
+ () => ({ content: JSON.stringify(lead, null, 2) })
929
+ );
930
+ const update = tool(
931
+ "update_lead",
932
+ "Merge fields into the customer profile, e.g. { budget: 2000, stage: 'recommend' }.",
933
+ { type: "object", properties: { fields: { type: "object" } }, required: ["fields"] },
934
+ (input) => {
935
+ Object.assign(lead, input.fields ?? {});
936
+ return { content: `updated: ${Object.keys(input.fields ?? {}).join(", ") || "(none)"}` };
937
+ }
938
+ );
939
+ return Object.assign(createSdkMcpServer({ name: "lead", tools: [get, update] }), { lead });
940
+ }
941
+ function createInvoiceServer(opts = {}) {
942
+ const invoices = [];
943
+ let n = 0;
944
+ const create = tool(
945
+ "create_invoice",
946
+ "Record an order and amount as a pending invoice to settle, returning an invoice id. Call this only after the customer has agreed to buy.",
947
+ {
948
+ type: "object",
949
+ properties: {
950
+ amount: { type: "number" },
951
+ currency: { type: "string" },
952
+ items: {},
953
+ customer: {}
954
+ },
955
+ required: ["amount"]
956
+ },
957
+ (input) => {
958
+ const inv = {
959
+ id: `inv_${++n}`,
960
+ amount: input.amount,
961
+ currency: input.currency ?? "USD",
962
+ items: input.items,
963
+ customer: input.customer,
964
+ status: "pending"
965
+ };
966
+ invoices.push(inv);
967
+ opts.onCreate?.(inv);
968
+ return {
969
+ content: JSON.stringify({ invoiceId: inv.id, status: inv.status, amount: inv.amount, currency: inv.currency })
970
+ };
971
+ }
972
+ );
973
+ return Object.assign(createSdkMcpServer({ name: "billing", tools: [create] }), { invoices });
974
+ }
975
+ function createHandoffServer(opts = {}) {
976
+ const handoff = tool(
977
+ "handoff_human",
978
+ "Escalate to a human agent with a reason and a short summary of the conversation so far. Use when you're stuck, the request is high-value, or the customer asks for a person.",
979
+ {
980
+ type: "object",
981
+ properties: { reason: { type: "string" }, summary: { type: "string" } },
982
+ required: ["reason"]
983
+ },
984
+ (input) => {
985
+ opts.onHandoff?.({ reason: input.reason, summary: input.summary ?? "" });
986
+ return { content: "Escalated to a human; they will take over shortly." };
987
+ }
988
+ );
989
+ return createSdkMcpServer({ name: "support", tools: [handoff] });
990
+ }
991
+
992
+ // src/pack.ts
993
+ function createSpecializedAgent(pack, opts = {}) {
994
+ const servers = [...pack.tools ?? []];
995
+ if (pack.knowledge?.catalog?.length) servers.unshift(createCatalogServer(pack.knowledge.catalog));
996
+ const parts = [pack.persona.trim()];
997
+ if (pack.playbook) parts.push(`## Playbook
998
+ ${pack.playbook.trim()}`);
999
+ if (pack.guardrails?.policy) parts.push(`## Policy
1000
+ ${pack.guardrails.policy.trim()}`);
1001
+ if (servers.some((s) => s.tools.some((t) => t.name === "search_catalog"))) {
1002
+ parts.push(
1003
+ "Use the `search_catalog` tool for every product, price, or availability question. Never invent a price or claim availability you did not look up."
1004
+ );
1005
+ }
1006
+ if (pack.knowledge?.faqs) parts.push(`## Reference
1007
+ ${pack.knowledge.faqs.trim()}`);
1008
+ const system = parts.join("\n\n");
1009
+ const confirmTools = pack.guardrails?.confirm ?? [];
1010
+ const allow = pack.guardrails?.allowedTools;
1011
+ const base = pack.guardrails?.canUseTool;
1012
+ const canUseTool = async (name, input) => {
1013
+ if (confirmTools.includes(name)) {
1014
+ const ok = opts.onConfirm ? await opts.onConfirm(name, input) : false;
1015
+ if (!ok) return { behavior: "deny", message: `${name} requires confirmation and it was not granted.` };
1016
+ }
1017
+ if (allow && !matchesAllow(name, allow)) {
1018
+ return { behavior: "deny", message: `${name} is not allowed by this pack's guardrails.` };
1019
+ }
1020
+ return base ? base(name, input) : { behavior: "allow" };
1021
+ };
1022
+ const provider = opts.provider ?? pack.model ?? createDefaultProvider();
1023
+ const includeBuiltins = opts.includeBuiltins ?? false;
1024
+ return {
1025
+ pack,
1026
+ system,
1027
+ servers,
1028
+ async *query(prompt, extra) {
1029
+ const registry = new ToolRegistry();
1030
+ if (includeBuiltins) registry.addBuiltins(builtinTools);
1031
+ for (const s of servers) registry.addServer(s);
1032
+ const content = typeof prompt === "string" ? [{ type: "text", text: prompt }] : prompt;
1033
+ const messages = [{ role: "user", content }];
1034
+ const result = yield* runLoop({
1035
+ provider,
1036
+ registry,
1037
+ permissions: new PermissionEngine({ canUseTool }),
1038
+ system,
1039
+ messages,
1040
+ toolContext: { workspaceDir: process.cwd() },
1041
+ maxTurns: extra?.maxTurns ?? opts.maxTurns
1042
+ });
1043
+ yield { type: "result", finalText: result.finalText, turns: result.turns };
1044
+ }
1045
+ };
1046
+ }
1047
+ async function loadPack(dir, opts = {}) {
1048
+ const read = async (f) => {
1049
+ const p = join4(dir, f);
1050
+ return existsSync2(p) ? await readFile4(p, "utf8") : void 0;
1051
+ };
1052
+ const catalogRaw = await read("catalog.json");
1053
+ const policy = await read("policy.md");
1054
+ return {
1055
+ name: dir.split(/[\\/]/).filter(Boolean).pop() ?? "pack",
1056
+ persona: await read("persona.md") ?? "",
1057
+ playbook: await read("playbook.md"),
1058
+ knowledge: {
1059
+ catalog: catalogRaw ? JSON.parse(catalogRaw) : void 0,
1060
+ faqs: await read("faqs.md")
1061
+ },
1062
+ guardrails: policy ? { policy } : void 0,
1063
+ tools: opts.tools
1064
+ };
1065
+ }
1066
+
1067
+ // src/mcp-client.ts
1068
+ async function connect(config) {
1069
+ const clientMod = await import("@modelcontextprotocol/sdk/client/index.js").catch(() => {
1070
+ throw new Error("MCP client needs @modelcontextprotocol/sdk: run `npm i @modelcontextprotocol/sdk`.");
1071
+ });
1072
+ const Client = clientMod.Client;
1073
+ let transport;
1074
+ if (config.type === "http") {
1075
+ const mod = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
1076
+ transport = new mod.StreamableHTTPClientTransport(
1077
+ new URL(config.url),
1078
+ config.headers ? { requestInit: { headers: config.headers } } : void 0
1079
+ );
1080
+ } else {
1081
+ const mod = await import("@modelcontextprotocol/sdk/client/stdio.js");
1082
+ transport = new mod.StdioClientTransport({
1083
+ command: config.command,
1084
+ args: config.args ?? [],
1085
+ env: { ...process.env, ...config.env ?? {} }
1086
+ });
1087
+ }
1088
+ const client = new Client({ name: "torus", version: "0.4.0" }, { capabilities: {} });
1089
+ await client.connect(transport);
1090
+ return client;
1091
+ }
1092
+ async function connectMcpServer(name, config) {
1093
+ const client = await connect(config);
1094
+ const listed = await client.listTools();
1095
+ const tools = (listed.tools ?? []).map(
1096
+ (t) => tool(
1097
+ t.name,
1098
+ t.description ?? "",
1099
+ t.inputSchema ?? { type: "object", properties: {} },
1100
+ async (input) => {
1101
+ const res = await client.callTool({ name: t.name, arguments: input ?? {} });
1102
+ const text = (res.content ?? []).filter((c) => c?.type === "text").map((c) => c.text).join("\n") || JSON.stringify(res.content ?? res);
1103
+ return { content: text, isError: !!res.isError };
1104
+ }
1105
+ )
1106
+ );
1107
+ return {
1108
+ server: createSdkMcpServer({ name, version: "1.0.0", tools }),
1109
+ close: async () => {
1110
+ try {
1111
+ await client.close();
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ };
1116
+ }
1117
+ async function connectMcpServers(configs) {
1118
+ const conns = await Promise.all(
1119
+ Object.entries(configs).map(([name, cfg]) => connectMcpServer(name, cfg))
1120
+ );
1121
+ return {
1122
+ servers: conns.map((c) => c.server),
1123
+ close: async () => {
1124
+ await Promise.all(conns.map((c) => c.close()));
1125
+ }
1126
+ };
1127
+ }
1128
+
882
1129
  // src/index.ts
883
1130
  async function* query(prompt, options = {}) {
884
1131
  const registry = new ToolRegistry();
@@ -917,8 +1164,15 @@ export {
917
1164
  builtinTools,
918
1165
  classifyComplexity,
919
1166
  classifyComplexityGemini,
1167
+ connectMcpServer,
1168
+ connectMcpServers,
1169
+ createCatalogServer,
920
1170
  createDefaultProvider,
1171
+ createHandoffServer,
1172
+ createInvoiceServer,
1173
+ createLeadMemoryServer,
921
1174
  createSdkMcpServer,
1175
+ createSpecializedAgent,
922
1176
  fastHeuristic,
923
1177
  getRoutingStats,
924
1178
  hasMedia,
@@ -926,6 +1180,7 @@ export {
926
1180
  judgeComplexityGemini,
927
1181
  latestUserText,
928
1182
  listDirTool,
1183
+ loadPack,
929
1184
  loadStageContext,
930
1185
  loadStages,
931
1186
  matchesAllow,