llmist 17.3.0 → 17.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -358,15 +358,15 @@ var init_execution_tree = __esm({
358
358
  const parentId = params.parentId ?? this.parentNodeId;
359
359
  const parent = parentId ? this.nodes.get(parentId) : null;
360
360
  const depth = parent ? parent.depth + 1 : this.baseDepth;
361
- const path3 = parent ? [...parent.path] : [];
361
+ const path4 = parent ? [...parent.path] : [];
362
362
  const id = this.generateLLMCallId(params.iteration, parentId);
363
- path3.push(id);
363
+ path4.push(id);
364
364
  const node = {
365
365
  id,
366
366
  type: "llm_call",
367
367
  parentId,
368
368
  depth,
369
- path: path3,
369
+ path: path4,
370
370
  createdAt: Date.now(),
371
371
  completedAt: null,
372
372
  iteration: params.iteration,
@@ -477,15 +477,15 @@ var init_execution_tree = __esm({
477
477
  const parentId = params.parentId ?? this.getCurrentLLMCallId() ?? this.parentNodeId;
478
478
  const parent = parentId ? this.nodes.get(parentId) : null;
479
479
  const depth = parent ? parent.depth + 1 : this.baseDepth;
480
- const path3 = parent ? [...parent.path] : [];
480
+ const path4 = parent ? [...parent.path] : [];
481
481
  const id = this.generateGadgetId(params.invocationId);
482
- path3.push(id);
482
+ path4.push(id);
483
483
  const node = {
484
484
  id,
485
485
  type: "gadget",
486
486
  parentId,
487
487
  depth,
488
- path: path3,
488
+ path: path4,
489
489
  createdAt: Date.now(),
490
490
  completedAt: null,
491
491
  invocationId: params.invocationId,
@@ -1424,8 +1424,8 @@ ${this.endPrefix}`
1424
1424
  });
1425
1425
  if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
1426
1426
  const idRefs = media.map((m, i) => {
1427
- const path3 = storedMedia?.[i]?.path;
1428
- const pathInfo = path3 ? ` \u2192 saved to: ${path3}` : "";
1427
+ const path4 = storedMedia?.[i]?.path;
1428
+ const pathInfo = path4 ? ` \u2192 saved to: ${path4}` : "";
1429
1429
  return `[Media: ${mediaIds[i]} (${m.kind})${pathInfo}]`;
1430
1430
  }).join("\n");
1431
1431
  const textWithIds = `Result (${invocationId}): ${result}
@@ -3071,6 +3071,16 @@ var init_conversation_manager = __esm({
3071
3071
  getBaseMessages() {
3072
3072
  return [...this.baseMessages, ...this.initialMessages];
3073
3073
  }
3074
+ /**
3075
+ * Replace the base (system + gadget catalog) messages.
3076
+ *
3077
+ * Used when async setup (e.g. MCP server connect-and-list) discovers
3078
+ * additional gadgets after the agent was constructed. Conversation history
3079
+ * is preserved; only the leading system block is swapped.
3080
+ */
3081
+ replaceBaseMessages(newBase) {
3082
+ this.baseMessages = newBase;
3083
+ }
3074
3084
  replaceHistory(newHistory) {
3075
3085
  this.historyBuilder = new LLMMessageBuilder();
3076
3086
  if (this.startPrefix && this.endPrefix) {
@@ -4088,29 +4098,29 @@ function schemaToJSONSchema(schema, options) {
4088
4098
  }
4089
4099
  function detectDescriptionMismatch(schema, jsonSchema) {
4090
4100
  const mismatches = [];
4091
- function checkSchema(zodSchema, json, path3) {
4101
+ function checkSchema(zodSchema, json, path4) {
4092
4102
  if (!zodSchema || typeof zodSchema !== "object") return;
4093
4103
  const def = zodSchema._def;
4094
4104
  const jsonObj = json;
4095
4105
  if (def?.description && !jsonObj?.description) {
4096
- mismatches.push(path3 || "root");
4106
+ mismatches.push(path4 || "root");
4097
4107
  }
4098
4108
  if (def?.typeName === "ZodObject" && def?.shape) {
4099
4109
  const shape = typeof def.shape === "function" ? def.shape() : def.shape;
4100
4110
  for (const [key, fieldSchema] of Object.entries(shape)) {
4101
4111
  const properties = jsonObj?.properties;
4102
4112
  const jsonProp = properties?.[key];
4103
- checkSchema(fieldSchema, jsonProp, path3 ? `${path3}.${key}` : key);
4113
+ checkSchema(fieldSchema, jsonProp, path4 ? `${path4}.${key}` : key);
4104
4114
  }
4105
4115
  }
4106
4116
  if (def?.typeName === "ZodArray" && def?.type) {
4107
- checkSchema(def.type, jsonObj?.items, path3 ? `${path3}[]` : "[]");
4117
+ checkSchema(def.type, jsonObj?.items, path4 ? `${path4}[]` : "[]");
4108
4118
  }
4109
4119
  if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
4110
- checkSchema(def.innerType, json, path3);
4120
+ checkSchema(def.innerType, json, path4);
4111
4121
  }
4112
4122
  if (def?.typeName === "ZodDefault" && def?.innerType) {
4113
- checkSchema(def.innerType, json, path3);
4123
+ checkSchema(def.innerType, json, path4);
4114
4124
  }
4115
4125
  }
4116
4126
  checkSchema(schema, jsonSchema, "");
@@ -4203,7 +4213,7 @@ Example fixes:
4203
4213
  );
4204
4214
  }
4205
4215
  }
4206
- function findUnknownTypes(schema, path3 = []) {
4216
+ function findUnknownTypes(schema, path4 = []) {
4207
4217
  const issues = [];
4208
4218
  if (!schema || typeof schema !== "object") {
4209
4219
  return issues;
@@ -4215,7 +4225,7 @@ function findUnknownTypes(schema, path3 = []) {
4215
4225
  }
4216
4226
  if (schema.properties) {
4217
4227
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
4218
- const propPath = [...path3, propName];
4228
+ const propPath = [...path4, propName];
4219
4229
  if (hasNoType(propSchema)) {
4220
4230
  issues.push(propPath.join(".") || propName);
4221
4231
  }
@@ -4223,7 +4233,7 @@ function findUnknownTypes(schema, path3 = []) {
4223
4233
  }
4224
4234
  }
4225
4235
  if (schema.items) {
4226
- const itemPath = [...path3, "[]"];
4236
+ const itemPath = [...path4, "[]"];
4227
4237
  if (hasNoType(schema.items)) {
4228
4238
  issues.push(itemPath.join("."));
4229
4239
  }
@@ -4231,17 +4241,17 @@ function findUnknownTypes(schema, path3 = []) {
4231
4241
  }
4232
4242
  if (schema.anyOf) {
4233
4243
  schema.anyOf.forEach((subSchema, index) => {
4234
- issues.push(...findUnknownTypes(subSchema, [...path3, `anyOf[${index}]`]));
4244
+ issues.push(...findUnknownTypes(subSchema, [...path4, `anyOf[${index}]`]));
4235
4245
  });
4236
4246
  }
4237
4247
  if (schema.oneOf) {
4238
4248
  schema.oneOf.forEach((subSchema, index) => {
4239
- issues.push(...findUnknownTypes(subSchema, [...path3, `oneOf[${index}]`]));
4249
+ issues.push(...findUnknownTypes(subSchema, [...path4, `oneOf[${index}]`]));
4240
4250
  });
4241
4251
  }
4242
4252
  if (schema.allOf) {
4243
4253
  schema.allOf.forEach((subSchema, index) => {
4244
- issues.push(...findUnknownTypes(subSchema, [...path3, `allOf[${index}]`]));
4254
+ issues.push(...findUnknownTypes(subSchema, [...path4, `allOf[${index}]`]));
4245
4255
  });
4246
4256
  }
4247
4257
  return issues;
@@ -13232,6 +13242,7 @@ var init_builder = __esm({
13232
13242
  subagents;
13233
13243
  policies;
13234
13244
  skills;
13245
+ mcp;
13235
13246
  constructor(client) {
13236
13247
  this.core = { client, initialMessages: [] };
13237
13248
  this.gadgets = { gadgets: [] };
@@ -13239,6 +13250,7 @@ var init_builder = __esm({
13239
13250
  this.subagents = {};
13240
13251
  this.policies = {};
13241
13252
  this.skills = { preActivated: [], skillDirs: [] };
13253
+ this.mcp = { servers: [] };
13242
13254
  }
13243
13255
  /** Set the model to use. Supports aliases like "sonnet", "flash". */
13244
13256
  withModel(model) {
@@ -13285,6 +13297,45 @@ var init_builder = __esm({
13285
13297
  this.gadgets.gadgets.push(...gadgets);
13286
13298
  return this;
13287
13299
  }
13300
+ /**
13301
+ * Attach a Model Context Protocol (MCP) server.
13302
+ *
13303
+ * The agent connects to the server lazily at the start of `run()`,
13304
+ * discovers its tools, and registers them as native gadgets so the LLM
13305
+ * can call them through the standard streaming block format.
13306
+ *
13307
+ * Calling this multiple times accumulates servers. Tools across servers
13308
+ * are merged into a single registry; in plan 1, conflicting tool names
13309
+ * raise a registration warning. Plan 2 introduces deterministic
13310
+ * `<server>__<tool>` prefixing for collisions.
13311
+ *
13312
+ * STDIO commands are gated by an allowlist (see allowlist.ts) — pass
13313
+ * `trust: true` on the spec to opt in for non-allowlisted binaries.
13314
+ *
13315
+ * Zero-overhead invariant: if you never call this method, the MCP
13316
+ * runtime module is never loaded. Agents without MCP pay nothing.
13317
+ *
13318
+ * @example
13319
+ * ```typescript
13320
+ * const agent = LLMist.createAgent()
13321
+ * .withModel("sonnet")
13322
+ * .withMcpServer({
13323
+ * name: "filesystem",
13324
+ * transport: "stdio",
13325
+ * command: "npx",
13326
+ * args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
13327
+ * })
13328
+ * .ask("list files in /tmp");
13329
+ * ```
13330
+ */
13331
+ withMcpServer(spec) {
13332
+ this.mcp.servers.push(spec);
13333
+ return this;
13334
+ }
13335
+ /** Inspect the configured MCP server specs. Useful for tests. */
13336
+ getMcpServerSpecs() {
13337
+ return this.mcp.servers;
13338
+ }
13288
13339
  /** Add conversation history messages. */
13289
13340
  withHistory(messages) {
13290
13341
  this.core.initialMessages.push(...normalizeHistory(messages));
@@ -13605,7 +13656,8 @@ ${preActivatedBlock}` : preActivatedBlock;
13605
13656
  parentObservers: this.subagents.parentObservers
13606
13657
  },
13607
13658
  sharedRateLimitTracker: this.subagents.sharedRateLimitTracker,
13608
- sharedRetryConfig: this.retry.sharedRetryConfig
13659
+ sharedRetryConfig: this.retry.sharedRetryConfig,
13660
+ mcpSpecs: this.mcp.servers.length > 0 ? [...this.mcp.servers] : void 0
13609
13661
  };
13610
13662
  }
13611
13663
  /** Create agent and start with a user prompt. */
@@ -14251,8 +14303,8 @@ var init_error_formatter = __esm({
14251
14303
  const parts = [];
14252
14304
  parts.push(`Error: Invalid parameters for '${gadgetName}':`);
14253
14305
  for (const issue of zodError.issues) {
14254
- const path3 = issue.path.join(".") || "root";
14255
- parts.push(` - ${path3}: ${issue.message}`);
14306
+ const path4 = issue.path.join(".") || "root";
14307
+ parts.push(` - ${path4}: ${issue.message}`);
14256
14308
  }
14257
14309
  parts.push("");
14258
14310
  parts.push("Gadget Usage:");
@@ -16612,184 +16664,1081 @@ var init_stream_processor_factory = __esm({
16612
16664
  }
16613
16665
  });
16614
16666
 
16615
- // src/agent/agent.ts
16616
- var OVERFLOW_RECOVERY_MIN_HISTORY, Agent;
16617
- var init_agent = __esm({
16618
- "src/agent/agent.ts"() {
16667
+ // src/mcp/errors.ts
16668
+ var McpError, McpUntrustedCommandError, McpConnectError, McpToolCallError, McpTimeoutError, JsonSchemaConversionError;
16669
+ var init_errors = __esm({
16670
+ "src/mcp/errors.ts"() {
16619
16671
  "use strict";
16620
- init_execution_tree();
16621
- init_messages();
16622
- init_model_shortcuts();
16623
- init_rate_limit();
16624
- init_retry();
16625
- init_exceptions();
16626
- init_media_store();
16627
- init_logger();
16628
- init_agent_internal_key();
16629
- init_manager();
16630
- init_conversation_manager();
16631
- init_conversation_updater();
16632
- init_event_handlers();
16633
- init_llm_call_lifecycle();
16634
- init_output_limit_manager();
16635
- init_retry_orchestrator();
16636
- init_safe_observe();
16637
- init_stream_processor_factory();
16638
- init_tree_hook_bridge();
16639
- OVERFLOW_RECOVERY_MIN_HISTORY = 4;
16640
- Agent = class {
16641
- client;
16642
- model;
16643
- maxIterations;
16644
- budget;
16645
- temperature;
16646
- logger;
16647
- hooks;
16648
- conversation;
16649
- registry;
16650
- prefixConfig;
16651
- conversationUpdater;
16652
- defaultMaxTokens;
16653
- hasUserPrompt;
16654
- // Gadget output limiting
16655
- outputLimitManager;
16656
- // Context compaction
16657
- compactionManager;
16658
- // Media storage (for gadgets returning images, audio, etc.)
16659
- mediaStore;
16660
- // Cancellation
16661
- signal;
16662
- reasoning;
16663
- caching;
16664
- // Retry configuration (shared reference also passed to StreamProcessorFactory)
16665
- retryConfig;
16666
- // Rate limit tracker for proactive throttling
16667
- rateLimitTracker;
16668
- // Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
16669
- completedInvocationIds = /* @__PURE__ */ new Set();
16670
- failedInvocationIds = /* @__PURE__ */ new Set();
16671
- // Queue for user messages injected during agent execution (REPL mid-session input)
16672
- pendingUserMessages = [];
16673
- // Execution Tree - first-class model for nested subagent support
16674
- tree;
16675
- parentNodeId;
16676
- // StreamProcessor factory - encapsulates all pass-through StreamProcessor config
16677
- streamProcessorFactory;
16678
- // LLM call lifecycle helper (encapsulates prepareLLMCall, completeLLMCall, notifyLLMError)
16679
- llmCallLifecycle;
16680
- /**
16681
- * Creates a new Agent instance.
16682
- * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
16683
- */
16684
- constructor(key, options) {
16685
- if (!isValidAgentKey(key)) {
16686
- throw new Error(
16687
- "Agent cannot be instantiated directly. Use LLMist.createAgent() or new AgentBuilder() instead."
16672
+ McpError = class extends Error {
16673
+ serverName;
16674
+ constructor(message, serverName) {
16675
+ super(message);
16676
+ this.name = "McpError";
16677
+ this.serverName = serverName;
16678
+ }
16679
+ };
16680
+ McpUntrustedCommandError = class extends McpError {
16681
+ command;
16682
+ constructor(command, serverName) {
16683
+ super(
16684
+ `Refusing to spawn MCP stdio command "${command}" because its basename is not in the default allowlist. To opt in, set { trust: true } on the server spec (library), or trust = true in your TOML mcp.servers block, or pass --mcp-trust ${serverName ?? "<name>"} on the CLI. See https://llmist.dev/library/advanced/mcp-security/ for context (CVE-2026-30623).`,
16685
+ serverName
16686
+ );
16687
+ this.name = "McpUntrustedCommandError";
16688
+ this.command = command;
16689
+ }
16690
+ };
16691
+ McpConnectError = class extends McpError {
16692
+ cause;
16693
+ constructor(message, opts) {
16694
+ super(message, opts?.serverName);
16695
+ this.name = "McpConnectError";
16696
+ this.cause = opts?.cause;
16697
+ }
16698
+ };
16699
+ McpToolCallError = class extends McpError {
16700
+ toolName;
16701
+ cause;
16702
+ constructor(toolName, message, opts) {
16703
+ super(message, opts?.serverName);
16704
+ this.name = "McpToolCallError";
16705
+ this.toolName = toolName;
16706
+ this.cause = opts?.cause;
16707
+ }
16708
+ };
16709
+ McpTimeoutError = class extends McpError {
16710
+ operation;
16711
+ timeoutMs;
16712
+ constructor(operation, timeoutMs, serverName) {
16713
+ super(
16714
+ `MCP operation "${operation}" on server "${serverName ?? "<unknown>"}" timed out after ${timeoutMs}ms`,
16715
+ serverName
16716
+ );
16717
+ this.name = "McpTimeoutError";
16718
+ this.operation = operation;
16719
+ this.timeoutMs = timeoutMs;
16720
+ }
16721
+ };
16722
+ JsonSchemaConversionError = class extends Error {
16723
+ schemaFragment;
16724
+ reason;
16725
+ constructor(reason, schemaFragment) {
16726
+ super(`JSON Schema \u2192 Zod conversion failed: ${reason}`);
16727
+ this.name = "JsonSchemaConversionError";
16728
+ this.reason = reason;
16729
+ this.schemaFragment = schemaFragment;
16730
+ }
16731
+ };
16732
+ }
16733
+ });
16734
+
16735
+ // src/mcp/allowlist.ts
16736
+ function assertCommandAllowed(command, trusted, customAllowlist) {
16737
+ if (!command || typeof command !== "string") {
16738
+ throw new McpUntrustedCommandError(String(command));
16739
+ }
16740
+ if (WHITESPACE_OR_META_RE.test(command)) {
16741
+ throw new McpUntrustedCommandError(command);
16742
+ }
16743
+ if (trusted) return;
16744
+ const allowlist = customAllowlist ?? DEFAULT_MCP_COMMAND_ALLOWLIST;
16745
+ const base = import_node_path6.default.basename(command);
16746
+ if (!allowlist.has(base)) {
16747
+ throw new McpUntrustedCommandError(command);
16748
+ }
16749
+ }
16750
+ var import_node_path6, DEFAULT_MCP_COMMAND_ALLOWLIST, WHITESPACE_OR_META_RE;
16751
+ var init_allowlist = __esm({
16752
+ "src/mcp/allowlist.ts"() {
16753
+ "use strict";
16754
+ import_node_path6 = __toESM(require("path"), 1);
16755
+ init_errors();
16756
+ DEFAULT_MCP_COMMAND_ALLOWLIST = /* @__PURE__ */ new Set([
16757
+ "npx",
16758
+ "node",
16759
+ "uvx",
16760
+ "uv",
16761
+ "python",
16762
+ "python3",
16763
+ "deno",
16764
+ "bun"
16765
+ ]);
16766
+ WHITESPACE_OR_META_RE = /[\s;|&`$<>()'"\\]/;
16767
+ }
16768
+ });
16769
+
16770
+ // src/mcp/client.ts
16771
+ async function loadSdk() {
16772
+ if (!cachedSdk) {
16773
+ cachedSdk = (async () => {
16774
+ const [client, stdio, http] = await Promise.all([
16775
+ import("@modelcontextprotocol/sdk/client/index.js"),
16776
+ import("@modelcontextprotocol/sdk/client/stdio.js"),
16777
+ import("@modelcontextprotocol/sdk/client/streamableHttp.js")
16778
+ ]);
16779
+ return {
16780
+ Client: client.Client,
16781
+ StdioClientTransport: stdio.StdioClientTransport,
16782
+ StreamableHTTPClientTransport: http.StreamableHTTPClientTransport
16783
+ };
16784
+ })();
16785
+ }
16786
+ return cachedSdk;
16787
+ }
16788
+ var cachedSdk, DEFAULT_CLIENT_INFO, McpClient;
16789
+ var init_client2 = __esm({
16790
+ "src/mcp/client.ts"() {
16791
+ "use strict";
16792
+ init_allowlist();
16793
+ init_errors();
16794
+ cachedSdk = null;
16795
+ DEFAULT_CLIENT_INFO = { name: "llmist", version: "0.0.0" };
16796
+ McpClient = class {
16797
+ constructor(spec, opts) {
16798
+ this.spec = spec;
16799
+ this.injectedTransport = opts?.transport;
16800
+ this.clientInfo = opts?.clientInfo ?? DEFAULT_CLIENT_INFO;
16801
+ }
16802
+ sdkClient = null;
16803
+ spawnedPid = null;
16804
+ closed = false;
16805
+ injectedTransport;
16806
+ clientInfo;
16807
+ get serverName() {
16808
+ return this.spec.name;
16809
+ }
16810
+ get pid() {
16811
+ return this.spawnedPid;
16812
+ }
16813
+ get serverCapabilities() {
16814
+ if (!this.sdkClient) return null;
16815
+ return this.sdkClient.getServerCapabilities() ?? null;
16816
+ }
16817
+ async connect() {
16818
+ if (this.sdkClient) return;
16819
+ let transport;
16820
+ if (this.injectedTransport) {
16821
+ transport = this.injectedTransport;
16822
+ } else if (this.spec.transport === "stdio") {
16823
+ assertCommandAllowed(this.spec.command, this.spec.trust === true);
16824
+ const { StdioClientTransport } = await loadSdk();
16825
+ const stdioTransport = new StdioClientTransport({
16826
+ command: this.spec.command,
16827
+ args: this.spec.args,
16828
+ env: this.spec.env
16829
+ });
16830
+ transport = stdioTransport;
16831
+ this.spawnedPid = null;
16832
+ } else {
16833
+ const { StreamableHTTPClientTransport } = await loadSdk();
16834
+ let url;
16835
+ try {
16836
+ url = new URL(this.spec.url);
16837
+ } catch (err) {
16838
+ throw new McpConnectError(
16839
+ `MCP server "${this.spec.name}" has an invalid URL: ${err.message}`,
16840
+ { serverName: this.spec.name, cause: err }
16841
+ );
16842
+ }
16843
+ transport = new StreamableHTTPClientTransport(url, {
16844
+ requestInit: this.spec.headers ? { headers: this.spec.headers } : void 0
16845
+ });
16846
+ }
16847
+ const { Client } = await loadSdk();
16848
+ const client = new Client(this.clientInfo, { capabilities: {} });
16849
+ try {
16850
+ await this.withTimeout(() => client.connect(transport), "connect");
16851
+ } catch (err) {
16852
+ throw new McpConnectError(
16853
+ `Failed to connect to MCP server "${this.spec.name}": ${err.message}`,
16854
+ { serverName: this.spec.name, cause: err }
16688
16855
  );
16689
16856
  }
16690
- this.client = options.client;
16691
- this.model = resolveModel(options.model);
16692
- this.maxIterations = options.maxIterations ?? 10;
16693
- this.budget = options.budget;
16694
- this.temperature = options.temperature;
16695
- this.logger = options.logger ?? createLogger({ name: "llmist:agent" });
16696
- this.registry = options.registry;
16697
- this.prefixConfig = options.prefixConfig;
16698
- this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
16699
- const outputLimitConfig = {
16700
- enabled: options.outputLimitConfig?.enabled,
16701
- limitPercent: options.outputLimitConfig?.limitPercent
16702
- };
16703
- this.outputLimitManager = new OutputLimitManager(
16704
- this.client,
16705
- this.model,
16706
- outputLimitConfig,
16707
- this.registry,
16708
- this.logger
16709
- );
16710
- this.mediaStore = new MediaStore();
16711
- this.hooks = this.outputLimitManager.getHooks(options.hooks);
16712
- const baseBuilder = new LLMMessageBuilder(options.promptConfig);
16713
- if (options.systemPrompt) {
16714
- baseBuilder.addSystem(options.systemPrompt);
16857
+ this.sdkClient = client;
16858
+ const maybePid = transport.pid;
16859
+ if (typeof maybePid === "number") {
16860
+ this.spawnedPid = maybePid;
16715
16861
  }
16716
- baseBuilder.addGadgets(this.registry.getAll(), {
16717
- startPrefix: this.prefixConfig?.gadgetStartPrefix,
16718
- endPrefix: this.prefixConfig?.gadgetEndPrefix,
16719
- argPrefix: this.prefixConfig?.gadgetArgPrefix
16720
- });
16721
- const baseMessages = baseBuilder.build();
16722
- const initialMessages = (options.initialMessages ?? []).map((message) => ({
16723
- role: message.role,
16724
- content: message.content
16862
+ }
16863
+ async listTools() {
16864
+ const client = this.requireClient();
16865
+ const res = await this.withTimeout(() => client.listTools(), "tools/list");
16866
+ return res.tools.map((t) => ({
16867
+ name: t.name,
16868
+ description: t.description,
16869
+ inputSchema: t.inputSchema
16725
16870
  }));
16726
- this.conversation = new ConversationManager(baseMessages, initialMessages, {
16727
- startPrefix: this.prefixConfig?.gadgetStartPrefix,
16728
- endPrefix: this.prefixConfig?.gadgetEndPrefix,
16729
- argPrefix: this.prefixConfig?.gadgetArgPrefix
16730
- });
16731
- this.hasUserPrompt = !!options.userPrompt;
16732
- if (options.userPrompt) {
16733
- this.conversation.addUserMessage(options.userPrompt);
16871
+ }
16872
+ async callTool(name, args) {
16873
+ const client = this.requireClient();
16874
+ try {
16875
+ const res = await this.withTimeout(
16876
+ () => client.callTool({
16877
+ name,
16878
+ arguments: args ?? {}
16879
+ }),
16880
+ `tools/call ${name}`
16881
+ );
16882
+ return {
16883
+ content: res.content ?? [],
16884
+ isError: res.isError
16885
+ };
16886
+ } catch (err) {
16887
+ throw new McpToolCallError(
16888
+ name,
16889
+ `MCP tool call "${name}" on server "${this.spec.name}" failed: ${err.message}`,
16890
+ { serverName: this.spec.name, cause: err }
16891
+ );
16734
16892
  }
16735
- this.conversationUpdater = new ConversationUpdater(
16736
- this.conversation,
16737
- options.textOnlyHandler ?? "terminate",
16738
- options.textWithGadgetsHandler,
16739
- this.logger
16740
- );
16741
- const compactionEnabled = options.compactionConfig?.enabled ?? true;
16742
- if (compactionEnabled) {
16743
- this.compactionManager = new CompactionManager(
16744
- this.client,
16745
- this.model,
16746
- options.compactionConfig,
16747
- this.logger
16893
+ }
16894
+ async listPrompts() {
16895
+ const client = this.requireClient();
16896
+ if (!client.listPrompts) {
16897
+ return [];
16898
+ }
16899
+ const listPrompts = client.listPrompts.bind(client);
16900
+ const res = await this.withTimeout(() => listPrompts(), "prompts/list");
16901
+ return res.prompts.map((p) => ({
16902
+ name: p.name,
16903
+ description: p.description,
16904
+ arguments: p.arguments
16905
+ }));
16906
+ }
16907
+ async getPrompt(name, args) {
16908
+ const client = this.requireClient();
16909
+ if (!client.getPrompt) {
16910
+ throw new McpToolCallError(name, "Server has no getPrompt method", {
16911
+ serverName: this.spec.name
16912
+ });
16913
+ }
16914
+ const getPrompt = client.getPrompt.bind(client);
16915
+ try {
16916
+ const res = await this.withTimeout(
16917
+ () => getPrompt({ name, arguments: args ?? {} }),
16918
+ `prompts/get ${name}`
16919
+ );
16920
+ return {
16921
+ description: res.description,
16922
+ messages: res.messages.map((m) => ({
16923
+ role: m.role,
16924
+ content: m.content
16925
+ }))
16926
+ };
16927
+ } catch (err) {
16928
+ throw new McpToolCallError(
16929
+ name,
16930
+ `MCP prompts/get "${name}" on server "${this.spec.name}" failed: ${err.message}`,
16931
+ { serverName: this.spec.name, cause: err }
16748
16932
  );
16749
16933
  }
16750
- this.signal = options.signal;
16751
- this.reasoning = options.reasoning;
16752
- this.caching = options.caching;
16753
- this.retryConfig = options.sharedRetryConfig ?? resolveRetryConfig(options.retryConfig);
16754
- if (options.sharedRateLimitTracker) {
16755
- this.rateLimitTracker = options.sharedRateLimitTracker;
16756
- } else {
16757
- const rateLimitConfig = resolveRateLimitConfig(options.rateLimitConfig);
16758
- if (rateLimitConfig.enabled) {
16759
- this.rateLimitTracker = new RateLimitTracker(options.rateLimitConfig);
16934
+ }
16935
+ async close() {
16936
+ if (this.closed) return;
16937
+ this.closed = true;
16938
+ if (this.sdkClient) {
16939
+ try {
16940
+ await this.sdkClient.close();
16941
+ } catch {
16760
16942
  }
16943
+ this.sdkClient = null;
16761
16944
  }
16762
- const treeConfig = options.treeConfig;
16763
- this.tree = treeConfig?.tree ?? new ExecutionTree();
16764
- this.parentNodeId = treeConfig?.parentNodeId ?? null;
16765
- this.streamProcessorFactory = new StreamProcessorFactory({
16766
- registry: this.registry,
16767
- prefixConfig: this.prefixConfig,
16768
- hooks: this.hooks,
16769
- logger: this.logger,
16770
- requestHumanInput: options.requestHumanInput,
16771
- defaultGadgetTimeoutMs: options.defaultGadgetTimeoutMs,
16772
- gadgetExecutionMode: options.gadgetExecutionMode ?? "parallel",
16773
- client: this.client,
16774
- mediaStore: this.mediaStore,
16775
- agentContextConfig: {
16776
- model: this.model,
16777
- temperature: this.temperature
16778
- },
16779
- subagentConfig: options.subagentConfig,
16780
- tree: this.tree,
16781
- baseDepth: treeConfig?.baseDepth ?? 0,
16782
- parentObservers: treeConfig?.parentObservers,
16783
- rateLimitTracker: this.rateLimitTracker,
16784
- retryConfig: this.retryConfig,
16785
- maxGadgetsPerResponse: options.maxGadgetsPerResponse ?? 0
16945
+ }
16946
+ requireClient() {
16947
+ if (!this.sdkClient) {
16948
+ throw new McpConnectError(
16949
+ `MCP client for server "${this.spec.name}" is not connected. Call connect() first.`,
16950
+ { serverName: this.spec.name }
16951
+ );
16952
+ }
16953
+ return this.sdkClient;
16954
+ }
16955
+ async withTimeout(fn, operation) {
16956
+ const timeoutMs = this.spec.timeoutMs;
16957
+ if (timeoutMs === void 0 || timeoutMs <= 0) {
16958
+ return fn();
16959
+ }
16960
+ return new Promise((resolve2, reject) => {
16961
+ let settled = false;
16962
+ const timeoutId = setTimeout(() => {
16963
+ if (settled) return;
16964
+ settled = true;
16965
+ reject(new McpTimeoutError(operation, timeoutMs, this.spec.name));
16966
+ }, timeoutMs);
16967
+ fn().then((result) => {
16968
+ if (settled) return;
16969
+ settled = true;
16970
+ clearTimeout(timeoutId);
16971
+ resolve2(result);
16972
+ }).catch((err) => {
16973
+ if (settled) return;
16974
+ settled = true;
16975
+ clearTimeout(timeoutId);
16976
+ reject(err);
16977
+ });
16786
16978
  });
16787
- this.llmCallLifecycle = new LLMCallLifecycle({
16788
- client: this.client,
16789
- conversation: this.conversation,
16790
- tree: this.tree,
16791
- hooks: this.hooks,
16792
- logger: this.logger,
16979
+ }
16980
+ };
16981
+ }
16982
+ });
16983
+
16984
+ // src/mcp/lifecycle.ts
16985
+ var McpLifecycle;
16986
+ var init_lifecycle = __esm({
16987
+ "src/mcp/lifecycle.ts"() {
16988
+ "use strict";
16989
+ init_logger();
16990
+ McpLifecycle = class {
16991
+ clients = [];
16992
+ closing = null;
16993
+ signalHandlersInstalled = false;
16994
+ sigtermHandler = null;
16995
+ sigintHandler = null;
16996
+ get size() {
16997
+ return this.clients.length;
16998
+ }
16999
+ register(client) {
17000
+ this.clients.push(client);
17001
+ }
17002
+ /**
17003
+ * Attach SIGTERM/SIGINT handlers that close every registered client when
17004
+ * the parent process is asked to exit. Idempotent (double install is a
17005
+ * no-op) and removable via `removeSignalHandlers()`.
17006
+ */
17007
+ installSignalHandlers() {
17008
+ if (this.signalHandlersInstalled) return;
17009
+ this.signalHandlersInstalled = true;
17010
+ this.sigtermHandler = () => {
17011
+ void this.closeAll();
17012
+ };
17013
+ this.sigintHandler = () => {
17014
+ void this.closeAll();
17015
+ };
17016
+ process.on("SIGTERM", this.sigtermHandler);
17017
+ process.on("SIGINT", this.sigintHandler);
17018
+ }
17019
+ removeSignalHandlers() {
17020
+ if (!this.signalHandlersInstalled) return;
17021
+ if (this.sigtermHandler) process.off("SIGTERM", this.sigtermHandler);
17022
+ if (this.sigintHandler) process.off("SIGINT", this.sigintHandler);
17023
+ this.sigtermHandler = null;
17024
+ this.sigintHandler = null;
17025
+ this.signalHandlersInstalled = false;
17026
+ }
17027
+ /**
17028
+ * Close every registered client in parallel. Errors from individual close()
17029
+ * calls are swallowed (logged via console.warn) — a teardown path must not
17030
+ * throw because that would mask the original reason the agent is shutting
17031
+ * down. Idempotent: concurrent calls all return the same in-flight promise.
17032
+ */
17033
+ async closeAll() {
17034
+ if (this.closing) return this.closing;
17035
+ const toClose = this.clients;
17036
+ this.clients = [];
17037
+ this.closing = (async () => {
17038
+ const results = await Promise.allSettled(toClose.map((c) => c.close()));
17039
+ for (const r of results) {
17040
+ if (r.status === "rejected") {
17041
+ defaultLogger.debug("MCP client close failed during teardown:", r.reason);
17042
+ }
17043
+ }
17044
+ })();
17045
+ try {
17046
+ await this.closing;
17047
+ } finally {
17048
+ this.closing = null;
17049
+ this.removeSignalHandlers();
17050
+ }
17051
+ }
17052
+ };
17053
+ }
17054
+ });
17055
+
17056
+ // src/mcp/multi-server.ts
17057
+ function resolveToolNames(input) {
17058
+ const counts = /* @__PURE__ */ new Map();
17059
+ for (const s of input) {
17060
+ for (const t of s.tools) {
17061
+ counts.set(t.name, (counts.get(t.name) ?? 0) + 1);
17062
+ }
17063
+ }
17064
+ const collidingServers = /* @__PURE__ */ new Set();
17065
+ for (const s of input) {
17066
+ if (s.tools.some((t) => (counts.get(t.name) ?? 0) > 1)) {
17067
+ collidingServers.add(s.server.name);
17068
+ }
17069
+ }
17070
+ return input.map((s) => ({
17071
+ ...s,
17072
+ prefix: collidingServers.has(s.server.name) ? `${s.server.name}__` : void 0
17073
+ }));
17074
+ }
17075
+ var init_multi_server = __esm({
17076
+ "src/mcp/multi-server.ts"() {
17077
+ "use strict";
17078
+ }
17079
+ });
17080
+
17081
+ // src/mcp/prompt-adapter.ts
17082
+ function mcpPromptToSkill(descriptor, client, opts) {
17083
+ return new McpPromptSkill(descriptor, client, opts);
17084
+ }
17085
+ var McpPromptSkill;
17086
+ var init_prompt_adapter = __esm({
17087
+ "src/mcp/prompt-adapter.ts"() {
17088
+ "use strict";
17089
+ McpPromptSkill = class {
17090
+ name;
17091
+ description;
17092
+ metadata;
17093
+ isUserInvocable = true;
17094
+ isModelInvocable = true;
17095
+ client;
17096
+ mcpToolName;
17097
+ constructor(descriptor, client, opts) {
17098
+ const prefix = opts?.prefix ?? "";
17099
+ this.name = prefix + descriptor.name;
17100
+ this.description = descriptor.description ?? `MCP prompt "${descriptor.name}" from server "${client.serverName}"`;
17101
+ this.metadata = {
17102
+ name: this.name,
17103
+ description: this.description,
17104
+ ...descriptor.arguments ? { arguments: descriptor.arguments } : {}
17105
+ };
17106
+ this.client = client;
17107
+ this.mcpToolName = descriptor.name;
17108
+ }
17109
+ /**
17110
+ * Render the prompt by calling the MCP server's prompts/get with the
17111
+ * supplied arguments and joining the resulting message text.
17112
+ */
17113
+ async getInstructions(args) {
17114
+ const result = await this.client.getPrompt(this.mcpToolName, args ?? {});
17115
+ const parts = [];
17116
+ for (const m of result.messages) {
17117
+ const c = m.content;
17118
+ if (c.type === "text" && typeof c.text === "string") {
17119
+ parts.push(c.text);
17120
+ } else {
17121
+ try {
17122
+ parts.push(JSON.stringify(c));
17123
+ } catch {
17124
+ parts.push(String(c));
17125
+ }
17126
+ }
17127
+ }
17128
+ return parts.join("\n");
17129
+ }
17130
+ };
17131
+ }
17132
+ });
17133
+
17134
+ // src/gadgets/helpers.ts
17135
+ function gadgetSuccess(data = {}) {
17136
+ return JSON.stringify({ success: true, ...data });
17137
+ }
17138
+ function gadgetError(message, details) {
17139
+ return JSON.stringify({ error: message, ...details });
17140
+ }
17141
+ function getErrorMessage(error) {
17142
+ return error instanceof Error ? error.message : String(error);
17143
+ }
17144
+ function withErrorHandling(execute) {
17145
+ return async (params, ctx) => {
17146
+ try {
17147
+ return await execute(params, ctx);
17148
+ } catch (error) {
17149
+ return gadgetError(getErrorMessage(error));
17150
+ }
17151
+ };
17152
+ }
17153
+ function createMediaOutput(kind, data, mimeType, options) {
17154
+ const buffer = data instanceof Buffer ? data : Buffer.from(data);
17155
+ return {
17156
+ kind,
17157
+ data: buffer.toString("base64"),
17158
+ mimeType,
17159
+ description: options?.description,
17160
+ metadata: options?.metadata,
17161
+ fileName: options?.fileName
17162
+ };
17163
+ }
17164
+ function resultWithMedia(result, media, cost) {
17165
+ if (media.length === 0) {
17166
+ throw new Error("resultWithMedia: media array cannot be empty");
17167
+ }
17168
+ return {
17169
+ result,
17170
+ media,
17171
+ cost
17172
+ };
17173
+ }
17174
+ function resultWithImage(result, imageData, options) {
17175
+ const buffer = imageData instanceof Buffer ? imageData : Buffer.from(imageData);
17176
+ const mimeType = options?.mimeType ?? detectImageMimeType(buffer);
17177
+ if (!mimeType) {
17178
+ throw new Error(
17179
+ "Could not detect image MIME type. Please provide mimeType explicitly in options."
17180
+ );
17181
+ }
17182
+ return {
17183
+ result,
17184
+ media: [
17185
+ {
17186
+ kind: "image",
17187
+ data: buffer.toString("base64"),
17188
+ mimeType,
17189
+ description: options?.description,
17190
+ metadata: options?.metadata,
17191
+ fileName: options?.fileName
17192
+ }
17193
+ ],
17194
+ cost: options?.cost
17195
+ };
17196
+ }
17197
+ function resultWithImages(result, images, cost) {
17198
+ if (images.length === 0) {
17199
+ throw new Error("resultWithImages: images array cannot be empty");
17200
+ }
17201
+ const media = images.map((img, index) => {
17202
+ const buffer = img.data instanceof Buffer ? img.data : Buffer.from(img.data);
17203
+ const mimeType = img.mimeType ?? detectImageMimeType(buffer);
17204
+ if (!mimeType) {
17205
+ throw new Error(
17206
+ `Could not detect MIME type for image at index ${index}. Please provide mimeType explicitly.`
17207
+ );
17208
+ }
17209
+ return {
17210
+ kind: "image",
17211
+ data: buffer.toString("base64"),
17212
+ mimeType,
17213
+ description: img.description,
17214
+ metadata: img.metadata,
17215
+ fileName: img.fileName
17216
+ };
17217
+ });
17218
+ return { result, media, cost };
17219
+ }
17220
+ function resultWithAudio(result, audioData, options) {
17221
+ const buffer = audioData instanceof Buffer ? audioData : Buffer.from(audioData);
17222
+ const mimeType = options?.mimeType ?? detectAudioMimeType(buffer);
17223
+ if (!mimeType) {
17224
+ throw new Error(
17225
+ "Could not detect audio MIME type. Please provide mimeType explicitly in options."
17226
+ );
17227
+ }
17228
+ const metadata = options?.durationMs ? { durationMs: options.durationMs } : void 0;
17229
+ return {
17230
+ result,
17231
+ media: [
17232
+ {
17233
+ kind: "audio",
17234
+ data: buffer.toString("base64"),
17235
+ mimeType,
17236
+ description: options?.description,
17237
+ metadata,
17238
+ fileName: options?.fileName
17239
+ }
17240
+ ],
17241
+ cost: options?.cost
17242
+ };
17243
+ }
17244
+ function resultWithFile(result, fileData, mimeType, options) {
17245
+ const buffer = fileData instanceof Buffer ? fileData : Buffer.from(fileData);
17246
+ return {
17247
+ result,
17248
+ media: [
17249
+ {
17250
+ kind: "file",
17251
+ data: buffer.toString("base64"),
17252
+ mimeType,
17253
+ description: options?.description,
17254
+ fileName: options?.fileName
17255
+ }
17256
+ ],
17257
+ cost: options?.cost
17258
+ };
17259
+ }
17260
+ var init_helpers = __esm({
17261
+ "src/gadgets/helpers.ts"() {
17262
+ "use strict";
17263
+ init_input_content();
17264
+ }
17265
+ });
17266
+
17267
+ // src/mcp/json-schema-to-zod.ts
17268
+ function jsonSchemaToZod(schema) {
17269
+ if (!schema || typeof schema !== "object") {
17270
+ return import_zod4.z.unknown();
17271
+ }
17272
+ if (schema.$ref) {
17273
+ throw new JsonSchemaConversionError("$ref is not supported in MCP tool schemas", schema);
17274
+ }
17275
+ if (schema.allOf) {
17276
+ throw new JsonSchemaConversionError(
17277
+ "allOf is not supported (MCP tools should use a single composed schema)",
17278
+ schema
17279
+ );
17280
+ }
17281
+ const union = schema.oneOf ?? schema.anyOf;
17282
+ if (union) {
17283
+ if (!Array.isArray(union) || union.length < 2) {
17284
+ throw new JsonSchemaConversionError("oneOf/anyOf must have at least two members", schema);
17285
+ }
17286
+ const branches = union.map((m) => jsonSchemaToZod(m));
17287
+ return applyDecorators(import_zod4.z.union(branches), schema);
17288
+ }
17289
+ const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
17290
+ if (type === void 0 && schema.enum && Array.isArray(schema.enum)) {
17291
+ return applyDecorators(buildEnum(schema.enum), schema);
17292
+ }
17293
+ if (type === void 0) {
17294
+ return applyDecorators(import_zod4.z.unknown(), schema);
17295
+ }
17296
+ switch (type) {
17297
+ case "string": {
17298
+ let s;
17299
+ if (schema.enum && Array.isArray(schema.enum)) {
17300
+ s = buildEnum(schema.enum);
17301
+ } else {
17302
+ s = import_zod4.z.string();
17303
+ }
17304
+ return applyDecorators(s, schema);
17305
+ }
17306
+ case "number":
17307
+ return applyDecorators(import_zod4.z.number(), schema);
17308
+ case "integer":
17309
+ return applyDecorators(import_zod4.z.number().int(), schema);
17310
+ case "boolean":
17311
+ return applyDecorators(import_zod4.z.boolean(), schema);
17312
+ case "null":
17313
+ return applyDecorators(import_zod4.z.null(), schema);
17314
+ case "array": {
17315
+ const items = schema.items;
17316
+ if (Array.isArray(items)) {
17317
+ throw new JsonSchemaConversionError("tuple-style items arrays are not supported", schema);
17318
+ }
17319
+ const inner = items ? jsonSchemaToZod(items) : import_zod4.z.unknown();
17320
+ return applyDecorators(import_zod4.z.array(inner), schema);
17321
+ }
17322
+ case "object": {
17323
+ const props = schema.properties ?? {};
17324
+ const required = new Set(schema.required ?? []);
17325
+ const keys = Object.keys(props);
17326
+ if (keys.length === 0) {
17327
+ return applyDecorators(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()), schema);
17328
+ }
17329
+ const shape = {};
17330
+ for (const key of keys) {
17331
+ const inner = jsonSchemaToZod(props[key]);
17332
+ shape[key] = required.has(key) ? inner : inner.optional();
17333
+ }
17334
+ return applyDecorators(import_zod4.z.object(shape), schema);
17335
+ }
17336
+ default:
17337
+ throw new JsonSchemaConversionError(`unknown JSON Schema type "${type}"`, schema);
17338
+ }
17339
+ }
17340
+ function buildEnum(values) {
17341
+ if (values.every((v) => typeof v === "string")) {
17342
+ const literals2 = values;
17343
+ if (literals2.length === 0) {
17344
+ throw new JsonSchemaConversionError("enum cannot be empty", values);
17345
+ }
17346
+ return import_zod4.z.enum(literals2);
17347
+ }
17348
+ const literals = values.map((v) => import_zod4.z.literal(v));
17349
+ if (literals.length === 0) {
17350
+ throw new JsonSchemaConversionError("enum cannot be empty", values);
17351
+ }
17352
+ if (literals.length === 1) {
17353
+ return literals[0];
17354
+ }
17355
+ return import_zod4.z.union(literals);
17356
+ }
17357
+ function applyDecorators(base, schema) {
17358
+ let s = base;
17359
+ if (schema.nullable === true) {
17360
+ s = s.nullable();
17361
+ }
17362
+ if (schema.description) {
17363
+ s = s.describe(schema.description);
17364
+ }
17365
+ if (schema.default !== void 0) {
17366
+ s = s.default(schema.default);
17367
+ }
17368
+ return s;
17369
+ }
17370
+ var import_zod4;
17371
+ var init_json_schema_to_zod = __esm({
17372
+ "src/mcp/json-schema-to-zod.ts"() {
17373
+ "use strict";
17374
+ import_zod4 = require("zod");
17375
+ init_errors();
17376
+ }
17377
+ });
17378
+
17379
+ // src/mcp/tool-adapter.ts
17380
+ function mcpToolToGadget(tool, client, opts) {
17381
+ const gadgetName = (opts?.prefix ?? "") + tool.name;
17382
+ const schema = buildSchema(tool.inputSchema);
17383
+ const description = tool.description ?? `MCP tool "${tool.name}" from server "${client.serverName}"`;
17384
+ return createGadget({
17385
+ name: gadgetName,
17386
+ description,
17387
+ schema,
17388
+ execute: async (params) => {
17389
+ const result = await client.callTool(tool.name, params);
17390
+ return mcpResultToGadgetReturn(result, tool.name);
17391
+ }
17392
+ });
17393
+ }
17394
+ function buildSchema(inputSchema) {
17395
+ if (!inputSchema) {
17396
+ return import_zod5.z.object({});
17397
+ }
17398
+ const converted = jsonSchemaToZod(inputSchema);
17399
+ if (!(converted instanceof import_zod5.z.ZodObject) && !(converted instanceof import_zod5.z.ZodRecord)) {
17400
+ return import_zod5.z.object({}).passthrough();
17401
+ }
17402
+ return converted;
17403
+ }
17404
+ function mcpResultToGadgetReturn(result, toolName) {
17405
+ const blocks = result.content ?? [];
17406
+ const textParts = [];
17407
+ const media = [];
17408
+ for (const block of blocks) {
17409
+ const kind = block.type;
17410
+ if (kind === "text" && typeof block.text === "string") {
17411
+ textParts.push(block.text);
17412
+ } else if (kind === "image") {
17413
+ const b = block;
17414
+ media.push({ kind: "image", data: b.data, mimeType: b.mimeType });
17415
+ } else if (kind === "audio") {
17416
+ const b = block;
17417
+ media.push({ kind: "audio", data: b.data, mimeType: b.mimeType });
17418
+ } else {
17419
+ try {
17420
+ textParts.push(JSON.stringify(block));
17421
+ } catch {
17422
+ textParts.push(String(block));
17423
+ }
17424
+ }
17425
+ }
17426
+ const text3 = textParts.join("\n");
17427
+ if (result.isError) {
17428
+ throw new Error(
17429
+ text3 ? text3 : `MCP tool "${toolName}" returned an error result with no text content`
17430
+ );
17431
+ }
17432
+ if (media.length === 0) {
17433
+ return text3;
17434
+ }
17435
+ if (media.length === 1 && media[0].kind === "image") {
17436
+ const img = media[0];
17437
+ return resultWithImage(text3, Buffer.from(img.data, "base64"), {
17438
+ mimeType: img.mimeType
17439
+ });
17440
+ }
17441
+ return {
17442
+ result: text3,
17443
+ media: media.map((m) => ({
17444
+ kind: m.kind,
17445
+ data: m.data,
17446
+ mimeType: m.mimeType
17447
+ }))
17448
+ };
17449
+ }
17450
+ var import_zod5;
17451
+ var init_tool_adapter = __esm({
17452
+ "src/mcp/tool-adapter.ts"() {
17453
+ "use strict";
17454
+ import_zod5 = require("zod");
17455
+ init_create_gadget();
17456
+ init_helpers();
17457
+ init_json_schema_to_zod();
17458
+ }
17459
+ });
17460
+
17461
+ // src/mcp/runtime.ts
17462
+ var runtime_exports = {};
17463
+ __export(runtime_exports, {
17464
+ setupMcpServers: () => setupMcpServers
17465
+ });
17466
+ async function setupMcpServers(opts) {
17467
+ const { specs, registry, conversation, prefixConfig, systemPrompt, logger: logger2, onPromptDiscovered } = opts;
17468
+ const lifecycle = new McpLifecycle();
17469
+ lifecycle.installSignalHandlers();
17470
+ const connected = [];
17471
+ await Promise.all(
17472
+ specs.map(async (spec) => {
17473
+ const client = new McpClient(spec);
17474
+ try {
17475
+ await client.connect();
17476
+ } catch (err) {
17477
+ logger2.warn(
17478
+ `MCP server "${spec.name}" failed to connect \u2014 skipping. Reason: ${err.message}`
17479
+ );
17480
+ return;
17481
+ }
17482
+ lifecycle.register(client);
17483
+ const caps = client.serverCapabilities;
17484
+ const hasTools = caps?.tools !== void 0;
17485
+ let tools = [];
17486
+ if (hasTools) {
17487
+ try {
17488
+ tools = await client.listTools();
17489
+ } catch (err) {
17490
+ logger2.warn(
17491
+ `MCP server "${spec.name}" listTools failed \u2014 skipping. Reason: ${err.message}`
17492
+ );
17493
+ return;
17494
+ }
17495
+ } else {
17496
+ logger2.debug(
17497
+ `MCP server "${spec.name}" did not advertise tools capability \u2014 skipping listTools.`
17498
+ );
17499
+ }
17500
+ if (caps?.resources !== void 0) {
17501
+ logger2.debug(
17502
+ `MCP server "${spec.name}" advertises 'resources' capability \u2014 not yet implemented in llmist (deferred to v1.5).`
17503
+ );
17504
+ }
17505
+ if (caps?.prompts !== void 0 && onPromptDiscovered) {
17506
+ try {
17507
+ const prompts = await client.listPrompts();
17508
+ for (const p of prompts) {
17509
+ onPromptDiscovered(mcpPromptToSkill(p, client));
17510
+ }
17511
+ } catch (err) {
17512
+ logger2.debug(
17513
+ `MCP server "${spec.name}" listPrompts failed \u2014 skipping prompts. Reason: ${err.message}`
17514
+ );
17515
+ }
17516
+ }
17517
+ connected.push({ client, serverToolList: { server: spec, tools } });
17518
+ })
17519
+ );
17520
+ const resolved = resolveToolNames(connected.map((c) => c.serverToolList));
17521
+ for (const r of resolved) {
17522
+ const cs = connected.find((c) => c.serverToolList.server.name === r.server.name);
17523
+ if (!cs) continue;
17524
+ for (const tool of r.tools) {
17525
+ const gadget = mcpToolToGadget(tool, cs.client, { prefix: r.prefix });
17526
+ try {
17527
+ registry.register(gadget.name ?? tool.name, gadget);
17528
+ } catch (err) {
17529
+ logger2.warn(
17530
+ `MCP server "${r.server.name}" tool "${tool.name}" was not registered: ${err.message}`
17531
+ );
17532
+ }
17533
+ }
17534
+ }
17535
+ const builder = new LLMMessageBuilder();
17536
+ if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
17537
+ builder.addSystem(systemPrompt);
17538
+ }
17539
+ builder.addGadgets(registry.getAll(), {
17540
+ startPrefix: prefixConfig?.gadgetStartPrefix,
17541
+ endPrefix: prefixConfig?.gadgetEndPrefix,
17542
+ argPrefix: prefixConfig?.gadgetArgPrefix
17543
+ });
17544
+ conversation.replaceBaseMessages(builder.build());
17545
+ return lifecycle;
17546
+ }
17547
+ var init_runtime = __esm({
17548
+ "src/mcp/runtime.ts"() {
17549
+ "use strict";
17550
+ init_messages();
17551
+ init_client2();
17552
+ init_lifecycle();
17553
+ init_multi_server();
17554
+ init_prompt_adapter();
17555
+ init_tool_adapter();
17556
+ }
17557
+ });
17558
+
17559
+ // src/agent/agent.ts
17560
+ var OVERFLOW_RECOVERY_MIN_HISTORY, Agent;
17561
+ var init_agent = __esm({
17562
+ "src/agent/agent.ts"() {
17563
+ "use strict";
17564
+ init_execution_tree();
17565
+ init_messages();
17566
+ init_model_shortcuts();
17567
+ init_rate_limit();
17568
+ init_retry();
17569
+ init_exceptions();
17570
+ init_media_store();
17571
+ init_logger();
17572
+ init_agent_internal_key();
17573
+ init_manager();
17574
+ init_conversation_manager();
17575
+ init_conversation_updater();
17576
+ init_event_handlers();
17577
+ init_llm_call_lifecycle();
17578
+ init_output_limit_manager();
17579
+ init_retry_orchestrator();
17580
+ init_safe_observe();
17581
+ init_stream_processor_factory();
17582
+ init_tree_hook_bridge();
17583
+ OVERFLOW_RECOVERY_MIN_HISTORY = 4;
17584
+ Agent = class {
17585
+ client;
17586
+ model;
17587
+ maxIterations;
17588
+ budget;
17589
+ temperature;
17590
+ logger;
17591
+ hooks;
17592
+ conversation;
17593
+ registry;
17594
+ prefixConfig;
17595
+ conversationUpdater;
17596
+ defaultMaxTokens;
17597
+ hasUserPrompt;
17598
+ // Gadget output limiting
17599
+ outputLimitManager;
17600
+ // Context compaction
17601
+ compactionManager;
17602
+ // Media storage (for gadgets returning images, audio, etc.)
17603
+ mediaStore;
17604
+ // Cancellation
17605
+ signal;
17606
+ reasoning;
17607
+ caching;
17608
+ // Retry configuration (shared reference also passed to StreamProcessorFactory)
17609
+ retryConfig;
17610
+ // Rate limit tracker for proactive throttling
17611
+ rateLimitTracker;
17612
+ // Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
17613
+ completedInvocationIds = /* @__PURE__ */ new Set();
17614
+ failedInvocationIds = /* @__PURE__ */ new Set();
17615
+ // Queue for user messages injected during agent execution (REPL mid-session input)
17616
+ pendingUserMessages = [];
17617
+ // Execution Tree - first-class model for nested subagent support
17618
+ tree;
17619
+ parentNodeId;
17620
+ // StreamProcessor factory - encapsulates all pass-through StreamProcessor config
17621
+ streamProcessorFactory;
17622
+ // LLM call lifecycle helper (encapsulates prepareLLMCall, completeLLMCall, notifyLLMError)
17623
+ llmCallLifecycle;
17624
+ // MCP integration — populated only when mcpSpecs were provided.
17625
+ mcpSpecs;
17626
+ mcpLifecycle = null;
17627
+ mcpDiscoveredPrompts = [];
17628
+ /**
17629
+ * Creates a new Agent instance.
17630
+ * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
17631
+ */
17632
+ constructor(key, options) {
17633
+ if (!isValidAgentKey(key)) {
17634
+ throw new Error(
17635
+ "Agent cannot be instantiated directly. Use LLMist.createAgent() or new AgentBuilder() instead."
17636
+ );
17637
+ }
17638
+ this.client = options.client;
17639
+ this.model = resolveModel(options.model);
17640
+ this.maxIterations = options.maxIterations ?? 10;
17641
+ this.budget = options.budget;
17642
+ this.temperature = options.temperature;
17643
+ this.logger = options.logger ?? createLogger({ name: "llmist:agent" });
17644
+ this.registry = options.registry;
17645
+ this.prefixConfig = options.prefixConfig;
17646
+ this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
17647
+ const outputLimitConfig = {
17648
+ enabled: options.outputLimitConfig?.enabled,
17649
+ limitPercent: options.outputLimitConfig?.limitPercent
17650
+ };
17651
+ this.outputLimitManager = new OutputLimitManager(
17652
+ this.client,
17653
+ this.model,
17654
+ outputLimitConfig,
17655
+ this.registry,
17656
+ this.logger
17657
+ );
17658
+ this.mediaStore = new MediaStore();
17659
+ this.hooks = this.outputLimitManager.getHooks(options.hooks);
17660
+ const baseBuilder = new LLMMessageBuilder(options.promptConfig);
17661
+ if (options.systemPrompt) {
17662
+ baseBuilder.addSystem(options.systemPrompt);
17663
+ }
17664
+ baseBuilder.addGadgets(this.registry.getAll(), {
17665
+ startPrefix: this.prefixConfig?.gadgetStartPrefix,
17666
+ endPrefix: this.prefixConfig?.gadgetEndPrefix,
17667
+ argPrefix: this.prefixConfig?.gadgetArgPrefix
17668
+ });
17669
+ const baseMessages = baseBuilder.build();
17670
+ const initialMessages = (options.initialMessages ?? []).map((message) => ({
17671
+ role: message.role,
17672
+ content: message.content
17673
+ }));
17674
+ this.conversation = new ConversationManager(baseMessages, initialMessages, {
17675
+ startPrefix: this.prefixConfig?.gadgetStartPrefix,
17676
+ endPrefix: this.prefixConfig?.gadgetEndPrefix,
17677
+ argPrefix: this.prefixConfig?.gadgetArgPrefix
17678
+ });
17679
+ this.hasUserPrompt = !!options.userPrompt;
17680
+ if (options.userPrompt) {
17681
+ this.conversation.addUserMessage(options.userPrompt);
17682
+ }
17683
+ this.conversationUpdater = new ConversationUpdater(
17684
+ this.conversation,
17685
+ options.textOnlyHandler ?? "terminate",
17686
+ options.textWithGadgetsHandler,
17687
+ this.logger
17688
+ );
17689
+ const compactionEnabled = options.compactionConfig?.enabled ?? true;
17690
+ if (compactionEnabled) {
17691
+ this.compactionManager = new CompactionManager(
17692
+ this.client,
17693
+ this.model,
17694
+ options.compactionConfig,
17695
+ this.logger
17696
+ );
17697
+ }
17698
+ this.signal = options.signal;
17699
+ this.mcpSpecs = options.mcpSpecs ?? [];
17700
+ this.reasoning = options.reasoning;
17701
+ this.caching = options.caching;
17702
+ this.retryConfig = options.sharedRetryConfig ?? resolveRetryConfig(options.retryConfig);
17703
+ if (options.sharedRateLimitTracker) {
17704
+ this.rateLimitTracker = options.sharedRateLimitTracker;
17705
+ } else {
17706
+ const rateLimitConfig = resolveRateLimitConfig(options.rateLimitConfig);
17707
+ if (rateLimitConfig.enabled) {
17708
+ this.rateLimitTracker = new RateLimitTracker(options.rateLimitConfig);
17709
+ }
17710
+ }
17711
+ const treeConfig = options.treeConfig;
17712
+ this.tree = treeConfig?.tree ?? new ExecutionTree();
17713
+ this.parentNodeId = treeConfig?.parentNodeId ?? null;
17714
+ this.streamProcessorFactory = new StreamProcessorFactory({
17715
+ registry: this.registry,
17716
+ prefixConfig: this.prefixConfig,
17717
+ hooks: this.hooks,
17718
+ logger: this.logger,
17719
+ requestHumanInput: options.requestHumanInput,
17720
+ defaultGadgetTimeoutMs: options.defaultGadgetTimeoutMs,
17721
+ gadgetExecutionMode: options.gadgetExecutionMode ?? "parallel",
17722
+ client: this.client,
17723
+ mediaStore: this.mediaStore,
17724
+ agentContextConfig: {
17725
+ model: this.model,
17726
+ temperature: this.temperature
17727
+ },
17728
+ subagentConfig: options.subagentConfig,
17729
+ tree: this.tree,
17730
+ baseDepth: treeConfig?.baseDepth ?? 0,
17731
+ parentObservers: treeConfig?.parentObservers,
17732
+ rateLimitTracker: this.rateLimitTracker,
17733
+ retryConfig: this.retryConfig,
17734
+ maxGadgetsPerResponse: options.maxGadgetsPerResponse ?? 0
17735
+ });
17736
+ this.llmCallLifecycle = new LLMCallLifecycle({
17737
+ client: this.client,
17738
+ conversation: this.conversation,
17739
+ tree: this.tree,
17740
+ hooks: this.hooks,
17741
+ logger: this.logger,
16793
17742
  rateLimitTracker: this.rateLimitTracker,
16794
17743
  signal: this.signal,
16795
17744
  temperature: this.temperature,
@@ -17007,6 +17956,18 @@ var init_agent = __esm({
17007
17956
  );
17008
17957
  }
17009
17958
  const unsubscribeBridge = bridgeTreeToHooks(this.tree, this.hooks, this.logger);
17959
+ if (this.mcpSpecs.length > 0) {
17960
+ const { setupMcpServers: setupMcpServers2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
17961
+ this.mcpLifecycle = await setupMcpServers2({
17962
+ specs: this.mcpSpecs,
17963
+ registry: this.registry,
17964
+ conversation: this.conversation,
17965
+ prefixConfig: this.prefixConfig,
17966
+ systemPrompt: this.conversation.getBaseMessages()[0]?.role === "system" ? this.conversation.getBaseMessages()[0].content : void 0,
17967
+ logger: this.logger,
17968
+ onPromptDiscovered: (skill) => this.mcpDiscoveredPrompts.push(skill)
17969
+ });
17970
+ }
17010
17971
  let currentIteration = 0;
17011
17972
  this.logger.info("Starting agent loop", {
17012
17973
  model: this.model,
@@ -17206,6 +18167,14 @@ var init_agent = __esm({
17206
18167
  }
17207
18168
  }
17208
18169
  unsubscribeBridge();
18170
+ if (this.mcpLifecycle) {
18171
+ try {
18172
+ await this.mcpLifecycle.closeAll();
18173
+ } catch (err) {
18174
+ this.logger.debug("MCP lifecycle teardown error (suppressed):", err);
18175
+ }
18176
+ this.mcpLifecycle = null;
18177
+ }
17209
18178
  }
17210
18179
  }
17211
18180
  /**
@@ -17405,6 +18374,7 @@ __export(index_exports, {
17405
18374
  ConversationManager: () => ConversationManager,
17406
18375
  DEFAULT_COMPACTION_CONFIG: () => DEFAULT_COMPACTION_CONFIG,
17407
18376
  DEFAULT_HINTS: () => DEFAULT_HINTS,
18377
+ DEFAULT_MCP_COMMAND_ALLOWLIST: () => DEFAULT_MCP_COMMAND_ALLOWLIST,
17408
18378
  DEFAULT_PROMPTS: () => DEFAULT_PROMPTS,
17409
18379
  DEFAULT_RATE_LIMIT_CONFIG: () => DEFAULT_RATE_LIMIT_CONFIG,
17410
18380
  DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
@@ -17424,10 +18394,17 @@ __export(index_exports, {
17424
18394
  HuggingFaceProvider: () => HuggingFaceProvider,
17425
18395
  HumanInputRequiredException: () => HumanInputRequiredException,
17426
18396
  HybridStrategy: () => HybridStrategy,
18397
+ JsonSchemaConversionError: () => JsonSchemaConversionError,
17427
18398
  LLMMessageBuilder: () => LLMMessageBuilder,
17428
18399
  LLMist: () => LLMist,
17429
18400
  LOAD_SKILL_GADGET_NAME: () => LOAD_SKILL_GADGET_NAME,
17430
18401
  MODEL_ALIASES: () => MODEL_ALIASES,
18402
+ McpClient: () => McpClient,
18403
+ McpConnectError: () => McpConnectError,
18404
+ McpError: () => McpError,
18405
+ McpLifecycle: () => McpLifecycle,
18406
+ McpToolCallError: () => McpToolCallError,
18407
+ McpUntrustedCommandError: () => McpUntrustedCommandError,
17431
18408
  MediaStore: () => MediaStore,
17432
18409
  ModelIdentifierParser: () => ModelIdentifierParser,
17433
18410
  ModelRegistry: () => ModelRegistry,
@@ -17443,6 +18420,7 @@ __export(index_exports, {
17443
18420
  SummarizationStrategy: () => SummarizationStrategy,
17444
18421
  TaskCompletionSignal: () => TaskCompletionSignal,
17445
18422
  TimeoutException: () => TimeoutException,
18423
+ assertCommandAllowed: () => assertCommandAllowed,
17446
18424
  audioFromBase64: () => audioFromBase64,
17447
18425
  audioFromBuffer: () => audioFromBuffer,
17448
18426
  collectEvents: () => collectEvents,
@@ -17457,6 +18435,7 @@ __export(index_exports, {
17457
18435
  createHuggingFaceProviderFromEnv: () => createHuggingFaceProviderFromEnv,
17458
18436
  createLoadSkillGadget: () => createLoadSkillGadget,
17459
18437
  createLogger: () => createLogger,
18438
+ createMcpServer: () => createMcpServer,
17460
18439
  createMediaOutput: () => createMediaOutput,
17461
18440
  createOpenAIProviderFromEnv: () => createOpenAIProviderFromEnv,
17462
18441
  createOpenRouterProviderFromEnv: () => createOpenRouterProviderFromEnv,
@@ -17479,7 +18458,9 @@ __export(index_exports, {
17479
18458
  formatLLMError: () => formatLLMError,
17480
18459
  formatLlmRequest: () => formatLlmRequest,
17481
18460
  gadgetError: () => gadgetError,
18461
+ gadgetResultToMcpContent: () => gadgetResultToMcpContent,
17482
18462
  gadgetSuccess: () => gadgetSuccess,
18463
+ gadgetToMcpTool: () => gadgetToMcpTool,
17483
18464
  getErrorMessage: () => getErrorMessage,
17484
18465
  getHostExports: () => getHostExports2,
17485
18466
  getModelId: () => getModelId,
@@ -17507,9 +18488,11 @@ __export(index_exports, {
17507
18488
  isSubagentEvent: () => isSubagentEvent,
17508
18489
  isTextPart: () => isTextPart,
17509
18490
  iterationProgressHint: () => iterationProgressHint,
18491
+ jsonSchemaToZod: () => jsonSchemaToZod,
17510
18492
  listPresets: () => listPresets,
17511
18493
  listSubagents: () => listSubagents,
17512
18494
  loadSkillsFromDirectory: () => loadSkillsFromDirectory,
18495
+ mcpToolToGadget: () => mcpToolToGadget,
17513
18496
  normalizeMessageContent: () => normalizeMessageContent,
17514
18497
  parallelGadgetHint: () => parallelGadgetHint,
17515
18498
  parseDataUrl: () => parseDataUrl,
@@ -17520,6 +18503,7 @@ __export(index_exports, {
17520
18503
  parseSkillContent: () => parseSkillContent,
17521
18504
  parseSkillFile: () => parseSkillFile,
17522
18505
  randomDelay: () => randomDelay,
18506
+ renderSkillForMcpPrompt: () => renderSkillForMcpPrompt,
17523
18507
  resetFileLoggingState: () => resetFileLoggingState,
17524
18508
  resolveConfig: () => resolveConfig,
17525
18509
  resolveHintTemplate: () => resolveHintTemplate,
@@ -17537,9 +18521,11 @@ __export(index_exports, {
17537
18521
  resultWithImage: () => resultWithImage,
17538
18522
  resultWithImages: () => resultWithImages,
17539
18523
  resultWithMedia: () => resultWithMedia,
18524
+ runGadgetForMcp: () => runGadgetForMcp,
17540
18525
  runWithHandlers: () => runWithHandlers,
17541
18526
  scanResources: () => scanResources,
17542
18527
  schemaToJSONSchema: () => schemaToJSONSchema,
18528
+ skillToMcpPrompt: () => skillToMcpPrompt,
17543
18529
  stream: () => stream,
17544
18530
  stripProviderPrefix: () => stripProviderPrefix,
17545
18531
  substituteArguments: () => substituteArguments,
@@ -17555,10 +18541,10 @@ __export(index_exports, {
17555
18541
  withErrorHandling: () => withErrorHandling,
17556
18542
  withRetry: () => withRetry,
17557
18543
  withTimeout: () => withTimeout,
17558
- z: () => import_zod4.z
18544
+ z: () => import_zod6.z
17559
18545
  });
17560
18546
  module.exports = __toCommonJS(index_exports);
17561
- var import_zod4 = require("zod");
18547
+ var import_zod6 = require("zod");
17562
18548
  init_agent();
17563
18549
  init_builder();
17564
18550
  init_event_handlers();
@@ -17741,140 +18727,254 @@ init_create_gadget();
17741
18727
  init_exceptions();
17742
18728
  init_executor();
17743
18729
  init_gadget();
18730
+ init_helpers();
18731
+ init_output_viewer();
18732
+ init_parser2();
18733
+ init_registry();
18734
+ init_typed_gadget();
17744
18735
 
17745
- // src/gadgets/helpers.ts
17746
- init_input_content();
17747
- function gadgetSuccess(data = {}) {
17748
- return JSON.stringify({ success: true, ...data });
17749
- }
17750
- function gadgetError(message, details) {
17751
- return JSON.stringify({ error: message, ...details });
17752
- }
17753
- function getErrorMessage(error) {
17754
- return error instanceof Error ? error.message : String(error);
17755
- }
17756
- function withErrorHandling(execute) {
17757
- return async (params, ctx) => {
17758
- try {
17759
- return await execute(params, ctx);
17760
- } catch (error) {
17761
- return gadgetError(getErrorMessage(error));
17762
- }
17763
- };
17764
- }
17765
- function createMediaOutput(kind, data, mimeType, options) {
17766
- const buffer = data instanceof Buffer ? data : Buffer.from(data);
18736
+ // src/mcp/index.ts
18737
+ init_allowlist();
18738
+ init_client2();
18739
+ init_errors();
18740
+
18741
+ // src/mcp/gadget-exporter.ts
18742
+ init_schema_to_json();
18743
+
18744
+ // src/gadgets/validation.ts
18745
+ function validateAndApplyDefaults(schema, params) {
18746
+ const result = schema.safeParse(params);
18747
+ if (result.success) {
18748
+ return {
18749
+ success: true,
18750
+ data: result.data
18751
+ };
18752
+ }
18753
+ const issues = result.error.issues.map((issue) => ({
18754
+ path: issue.path.join(".") || "root",
18755
+ message: issue.message
18756
+ }));
18757
+ const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
17767
18758
  return {
17768
- kind,
17769
- data: buffer.toString("base64"),
17770
- mimeType,
17771
- description: options?.description,
17772
- metadata: options?.metadata,
17773
- fileName: options?.fileName
18759
+ success: false,
18760
+ error: formattedError,
18761
+ issues
17774
18762
  };
17775
18763
  }
17776
- function resultWithMedia(result, media, cost) {
17777
- if (media.length === 0) {
17778
- throw new Error("resultWithMedia: media array cannot be empty");
18764
+ function validateGadgetParams(gadget, params) {
18765
+ if (!gadget.parameterSchema) {
18766
+ return {
18767
+ success: true,
18768
+ data: params
18769
+ };
17779
18770
  }
17780
- return {
17781
- result,
17782
- media,
17783
- cost
17784
- };
18771
+ return validateAndApplyDefaults(gadget.parameterSchema, params);
17785
18772
  }
17786
- function resultWithImage(result, imageData, options) {
17787
- const buffer = imageData instanceof Buffer ? imageData : Buffer.from(imageData);
17788
- const mimeType = options?.mimeType ?? detectImageMimeType(buffer);
17789
- if (!mimeType) {
17790
- throw new Error(
17791
- "Could not detect image MIME type. Please provide mimeType explicitly in options."
17792
- );
18773
+
18774
+ // src/mcp/gadget-exporter.ts
18775
+ function gadgetToMcpTool(gadget) {
18776
+ const description = gadget.description && gadget.description.length > 0 ? gadget.description : `Native llmist gadget "${gadget.name ?? "unnamed"}"`;
18777
+ let inputSchema;
18778
+ if (gadget.parameterSchema) {
18779
+ inputSchema = schemaToJSONSchema(gadget.parameterSchema);
18780
+ } else {
18781
+ inputSchema = { type: "object", properties: {} };
17793
18782
  }
17794
18783
  return {
17795
- result,
17796
- media: [
17797
- {
17798
- kind: "image",
17799
- data: buffer.toString("base64"),
17800
- mimeType,
17801
- description: options?.description,
17802
- metadata: options?.metadata,
17803
- fileName: options?.fileName
17804
- }
17805
- ],
17806
- cost: options?.cost
18784
+ name: gadget.name ?? "unnamed-gadget",
18785
+ description,
18786
+ inputSchema
17807
18787
  };
17808
18788
  }
17809
- function resultWithImages(result, images, cost) {
17810
- if (images.length === 0) {
17811
- throw new Error("resultWithImages: images array cannot be empty");
18789
+ function gadgetResultToMcpContent(ret) {
18790
+ if (typeof ret === "string") {
18791
+ return [{ type: "text", text: ret }];
18792
+ }
18793
+ if (ret && typeof ret === "object" && "result" in ret) {
18794
+ const r = ret;
18795
+ const blocks = [];
18796
+ if (typeof r.result === "string") {
18797
+ blocks.push({ type: "text", text: r.result });
18798
+ } else {
18799
+ blocks.push({ type: "text", text: JSON.stringify(r.result) });
18800
+ }
18801
+ if (r.media) {
18802
+ for (const m of r.media) {
18803
+ if (m.kind === "image" || m.kind === "audio") {
18804
+ blocks.push({
18805
+ type: m.kind,
18806
+ data: m.data,
18807
+ mimeType: m.mimeType
18808
+ });
18809
+ }
18810
+ }
18811
+ }
18812
+ return blocks;
17812
18813
  }
17813
- const media = images.map((img, index) => {
17814
- const buffer = img.data instanceof Buffer ? img.data : Buffer.from(img.data);
17815
- const mimeType = img.mimeType ?? detectImageMimeType(buffer);
17816
- if (!mimeType) {
17817
- throw new Error(
17818
- `Could not detect MIME type for image at index ${index}. Please provide mimeType explicitly.`
17819
- );
18814
+ return [{ type: "text", text: JSON.stringify(ret) }];
18815
+ }
18816
+ async function runGadgetForMcp(gadget, rawParams) {
18817
+ if (gadget.parameterSchema) {
18818
+ const validation = validateAndApplyDefaults(
18819
+ gadget.parameterSchema,
18820
+ rawParams ?? {}
18821
+ );
18822
+ if (!validation.success) {
18823
+ return {
18824
+ isError: true,
18825
+ content: [
18826
+ {
18827
+ type: "text",
18828
+ text: `Invalid arguments for gadget "${gadget.name}": ${validation.error}`
18829
+ }
18830
+ ]
18831
+ };
17820
18832
  }
18833
+ rawParams = validation.data;
18834
+ }
18835
+ try {
18836
+ const result = await gadget.execute(rawParams);
17821
18837
  return {
17822
- kind: "image",
17823
- data: buffer.toString("base64"),
17824
- mimeType,
17825
- description: img.description,
17826
- metadata: img.metadata,
17827
- fileName: img.fileName
18838
+ content: gadgetResultToMcpContent(result)
17828
18839
  };
17829
- });
17830
- return { result, media, cost };
18840
+ } catch (err) {
18841
+ return {
18842
+ isError: true,
18843
+ content: [
18844
+ {
18845
+ type: "text",
18846
+ text: `Gadget "${gadget.name}" failed: ${err.message}`
18847
+ }
18848
+ ]
18849
+ };
18850
+ }
17831
18851
  }
17832
- function resultWithAudio(result, audioData, options) {
17833
- const buffer = audioData instanceof Buffer ? audioData : Buffer.from(audioData);
17834
- const mimeType = options?.mimeType ?? detectAudioMimeType(buffer);
17835
- if (!mimeType) {
17836
- throw new Error(
17837
- "Could not detect audio MIME type. Please provide mimeType explicitly in options."
17838
- );
18852
+
18853
+ // src/mcp/index.ts
18854
+ init_json_schema_to_zod();
18855
+ init_lifecycle();
18856
+
18857
+ // src/mcp/skill-exporter.ts
18858
+ function skillToMcpPrompt(skill) {
18859
+ const description = skill.description && skill.description.length > 0 ? skill.description : `Native llmist skill "${skill.name}"`;
18860
+ const args = [];
18861
+ if (skill.metadata.argumentHint) {
18862
+ args.push({
18863
+ name: "arguments",
18864
+ description: skill.metadata.argumentHint,
18865
+ required: false
18866
+ });
17839
18867
  }
17840
- const metadata = options?.durationMs ? { durationMs: options.durationMs } : void 0;
17841
18868
  return {
17842
- result,
17843
- media: [
18869
+ name: skill.name,
18870
+ description,
18871
+ ...args.length > 0 ? { arguments: args } : {}
18872
+ };
18873
+ }
18874
+ async function renderSkillForMcpPrompt(skill, args) {
18875
+ const argString = typeof args.arguments === "string" ? args.arguments : Object.values(args).filter((v) => typeof v === "string").join(" ");
18876
+ const activation = await skill.activate({
18877
+ arguments: argString || void 0
18878
+ });
18879
+ return {
18880
+ description: skill.description,
18881
+ messages: [
17844
18882
  {
17845
- kind: "audio",
17846
- data: buffer.toString("base64"),
17847
- mimeType,
17848
- description: options?.description,
17849
- metadata,
17850
- fileName: options?.fileName
18883
+ role: "user",
18884
+ content: { type: "text", text: activation.resolvedInstructions }
17851
18885
  }
17852
- ],
17853
- cost: options?.cost
18886
+ ]
17854
18887
  };
17855
18888
  }
17856
- function resultWithFile(result, fileData, mimeType, options) {
17857
- const buffer = fileData instanceof Buffer ? fileData : Buffer.from(fileData);
18889
+
18890
+ // src/mcp/server.ts
18891
+ var DEFAULT_SERVER_INFO = { name: "llmist", version: "0.0.0" };
18892
+ function createMcpServer(opts) {
18893
+ const { gadgets, skills } = opts;
18894
+ const hasTools = gadgets.getAll().length > 0;
18895
+ const hasPrompts = !!skills && skills.size > 0;
18896
+ const capabilities = {};
18897
+ if (hasTools) capabilities.tools = {};
18898
+ if (hasPrompts) capabilities.prompts = {};
18899
+ let sdkServer = null;
18900
+ let running = false;
18901
+ async function ensureServer() {
18902
+ if (sdkServer) return sdkServer;
18903
+ const [serverMod, typesMod] = await Promise.all([
18904
+ import("@modelcontextprotocol/sdk/server/index.js"),
18905
+ import("@modelcontextprotocol/sdk/types.js")
18906
+ ]);
18907
+ const ServerClass = serverMod.Server;
18908
+ const server = new ServerClass(opts.serverInfo ?? DEFAULT_SERVER_INFO, {
18909
+ capabilities
18910
+ });
18911
+ if (hasTools) {
18912
+ server.setRequestHandler(typesMod.ListToolsRequestSchema, async () => ({
18913
+ tools: gadgets.getAll().map(gadgetToMcpTool)
18914
+ }));
18915
+ server.setRequestHandler(
18916
+ typesMod.CallToolRequestSchema,
18917
+ async (req) => {
18918
+ const gadget = gadgets.get(req.params.name);
18919
+ if (!gadget) {
18920
+ return {
18921
+ isError: true,
18922
+ content: [
18923
+ {
18924
+ type: "text",
18925
+ text: `Unknown tool "${req.params.name}". Call tools/list first.`
18926
+ }
18927
+ ]
18928
+ };
18929
+ }
18930
+ return runGadgetForMcp(gadget, req.params.arguments ?? {});
18931
+ }
18932
+ );
18933
+ }
18934
+ if (hasPrompts && skills) {
18935
+ server.setRequestHandler(typesMod.ListPromptsRequestSchema, async () => ({
18936
+ prompts: Array.from(skills.getAll()).map(skillToMcpPrompt)
18937
+ }));
18938
+ server.setRequestHandler(
18939
+ typesMod.GetPromptRequestSchema,
18940
+ async (req) => {
18941
+ const skill = skills.get(req.params.name);
18942
+ if (!skill) {
18943
+ throw new Error(`Unknown prompt "${req.params.name}"`);
18944
+ }
18945
+ const result = await renderSkillForMcpPrompt(skill, req.params.arguments ?? {});
18946
+ return result;
18947
+ }
18948
+ );
18949
+ }
18950
+ sdkServer = server;
18951
+ return server;
18952
+ }
17858
18953
  return {
17859
- result,
17860
- media: [
17861
- {
17862
- kind: "file",
17863
- data: buffer.toString("base64"),
17864
- mimeType,
17865
- description: options?.description,
17866
- fileName: options?.fileName
18954
+ get running() {
18955
+ return running;
18956
+ },
18957
+ async connect(transport) {
18958
+ const server = await ensureServer();
18959
+ await server.connect(transport);
18960
+ running = true;
18961
+ },
18962
+ async stop() {
18963
+ if (!sdkServer) return;
18964
+ try {
18965
+ await sdkServer.close();
18966
+ } catch {
17867
18967
  }
17868
- ],
17869
- cost: options?.cost
18968
+ sdkServer = null;
18969
+ running = false;
18970
+ }
17870
18971
  };
17871
18972
  }
17872
18973
 
18974
+ // src/mcp/index.ts
18975
+ init_tool_adapter();
18976
+
17873
18977
  // src/index.ts
17874
- init_output_viewer();
17875
- init_parser2();
17876
- init_registry();
17877
- init_typed_gadget();
17878
18978
  init_constants2();
17879
18979
 
17880
18980
  // src/utils/config-resolver.ts
@@ -17983,38 +19083,6 @@ function hasHostExports(ctx) {
17983
19083
  init_media_store();
17984
19084
  init_schema_to_json();
17985
19085
  init_schema_validator();
17986
-
17987
- // src/gadgets/validation.ts
17988
- function validateAndApplyDefaults(schema, params) {
17989
- const result = schema.safeParse(params);
17990
- if (result.success) {
17991
- return {
17992
- success: true,
17993
- data: result.data
17994
- };
17995
- }
17996
- const issues = result.error.issues.map((issue) => ({
17997
- path: issue.path.join(".") || "root",
17998
- message: issue.message
17999
- }));
18000
- const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
18001
- return {
18002
- success: false,
18003
- error: formattedError,
18004
- issues
18005
- };
18006
- }
18007
- function validateGadgetParams(gadget, params) {
18008
- if (!gadget.parameterSchema) {
18009
- return {
18010
- success: true,
18011
- data: params
18012
- };
18013
- }
18014
- return validateAndApplyDefaults(gadget.parameterSchema, params);
18015
- }
18016
-
18017
- // src/index.ts
18018
19086
  init_logger();
18019
19087
 
18020
19088
  // src/package/manifest.ts
@@ -18313,6 +19381,7 @@ function getHostExports2(ctx) {
18313
19381
  ConversationManager,
18314
19382
  DEFAULT_COMPACTION_CONFIG,
18315
19383
  DEFAULT_HINTS,
19384
+ DEFAULT_MCP_COMMAND_ALLOWLIST,
18316
19385
  DEFAULT_PROMPTS,
18317
19386
  DEFAULT_RATE_LIMIT_CONFIG,
18318
19387
  DEFAULT_RETRY_CONFIG,
@@ -18332,10 +19401,17 @@ function getHostExports2(ctx) {
18332
19401
  HuggingFaceProvider,
18333
19402
  HumanInputRequiredException,
18334
19403
  HybridStrategy,
19404
+ JsonSchemaConversionError,
18335
19405
  LLMMessageBuilder,
18336
19406
  LLMist,
18337
19407
  LOAD_SKILL_GADGET_NAME,
18338
19408
  MODEL_ALIASES,
19409
+ McpClient,
19410
+ McpConnectError,
19411
+ McpError,
19412
+ McpLifecycle,
19413
+ McpToolCallError,
19414
+ McpUntrustedCommandError,
18339
19415
  MediaStore,
18340
19416
  ModelIdentifierParser,
18341
19417
  ModelRegistry,
@@ -18351,6 +19427,7 @@ function getHostExports2(ctx) {
18351
19427
  SummarizationStrategy,
18352
19428
  TaskCompletionSignal,
18353
19429
  TimeoutException,
19430
+ assertCommandAllowed,
18354
19431
  audioFromBase64,
18355
19432
  audioFromBuffer,
18356
19433
  collectEvents,
@@ -18365,6 +19442,7 @@ function getHostExports2(ctx) {
18365
19442
  createHuggingFaceProviderFromEnv,
18366
19443
  createLoadSkillGadget,
18367
19444
  createLogger,
19445
+ createMcpServer,
18368
19446
  createMediaOutput,
18369
19447
  createOpenAIProviderFromEnv,
18370
19448
  createOpenRouterProviderFromEnv,
@@ -18387,7 +19465,9 @@ function getHostExports2(ctx) {
18387
19465
  formatLLMError,
18388
19466
  formatLlmRequest,
18389
19467
  gadgetError,
19468
+ gadgetResultToMcpContent,
18390
19469
  gadgetSuccess,
19470
+ gadgetToMcpTool,
18391
19471
  getErrorMessage,
18392
19472
  getHostExports,
18393
19473
  getModelId,
@@ -18415,9 +19495,11 @@ function getHostExports2(ctx) {
18415
19495
  isSubagentEvent,
18416
19496
  isTextPart,
18417
19497
  iterationProgressHint,
19498
+ jsonSchemaToZod,
18418
19499
  listPresets,
18419
19500
  listSubagents,
18420
19501
  loadSkillsFromDirectory,
19502
+ mcpToolToGadget,
18421
19503
  normalizeMessageContent,
18422
19504
  parallelGadgetHint,
18423
19505
  parseDataUrl,
@@ -18428,6 +19510,7 @@ function getHostExports2(ctx) {
18428
19510
  parseSkillContent,
18429
19511
  parseSkillFile,
18430
19512
  randomDelay,
19513
+ renderSkillForMcpPrompt,
18431
19514
  resetFileLoggingState,
18432
19515
  resolveConfig,
18433
19516
  resolveHintTemplate,
@@ -18445,9 +19528,11 @@ function getHostExports2(ctx) {
18445
19528
  resultWithImage,
18446
19529
  resultWithImages,
18447
19530
  resultWithMedia,
19531
+ runGadgetForMcp,
18448
19532
  runWithHandlers,
18449
19533
  scanResources,
18450
19534
  schemaToJSONSchema,
19535
+ skillToMcpPrompt,
18451
19536
  stream,
18452
19537
  stripProviderPrefix,
18453
19538
  substituteArguments,