@frontmcp/plugin-codecall 1.8.2 → 1.8.4

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/esm/index.mjs CHANGED
@@ -374,6 +374,46 @@ function presetDefaults(preset) {
374
374
 
375
375
  // plugins/plugin-codecall/src/services/audit-logger.service.ts
376
376
  import { Provider as Provider2, ProviderScope as ProviderScope2 } from "@frontmcp/sdk";
377
+
378
+ // plugins/plugin-codecall/src/utils/client-error-message.ts
379
+ var EMBEDDED_STACK_FRAME_RE = /\n[ \t]*at\s[^\n]*/g;
380
+ var QUOTED_PATH_RE = /(['"`])((?:~?\/|[A-Za-z]:[\\/]|\\\\)[^\s'"`\\/][^'"`\n]*)\1/g;
381
+ var URL_RE = /\b[A-Za-z][A-Za-z0-9+.-]{1,31}:\/\/(?:[^\s'"`<>,;]|[,;](?!~?\/|[A-Za-z]:[\\/]|\\\\))*/g;
382
+ var QUERY_PATH_VALUE_RE = /([?&][^=&#]*=)(?:~?\/|[A-Za-z]:[\\/]|\\\\)[^&#]*/g;
383
+ var POSIX_SEGMENT = String.raw`[^\s/\\'"\x60<>|,;:()\[\]{}]+`;
384
+ var WINDOWS_SEGMENT = String.raw`[^\s/\\'"\x60<>|,;:*?]+`;
385
+ function segments(segment, separator) {
386
+ const spaced = `${segment}(?: ${segment}){0,2}`;
387
+ return `(?:(?:${spaced}${separator})+(?:${segment})?|${segment})`;
388
+ }
389
+ var UNQUOTED_PATH_RE = new RegExp(
390
+ [
391
+ String.raw`(?<![\w.~/\\)\]-])~?/+${segments(POSIX_SEGMENT, "/+")}`,
392
+ String.raw`(?<![\w\\/])[A-Za-z]:[\\/]+${segments(WINDOWS_SEGMENT, String.raw`[\\/]+`)}?`,
393
+ String.raw`(?<![\w\\])\\\\${segments(WINDOWS_SEGMENT, String.raw`[\\/]+`)}`
394
+ ].join("|"),
395
+ "g"
396
+ );
397
+ function redactPaths(text) {
398
+ return text.replace(QUOTED_PATH_RE, "$1[path]$1").replace(UNQUOTED_PATH_RE, "[path]");
399
+ }
400
+ function toClientErrorMessage(message) {
401
+ if (!message) return "";
402
+ return redactAbsolutePaths(message.replace(EMBEDDED_STACK_FRAME_RE, ""));
403
+ }
404
+ function redactAbsolutePaths(text) {
405
+ let result = "";
406
+ let last = 0;
407
+ for (const match of text.matchAll(URL_RE)) {
408
+ const start = match.index ?? 0;
409
+ result += redactPaths(text.slice(last, start));
410
+ result += /^file:/i.test(match[0]) ? "[path]" : match[0].replace(QUERY_PATH_VALUE_RE, "$1[path]");
411
+ last = start + match[0].length;
412
+ }
413
+ return result + redactPaths(text.slice(last));
414
+ }
415
+
416
+ // plugins/plugin-codecall/src/services/audit-logger.service.ts
377
417
  var AUDIT_EVENT_TYPES = {
378
418
  /** Script execution started */
379
419
  EXECUTION_START: "codecall:execution:start",
@@ -650,9 +690,8 @@ var AuditLoggerService = class {
650
690
  */
651
691
  sanitizeError(error) {
652
692
  if (!error) return "Unknown error";
653
- let sanitized = error.replace(/(?:\/[\w.-]+)+|(?:[A-Za-z]:\\[\w\\.-]+)+/g, "[path]");
693
+ let sanitized = toClientErrorMessage(error);
654
694
  sanitized = sanitized.replace(/:\d+:\d+/g, "");
655
- sanitized = sanitized.replace(/\n\s*at .*/g, "");
656
695
  if (sanitized.length > 200) {
657
696
  sanitized = sanitized.substring(0, 200) + "...";
658
697
  }
@@ -671,6 +710,10 @@ import { TFIDFVectoria, VectoriaDB } from "vectoriadb";
671
710
 
672
711
  // plugins/plugin-codecall/src/security/codecall-tool-policy.ts
673
712
  var BLOCKED_NAMESPACE_PATTERNS = Object.freeze([/^system:/, /^internal:/, /^__/]);
713
+ function isOfferedToCaller(availableWhen, surface) {
714
+ const offered = availableWhen?.surface;
715
+ return offered === void 0 || surface === void 0 || offered.includes(surface);
716
+ }
674
717
  var ALLOWED = { allowed: true };
675
718
  function deny(reason) {
676
719
  return { allowed: false, reason };
@@ -691,16 +734,46 @@ function toCodeCallPolicyTool(entry, requestedName, scope) {
691
734
  tags: metadata?.tags,
692
735
  // `internal` tools refuse every external tools/call, CodeCall's included, so no surface may offer them.
693
736
  hidden: metadata?.hideFromDiscovery === true || metadata?.visibility === "hidden" || metadata?.visibility === "internal",
694
- codecall: metadata?.codecall
737
+ codecall: metadata?.codecall,
738
+ annotations: metadata?.annotations,
739
+ metadata
695
740
  };
696
741
  }
742
+ function isPlainData(value) {
743
+ if (Array.isArray(value)) return true;
744
+ const proto = Object.getPrototypeOf(value);
745
+ return proto === Object.prototype || proto === null;
746
+ }
747
+ function readOnlyCopy(value, copies) {
748
+ if (typeof value !== "object" || value === null || !isPlainData(value)) return value;
749
+ const existing = copies.get(value);
750
+ if (existing !== void 0) return existing;
751
+ const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
752
+ copies.set(value, copy);
753
+ for (const key of Reflect.ownKeys(value)) {
754
+ if (!Object.prototype.propertyIsEnumerable.call(value, key)) continue;
755
+ Object.defineProperty(copy, key, {
756
+ value: readOnlyCopy(Reflect.get(value, key), copies),
757
+ enumerable: true,
758
+ writable: true,
759
+ configurable: true
760
+ });
761
+ }
762
+ return Object.freeze(copy);
763
+ }
697
764
  function toToolFilterInfo(tool) {
765
+ const copies = /* @__PURE__ */ new WeakMap();
766
+ const annotations = readOnlyCopy(tool.annotations, copies);
767
+ const metadata = tool.metadata ? readOnlyCopy({ ...tool.metadata, ...annotations ? { annotations: tool.annotations } : {} }, copies) : void 0;
698
768
  return {
699
769
  name: tool.name,
770
+ fullName: tool.fullName,
700
771
  appId: tool.appId,
701
772
  source: tool.codecall?.source,
702
773
  description: tool.description,
703
- tags: tool.codecall?.tags ?? tool.tags
774
+ tags: readOnlyCopy(tool.codecall?.tags ?? tool.tags, copies),
775
+ annotations,
776
+ metadata
704
777
  };
705
778
  }
706
779
  function ownNamesOf(tool) {
@@ -787,6 +860,9 @@ function readCodeCallPolicyConfig(config) {
787
860
  function checkCodeCallToolAccess(scope, config, name, options = {}) {
788
861
  const entry = resolveCodeCallTool(scope, name);
789
862
  if (!entry) return denyUnknownTool(name);
863
+ if (!isOfferedToCaller(entry.metadata?.availableWhen, options.surface)) {
864
+ return deny(`Tool "${name}" is not offered on the caller's surface "${options.surface}"`);
865
+ }
790
866
  const policyTool = toCodeCallPolicyTool(entry, name, scope);
791
867
  const baseDecision = checkCodeCallToolPolicy(policyTool, readCodeCallPolicyConfig(config));
792
868
  if (!baseDecision.allowed) return baseDecision;
@@ -1320,6 +1396,8 @@ var ToolSearchService = class _ToolSearchService {
1320
1396
  subscriptionReject = null;
1321
1397
  retryTimeoutId = null;
1322
1398
  disposed = false;
1399
+ /** `availableWhen` of each indexed tool, by indexed name, for per-caller surface filtering. */
1400
+ availabilityByTool = /* @__PURE__ */ new Map();
1323
1401
  constructor(config = {}, scope) {
1324
1402
  this.scope = scope;
1325
1403
  const embeddingOptions = config.embeddingOptions || {
@@ -1429,6 +1507,7 @@ var ToolSearchService = class _ToolSearchService {
1429
1507
  */
1430
1508
  async handleToolChange(tools) {
1431
1509
  this.vectorDB.clear();
1510
+ this.availabilityByTool = /* @__PURE__ */ new Map();
1432
1511
  if (tools.length === 0) {
1433
1512
  this.initialized = true;
1434
1513
  return;
@@ -1443,6 +1522,7 @@ var ToolSearchService = class _ToolSearchService {
1443
1522
  return;
1444
1523
  }
1445
1524
  const documents = filteredTools.map((tool) => {
1525
+ this.availabilityByTool.set(tool.name, tool.metadata.availableWhen);
1446
1526
  const searchableText = this.extractSearchableText(tool);
1447
1527
  const appId = this.extractAppId(tool);
1448
1528
  const toolName = tool.name;
@@ -1571,12 +1651,15 @@ var ToolSearchService = class _ToolSearchService {
1571
1651
  */
1572
1652
  async search(query, options = {}) {
1573
1653
  await this.ensureSubscribed();
1574
- const { topK = this.config.defaultTopK, appIds, excludeToolNames = [] } = options;
1654
+ const { topK = this.config.defaultTopK, appIds, excludeToolNames = [], surface } = options;
1575
1655
  const minScore = this.config.defaultSimilarityThreshold;
1576
1656
  const filter = (metadata) => {
1577
1657
  if (excludeToolNames.includes(metadata.toolName)) {
1578
1658
  return false;
1579
1659
  }
1660
+ if (!isOfferedToCaller(metadata.toolInstance.metadata.availableWhen, surface)) {
1661
+ return false;
1662
+ }
1580
1663
  if (appIds && appIds.length > 0) {
1581
1664
  if (!metadata.appId || !appIds.includes(metadata.appId)) {
1582
1665
  return false;
@@ -1608,9 +1691,12 @@ var ToolSearchService = class _ToolSearchService {
1608
1691
  }
1609
1692
  }
1610
1693
  /**
1611
- * Gets the total number of indexed tools
1694
+ * Gets the total number of indexed tools, or of those a caller on `surface` may reach
1612
1695
  */
1613
- getTotalCount() {
1696
+ getTotalCount(surface) {
1697
+ if (surface !== void 0) {
1698
+ return this.getAllToolNames().filter((toolName) => this.hasTool(toolName, surface)).length;
1699
+ }
1614
1700
  if (this.vectorDB instanceof VectoriaDB) {
1615
1701
  return this.vectorDB.size();
1616
1702
  } else {
@@ -1618,20 +1704,18 @@ var ToolSearchService = class _ToolSearchService {
1618
1704
  }
1619
1705
  }
1620
1706
  /**
1621
- * Checks if a tool exists in the index
1707
+ * Checks if a tool exists in the index, for a caller on `surface` when given
1622
1708
  */
1623
- hasTool(toolName) {
1624
- if (this.vectorDB instanceof VectoriaDB) {
1625
- return this.vectorDB.has(toolName);
1626
- } else {
1627
- return this.vectorDB.hasDocument(toolName);
1628
- }
1709
+ hasTool(toolName, surface) {
1710
+ const indexed = this.vectorDB instanceof VectoriaDB ? this.vectorDB.has(toolName) : this.vectorDB.hasDocument(toolName);
1711
+ return indexed && isOfferedToCaller(this.availabilityByTool.get(toolName), surface);
1629
1712
  }
1630
1713
  /**
1631
1714
  * Clears the entire index
1632
1715
  */
1633
1716
  clear() {
1634
1717
  this.vectorDB.clear();
1718
+ this.availabilityByTool = /* @__PURE__ */ new Map();
1635
1719
  this.initialized = false;
1636
1720
  }
1637
1721
  /**
@@ -1649,8 +1733,163 @@ var ToolSearchService = class _ToolSearchService {
1649
1733
  };
1650
1734
 
1651
1735
  // plugins/plugin-codecall/src/services/enclave.service.ts
1652
- import { Enclave } from "@enclave-vm/core";
1736
+ import {
1737
+ Enclave
1738
+ } from "@enclave-vm/core";
1653
1739
  import { Provider as Provider3, ProviderScope as ProviderScope3 } from "@frontmcp/sdk";
1740
+
1741
+ // plugins/plugin-codecall/src/utils/build-tool-namespaces.ts
1742
+ var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1743
+ var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1744
+ var RESERVED_NAMESPACES = /* @__PURE__ */ new Set([
1745
+ "console",
1746
+ "Math",
1747
+ "JSON",
1748
+ "Object",
1749
+ "Array",
1750
+ "String",
1751
+ "Number",
1752
+ "Boolean",
1753
+ "Promise",
1754
+ "Symbol",
1755
+ "Map",
1756
+ "Set",
1757
+ "WeakMap",
1758
+ "WeakSet",
1759
+ "Date",
1760
+ "Error",
1761
+ "RegExp",
1762
+ "globalThis",
1763
+ "global",
1764
+ "window",
1765
+ "self",
1766
+ "undefined",
1767
+ "null",
1768
+ "true",
1769
+ "false",
1770
+ "callTool",
1771
+ "getTool",
1772
+ "mcpLog",
1773
+ "mcpNotify"
1774
+ ]);
1775
+ var UNBINDABLE_NAMESPACES = new Set(
1776
+ [
1777
+ // Reserved words and strict-mode restricted bindings
1778
+ "break case catch class const continue debugger default delete do else enum export extends finally for",
1779
+ "function if import in instanceof new return super switch this throw try typeof var void while with yield",
1780
+ "let static implements interface package private protected public await eval arguments NaN Infinity",
1781
+ // AgentScript globals
1782
+ "parallel isNaN isFinite parseInt parseFloat encodeURI encodeURIComponent decodeURI decodeURIComponent",
1783
+ // Identifiers the AgentScript validator refuses
1784
+ "process require module exports Buffer Function AsyncFunction GeneratorFunction Reflect Proxy TypeError",
1785
+ "ReferenceError SyntaxError RangeError URIError EvalError AggregateError fetch XMLHttpRequest WebSocket",
1786
+ "localStorage sessionStorage indexedDB crypto performance structuredClone AbortController AbortSignal",
1787
+ "MessageChannel MessagePort BroadcastChannel TextEncoder TextDecoder Intl setTimeout setInterval",
1788
+ "setImmediate clearTimeout clearInterval clearImmediate queueMicrotask WebAssembly Worker SharedWorker",
1789
+ "ServiceWorker WeakRef FinalizationRegistry BigInt Atomics SharedArrayBuffer ArrayBuffer DataView",
1790
+ "importScripts ShadowRealm Iterator AsyncIterator"
1791
+ ].join(" ").split(" ")
1792
+ );
1793
+ var UNCALLABLE_METHODS = new Set(
1794
+ [
1795
+ "process require module exports Buffer eval Function AsyncFunction GeneratorFunction arguments RegExp",
1796
+ "Promise Symbol Proxy Reflect Error TypeError ReferenceError SyntaxError RangeError URIError EvalError",
1797
+ "AggregateError fetch XMLHttpRequest WebSocket localStorage sessionStorage indexedDB crypto performance",
1798
+ "structuredClone AbortController AbortSignal MessageChannel MessagePort BroadcastChannel TextEncoder",
1799
+ "TextDecoder Intl setTimeout setInterval setImmediate clearTimeout clearInterval clearImmediate",
1800
+ "queueMicrotask WebAssembly Worker SharedWorker ServiceWorker WeakMap WeakSet WeakRef FinalizationRegistry",
1801
+ "Map Set Atomics SharedArrayBuffer importScripts ShadowRealm Iterator AsyncIterator"
1802
+ ].join(" ").split(" ")
1803
+ );
1804
+ var SANDBOX_TOOL_NAME_RE = /^[a-zA-Z][a-zA-Z0-9:._-]*$/;
1805
+ var MAX_SANDBOX_TOOL_NAME_LENGTH = 256;
1806
+ function isBindableNamespace(ns, reservedGlobals) {
1807
+ return IDENT_RE.test(ns) && !PROTOTYPE_KEYS.has(ns) && !ns.startsWith("__") && !RESERVED_NAMESPACES.has(ns) && !UNBINDABLE_NAMESPACES.has(ns) && !reservedGlobals.has(ns);
1808
+ }
1809
+ function isBindableMethod(ns, method, toolName) {
1810
+ return IDENT_RE.test(method) && !PROTOTYPE_KEYS.has(method) && !method.startsWith("__") && !UNCALLABLE_METHODS.has(method) && toolName === `${ns}.${method}` && toolName.length <= MAX_SANDBOX_TOOL_NAME_LENGTH && SANDBOX_TOOL_NAME_RE.test(toolName);
1811
+ }
1812
+ function buildToolNamespaces(tools) {
1813
+ const namespaces = /* @__PURE__ */ Object.create(null);
1814
+ const skipped = [];
1815
+ for (const tool of tools) {
1816
+ const name = tool?.name;
1817
+ if (typeof name !== "string" || name.length === 0) continue;
1818
+ const dot = name.indexOf(".");
1819
+ if (dot <= 0 || dot === name.length - 1) {
1820
+ skipped.push({ name, reason: "no-namespace-prefix" });
1821
+ continue;
1822
+ }
1823
+ const ns = name.slice(0, dot);
1824
+ const method = name.slice(dot + 1);
1825
+ if (!IDENT_RE.test(ns) || !IDENT_RE.test(method)) {
1826
+ skipped.push({ name, reason: "invalid-identifier" });
1827
+ continue;
1828
+ }
1829
+ if (PROTOTYPE_KEYS.has(ns) || PROTOTYPE_KEYS.has(method)) {
1830
+ skipped.push({ name, reason: "prototype-key" });
1831
+ continue;
1832
+ }
1833
+ if (RESERVED_NAMESPACES.has(ns) || UNBINDABLE_NAMESPACES.has(ns) || ns.startsWith("__")) {
1834
+ skipped.push({ name, reason: "reserved-namespace" });
1835
+ continue;
1836
+ }
1837
+ if (!isBindableMethod(ns, method, name)) {
1838
+ skipped.push({ name, reason: "unsupported-name" });
1839
+ continue;
1840
+ }
1841
+ const bucket = namespaces[ns] ?? (namespaces[ns] = /* @__PURE__ */ Object.create(null));
1842
+ if (Object.prototype.hasOwnProperty.call(bucket, method)) {
1843
+ skipped.push({ name, reason: "duplicate-method" });
1844
+ continue;
1845
+ }
1846
+ bucket[method] = name;
1847
+ }
1848
+ return { namespaces, skipped };
1849
+ }
1850
+ function toSandboxToolNamespaces(namespaces, reservedGlobals = []) {
1851
+ if (!namespaces) return void 0;
1852
+ const reserved = new Set(reservedGlobals);
1853
+ const bindable = /* @__PURE__ */ Object.create(null);
1854
+ let count = 0;
1855
+ for (const ns of Object.keys(namespaces)) {
1856
+ if (!isBindableNamespace(ns, reserved)) continue;
1857
+ const methods = namespaces[ns];
1858
+ const bucket = /* @__PURE__ */ Object.create(null);
1859
+ let methodCount = 0;
1860
+ for (const method of Object.keys(methods)) {
1861
+ const toolName = methods[method];
1862
+ if (typeof toolName !== "string" || !isBindableMethod(ns, method, toolName)) continue;
1863
+ bucket[method] = toolName;
1864
+ methodCount += 1;
1865
+ }
1866
+ if (methodCount > 0) {
1867
+ bindable[ns] = bucket;
1868
+ count += 1;
1869
+ }
1870
+ }
1871
+ return count > 0 ? bindable : void 0;
1872
+ }
1873
+
1874
+ // plugins/plugin-codecall/src/services/enclave.service.ts
1875
+ function toolFailureMessage(error, toolName) {
1876
+ if (typeof error === "string" && error) return error;
1877
+ const message = error?.message;
1878
+ return typeof message === "string" && message ? message : `Tool call failed: ${toolName}`;
1879
+ }
1880
+ function toolFailureName(error) {
1881
+ const name = error && typeof error === "object" ? error.name : void 0;
1882
+ return typeof name === "string" && name ? name.slice(0, 128) : "ToolError";
1883
+ }
1884
+ var PARSE_FAILURE_PREFIX = "Failed to parse AgentScript code: ";
1885
+ var PARSE_POSITION_RE = /\((\d+):(\d+)\)$/;
1886
+ function parseFailureOf(message) {
1887
+ const match = PARSE_POSITION_RE.exec(message);
1888
+ const line = match ? Number(match[1]) - 1 : 0;
1889
+ if (!match || line < 1) return { message };
1890
+ const column = Number(match[2]);
1891
+ return { message: `${message.slice(0, match.index)}(${line}:${column})`, location: { line, column } };
1892
+ }
1654
1893
  var ScriptTooLargeError = class extends Error {
1655
1894
  code = "SCRIPT_TOO_LARGE";
1656
1895
  scriptLength;
@@ -1688,8 +1927,20 @@ var EnclaveService = class {
1688
1927
  throw new ScriptTooLargeError(code.length, maxLength);
1689
1928
  }
1690
1929
  }
1930
+ const toolFailures = [];
1691
1931
  const toolHandler = async (toolName, args) => {
1692
- return environment.callTool(toolName, args);
1932
+ try {
1933
+ return await environment.callTool(toolName, args);
1934
+ } catch (error) {
1935
+ const code2 = error?.code;
1936
+ toolFailures.push({
1937
+ toolName,
1938
+ name: toolFailureName(error),
1939
+ message: toolFailureMessage(error, toolName),
1940
+ ...typeof code2 === "string" ? { code: code2 } : {}
1941
+ });
1942
+ throw error;
1943
+ }
1693
1944
  };
1694
1945
  const sidecar = this.sidecarOptions.enabled ? {
1695
1946
  enabled: true,
@@ -1699,11 +1950,29 @@ var EnclaveService = class {
1699
1950
  maxResolvedSize: this.sidecarOptions.maxResolvedSize,
1700
1951
  allowComposites: this.sidecarOptions.allowComposites
1701
1952
  } : void 0;
1702
- const RESERVED_GLOBALS = /* @__PURE__ */ new Set(["getTool", "mcpLog", "mcpNotify"]);
1703
- const safeNamespaces = Object.fromEntries(
1704
- Object.entries(environment.namespaces ?? {}).filter(([key]) => !RESERVED_GLOBALS.has(key))
1705
- );
1706
- const enclave = new Enclave({
1953
+ const { mcpLog, mcpNotify } = environment;
1954
+ const globals = {
1955
+ // Provide getTool as a custom global
1956
+ getTool: environment.getTool,
1957
+ // Provide logging functions if available
1958
+ ...mcpLog ? {
1959
+ mcpLog: (level, message, metadata) => {
1960
+ mcpLog(level, message, metadata);
1961
+ logs.push(`[mcp:${level}] ${message}`);
1962
+ }
1963
+ } : {},
1964
+ ...mcpNotify ? {
1965
+ mcpNotify: (event, payload) => {
1966
+ mcpNotify(event, payload);
1967
+ logs.push(`[notify] ${event}`);
1968
+ }
1969
+ } : {}
1970
+ // Note: enclave-vm v2.0.0+ provides its own __safe_console internally with rate limiting
1971
+ // and output size limits. Passing console in globals causes "Cannot redefine property"
1972
+ // errors due to Double VM architecture. Console output from user scripts goes to stdout
1973
+ // via enclave's internal console, not to this logs array. Only mcpLog/mcpNotify are captured.
1974
+ };
1975
+ const options = {
1707
1976
  timeout: this.vmOptions.timeoutMs,
1708
1977
  maxToolCalls: this.vmOptions.maxSteps || 100,
1709
1978
  maxIterations: 1e4,
@@ -1713,38 +1982,20 @@ var EnclaveService = class {
1713
1982
  validate: true,
1714
1983
  transform: true,
1715
1984
  sidecar,
1985
+ // Error stacks name no host file, whatever stage raised them.
1986
+ sanitizeStackTraces: true,
1716
1987
  // Allow functions in globals since we intentionally provide getTool, mcpLog, mcpNotify, and console
1717
1988
  allowFunctionsInGlobals: true,
1718
- globals: {
1719
- // Tool namespaces (e.g. `acme.getUser`) flow through here. Spread
1720
- // FIRST so the runtime reserved globals below are guaranteed
1721
- // last-wins. Reserved keys have already been stripped above —
1722
- // belt-and-braces ordering makes the invariant locally obvious.
1723
- ...safeNamespaces,
1724
- // Provide getTool as a custom global
1725
- getTool: environment.getTool,
1726
- // Provide logging functions if available
1727
- ...environment.mcpLog ? {
1728
- mcpLog: (level, message, metadata) => {
1729
- environment.mcpLog(level, message, metadata);
1730
- logs.push(`[mcp:${level}] ${message}`);
1731
- }
1732
- } : {},
1733
- ...environment.mcpNotify ? {
1734
- mcpNotify: (event, payload) => {
1735
- environment.mcpNotify(event, payload);
1736
- logs.push(`[notify] ${event}`);
1737
- }
1738
- } : {}
1739
- // Note: enclave-vm v2.0.0+ provides its own __safe_console internally with rate limiting
1740
- // and output size limits. Passing console in globals causes "Cannot redefine property"
1741
- // errors due to Double VM architecture. Console output from user scripts goes to stdout
1742
- // via enclave's internal console, not to this logs array. Only mcpLog/mcpNotify are captured.
1743
- }
1744
- });
1989
+ globals,
1990
+ // Tool namespaces (`acme.getUser()` for a tool named `acme.getUser`) are built by the sandbox
1991
+ // itself: a method call is exactly `callTool('acme.getUser', args, options)`, with the
1992
+ // sandbox's tool-call cap, rate limit, suspicious-sequence checks and `throwOnError`.
1993
+ toolNamespaces: toSandboxToolNamespaces(environment.toolNamespaces, Object.keys(globals))
1994
+ };
1995
+ const enclave = createEnclave(options);
1745
1996
  try {
1746
1997
  const result = await enclave.run(code);
1747
- return this.mapEnclaveResult(result, logs);
1998
+ return this.mapEnclaveResult(result, logs, toolFailures);
1748
1999
  } finally {
1749
2000
  enclave.dispose();
1750
2001
  }
@@ -1752,58 +2003,56 @@ var EnclaveService = class {
1752
2003
  /**
1753
2004
  * Map Enclave ExecutionResult to EnclaveExecutionResult
1754
2005
  */
1755
- mapEnclaveResult(result, logs) {
2006
+ mapEnclaveResult(result, logs, toolFailures = []) {
2007
+ const stats = {
2008
+ duration: result.stats.duration,
2009
+ toolCallCount: result.stats.toolCallCount,
2010
+ iterationCount: result.stats.iterationCount
2011
+ };
1756
2012
  if (result.success) {
2013
+ return { success: true, result: result.value, logs, timedOut: false, stats };
2014
+ }
2015
+ const error = result.error ?? { name: "Error", message: "Script execution failed" };
2016
+ if (error.code === "ENCLAVE_ERROR" && error.message?.startsWith(PARSE_FAILURE_PREFIX)) {
2017
+ const { message, location } = parseFailureOf(error.message);
1757
2018
  return {
1758
- success: true,
1759
- result: result.value,
2019
+ success: false,
2020
+ error: { message, name: "SyntaxError", code: "SYNTAX_ERROR", ...location ? { location } : {} },
1760
2021
  logs,
1761
2022
  timedOut: false,
1762
- stats: {
1763
- duration: result.stats.duration,
1764
- toolCallCount: result.stats.toolCallCount,
1765
- iterationCount: result.stats.iterationCount
1766
- }
2023
+ stats
1767
2024
  };
1768
2025
  }
1769
- const error = result.error;
1770
- const timedOut = error.message?.includes("timed out") || error.code === "TIMEOUT";
1771
- if (error.code === "VALIDATION_ERROR") {
1772
- const issues = error.data?.issues ?? [];
1773
- const blockedPatterns = [...new Set(issues.map((issue) => issue.code).filter((code) => !!code))];
2026
+ const toolFailure = [...toolFailures].reverse().find((failure) => failure.message === error.message && failure.name === error.name);
2027
+ if (toolFailure) {
1774
2028
  return {
1775
2029
  success: false,
1776
2030
  error: {
1777
2031
  message: error.message,
1778
- name: "ValidationError",
1779
- code: error.code,
1780
- ...blockedPatterns.length > 0 ? { blockedPatterns } : {}
2032
+ name: error.name,
2033
+ ...toolFailure.code ? { code: toolFailure.code } : {},
2034
+ toolName: toolFailure.toolName
1781
2035
  },
1782
2036
  logs,
1783
2037
  timedOut: false,
1784
- stats: {
1785
- duration: result.stats.duration,
1786
- toolCallCount: result.stats.toolCallCount,
1787
- iterationCount: result.stats.iterationCount
1788
- }
2038
+ stats
1789
2039
  };
1790
2040
  }
1791
- const errorData = error.data;
1792
- const toolName = errorData?.["toolName"];
1793
- if (toolName) {
2041
+ const timedOut = error.message?.includes("timed out") || error.code === "TIMEOUT";
2042
+ if (error.code === "VALIDATION_ERROR") {
2043
+ const issues = error.data?.issues ?? [];
2044
+ const blockedPatterns = [...new Set(issues.map((issue) => issue.code).filter((code) => !!code))];
1794
2045
  return {
1795
2046
  success: false,
1796
2047
  error: {
1797
2048
  message: error.message,
1798
- name: error.name,
1799
- stack: error.stack,
2049
+ name: "ValidationError",
1800
2050
  code: error.code,
1801
- toolName,
1802
- toolInput: errorData?.["toolInput"],
1803
- details: errorData?.["details"]
2051
+ ...blockedPatterns.length > 0 ? { blockedPatterns } : {}
1804
2052
  },
1805
2053
  logs,
1806
- timedOut
2054
+ timedOut: false,
2055
+ stats
1807
2056
  };
1808
2057
  }
1809
2058
  return {
@@ -1816,11 +2065,7 @@ var EnclaveService = class {
1816
2065
  },
1817
2066
  logs,
1818
2067
  timedOut,
1819
- stats: {
1820
- duration: result.stats.duration,
1821
- toolCallCount: result.stats.toolCallCount,
1822
- iterationCount: result.stats.iterationCount
1823
- }
2068
+ stats
1824
2069
  };
1825
2070
  }
1826
2071
  };
@@ -1831,9 +2076,19 @@ EnclaveService = __decorateClass([
1831
2076
  scope: ProviderScope3.GLOBAL
1832
2077
  })
1833
2078
  ], EnclaveService);
2079
+ function createEnclave(options) {
2080
+ try {
2081
+ return new Enclave(options);
2082
+ } catch (error) {
2083
+ if (!options.toolNamespaces || !(error instanceof TypeError) || !error.message.startsWith("Invalid toolNamespaces")) {
2084
+ throw error;
2085
+ }
2086
+ return new Enclave({ ...options, toolNamespaces: void 0 });
2087
+ }
2088
+ }
1834
2089
 
1835
2090
  // plugins/plugin-codecall/src/tools/search.tool.ts
1836
- import { Tool, ToolContext } from "@frontmcp/sdk";
2091
+ import { getCallSurface, Tool, ToolContext } from "@frontmcp/sdk";
1837
2092
 
1838
2093
  // plugins/plugin-codecall/src/tools/search.schema.ts
1839
2094
  import { z as z2 } from "@frontmcp/lazy-zod";
@@ -1891,7 +2146,10 @@ var SearchTool = class extends ToolContext {
1891
2146
  const audit = this.tryGet(AuditLoggerService);
1892
2147
  const executionId = audit ? audit.generateExecutionId() : "";
1893
2148
  const startedAt = Date.now();
1894
- const nonExistentExcludedTools = excludeToolNames.filter((toolName) => !searchService.hasTool(toolName));
2149
+ const surface = getCallSurface();
2150
+ const nonExistentExcludedTools = excludeToolNames.filter(
2151
+ (toolName) => !searchService.hasTool(toolName, surface)
2152
+ );
1895
2153
  if (nonExistentExcludedTools.length > 0) {
1896
2154
  warnings.push({
1897
2155
  type: "excluded_tool_not_found",
@@ -1905,7 +2163,8 @@ var SearchTool = class extends ToolContext {
1905
2163
  const searchResults = await searchService.search(query, {
1906
2164
  topK,
1907
2165
  appIds,
1908
- excludeToolNames
2166
+ excludeToolNames,
2167
+ surface
1909
2168
  });
1910
2169
  for (const result of searchResults) {
1911
2170
  if (result.relevanceScore < minRelevanceScore) {
@@ -1950,7 +2209,7 @@ var SearchTool = class extends ToolContext {
1950
2209
  return {
1951
2210
  tools,
1952
2211
  warnings,
1953
- totalAvailableTools: searchService.getTotalCount()
2212
+ totalAvailableTools: searchService.getTotalCount(surface)
1954
2213
  };
1955
2214
  }
1956
2215
  };
@@ -2263,7 +2522,7 @@ SearchKnowledgeTool = __decorateClass([
2263
2522
 
2264
2523
  // plugins/plugin-codecall/src/tools/describe.tool.ts
2265
2524
  import { toJSONSchema, z as z6, ZodType } from "@frontmcp/lazy-zod";
2266
- import { Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
2525
+ import { getCallSurface as getCallSurface2, Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
2267
2526
 
2268
2527
  // plugins/plugin-codecall/src/errors/tool-call.errors.ts
2269
2528
  var TOOL_CALL_ERROR_CODES = {
@@ -2301,11 +2560,10 @@ function getSanitizedMessage(code, toolName, rawMessage) {
2301
2560
  }
2302
2561
  }
2303
2562
  function sanitizeValidationMessage(message) {
2304
- let sanitized = message.replace(/(?:\/[\w.-]+)+|(?:[A-Za-z]:\\[\w\\.-]+)+/g, "[path]");
2563
+ let sanitized = toClientErrorMessage(message);
2305
2564
  sanitized = sanitized.replace(/\bat line \d+/gi, "");
2306
2565
  sanitized = sanitized.replace(/\bline \d+/gi, "");
2307
2566
  sanitized = sanitized.replace(/:\d+:\d+/g, "");
2308
- sanitized = sanitized.replace(/\n\s*at .*/g, "");
2309
2567
  if (sanitized.length > 200) {
2310
2568
  sanitized = sanitized.substring(0, 200) + "...";
2311
2569
  }
@@ -2343,75 +2601,6 @@ function assertNotSelfReference(toolName) {
2343
2601
  }
2344
2602
  }
2345
2603
 
2346
- // plugins/plugin-codecall/src/utils/build-tool-namespaces.ts
2347
- var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2348
- var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2349
- var RESERVED_NAMESPACES = /* @__PURE__ */ new Set([
2350
- "console",
2351
- "Math",
2352
- "JSON",
2353
- "Object",
2354
- "Array",
2355
- "String",
2356
- "Number",
2357
- "Boolean",
2358
- "Promise",
2359
- "Symbol",
2360
- "Map",
2361
- "Set",
2362
- "WeakMap",
2363
- "WeakSet",
2364
- "Date",
2365
- "Error",
2366
- "RegExp",
2367
- "globalThis",
2368
- "global",
2369
- "window",
2370
- "self",
2371
- "undefined",
2372
- "null",
2373
- "true",
2374
- "false",
2375
- "callTool",
2376
- "getTool",
2377
- "mcpLog",
2378
- "mcpNotify"
2379
- ]);
2380
- function buildToolNamespaces(tools, callTool) {
2381
- const namespaces = /* @__PURE__ */ Object.create(null);
2382
- const skipped = [];
2383
- for (const tool of tools) {
2384
- const name = tool?.name;
2385
- if (typeof name !== "string" || name.length === 0) continue;
2386
- const dot = name.indexOf(".");
2387
- if (dot <= 0 || dot === name.length - 1) {
2388
- skipped.push({ name, reason: "no-namespace-prefix" });
2389
- continue;
2390
- }
2391
- const ns = name.slice(0, dot);
2392
- const method = name.slice(dot + 1);
2393
- if (!IDENT_RE.test(ns) || !IDENT_RE.test(method)) {
2394
- skipped.push({ name, reason: "invalid-identifier" });
2395
- continue;
2396
- }
2397
- if (PROTOTYPE_KEYS.has(ns)) {
2398
- skipped.push({ name, reason: "prototype-key" });
2399
- continue;
2400
- }
2401
- if (RESERVED_NAMESPACES.has(ns)) {
2402
- skipped.push({ name, reason: "reserved-namespace" });
2403
- continue;
2404
- }
2405
- const bucket = namespaces[ns] ?? (namespaces[ns] = /* @__PURE__ */ Object.create(null));
2406
- if (Object.prototype.hasOwnProperty.call(bucket, method)) {
2407
- skipped.push({ name, reason: "duplicate-method" });
2408
- continue;
2409
- }
2410
- bucket[method] = (input, options) => callTool(name, input, options);
2411
- }
2412
- return { namespaces, skipped };
2413
- }
2414
-
2415
2604
  // plugins/plugin-codecall/src/utils/describe.utils.ts
2416
2605
  var INTENT_PATTERNS = {
2417
2606
  create: /^(create|add|new|insert|make|append|register|generate|produce|build|construct|provision|instantiate|define|compose|draft)/i,
@@ -2773,12 +2962,13 @@ var DescribeTool = class extends ToolContext4 {
2773
2962
  const tools = [];
2774
2963
  const notFound = [];
2775
2964
  const config = this.get(CodeCallConfig);
2965
+ const surface = getCallSurface2();
2776
2966
  for (const toolName of toolNames) {
2777
2967
  if (isBlockedSelfReference(toolName)) {
2778
2968
  notFound.push(toolName);
2779
2969
  continue;
2780
2970
  }
2781
- const access = checkCodeCallToolAccess(this.scope, config, toolName);
2971
+ const access = checkCodeCallToolAccess(this.scope, config, toolName, { surface });
2782
2972
  if (!access.allowed) {
2783
2973
  audit?.logSecurityAccessDenied(executionId, toolName, access.reason);
2784
2974
  notFound.push(toolName);
@@ -2936,7 +3126,7 @@ DescribeTool = __decorateClass([
2936
3126
  ], DescribeTool);
2937
3127
 
2938
3128
  // plugins/plugin-codecall/src/tools/execute.tool.ts
2939
- import { Tool as Tool5, ToolContext as ToolContext5 } from "@frontmcp/sdk";
3129
+ import { getCallSurface as getCallSurface3, Tool as Tool5, ToolContext as ToolContext5 } from "@frontmcp/sdk";
2940
3130
 
2941
3131
  // plugins/plugin-codecall/src/tools/execute.schema.ts
2942
3132
  import { z as z7 } from "@frontmcp/lazy-zod";
@@ -2990,6 +3180,7 @@ var runtimeErrorPayloadSchema = z7.object({
2990
3180
  source: z7.literal("script"),
2991
3181
  message: z7.string(),
2992
3182
  name: z7.string().optional(),
3183
+ /** @deprecated Never set: stack traces name server files, so results don't carry them. */
2993
3184
  stack: z7.string().optional()
2994
3185
  });
2995
3186
  var toolErrorPayloadSchema = z7.object({
@@ -2998,6 +3189,7 @@ var toolErrorPayloadSchema = z7.object({
2998
3189
  toolInput: z7.unknown(),
2999
3190
  message: z7.string(),
3000
3191
  code: z7.string().optional(),
3192
+ /** @deprecated Never set: the enclave's error data is not sanitized for clients. */
3001
3193
  details: z7.unknown().optional()
3002
3194
  });
3003
3195
  var timeoutErrorPayloadSchema = z7.object({
@@ -3062,11 +3254,12 @@ var ExecuteTool = class extends ToolContext5 {
3062
3254
  * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
3063
3255
  * unexamined.
3064
3256
  */
3065
- checkToolPolicy(name) {
3066
- return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name);
3257
+ checkToolPolicy(name, surface) {
3258
+ return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name, { surface });
3067
3259
  }
3068
3260
  async execute(input) {
3069
3261
  const { script, allowedTools } = input;
3262
+ const callerSurface = getCallSurface3();
3070
3263
  const audit = this.tryGet(AuditLoggerService);
3071
3264
  const executionId = audit ? audit.generateExecutionId() : "";
3072
3265
  const callDepth = 1;
@@ -3079,7 +3272,7 @@ var ExecuteTool = class extends ToolContext5 {
3079
3272
  audit?.logSecuritySelfReference(executionId, name);
3080
3273
  }
3081
3274
  assertNotSelfReference(name);
3082
- const decision = this.checkToolPolicy(name);
3275
+ const decision = this.checkToolPolicy(name, callerSurface);
3083
3276
  if (!decision.allowed) {
3084
3277
  audit?.logSecurityAccessDenied(executionId, name, decision.reason);
3085
3278
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
@@ -3108,7 +3301,8 @@ var ExecuteTool = class extends ToolContext5 {
3108
3301
  }
3109
3302
  };
3110
3303
  const ctx = {
3111
- authInfo: this.authInfo
3304
+ authInfo: this.authInfo,
3305
+ surface: callerSurface
3112
3306
  };
3113
3307
  const mcpResult = await this.scope.runFlow("tools:call-tool", { request, ctx });
3114
3308
  if (!mcpResult) {
@@ -3148,7 +3342,7 @@ var ExecuteTool = class extends ToolContext5 {
3148
3342
  audit?.logSecuritySelfReference(executionId, name);
3149
3343
  return void 0;
3150
3344
  }
3151
- const introspectionAccess = this.checkToolPolicy(name);
3345
+ const introspectionAccess = this.checkToolPolicy(name, callerSurface);
3152
3346
  if (!introspectionAccess.allowed) {
3153
3347
  audit?.logSecurityAccessDenied(executionId, name, introspectionAccess.reason);
3154
3348
  return void 0;
@@ -3178,8 +3372,8 @@ var ExecuteTool = class extends ToolContext5 {
3178
3372
  const callableTools = this.scope.tools.getTools(true).filter(
3179
3373
  (tool) => checkCodeCallToolPolicy(toCodeCallPolicyTool(tool, void 0, this.scope), policyConfig).allowed
3180
3374
  );
3181
- const { namespaces, skipped } = buildToolNamespaces(callableTools, environment.callTool);
3182
- environment.namespaces = namespaces;
3375
+ const { namespaces, skipped } = buildToolNamespaces(callableTools);
3376
+ environment.toolNamespaces = namespaces;
3183
3377
  if (skipped.length > 0) {
3184
3378
  this.logger?.debug?.("codecall: tools skipped during namespace generation", {
3185
3379
  count: skipped.length,
@@ -3211,6 +3405,16 @@ var ExecuteTool = class extends ToolContext5 {
3211
3405
  }
3212
3406
  if (!executionResult.success) {
3213
3407
  const error = executionResult.error;
3408
+ if (error.code === "SYNTAX_ERROR") {
3409
+ audit?.logExecutionFailure(executionId, script, durationMs, error.message);
3410
+ return {
3411
+ status: "syntax_error",
3412
+ error: {
3413
+ message: toClientErrorMessage(error.message) || "Syntax error in script",
3414
+ ...error.location ? { location: error.location } : {}
3415
+ }
3416
+ };
3417
+ }
3214
3418
  if (error.code === "VALIDATION_ERROR" || error.name === "ValidationError") {
3215
3419
  audit?.logSecurityAstBlocked(executionId, error.blockedPatterns?.join(", ") ?? "UNKNOWN", error.message);
3216
3420
  audit?.logExecutionFailure(executionId, script, durationMs, error.message);
@@ -3230,9 +3434,9 @@ var ExecuteTool = class extends ToolContext5 {
3230
3434
  source: "tool",
3231
3435
  toolName: error.toolName,
3232
3436
  toolInput: error.toolInput,
3233
- message: error.message,
3234
- code: error.code,
3235
- details: error.details
3437
+ message: toClientErrorMessage(error.message),
3438
+ code: error.code
3439
+ // No `details`: the enclave's error data is not sanitized for clients.
3236
3440
  }
3237
3441
  };
3238
3442
  }
@@ -3241,9 +3445,8 @@ var ExecuteTool = class extends ToolContext5 {
3241
3445
  status: "runtime_error",
3242
3446
  error: {
3243
3447
  source: "script",
3244
- message: error.message,
3245
- name: error.name,
3246
- stack: error.stack
3448
+ message: toClientErrorMessage(error.message),
3449
+ name: error.name
3247
3450
  }
3248
3451
  };
3249
3452
  }
@@ -3257,13 +3460,12 @@ var ExecuteTool = class extends ToolContext5 {
3257
3460
  const errorName = error instanceof Error ? error.name : "Error";
3258
3461
  const errorMessage = error instanceof Error ? error.message : String(error);
3259
3462
  audit?.logExecutionFailure(executionId, script, elapsed(), errorMessage);
3260
- const errorStack = error instanceof Error ? error.stack : void 0;
3261
3463
  const errorLoc = error.loc;
3262
3464
  if (errorName === "SyntaxError" || errorMessage?.includes("syntax")) {
3263
3465
  return {
3264
3466
  status: "syntax_error",
3265
3467
  error: {
3266
- message: errorMessage || "Syntax error in script",
3468
+ message: toClientErrorMessage(errorMessage) || "Syntax error in script",
3267
3469
  location: errorLoc ? { line: errorLoc.line, column: errorLoc.column } : void 0
3268
3470
  }
3269
3471
  };
@@ -3272,9 +3474,8 @@ var ExecuteTool = class extends ToolContext5 {
3272
3474
  status: "runtime_error",
3273
3475
  error: {
3274
3476
  source: "script",
3275
- message: errorMessage || "An unexpected error occurred during script execution",
3276
- name: errorName,
3277
- stack: errorStack
3477
+ message: toClientErrorMessage(errorMessage) || "An unexpected error occurred during script execution",
3478
+ name: errorName
3278
3479
  }
3279
3480
  };
3280
3481
  }
@@ -3299,7 +3500,7 @@ ExecuteTool = __decorateClass([
3299
3500
  ], ExecuteTool);
3300
3501
 
3301
3502
  // plugins/plugin-codecall/src/tools/invoke.tool.ts
3302
- import { Tool as Tool6, ToolContext as ToolContext6 } from "@frontmcp/sdk";
3503
+ import { getCallSurface as getCallSurface4, Tool as Tool6, ToolContext as ToolContext6 } from "@frontmcp/sdk";
3303
3504
 
3304
3505
  // plugins/plugin-codecall/src/tools/invoke.schema.ts
3305
3506
  import { z as z8 } from "@frontmcp/lazy-zod";
@@ -3343,7 +3544,11 @@ var InvokeTool = class extends ToolContext6 {
3343
3544
  `Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
3344
3545
  );
3345
3546
  }
3346
- const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
3547
+ const surface = getCallSurface4();
3548
+ const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, {
3549
+ directCall: true,
3550
+ surface
3551
+ });
3347
3552
  if (!decision.allowed) {
3348
3553
  audit?.logSecurityAccessDenied(executionId, toolName, decision.reason);
3349
3554
  return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
@@ -3356,7 +3561,8 @@ var InvokeTool = class extends ToolContext6 {
3356
3561
  }
3357
3562
  };
3358
3563
  const ctx = {
3359
- authInfo: this.authInfo
3564
+ authInfo: this.authInfo,
3565
+ surface
3360
3566
  };
3361
3567
  const result = await this.scope.runFlow("tools:call-tool", { request, ctx });
3362
3568
  if (!result) {