llmist 17.2.1 → 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;
@@ -4660,6 +4670,9 @@ var init_create_gadget = __esm({
4660
4670
  });
4661
4671
 
4662
4672
  // src/gadgets/output-viewer.ts
4673
+ function pluralize(count, singular, plural = `${singular}s`) {
4674
+ return count === 1 ? singular : plural;
4675
+ }
4663
4676
  function applyPattern(lines, pattern) {
4664
4677
  const regex = new RegExp(pattern.regex);
4665
4678
  if (!pattern.include) {
@@ -4684,80 +4697,169 @@ function applyPatterns(lines, patterns) {
4684
4697
  }
4685
4698
  return result;
4686
4699
  }
4687
- function applyLineLimit(lines, limit) {
4700
+ function parseLimitWindow(limit) {
4688
4701
  const trimmed = limit.trim();
4689
4702
  if (trimmed.endsWith("-") && !trimmed.startsWith("-")) {
4690
4703
  const n = parseInt(trimmed.slice(0, -1), 10);
4691
- if (!isNaN(n) && n > 0) {
4692
- return lines.slice(0, n);
4704
+ if (!Number.isNaN(n) && n > 0) {
4705
+ return { kind: "first", count: n };
4693
4706
  }
4694
4707
  }
4695
4708
  if (trimmed.startsWith("-") && !trimmed.includes("-", 1)) {
4696
4709
  const n = parseInt(trimmed, 10);
4697
- if (!isNaN(n) && n < 0) {
4698
- return lines.slice(n);
4710
+ if (!Number.isNaN(n) && n < 0) {
4711
+ return { kind: "last", count: Math.abs(n) };
4699
4712
  }
4700
4713
  }
4701
4714
  const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
4702
4715
  if (rangeMatch) {
4703
4716
  const start = parseInt(rangeMatch[1], 10);
4704
4717
  const end = parseInt(rangeMatch[2], 10);
4705
- if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start) {
4706
- return lines.slice(start - 1, end);
4718
+ if (!Number.isNaN(start) && !Number.isNaN(end) && start > 0 && end >= start) {
4719
+ return { kind: "range", start, end };
4720
+ }
4721
+ }
4722
+ return null;
4723
+ }
4724
+ function applyLineLimit(lines, limit) {
4725
+ const window = parseLimitWindow(limit);
4726
+ if (!window) return lines;
4727
+ switch (window.kind) {
4728
+ case "first":
4729
+ return lines.slice(0, window.count);
4730
+ case "last":
4731
+ return lines.slice(-window.count);
4732
+ case "range":
4733
+ return lines.slice(window.start - 1, window.end);
4734
+ }
4735
+ }
4736
+ function applyCharacterLimit(content, limit, maxOutputChars) {
4737
+ const total = content.length;
4738
+ if (total === 0) {
4739
+ return { text: "", start: 0, end: 0, total: 0, truncatedBySize: false, hasMoreAfter: false };
4740
+ }
4741
+ let startIndex = 0;
4742
+ let endExclusive = total;
4743
+ const window = limit ? parseLimitWindow(limit) : null;
4744
+ if (window) {
4745
+ switch (window.kind) {
4746
+ case "first":
4747
+ endExclusive = Math.min(window.count, total);
4748
+ break;
4749
+ case "last":
4750
+ startIndex = Math.max(0, total - window.count);
4751
+ break;
4752
+ case "range":
4753
+ startIndex = Math.min(window.start - 1, total);
4754
+ endExclusive = Math.min(window.end, total);
4755
+ break;
4707
4756
  }
4708
4757
  }
4709
- return lines;
4758
+ let text3 = content.slice(startIndex, endExclusive);
4759
+ let truncatedBySize = false;
4760
+ if (text3.length > maxOutputChars) {
4761
+ text3 = window?.kind === "last" ? text3.slice(-maxOutputChars) : text3.slice(0, maxOutputChars);
4762
+ if (window?.kind === "last") {
4763
+ startIndex = endExclusive - text3.length;
4764
+ }
4765
+ truncatedBySize = true;
4766
+ }
4767
+ return {
4768
+ text: text3,
4769
+ start: text3.length === 0 ? 0 : startIndex + 1,
4770
+ end: text3.length === 0 ? 0 : startIndex + text3.length,
4771
+ total,
4772
+ truncatedBySize,
4773
+ hasMoreAfter: startIndex + text3.length < total
4774
+ };
4775
+ }
4776
+ function buildCharacterRangeHint(start, total) {
4777
+ if (total <= 0 || start > total) return null;
4778
+ const end = Math.min(total, start + CHARACTER_HINT_WINDOW - 1);
4779
+ return `${start}-${end}`;
4780
+ }
4781
+ function buildCharacterModeSuggestion(stored, opts = {}) {
4782
+ const hint = buildCharacterRangeHint(opts.start ?? 1, stored.charCount);
4783
+ const action = opts.removePatterns ? "Remove patterns and then try" : "Try";
4784
+ const lineLabel = pluralize(stored.lineCount, "line");
4785
+ return `This output is dense (${stored.lineCount.toLocaleString()} ${lineLabel}; longest line ${stored.maxLineLength.toLocaleString()} chars). ${action} mode: "character"` + (hint ? `, limit: "${hint}"` : "") + ".";
4786
+ }
4787
+ function shouldSuggestCharacterMode(stored, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
4788
+ return stored.lineCount <= 3 && (stored.maxLineLength > maxOutputChars || stored.maxLineLength >= DENSE_LINE_THRESHOLD);
4710
4789
  }
4711
4790
  function createGadgetOutputViewer(store, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
4712
4791
  return createGadget({
4713
4792
  name: "GadgetOutputViewer",
4714
- description: "View stored output from gadgets that returned too much data. Use patterns to filter lines (like grep) and limit to control output size. Patterns are applied first in order, then the limit is applied to the result.",
4793
+ description: 'View stored output from gadgets that returned too much data. Use mode "line" for grep-like filtering and mode "character" for raw chunked browsing when the output is dense or effectively single-line. Patterns work only in line mode.',
4715
4794
  schema: import_zod.z.object({
4716
4795
  id: import_zod.z.string().describe("ID of the stored output (from the truncation message)"),
4796
+ mode: import_zod.z.enum(["line", "character"]).default("line").describe(
4797
+ 'Browse by "line" (supports patterns) or by "character" (raw windows for dense output).'
4798
+ ),
4717
4799
  patterns: import_zod.z.array(patternSchema).optional().describe(
4718
- "Filter patterns applied in order (like piping through grep). Each pattern can include or exclude lines with optional before/after context."
4800
+ 'Line-mode filter patterns applied in order (like piping through grep). Not supported in mode "character".'
4719
4801
  ),
4720
4802
  limit: import_zod.z.string().optional().describe(
4721
- "Line range to return after filtering. Formats: '100-' (first 100), '-25' (last 25), '50-100' (lines 50-100)"
4803
+ `Pagination window. In mode "line" it is a line range; in mode "character" it is a character range. Formats: "100-" (first 100), "-25" (last 25), "50-100" (inclusive range).`
4722
4804
  )
4723
4805
  }),
4724
4806
  examples: [
4725
4807
  {
4726
4808
  comment: "View first 50 lines of stored output",
4727
- params: { id: "Search_abc12345", limit: "50-" }
4809
+ params: { id: "Search_abc12345", mode: "line", limit: "50-" }
4728
4810
  },
4729
4811
  {
4730
4812
  comment: "Filter for error lines with context",
4731
4813
  params: {
4732
4814
  id: "Search_abc12345",
4815
+ mode: "line",
4733
4816
  patterns: [{ regex: "error|Error|ERROR", include: true, before: 2, after: 5 }]
4734
4817
  }
4735
4818
  },
4736
4819
  {
4737
- comment: "Exclude blank lines, then show first 100",
4820
+ comment: "Exclude blank lines, then show first 100 lines",
4738
4821
  params: {
4739
4822
  id: "Search_abc12345",
4823
+ mode: "line",
4740
4824
  patterns: [{ regex: "^\\s*$", include: false, before: 0, after: 0 }],
4741
4825
  limit: "100-"
4742
4826
  }
4743
4827
  },
4744
4828
  {
4745
- comment: "Chain filters: find TODOs, exclude tests, limit to 50 lines",
4829
+ comment: "Browse the raw output by character window when line mode is too dense",
4746
4830
  params: {
4747
4831
  id: "Search_abc12345",
4748
- patterns: [
4749
- { regex: "TODO", include: true, before: 1, after: 1 },
4750
- { regex: "test|spec", include: false, before: 0, after: 0 }
4751
- ],
4752
- limit: "50-"
4832
+ mode: "character",
4833
+ limit: "1-2000"
4753
4834
  }
4754
4835
  }
4755
4836
  ],
4756
- execute: ({ id, patterns, limit }) => {
4837
+ execute: ({ id, mode, patterns, limit }) => {
4757
4838
  const stored = store.get(id);
4758
4839
  if (!stored) {
4759
4840
  return `Error: No stored output with id "${id}". Available IDs: ${store.getIds().join(", ") || "(none)"}`;
4760
4841
  }
4842
+ const suggestCharacterMode = shouldSuggestCharacterMode(stored, maxOutputChars);
4843
+ if (mode === "character") {
4844
+ if (patterns && patterns.length > 0) {
4845
+ return 'Error: patterns are only supported in mode "line". Remove patterns or switch back to mode: "line".';
4846
+ }
4847
+ const window = applyCharacterLimit(stored.content, limit, maxOutputChars);
4848
+ if (window.total === 0) {
4849
+ return "[Mode: character | Output is empty]";
4850
+ }
4851
+ const header2 = [
4852
+ `[Mode: character | Showing chars ${window.start.toLocaleString()}-${window.end.toLocaleString()} of ${window.total.toLocaleString()}${window.truncatedBySize ? " (truncated due to viewer size limit)" : ""}]`
4853
+ ];
4854
+ if (window.hasMoreAfter) {
4855
+ const nextRange = buildCharacterRangeHint(window.end + 1, window.total);
4856
+ if (nextRange) {
4857
+ header2.push(`[Next chunk: mode: "character", limit: "${nextRange}"]`);
4858
+ }
4859
+ }
4860
+ return `${header2.join("\n")}
4861
+ ${window.text}`;
4862
+ }
4761
4863
  let lines = stored.content.split("\n");
4762
4864
  if (patterns && patterns.length > 0) {
4763
4865
  lines = applyPatterns(
@@ -4773,55 +4875,77 @@ function createGadgetOutputViewer(store, maxOutputChars = DEFAULT_MAX_OUTPUT_CHA
4773
4875
  if (limit) {
4774
4876
  lines = applyLineLimit(lines, limit);
4775
4877
  }
4776
- let output = lines.join("\n");
4777
4878
  const totalLines = stored.lineCount;
4879
+ const totalLineLabel = pluralize(totalLines, "line");
4778
4880
  const returnedLines = lines.length;
4779
4881
  if (returnedLines === 0) {
4780
- return `No lines matched the filters. Original output had ${totalLines} lines.`;
4882
+ const base = `No lines matched the filters. Original output had ${totalLines.toLocaleString()} lines.`;
4883
+ if (!suggestCharacterMode) return base;
4884
+ return `${base} ${buildCharacterModeSuggestion(stored, {
4885
+ removePatterns: Boolean(patterns && patterns.length > 0)
4886
+ })}`;
4781
4887
  }
4888
+ let output = lines.join("\n");
4782
4889
  let truncatedBySize = false;
4783
4890
  let linesIncluded = returnedLines;
4891
+ let clippedFirstLine = false;
4784
4892
  if (output.length > maxOutputChars) {
4785
4893
  truncatedBySize = true;
4786
4894
  let truncatedOutput = "";
4787
4895
  linesIncluded = 0;
4788
4896
  for (const line of lines) {
4789
- if (truncatedOutput.length + line.length + 1 > maxOutputChars) break;
4790
- truncatedOutput += line + "\n";
4897
+ const addition = linesIncluded === 0 ? line : `
4898
+ ${line}`;
4899
+ if (truncatedOutput.length + addition.length > maxOutputChars) break;
4900
+ truncatedOutput += addition;
4791
4901
  linesIncluded++;
4792
4902
  }
4903
+ if (linesIncluded === 0) {
4904
+ clippedFirstLine = true;
4905
+ linesIncluded = 1;
4906
+ truncatedOutput = lines[0].slice(0, maxOutputChars);
4907
+ }
4793
4908
  output = truncatedOutput;
4794
4909
  }
4795
4910
  let header;
4796
- if (truncatedBySize) {
4911
+ if (clippedFirstLine) {
4912
+ header = `[Mode: line | Showing 1 partial line of ${totalLines.toLocaleString()} ${totalLineLabel} (the selected line exceeds the viewer size limit)]
4913
+ `;
4914
+ } else if (truncatedBySize) {
4797
4915
  const remainingLines = returnedLines - linesIncluded;
4798
- header = `[Showing ${linesIncluded} of ${totalLines} lines (truncated due to size limit)]
4799
- [... ${remainingLines.toLocaleString()} more lines. Use limit parameter to paginate, e.g., limit: "${linesIncluded + 1}-${linesIncluded + 200}"]
4916
+ header = `[Mode: line | Showing ${linesIncluded.toLocaleString()} of ${totalLines.toLocaleString()} ${totalLineLabel} (truncated due to size limit)]
4917
+ [... ${remainingLines.toLocaleString()} more ${pluralize(remainingLines, "line")}. Use limit parameter to paginate, e.g., limit: "${linesIncluded + 1}-${linesIncluded + 200}"]
4800
4918
  `;
4801
4919
  } else if (returnedLines < totalLines) {
4802
- header = `[Showing ${returnedLines} of ${totalLines} lines]
4920
+ header = `[Mode: line | Showing ${returnedLines.toLocaleString()} of ${totalLines.toLocaleString()} ${totalLineLabel}]
4803
4921
  `;
4804
4922
  } else {
4805
- header = `[Showing all ${totalLines} lines]
4923
+ header = `[Mode: line | Showing all ${totalLines.toLocaleString()} ${totalLineLabel}]
4806
4924
  `;
4807
4925
  }
4808
- return header + output;
4926
+ const footer = suggestCharacterMode || clippedFirstLine ? `
4927
+ [Tip: ${buildCharacterModeSuggestion(stored, {
4928
+ removePatterns: Boolean(patterns && patterns.length > 0)
4929
+ })}]` : "";
4930
+ return header + output + footer;
4809
4931
  }
4810
4932
  });
4811
4933
  }
4812
- var import_zod, patternSchema, DEFAULT_MAX_OUTPUT_CHARS;
4934
+ var import_zod, DEFAULT_MAX_OUTPUT_CHARS, CHARACTER_HINT_WINDOW, DENSE_LINE_THRESHOLD, patternSchema;
4813
4935
  var init_output_viewer = __esm({
4814
4936
  "src/gadgets/output-viewer.ts"() {
4815
4937
  "use strict";
4816
4938
  import_zod = require("zod");
4817
4939
  init_create_gadget();
4940
+ DEFAULT_MAX_OUTPUT_CHARS = 76800;
4941
+ CHARACTER_HINT_WINDOW = 2e3;
4942
+ DENSE_LINE_THRESHOLD = 4e3;
4818
4943
  patternSchema = import_zod.z.object({
4819
4944
  regex: import_zod.z.string().describe("Regular expression to match"),
4820
4945
  include: import_zod.z.boolean().default(true).describe("true = keep matching lines, false = exclude matching lines"),
4821
4946
  before: import_zod.z.number().int().min(0).default(0).describe("Context lines before each match (like grep -B)"),
4822
4947
  after: import_zod.z.number().int().min(0).default(0).describe("Context lines after each match (like grep -A)")
4823
4948
  });
4824
- DEFAULT_MAX_OUTPUT_CHARS = 76800;
4825
4949
  }
4826
4950
  });
4827
4951
 
@@ -4843,12 +4967,15 @@ var init_gadget_output_store = __esm({
4843
4967
  store(gadgetName, content) {
4844
4968
  const id = this.generateId(gadgetName);
4845
4969
  const encoder = new TextEncoder();
4970
+ const lines = content.split("\n");
4846
4971
  const stored = {
4847
4972
  id,
4848
4973
  gadgetName,
4849
4974
  content,
4975
+ charCount: content.length,
4850
4976
  byteSize: encoder.encode(content).length,
4851
- lineCount: content.split("\n").length,
4977
+ lineCount: lines.length,
4978
+ maxLineLength: lines.reduce((max, line) => Math.max(max, line.length), 0),
4852
4979
  timestamp: /* @__PURE__ */ new Date()
4853
4980
  };
4854
4981
  this.outputs.set(id, stored);
@@ -4952,16 +5079,20 @@ var init_output_limit_manager = __esm({
4952
5079
  }
4953
5080
  if (result.length > this.charLimit) {
4954
5081
  const id = this.outputStore.store(ctx.gadgetName, result);
4955
- const lines = result.split("\n").length;
4956
- const bytes = new TextEncoder().encode(result).length;
5082
+ const stored = this.outputStore.get(id);
5083
+ const lines = stored?.lineCount ?? result.split("\n").length;
5084
+ const bytes = stored?.byteSize ?? new TextEncoder().encode(result).length;
5085
+ const denseSuggestion = stored && shouldSuggestCharacterMode(stored, this.charLimit) ? ` ${buildCharacterModeSuggestion(stored)}` : "";
4957
5086
  this.logger.info("Gadget output exceeded limit, stored for browsing", {
4958
5087
  gadgetName: ctx.gadgetName,
4959
5088
  outputId: id,
4960
5089
  bytes,
4961
5090
  lines,
5091
+ charCount: stored?.charCount,
5092
+ maxLineLength: stored?.maxLineLength,
4962
5093
  charLimit: this.charLimit
4963
5094
  });
4964
- return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it]`;
5095
+ return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it.]` + denseSuggestion;
4965
5096
  }
4966
5097
  return result;
4967
5098
  };
@@ -13111,6 +13242,7 @@ var init_builder = __esm({
13111
13242
  subagents;
13112
13243
  policies;
13113
13244
  skills;
13245
+ mcp;
13114
13246
  constructor(client) {
13115
13247
  this.core = { client, initialMessages: [] };
13116
13248
  this.gadgets = { gadgets: [] };
@@ -13118,6 +13250,7 @@ var init_builder = __esm({
13118
13250
  this.subagents = {};
13119
13251
  this.policies = {};
13120
13252
  this.skills = { preActivated: [], skillDirs: [] };
13253
+ this.mcp = { servers: [] };
13121
13254
  }
13122
13255
  /** Set the model to use. Supports aliases like "sonnet", "flash". */
13123
13256
  withModel(model) {
@@ -13164,6 +13297,45 @@ var init_builder = __esm({
13164
13297
  this.gadgets.gadgets.push(...gadgets);
13165
13298
  return this;
13166
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
+ }
13167
13339
  /** Add conversation history messages. */
13168
13340
  withHistory(messages) {
13169
13341
  this.core.initialMessages.push(...normalizeHistory(messages));
@@ -13484,7 +13656,8 @@ ${preActivatedBlock}` : preActivatedBlock;
13484
13656
  parentObservers: this.subagents.parentObservers
13485
13657
  },
13486
13658
  sharedRateLimitTracker: this.subagents.sharedRateLimitTracker,
13487
- sharedRetryConfig: this.retry.sharedRetryConfig
13659
+ sharedRetryConfig: this.retry.sharedRetryConfig,
13660
+ mcpSpecs: this.mcp.servers.length > 0 ? [...this.mcp.servers] : void 0
13488
13661
  };
13489
13662
  }
13490
13663
  /** Create agent and start with a user prompt. */
@@ -14130,8 +14303,8 @@ var init_error_formatter = __esm({
14130
14303
  const parts = [];
14131
14304
  parts.push(`Error: Invalid parameters for '${gadgetName}':`);
14132
14305
  for (const issue of zodError.issues) {
14133
- const path3 = issue.path.join(".") || "root";
14134
- parts.push(` - ${path3}: ${issue.message}`);
14306
+ const path4 = issue.path.join(".") || "root";
14307
+ parts.push(` - ${path4}: ${issue.message}`);
14135
14308
  }
14136
14309
  parts.push("");
14137
14310
  parts.push("Gadget Usage:");
@@ -16491,157 +16664,1054 @@ var init_stream_processor_factory = __esm({
16491
16664
  }
16492
16665
  });
16493
16666
 
16494
- // src/agent/agent.ts
16495
- var OVERFLOW_RECOVERY_MIN_HISTORY, Agent;
16496
- var init_agent = __esm({
16497
- "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"() {
16498
16671
  "use strict";
16499
- init_execution_tree();
16500
- init_messages();
16501
- init_model_shortcuts();
16502
- init_rate_limit();
16503
- init_retry();
16504
- init_exceptions();
16505
- init_media_store();
16506
- init_logger();
16507
- init_agent_internal_key();
16508
- init_manager();
16509
- init_conversation_manager();
16510
- init_conversation_updater();
16511
- init_event_handlers();
16512
- init_llm_call_lifecycle();
16513
- init_output_limit_manager();
16514
- init_retry_orchestrator();
16515
- init_safe_observe();
16516
- init_stream_processor_factory();
16517
- init_tree_hook_bridge();
16518
- OVERFLOW_RECOVERY_MIN_HISTORY = 4;
16519
- Agent = class {
16520
- client;
16521
- model;
16522
- maxIterations;
16523
- budget;
16524
- temperature;
16525
- logger;
16526
- hooks;
16527
- conversation;
16528
- registry;
16529
- prefixConfig;
16530
- conversationUpdater;
16531
- defaultMaxTokens;
16532
- hasUserPrompt;
16533
- // Gadget output limiting
16534
- outputLimitManager;
16535
- // Context compaction
16536
- compactionManager;
16537
- // Media storage (for gadgets returning images, audio, etc.)
16538
- mediaStore;
16539
- // Cancellation
16540
- signal;
16541
- reasoning;
16542
- caching;
16543
- // Retry configuration (shared reference also passed to StreamProcessorFactory)
16544
- retryConfig;
16545
- // Rate limit tracker for proactive throttling
16546
- rateLimitTracker;
16547
- // Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
16548
- completedInvocationIds = /* @__PURE__ */ new Set();
16549
- failedInvocationIds = /* @__PURE__ */ new Set();
16550
- // Queue for user messages injected during agent execution (REPL mid-session input)
16551
- pendingUserMessages = [];
16552
- // Execution Tree - first-class model for nested subagent support
16553
- tree;
16554
- parentNodeId;
16555
- // StreamProcessor factory - encapsulates all pass-through StreamProcessor config
16556
- streamProcessorFactory;
16557
- // LLM call lifecycle helper (encapsulates prepareLLMCall, completeLLMCall, notifyLLMError)
16558
- llmCallLifecycle;
16559
- /**
16560
- * Creates a new Agent instance.
16561
- * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
16562
- */
16563
- constructor(key, options) {
16564
- if (!isValidAgentKey(key)) {
16565
- throw new Error(
16566
- "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 }
16567
16855
  );
16568
16856
  }
16569
- this.client = options.client;
16570
- this.model = resolveModel(options.model);
16571
- this.maxIterations = options.maxIterations ?? 10;
16572
- this.budget = options.budget;
16573
- this.temperature = options.temperature;
16574
- this.logger = options.logger ?? createLogger({ name: "llmist:agent" });
16575
- this.registry = options.registry;
16576
- this.prefixConfig = options.prefixConfig;
16577
- this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
16578
- const outputLimitConfig = {
16579
- enabled: options.outputLimitConfig?.enabled,
16580
- limitPercent: options.outputLimitConfig?.limitPercent
16581
- };
16582
- this.outputLimitManager = new OutputLimitManager(
16583
- this.client,
16584
- this.model,
16585
- outputLimitConfig,
16586
- this.registry,
16587
- this.logger
16588
- );
16589
- this.mediaStore = new MediaStore();
16590
- this.hooks = this.outputLimitManager.getHooks(options.hooks);
16591
- const baseBuilder = new LLMMessageBuilder(options.promptConfig);
16592
- if (options.systemPrompt) {
16593
- baseBuilder.addSystem(options.systemPrompt);
16857
+ this.sdkClient = client;
16858
+ const maybePid = transport.pid;
16859
+ if (typeof maybePid === "number") {
16860
+ this.spawnedPid = maybePid;
16594
16861
  }
16595
- baseBuilder.addGadgets(this.registry.getAll(), {
16596
- startPrefix: this.prefixConfig?.gadgetStartPrefix,
16597
- endPrefix: this.prefixConfig?.gadgetEndPrefix,
16598
- argPrefix: this.prefixConfig?.gadgetArgPrefix
16599
- });
16600
- const baseMessages = baseBuilder.build();
16601
- const initialMessages = (options.initialMessages ?? []).map((message) => ({
16602
- role: message.role,
16603
- 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
16604
16870
  }));
16605
- this.conversation = new ConversationManager(baseMessages, initialMessages, {
16606
- startPrefix: this.prefixConfig?.gadgetStartPrefix,
16607
- endPrefix: this.prefixConfig?.gadgetEndPrefix,
16608
- argPrefix: this.prefixConfig?.gadgetArgPrefix
16609
- });
16610
- this.hasUserPrompt = !!options.userPrompt;
16611
- if (options.userPrompt) {
16612
- 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
+ );
16613
16892
  }
16614
- this.conversationUpdater = new ConversationUpdater(
16615
- this.conversation,
16616
- options.textOnlyHandler ?? "terminate",
16617
- options.textWithGadgetsHandler,
16618
- this.logger
16619
- );
16620
- const compactionEnabled = options.compactionConfig?.enabled ?? true;
16621
- if (compactionEnabled) {
16622
- this.compactionManager = new CompactionManager(
16623
- this.client,
16624
- this.model,
16625
- options.compactionConfig,
16626
- 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 }
16627
16932
  );
16628
16933
  }
16629
- this.signal = options.signal;
16630
- this.reasoning = options.reasoning;
16631
- this.caching = options.caching;
16632
- this.retryConfig = options.sharedRetryConfig ?? resolveRetryConfig(options.retryConfig);
16633
- if (options.sharedRateLimitTracker) {
16634
- this.rateLimitTracker = options.sharedRateLimitTracker;
16635
- } else {
16636
- const rateLimitConfig = resolveRateLimitConfig(options.rateLimitConfig);
16637
- if (rateLimitConfig.enabled) {
16638
- 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 {
16639
16942
  }
16943
+ this.sdkClient = null;
16640
16944
  }
16641
- const treeConfig = options.treeConfig;
16642
- this.tree = treeConfig?.tree ?? new ExecutionTree();
16643
- this.parentNodeId = treeConfig?.parentNodeId ?? null;
16644
- this.streamProcessorFactory = new StreamProcessorFactory({
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
+ });
16978
+ });
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({
16645
17715
  registry: this.registry,
16646
17716
  prefixConfig: this.prefixConfig,
16647
17717
  hooks: this.hooks,
@@ -16886,6 +17956,18 @@ var init_agent = __esm({
16886
17956
  );
16887
17957
  }
16888
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
+ }
16889
17971
  let currentIteration = 0;
16890
17972
  this.logger.info("Starting agent loop", {
16891
17973
  model: this.model,
@@ -17085,6 +18167,14 @@ var init_agent = __esm({
17085
18167
  }
17086
18168
  }
17087
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
+ }
17088
18178
  }
17089
18179
  }
17090
18180
  /**
@@ -17284,6 +18374,7 @@ __export(index_exports, {
17284
18374
  ConversationManager: () => ConversationManager,
17285
18375
  DEFAULT_COMPACTION_CONFIG: () => DEFAULT_COMPACTION_CONFIG,
17286
18376
  DEFAULT_HINTS: () => DEFAULT_HINTS,
18377
+ DEFAULT_MCP_COMMAND_ALLOWLIST: () => DEFAULT_MCP_COMMAND_ALLOWLIST,
17287
18378
  DEFAULT_PROMPTS: () => DEFAULT_PROMPTS,
17288
18379
  DEFAULT_RATE_LIMIT_CONFIG: () => DEFAULT_RATE_LIMIT_CONFIG,
17289
18380
  DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
@@ -17303,10 +18394,17 @@ __export(index_exports, {
17303
18394
  HuggingFaceProvider: () => HuggingFaceProvider,
17304
18395
  HumanInputRequiredException: () => HumanInputRequiredException,
17305
18396
  HybridStrategy: () => HybridStrategy,
18397
+ JsonSchemaConversionError: () => JsonSchemaConversionError,
17306
18398
  LLMMessageBuilder: () => LLMMessageBuilder,
17307
18399
  LLMist: () => LLMist,
17308
18400
  LOAD_SKILL_GADGET_NAME: () => LOAD_SKILL_GADGET_NAME,
17309
18401
  MODEL_ALIASES: () => MODEL_ALIASES,
18402
+ McpClient: () => McpClient,
18403
+ McpConnectError: () => McpConnectError,
18404
+ McpError: () => McpError,
18405
+ McpLifecycle: () => McpLifecycle,
18406
+ McpToolCallError: () => McpToolCallError,
18407
+ McpUntrustedCommandError: () => McpUntrustedCommandError,
17310
18408
  MediaStore: () => MediaStore,
17311
18409
  ModelIdentifierParser: () => ModelIdentifierParser,
17312
18410
  ModelRegistry: () => ModelRegistry,
@@ -17322,6 +18420,7 @@ __export(index_exports, {
17322
18420
  SummarizationStrategy: () => SummarizationStrategy,
17323
18421
  TaskCompletionSignal: () => TaskCompletionSignal,
17324
18422
  TimeoutException: () => TimeoutException,
18423
+ assertCommandAllowed: () => assertCommandAllowed,
17325
18424
  audioFromBase64: () => audioFromBase64,
17326
18425
  audioFromBuffer: () => audioFromBuffer,
17327
18426
  collectEvents: () => collectEvents,
@@ -17336,6 +18435,7 @@ __export(index_exports, {
17336
18435
  createHuggingFaceProviderFromEnv: () => createHuggingFaceProviderFromEnv,
17337
18436
  createLoadSkillGadget: () => createLoadSkillGadget,
17338
18437
  createLogger: () => createLogger,
18438
+ createMcpServer: () => createMcpServer,
17339
18439
  createMediaOutput: () => createMediaOutput,
17340
18440
  createOpenAIProviderFromEnv: () => createOpenAIProviderFromEnv,
17341
18441
  createOpenRouterProviderFromEnv: () => createOpenRouterProviderFromEnv,
@@ -17358,7 +18458,9 @@ __export(index_exports, {
17358
18458
  formatLLMError: () => formatLLMError,
17359
18459
  formatLlmRequest: () => formatLlmRequest,
17360
18460
  gadgetError: () => gadgetError,
18461
+ gadgetResultToMcpContent: () => gadgetResultToMcpContent,
17361
18462
  gadgetSuccess: () => gadgetSuccess,
18463
+ gadgetToMcpTool: () => gadgetToMcpTool,
17362
18464
  getErrorMessage: () => getErrorMessage,
17363
18465
  getHostExports: () => getHostExports2,
17364
18466
  getModelId: () => getModelId,
@@ -17386,9 +18488,11 @@ __export(index_exports, {
17386
18488
  isSubagentEvent: () => isSubagentEvent,
17387
18489
  isTextPart: () => isTextPart,
17388
18490
  iterationProgressHint: () => iterationProgressHint,
18491
+ jsonSchemaToZod: () => jsonSchemaToZod,
17389
18492
  listPresets: () => listPresets,
17390
18493
  listSubagents: () => listSubagents,
17391
18494
  loadSkillsFromDirectory: () => loadSkillsFromDirectory,
18495
+ mcpToolToGadget: () => mcpToolToGadget,
17392
18496
  normalizeMessageContent: () => normalizeMessageContent,
17393
18497
  parallelGadgetHint: () => parallelGadgetHint,
17394
18498
  parseDataUrl: () => parseDataUrl,
@@ -17399,6 +18503,7 @@ __export(index_exports, {
17399
18503
  parseSkillContent: () => parseSkillContent,
17400
18504
  parseSkillFile: () => parseSkillFile,
17401
18505
  randomDelay: () => randomDelay,
18506
+ renderSkillForMcpPrompt: () => renderSkillForMcpPrompt,
17402
18507
  resetFileLoggingState: () => resetFileLoggingState,
17403
18508
  resolveConfig: () => resolveConfig,
17404
18509
  resolveHintTemplate: () => resolveHintTemplate,
@@ -17416,9 +18521,11 @@ __export(index_exports, {
17416
18521
  resultWithImage: () => resultWithImage,
17417
18522
  resultWithImages: () => resultWithImages,
17418
18523
  resultWithMedia: () => resultWithMedia,
18524
+ runGadgetForMcp: () => runGadgetForMcp,
17419
18525
  runWithHandlers: () => runWithHandlers,
17420
18526
  scanResources: () => scanResources,
17421
18527
  schemaToJSONSchema: () => schemaToJSONSchema,
18528
+ skillToMcpPrompt: () => skillToMcpPrompt,
17422
18529
  stream: () => stream,
17423
18530
  stripProviderPrefix: () => stripProviderPrefix,
17424
18531
  substituteArguments: () => substituteArguments,
@@ -17434,10 +18541,10 @@ __export(index_exports, {
17434
18541
  withErrorHandling: () => withErrorHandling,
17435
18542
  withRetry: () => withRetry,
17436
18543
  withTimeout: () => withTimeout,
17437
- z: () => import_zod4.z
18544
+ z: () => import_zod6.z
17438
18545
  });
17439
18546
  module.exports = __toCommonJS(index_exports);
17440
- var import_zod4 = require("zod");
18547
+ var import_zod6 = require("zod");
17441
18548
  init_agent();
17442
18549
  init_builder();
17443
18550
  init_event_handlers();
@@ -17620,140 +18727,254 @@ init_create_gadget();
17620
18727
  init_exceptions();
17621
18728
  init_executor();
17622
18729
  init_gadget();
18730
+ init_helpers();
18731
+ init_output_viewer();
18732
+ init_parser2();
18733
+ init_registry();
18734
+ init_typed_gadget();
17623
18735
 
17624
- // src/gadgets/helpers.ts
17625
- init_input_content();
17626
- function gadgetSuccess(data = {}) {
17627
- return JSON.stringify({ success: true, ...data });
17628
- }
17629
- function gadgetError(message, details) {
17630
- return JSON.stringify({ error: message, ...details });
17631
- }
17632
- function getErrorMessage(error) {
17633
- return error instanceof Error ? error.message : String(error);
17634
- }
17635
- function withErrorHandling(execute) {
17636
- return async (params, ctx) => {
17637
- try {
17638
- return await execute(params, ctx);
17639
- } catch (error) {
17640
- return gadgetError(getErrorMessage(error));
17641
- }
17642
- };
17643
- }
17644
- function createMediaOutput(kind, data, mimeType, options) {
17645
- 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("; ")}`;
17646
18758
  return {
17647
- kind,
17648
- data: buffer.toString("base64"),
17649
- mimeType,
17650
- description: options?.description,
17651
- metadata: options?.metadata,
17652
- fileName: options?.fileName
18759
+ success: false,
18760
+ error: formattedError,
18761
+ issues
17653
18762
  };
17654
18763
  }
17655
- function resultWithMedia(result, media, cost) {
17656
- if (media.length === 0) {
17657
- 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
+ };
17658
18770
  }
17659
- return {
17660
- result,
17661
- media,
17662
- cost
17663
- };
18771
+ return validateAndApplyDefaults(gadget.parameterSchema, params);
17664
18772
  }
17665
- function resultWithImage(result, imageData, options) {
17666
- const buffer = imageData instanceof Buffer ? imageData : Buffer.from(imageData);
17667
- const mimeType = options?.mimeType ?? detectImageMimeType(buffer);
17668
- if (!mimeType) {
17669
- throw new Error(
17670
- "Could not detect image MIME type. Please provide mimeType explicitly in options."
17671
- );
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: {} };
17672
18782
  }
17673
18783
  return {
17674
- result,
17675
- media: [
17676
- {
17677
- kind: "image",
17678
- data: buffer.toString("base64"),
17679
- mimeType,
17680
- description: options?.description,
17681
- metadata: options?.metadata,
17682
- fileName: options?.fileName
17683
- }
17684
- ],
17685
- cost: options?.cost
18784
+ name: gadget.name ?? "unnamed-gadget",
18785
+ description,
18786
+ inputSchema
17686
18787
  };
17687
18788
  }
17688
- function resultWithImages(result, images, cost) {
17689
- if (images.length === 0) {
17690
- 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;
17691
18813
  }
17692
- const media = images.map((img, index) => {
17693
- const buffer = img.data instanceof Buffer ? img.data : Buffer.from(img.data);
17694
- const mimeType = img.mimeType ?? detectImageMimeType(buffer);
17695
- if (!mimeType) {
17696
- throw new Error(
17697
- `Could not detect MIME type for image at index ${index}. Please provide mimeType explicitly.`
17698
- );
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
+ };
17699
18832
  }
18833
+ rawParams = validation.data;
18834
+ }
18835
+ try {
18836
+ const result = await gadget.execute(rawParams);
17700
18837
  return {
17701
- kind: "image",
17702
- data: buffer.toString("base64"),
17703
- mimeType,
17704
- description: img.description,
17705
- metadata: img.metadata,
17706
- fileName: img.fileName
18838
+ content: gadgetResultToMcpContent(result)
17707
18839
  };
17708
- });
17709
- 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
+ }
17710
18851
  }
17711
- function resultWithAudio(result, audioData, options) {
17712
- const buffer = audioData instanceof Buffer ? audioData : Buffer.from(audioData);
17713
- const mimeType = options?.mimeType ?? detectAudioMimeType(buffer);
17714
- if (!mimeType) {
17715
- throw new Error(
17716
- "Could not detect audio MIME type. Please provide mimeType explicitly in options."
17717
- );
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
+ });
17718
18867
  }
17719
- const metadata = options?.durationMs ? { durationMs: options.durationMs } : void 0;
17720
18868
  return {
17721
- result,
17722
- 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: [
17723
18882
  {
17724
- kind: "audio",
17725
- data: buffer.toString("base64"),
17726
- mimeType,
17727
- description: options?.description,
17728
- metadata,
17729
- fileName: options?.fileName
18883
+ role: "user",
18884
+ content: { type: "text", text: activation.resolvedInstructions }
17730
18885
  }
17731
- ],
17732
- cost: options?.cost
18886
+ ]
17733
18887
  };
17734
18888
  }
17735
- function resultWithFile(result, fileData, mimeType, options) {
17736
- 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
+ }
17737
18953
  return {
17738
- result,
17739
- media: [
17740
- {
17741
- kind: "file",
17742
- data: buffer.toString("base64"),
17743
- mimeType,
17744
- description: options?.description,
17745
- 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 {
17746
18967
  }
17747
- ],
17748
- cost: options?.cost
18968
+ sdkServer = null;
18969
+ running = false;
18970
+ }
17749
18971
  };
17750
18972
  }
17751
18973
 
18974
+ // src/mcp/index.ts
18975
+ init_tool_adapter();
18976
+
17752
18977
  // src/index.ts
17753
- init_output_viewer();
17754
- init_parser2();
17755
- init_registry();
17756
- init_typed_gadget();
17757
18978
  init_constants2();
17758
18979
 
17759
18980
  // src/utils/config-resolver.ts
@@ -17862,38 +19083,6 @@ function hasHostExports(ctx) {
17862
19083
  init_media_store();
17863
19084
  init_schema_to_json();
17864
19085
  init_schema_validator();
17865
-
17866
- // src/gadgets/validation.ts
17867
- function validateAndApplyDefaults(schema, params) {
17868
- const result = schema.safeParse(params);
17869
- if (result.success) {
17870
- return {
17871
- success: true,
17872
- data: result.data
17873
- };
17874
- }
17875
- const issues = result.error.issues.map((issue) => ({
17876
- path: issue.path.join(".") || "root",
17877
- message: issue.message
17878
- }));
17879
- const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
17880
- return {
17881
- success: false,
17882
- error: formattedError,
17883
- issues
17884
- };
17885
- }
17886
- function validateGadgetParams(gadget, params) {
17887
- if (!gadget.parameterSchema) {
17888
- return {
17889
- success: true,
17890
- data: params
17891
- };
17892
- }
17893
- return validateAndApplyDefaults(gadget.parameterSchema, params);
17894
- }
17895
-
17896
- // src/index.ts
17897
19086
  init_logger();
17898
19087
 
17899
19088
  // src/package/manifest.ts
@@ -18192,6 +19381,7 @@ function getHostExports2(ctx) {
18192
19381
  ConversationManager,
18193
19382
  DEFAULT_COMPACTION_CONFIG,
18194
19383
  DEFAULT_HINTS,
19384
+ DEFAULT_MCP_COMMAND_ALLOWLIST,
18195
19385
  DEFAULT_PROMPTS,
18196
19386
  DEFAULT_RATE_LIMIT_CONFIG,
18197
19387
  DEFAULT_RETRY_CONFIG,
@@ -18211,10 +19401,17 @@ function getHostExports2(ctx) {
18211
19401
  HuggingFaceProvider,
18212
19402
  HumanInputRequiredException,
18213
19403
  HybridStrategy,
19404
+ JsonSchemaConversionError,
18214
19405
  LLMMessageBuilder,
18215
19406
  LLMist,
18216
19407
  LOAD_SKILL_GADGET_NAME,
18217
19408
  MODEL_ALIASES,
19409
+ McpClient,
19410
+ McpConnectError,
19411
+ McpError,
19412
+ McpLifecycle,
19413
+ McpToolCallError,
19414
+ McpUntrustedCommandError,
18218
19415
  MediaStore,
18219
19416
  ModelIdentifierParser,
18220
19417
  ModelRegistry,
@@ -18230,6 +19427,7 @@ function getHostExports2(ctx) {
18230
19427
  SummarizationStrategy,
18231
19428
  TaskCompletionSignal,
18232
19429
  TimeoutException,
19430
+ assertCommandAllowed,
18233
19431
  audioFromBase64,
18234
19432
  audioFromBuffer,
18235
19433
  collectEvents,
@@ -18244,6 +19442,7 @@ function getHostExports2(ctx) {
18244
19442
  createHuggingFaceProviderFromEnv,
18245
19443
  createLoadSkillGadget,
18246
19444
  createLogger,
19445
+ createMcpServer,
18247
19446
  createMediaOutput,
18248
19447
  createOpenAIProviderFromEnv,
18249
19448
  createOpenRouterProviderFromEnv,
@@ -18266,7 +19465,9 @@ function getHostExports2(ctx) {
18266
19465
  formatLLMError,
18267
19466
  formatLlmRequest,
18268
19467
  gadgetError,
19468
+ gadgetResultToMcpContent,
18269
19469
  gadgetSuccess,
19470
+ gadgetToMcpTool,
18270
19471
  getErrorMessage,
18271
19472
  getHostExports,
18272
19473
  getModelId,
@@ -18294,9 +19495,11 @@ function getHostExports2(ctx) {
18294
19495
  isSubagentEvent,
18295
19496
  isTextPart,
18296
19497
  iterationProgressHint,
19498
+ jsonSchemaToZod,
18297
19499
  listPresets,
18298
19500
  listSubagents,
18299
19501
  loadSkillsFromDirectory,
19502
+ mcpToolToGadget,
18300
19503
  normalizeMessageContent,
18301
19504
  parallelGadgetHint,
18302
19505
  parseDataUrl,
@@ -18307,6 +19510,7 @@ function getHostExports2(ctx) {
18307
19510
  parseSkillContent,
18308
19511
  parseSkillFile,
18309
19512
  randomDelay,
19513
+ renderSkillForMcpPrompt,
18310
19514
  resetFileLoggingState,
18311
19515
  resolveConfig,
18312
19516
  resolveHintTemplate,
@@ -18324,9 +19528,11 @@ function getHostExports2(ctx) {
18324
19528
  resultWithImage,
18325
19529
  resultWithImages,
18326
19530
  resultWithMedia,
19531
+ runGadgetForMcp,
18327
19532
  runWithHandlers,
18328
19533
  scanResources,
18329
19534
  schemaToJSONSchema,
19535
+ skillToMcpPrompt,
18330
19536
  stream,
18331
19537
  stripProviderPrefix,
18332
19538
  substituteArguments,