llmist 17.3.0 → 17.5.1

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