@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/index.js CHANGED
@@ -413,6 +413,46 @@ function presetDefaults(preset) {
413
413
 
414
414
  // plugins/plugin-codecall/src/services/audit-logger.service.ts
415
415
  var import_sdk2 = require("@frontmcp/sdk");
416
+
417
+ // plugins/plugin-codecall/src/utils/client-error-message.ts
418
+ var EMBEDDED_STACK_FRAME_RE = /\n[ \t]*at\s[^\n]*/g;
419
+ var QUOTED_PATH_RE = /(['"`])((?:~?\/|[A-Za-z]:[\\/]|\\\\)[^\s'"`\\/][^'"`\n]*)\1/g;
420
+ var URL_RE = /\b[A-Za-z][A-Za-z0-9+.-]{1,31}:\/\/(?:[^\s'"`<>,;]|[,;](?!~?\/|[A-Za-z]:[\\/]|\\\\))*/g;
421
+ var QUERY_PATH_VALUE_RE = /([?&][^=&#]*=)(?:~?\/|[A-Za-z]:[\\/]|\\\\)[^&#]*/g;
422
+ var POSIX_SEGMENT = String.raw`[^\s/\\'"\x60<>|,;:()\[\]{}]+`;
423
+ var WINDOWS_SEGMENT = String.raw`[^\s/\\'"\x60<>|,;:*?]+`;
424
+ function segments(segment, separator) {
425
+ const spaced = `${segment}(?: ${segment}){0,2}`;
426
+ return `(?:(?:${spaced}${separator})+(?:${segment})?|${segment})`;
427
+ }
428
+ var UNQUOTED_PATH_RE = new RegExp(
429
+ [
430
+ String.raw`(?<![\w.~/\\)\]-])~?/+${segments(POSIX_SEGMENT, "/+")}`,
431
+ String.raw`(?<![\w\\/])[A-Za-z]:[\\/]+${segments(WINDOWS_SEGMENT, String.raw`[\\/]+`)}?`,
432
+ String.raw`(?<![\w\\])\\\\${segments(WINDOWS_SEGMENT, String.raw`[\\/]+`)}`
433
+ ].join("|"),
434
+ "g"
435
+ );
436
+ function redactPaths(text) {
437
+ return text.replace(QUOTED_PATH_RE, "$1[path]$1").replace(UNQUOTED_PATH_RE, "[path]");
438
+ }
439
+ function toClientErrorMessage(message) {
440
+ if (!message) return "";
441
+ return redactAbsolutePaths(message.replace(EMBEDDED_STACK_FRAME_RE, ""));
442
+ }
443
+ function redactAbsolutePaths(text) {
444
+ let result = "";
445
+ let last = 0;
446
+ for (const match of text.matchAll(URL_RE)) {
447
+ const start = match.index ?? 0;
448
+ result += redactPaths(text.slice(last, start));
449
+ result += /^file:/i.test(match[0]) ? "[path]" : match[0].replace(QUERY_PATH_VALUE_RE, "$1[path]");
450
+ last = start + match[0].length;
451
+ }
452
+ return result + redactPaths(text.slice(last));
453
+ }
454
+
455
+ // plugins/plugin-codecall/src/services/audit-logger.service.ts
416
456
  var AUDIT_EVENT_TYPES = {
417
457
  /** Script execution started */
418
458
  EXECUTION_START: "codecall:execution:start",
@@ -689,9 +729,8 @@ var AuditLoggerService = class {
689
729
  */
690
730
  sanitizeError(error) {
691
731
  if (!error) return "Unknown error";
692
- let sanitized = error.replace(/(?:\/[\w.-]+)+|(?:[A-Za-z]:\\[\w\\.-]+)+/g, "[path]");
732
+ let sanitized = toClientErrorMessage(error);
693
733
  sanitized = sanitized.replace(/:\d+:\d+/g, "");
694
- sanitized = sanitized.replace(/\n\s*at .*/g, "");
695
734
  if (sanitized.length > 200) {
696
735
  sanitized = sanitized.substring(0, 200) + "...";
697
736
  }
@@ -710,6 +749,10 @@ var import_vectoriadb = require("vectoriadb");
710
749
 
711
750
  // plugins/plugin-codecall/src/security/codecall-tool-policy.ts
712
751
  var BLOCKED_NAMESPACE_PATTERNS = Object.freeze([/^system:/, /^internal:/, /^__/]);
752
+ function isOfferedToCaller(availableWhen, surface) {
753
+ const offered = availableWhen?.surface;
754
+ return offered === void 0 || surface === void 0 || offered.includes(surface);
755
+ }
713
756
  var ALLOWED = { allowed: true };
714
757
  function deny(reason) {
715
758
  return { allowed: false, reason };
@@ -730,16 +773,46 @@ function toCodeCallPolicyTool(entry, requestedName, scope) {
730
773
  tags: metadata?.tags,
731
774
  // `internal` tools refuse every external tools/call, CodeCall's included, so no surface may offer them.
732
775
  hidden: metadata?.hideFromDiscovery === true || metadata?.visibility === "hidden" || metadata?.visibility === "internal",
733
- codecall: metadata?.codecall
776
+ codecall: metadata?.codecall,
777
+ annotations: metadata?.annotations,
778
+ metadata
734
779
  };
735
780
  }
781
+ function isPlainData(value) {
782
+ if (Array.isArray(value)) return true;
783
+ const proto = Object.getPrototypeOf(value);
784
+ return proto === Object.prototype || proto === null;
785
+ }
786
+ function readOnlyCopy(value, copies) {
787
+ if (typeof value !== "object" || value === null || !isPlainData(value)) return value;
788
+ const existing = copies.get(value);
789
+ if (existing !== void 0) return existing;
790
+ const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
791
+ copies.set(value, copy);
792
+ for (const key of Reflect.ownKeys(value)) {
793
+ if (!Object.prototype.propertyIsEnumerable.call(value, key)) continue;
794
+ Object.defineProperty(copy, key, {
795
+ value: readOnlyCopy(Reflect.get(value, key), copies),
796
+ enumerable: true,
797
+ writable: true,
798
+ configurable: true
799
+ });
800
+ }
801
+ return Object.freeze(copy);
802
+ }
736
803
  function toToolFilterInfo(tool) {
804
+ const copies = /* @__PURE__ */ new WeakMap();
805
+ const annotations = readOnlyCopy(tool.annotations, copies);
806
+ const metadata = tool.metadata ? readOnlyCopy({ ...tool.metadata, ...annotations ? { annotations: tool.annotations } : {} }, copies) : void 0;
737
807
  return {
738
808
  name: tool.name,
809
+ fullName: tool.fullName,
739
810
  appId: tool.appId,
740
811
  source: tool.codecall?.source,
741
812
  description: tool.description,
742
- tags: tool.codecall?.tags ?? tool.tags
813
+ tags: readOnlyCopy(tool.codecall?.tags ?? tool.tags, copies),
814
+ annotations,
815
+ metadata
743
816
  };
744
817
  }
745
818
  function ownNamesOf(tool) {
@@ -826,6 +899,9 @@ function readCodeCallPolicyConfig(config) {
826
899
  function checkCodeCallToolAccess(scope, config, name, options = {}) {
827
900
  const entry = resolveCodeCallTool(scope, name);
828
901
  if (!entry) return denyUnknownTool(name);
902
+ if (!isOfferedToCaller(entry.metadata?.availableWhen, options.surface)) {
903
+ return deny(`Tool "${name}" is not offered on the caller's surface "${options.surface}"`);
904
+ }
829
905
  const policyTool = toCodeCallPolicyTool(entry, name, scope);
830
906
  const baseDecision = checkCodeCallToolPolicy(policyTool, readCodeCallPolicyConfig(config));
831
907
  if (!baseDecision.allowed) return baseDecision;
@@ -1359,6 +1435,8 @@ var ToolSearchService = class _ToolSearchService {
1359
1435
  subscriptionReject = null;
1360
1436
  retryTimeoutId = null;
1361
1437
  disposed = false;
1438
+ /** `availableWhen` of each indexed tool, by indexed name, for per-caller surface filtering. */
1439
+ availabilityByTool = /* @__PURE__ */ new Map();
1362
1440
  constructor(config = {}, scope) {
1363
1441
  this.scope = scope;
1364
1442
  const embeddingOptions = config.embeddingOptions || {
@@ -1468,6 +1546,7 @@ var ToolSearchService = class _ToolSearchService {
1468
1546
  */
1469
1547
  async handleToolChange(tools) {
1470
1548
  this.vectorDB.clear();
1549
+ this.availabilityByTool = /* @__PURE__ */ new Map();
1471
1550
  if (tools.length === 0) {
1472
1551
  this.initialized = true;
1473
1552
  return;
@@ -1482,6 +1561,7 @@ var ToolSearchService = class _ToolSearchService {
1482
1561
  return;
1483
1562
  }
1484
1563
  const documents = filteredTools.map((tool) => {
1564
+ this.availabilityByTool.set(tool.name, tool.metadata.availableWhen);
1485
1565
  const searchableText = this.extractSearchableText(tool);
1486
1566
  const appId = this.extractAppId(tool);
1487
1567
  const toolName = tool.name;
@@ -1610,12 +1690,15 @@ var ToolSearchService = class _ToolSearchService {
1610
1690
  */
1611
1691
  async search(query, options = {}) {
1612
1692
  await this.ensureSubscribed();
1613
- const { topK = this.config.defaultTopK, appIds, excludeToolNames = [] } = options;
1693
+ const { topK = this.config.defaultTopK, appIds, excludeToolNames = [], surface } = options;
1614
1694
  const minScore = this.config.defaultSimilarityThreshold;
1615
1695
  const filter = (metadata) => {
1616
1696
  if (excludeToolNames.includes(metadata.toolName)) {
1617
1697
  return false;
1618
1698
  }
1699
+ if (!isOfferedToCaller(metadata.toolInstance.metadata.availableWhen, surface)) {
1700
+ return false;
1701
+ }
1619
1702
  if (appIds && appIds.length > 0) {
1620
1703
  if (!metadata.appId || !appIds.includes(metadata.appId)) {
1621
1704
  return false;
@@ -1647,9 +1730,12 @@ var ToolSearchService = class _ToolSearchService {
1647
1730
  }
1648
1731
  }
1649
1732
  /**
1650
- * Gets the total number of indexed tools
1733
+ * Gets the total number of indexed tools, or of those a caller on `surface` may reach
1651
1734
  */
1652
- getTotalCount() {
1735
+ getTotalCount(surface) {
1736
+ if (surface !== void 0) {
1737
+ return this.getAllToolNames().filter((toolName) => this.hasTool(toolName, surface)).length;
1738
+ }
1653
1739
  if (this.vectorDB instanceof import_vectoriadb.VectoriaDB) {
1654
1740
  return this.vectorDB.size();
1655
1741
  } else {
@@ -1657,20 +1743,18 @@ var ToolSearchService = class _ToolSearchService {
1657
1743
  }
1658
1744
  }
1659
1745
  /**
1660
- * Checks if a tool exists in the index
1746
+ * Checks if a tool exists in the index, for a caller on `surface` when given
1661
1747
  */
1662
- hasTool(toolName) {
1663
- if (this.vectorDB instanceof import_vectoriadb.VectoriaDB) {
1664
- return this.vectorDB.has(toolName);
1665
- } else {
1666
- return this.vectorDB.hasDocument(toolName);
1667
- }
1748
+ hasTool(toolName, surface) {
1749
+ const indexed = this.vectorDB instanceof import_vectoriadb.VectoriaDB ? this.vectorDB.has(toolName) : this.vectorDB.hasDocument(toolName);
1750
+ return indexed && isOfferedToCaller(this.availabilityByTool.get(toolName), surface);
1668
1751
  }
1669
1752
  /**
1670
1753
  * Clears the entire index
1671
1754
  */
1672
1755
  clear() {
1673
1756
  this.vectorDB.clear();
1757
+ this.availabilityByTool = /* @__PURE__ */ new Map();
1674
1758
  this.initialized = false;
1675
1759
  }
1676
1760
  /**
@@ -1690,6 +1774,159 @@ var ToolSearchService = class _ToolSearchService {
1690
1774
  // plugins/plugin-codecall/src/services/enclave.service.ts
1691
1775
  var import_core = require("@enclave-vm/core");
1692
1776
  var import_sdk3 = require("@frontmcp/sdk");
1777
+
1778
+ // plugins/plugin-codecall/src/utils/build-tool-namespaces.ts
1779
+ var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1780
+ var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1781
+ var RESERVED_NAMESPACES = /* @__PURE__ */ new Set([
1782
+ "console",
1783
+ "Math",
1784
+ "JSON",
1785
+ "Object",
1786
+ "Array",
1787
+ "String",
1788
+ "Number",
1789
+ "Boolean",
1790
+ "Promise",
1791
+ "Symbol",
1792
+ "Map",
1793
+ "Set",
1794
+ "WeakMap",
1795
+ "WeakSet",
1796
+ "Date",
1797
+ "Error",
1798
+ "RegExp",
1799
+ "globalThis",
1800
+ "global",
1801
+ "window",
1802
+ "self",
1803
+ "undefined",
1804
+ "null",
1805
+ "true",
1806
+ "false",
1807
+ "callTool",
1808
+ "getTool",
1809
+ "mcpLog",
1810
+ "mcpNotify"
1811
+ ]);
1812
+ var UNBINDABLE_NAMESPACES = new Set(
1813
+ [
1814
+ // Reserved words and strict-mode restricted bindings
1815
+ "break case catch class const continue debugger default delete do else enum export extends finally for",
1816
+ "function if import in instanceof new return super switch this throw try typeof var void while with yield",
1817
+ "let static implements interface package private protected public await eval arguments NaN Infinity",
1818
+ // AgentScript globals
1819
+ "parallel isNaN isFinite parseInt parseFloat encodeURI encodeURIComponent decodeURI decodeURIComponent",
1820
+ // Identifiers the AgentScript validator refuses
1821
+ "process require module exports Buffer Function AsyncFunction GeneratorFunction Reflect Proxy TypeError",
1822
+ "ReferenceError SyntaxError RangeError URIError EvalError AggregateError fetch XMLHttpRequest WebSocket",
1823
+ "localStorage sessionStorage indexedDB crypto performance structuredClone AbortController AbortSignal",
1824
+ "MessageChannel MessagePort BroadcastChannel TextEncoder TextDecoder Intl setTimeout setInterval",
1825
+ "setImmediate clearTimeout clearInterval clearImmediate queueMicrotask WebAssembly Worker SharedWorker",
1826
+ "ServiceWorker WeakRef FinalizationRegistry BigInt Atomics SharedArrayBuffer ArrayBuffer DataView",
1827
+ "importScripts ShadowRealm Iterator AsyncIterator"
1828
+ ].join(" ").split(" ")
1829
+ );
1830
+ var UNCALLABLE_METHODS = new Set(
1831
+ [
1832
+ "process require module exports Buffer eval Function AsyncFunction GeneratorFunction arguments RegExp",
1833
+ "Promise Symbol Proxy Reflect Error TypeError ReferenceError SyntaxError RangeError URIError EvalError",
1834
+ "AggregateError fetch XMLHttpRequest WebSocket localStorage sessionStorage indexedDB crypto performance",
1835
+ "structuredClone AbortController AbortSignal MessageChannel MessagePort BroadcastChannel TextEncoder",
1836
+ "TextDecoder Intl setTimeout setInterval setImmediate clearTimeout clearInterval clearImmediate",
1837
+ "queueMicrotask WebAssembly Worker SharedWorker ServiceWorker WeakMap WeakSet WeakRef FinalizationRegistry",
1838
+ "Map Set Atomics SharedArrayBuffer importScripts ShadowRealm Iterator AsyncIterator"
1839
+ ].join(" ").split(" ")
1840
+ );
1841
+ var SANDBOX_TOOL_NAME_RE = /^[a-zA-Z][a-zA-Z0-9:._-]*$/;
1842
+ var MAX_SANDBOX_TOOL_NAME_LENGTH = 256;
1843
+ function isBindableNamespace(ns, reservedGlobals) {
1844
+ return IDENT_RE.test(ns) && !PROTOTYPE_KEYS.has(ns) && !ns.startsWith("__") && !RESERVED_NAMESPACES.has(ns) && !UNBINDABLE_NAMESPACES.has(ns) && !reservedGlobals.has(ns);
1845
+ }
1846
+ function isBindableMethod(ns, method, toolName) {
1847
+ 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);
1848
+ }
1849
+ function buildToolNamespaces(tools) {
1850
+ const namespaces = /* @__PURE__ */ Object.create(null);
1851
+ const skipped = [];
1852
+ for (const tool of tools) {
1853
+ const name = tool?.name;
1854
+ if (typeof name !== "string" || name.length === 0) continue;
1855
+ const dot = name.indexOf(".");
1856
+ if (dot <= 0 || dot === name.length - 1) {
1857
+ skipped.push({ name, reason: "no-namespace-prefix" });
1858
+ continue;
1859
+ }
1860
+ const ns = name.slice(0, dot);
1861
+ const method = name.slice(dot + 1);
1862
+ if (!IDENT_RE.test(ns) || !IDENT_RE.test(method)) {
1863
+ skipped.push({ name, reason: "invalid-identifier" });
1864
+ continue;
1865
+ }
1866
+ if (PROTOTYPE_KEYS.has(ns) || PROTOTYPE_KEYS.has(method)) {
1867
+ skipped.push({ name, reason: "prototype-key" });
1868
+ continue;
1869
+ }
1870
+ if (RESERVED_NAMESPACES.has(ns) || UNBINDABLE_NAMESPACES.has(ns) || ns.startsWith("__")) {
1871
+ skipped.push({ name, reason: "reserved-namespace" });
1872
+ continue;
1873
+ }
1874
+ if (!isBindableMethod(ns, method, name)) {
1875
+ skipped.push({ name, reason: "unsupported-name" });
1876
+ continue;
1877
+ }
1878
+ const bucket = namespaces[ns] ?? (namespaces[ns] = /* @__PURE__ */ Object.create(null));
1879
+ if (Object.prototype.hasOwnProperty.call(bucket, method)) {
1880
+ skipped.push({ name, reason: "duplicate-method" });
1881
+ continue;
1882
+ }
1883
+ bucket[method] = name;
1884
+ }
1885
+ return { namespaces, skipped };
1886
+ }
1887
+ function toSandboxToolNamespaces(namespaces, reservedGlobals = []) {
1888
+ if (!namespaces) return void 0;
1889
+ const reserved = new Set(reservedGlobals);
1890
+ const bindable = /* @__PURE__ */ Object.create(null);
1891
+ let count = 0;
1892
+ for (const ns of Object.keys(namespaces)) {
1893
+ if (!isBindableNamespace(ns, reserved)) continue;
1894
+ const methods = namespaces[ns];
1895
+ const bucket = /* @__PURE__ */ Object.create(null);
1896
+ let methodCount = 0;
1897
+ for (const method of Object.keys(methods)) {
1898
+ const toolName = methods[method];
1899
+ if (typeof toolName !== "string" || !isBindableMethod(ns, method, toolName)) continue;
1900
+ bucket[method] = toolName;
1901
+ methodCount += 1;
1902
+ }
1903
+ if (methodCount > 0) {
1904
+ bindable[ns] = bucket;
1905
+ count += 1;
1906
+ }
1907
+ }
1908
+ return count > 0 ? bindable : void 0;
1909
+ }
1910
+
1911
+ // plugins/plugin-codecall/src/services/enclave.service.ts
1912
+ function toolFailureMessage(error, toolName) {
1913
+ if (typeof error === "string" && error) return error;
1914
+ const message = error?.message;
1915
+ return typeof message === "string" && message ? message : `Tool call failed: ${toolName}`;
1916
+ }
1917
+ function toolFailureName(error) {
1918
+ const name = error && typeof error === "object" ? error.name : void 0;
1919
+ return typeof name === "string" && name ? name.slice(0, 128) : "ToolError";
1920
+ }
1921
+ var PARSE_FAILURE_PREFIX = "Failed to parse AgentScript code: ";
1922
+ var PARSE_POSITION_RE = /\((\d+):(\d+)\)$/;
1923
+ function parseFailureOf(message) {
1924
+ const match = PARSE_POSITION_RE.exec(message);
1925
+ const line = match ? Number(match[1]) - 1 : 0;
1926
+ if (!match || line < 1) return { message };
1927
+ const column = Number(match[2]);
1928
+ return { message: `${message.slice(0, match.index)}(${line}:${column})`, location: { line, column } };
1929
+ }
1693
1930
  var ScriptTooLargeError = class extends Error {
1694
1931
  code = "SCRIPT_TOO_LARGE";
1695
1932
  scriptLength;
@@ -1727,8 +1964,20 @@ var EnclaveService = class {
1727
1964
  throw new ScriptTooLargeError(code.length, maxLength);
1728
1965
  }
1729
1966
  }
1967
+ const toolFailures = [];
1730
1968
  const toolHandler = async (toolName, args) => {
1731
- return environment.callTool(toolName, args);
1969
+ try {
1970
+ return await environment.callTool(toolName, args);
1971
+ } catch (error) {
1972
+ const code2 = error?.code;
1973
+ toolFailures.push({
1974
+ toolName,
1975
+ name: toolFailureName(error),
1976
+ message: toolFailureMessage(error, toolName),
1977
+ ...typeof code2 === "string" ? { code: code2 } : {}
1978
+ });
1979
+ throw error;
1980
+ }
1732
1981
  };
1733
1982
  const sidecar = this.sidecarOptions.enabled ? {
1734
1983
  enabled: true,
@@ -1738,11 +1987,29 @@ var EnclaveService = class {
1738
1987
  maxResolvedSize: this.sidecarOptions.maxResolvedSize,
1739
1988
  allowComposites: this.sidecarOptions.allowComposites
1740
1989
  } : void 0;
1741
- const RESERVED_GLOBALS = /* @__PURE__ */ new Set(["getTool", "mcpLog", "mcpNotify"]);
1742
- const safeNamespaces = Object.fromEntries(
1743
- Object.entries(environment.namespaces ?? {}).filter(([key]) => !RESERVED_GLOBALS.has(key))
1744
- );
1745
- const enclave = new import_core.Enclave({
1990
+ const { mcpLog, mcpNotify } = environment;
1991
+ const globals = {
1992
+ // Provide getTool as a custom global
1993
+ getTool: environment.getTool,
1994
+ // Provide logging functions if available
1995
+ ...mcpLog ? {
1996
+ mcpLog: (level, message, metadata) => {
1997
+ mcpLog(level, message, metadata);
1998
+ logs.push(`[mcp:${level}] ${message}`);
1999
+ }
2000
+ } : {},
2001
+ ...mcpNotify ? {
2002
+ mcpNotify: (event, payload) => {
2003
+ mcpNotify(event, payload);
2004
+ logs.push(`[notify] ${event}`);
2005
+ }
2006
+ } : {}
2007
+ // Note: enclave-vm v2.0.0+ provides its own __safe_console internally with rate limiting
2008
+ // and output size limits. Passing console in globals causes "Cannot redefine property"
2009
+ // errors due to Double VM architecture. Console output from user scripts goes to stdout
2010
+ // via enclave's internal console, not to this logs array. Only mcpLog/mcpNotify are captured.
2011
+ };
2012
+ const options = {
1746
2013
  timeout: this.vmOptions.timeoutMs,
1747
2014
  maxToolCalls: this.vmOptions.maxSteps || 100,
1748
2015
  maxIterations: 1e4,
@@ -1752,38 +2019,20 @@ var EnclaveService = class {
1752
2019
  validate: true,
1753
2020
  transform: true,
1754
2021
  sidecar,
2022
+ // Error stacks name no host file, whatever stage raised them.
2023
+ sanitizeStackTraces: true,
1755
2024
  // Allow functions in globals since we intentionally provide getTool, mcpLog, mcpNotify, and console
1756
2025
  allowFunctionsInGlobals: true,
1757
- globals: {
1758
- // Tool namespaces (e.g. `acme.getUser`) flow through here. Spread
1759
- // FIRST so the runtime reserved globals below are guaranteed
1760
- // last-wins. Reserved keys have already been stripped above —
1761
- // belt-and-braces ordering makes the invariant locally obvious.
1762
- ...safeNamespaces,
1763
- // Provide getTool as a custom global
1764
- getTool: environment.getTool,
1765
- // Provide logging functions if available
1766
- ...environment.mcpLog ? {
1767
- mcpLog: (level, message, metadata) => {
1768
- environment.mcpLog(level, message, metadata);
1769
- logs.push(`[mcp:${level}] ${message}`);
1770
- }
1771
- } : {},
1772
- ...environment.mcpNotify ? {
1773
- mcpNotify: (event, payload) => {
1774
- environment.mcpNotify(event, payload);
1775
- logs.push(`[notify] ${event}`);
1776
- }
1777
- } : {}
1778
- // Note: enclave-vm v2.0.0+ provides its own __safe_console internally with rate limiting
1779
- // and output size limits. Passing console in globals causes "Cannot redefine property"
1780
- // errors due to Double VM architecture. Console output from user scripts goes to stdout
1781
- // via enclave's internal console, not to this logs array. Only mcpLog/mcpNotify are captured.
1782
- }
1783
- });
2026
+ globals,
2027
+ // Tool namespaces (`acme.getUser()` for a tool named `acme.getUser`) are built by the sandbox
2028
+ // itself: a method call is exactly `callTool('acme.getUser', args, options)`, with the
2029
+ // sandbox's tool-call cap, rate limit, suspicious-sequence checks and `throwOnError`.
2030
+ toolNamespaces: toSandboxToolNamespaces(environment.toolNamespaces, Object.keys(globals))
2031
+ };
2032
+ const enclave = createEnclave(options);
1784
2033
  try {
1785
2034
  const result = await enclave.run(code);
1786
- return this.mapEnclaveResult(result, logs);
2035
+ return this.mapEnclaveResult(result, logs, toolFailures);
1787
2036
  } finally {
1788
2037
  enclave.dispose();
1789
2038
  }
@@ -1791,58 +2040,56 @@ var EnclaveService = class {
1791
2040
  /**
1792
2041
  * Map Enclave ExecutionResult to EnclaveExecutionResult
1793
2042
  */
1794
- mapEnclaveResult(result, logs) {
2043
+ mapEnclaveResult(result, logs, toolFailures = []) {
2044
+ const stats = {
2045
+ duration: result.stats.duration,
2046
+ toolCallCount: result.stats.toolCallCount,
2047
+ iterationCount: result.stats.iterationCount
2048
+ };
1795
2049
  if (result.success) {
2050
+ return { success: true, result: result.value, logs, timedOut: false, stats };
2051
+ }
2052
+ const error = result.error ?? { name: "Error", message: "Script execution failed" };
2053
+ if (error.code === "ENCLAVE_ERROR" && error.message?.startsWith(PARSE_FAILURE_PREFIX)) {
2054
+ const { message, location } = parseFailureOf(error.message);
1796
2055
  return {
1797
- success: true,
1798
- result: result.value,
2056
+ success: false,
2057
+ error: { message, name: "SyntaxError", code: "SYNTAX_ERROR", ...location ? { location } : {} },
1799
2058
  logs,
1800
2059
  timedOut: false,
1801
- stats: {
1802
- duration: result.stats.duration,
1803
- toolCallCount: result.stats.toolCallCount,
1804
- iterationCount: result.stats.iterationCount
1805
- }
2060
+ stats
1806
2061
  };
1807
2062
  }
1808
- const error = result.error;
1809
- const timedOut = error.message?.includes("timed out") || error.code === "TIMEOUT";
1810
- if (error.code === "VALIDATION_ERROR") {
1811
- const issues = error.data?.issues ?? [];
1812
- const blockedPatterns = [...new Set(issues.map((issue) => issue.code).filter((code) => !!code))];
2063
+ const toolFailure = [...toolFailures].reverse().find((failure) => failure.message === error.message && failure.name === error.name);
2064
+ if (toolFailure) {
1813
2065
  return {
1814
2066
  success: false,
1815
2067
  error: {
1816
2068
  message: error.message,
1817
- name: "ValidationError",
1818
- code: error.code,
1819
- ...blockedPatterns.length > 0 ? { blockedPatterns } : {}
2069
+ name: error.name,
2070
+ ...toolFailure.code ? { code: toolFailure.code } : {},
2071
+ toolName: toolFailure.toolName
1820
2072
  },
1821
2073
  logs,
1822
2074
  timedOut: false,
1823
- stats: {
1824
- duration: result.stats.duration,
1825
- toolCallCount: result.stats.toolCallCount,
1826
- iterationCount: result.stats.iterationCount
1827
- }
2075
+ stats
1828
2076
  };
1829
2077
  }
1830
- const errorData = error.data;
1831
- const toolName = errorData?.["toolName"];
1832
- if (toolName) {
2078
+ const timedOut = error.message?.includes("timed out") || error.code === "TIMEOUT";
2079
+ if (error.code === "VALIDATION_ERROR") {
2080
+ const issues = error.data?.issues ?? [];
2081
+ const blockedPatterns = [...new Set(issues.map((issue) => issue.code).filter((code) => !!code))];
1833
2082
  return {
1834
2083
  success: false,
1835
2084
  error: {
1836
2085
  message: error.message,
1837
- name: error.name,
1838
- stack: error.stack,
2086
+ name: "ValidationError",
1839
2087
  code: error.code,
1840
- toolName,
1841
- toolInput: errorData?.["toolInput"],
1842
- details: errorData?.["details"]
2088
+ ...blockedPatterns.length > 0 ? { blockedPatterns } : {}
1843
2089
  },
1844
2090
  logs,
1845
- timedOut
2091
+ timedOut: false,
2092
+ stats
1846
2093
  };
1847
2094
  }
1848
2095
  return {
@@ -1855,11 +2102,7 @@ var EnclaveService = class {
1855
2102
  },
1856
2103
  logs,
1857
2104
  timedOut,
1858
- stats: {
1859
- duration: result.stats.duration,
1860
- toolCallCount: result.stats.toolCallCount,
1861
- iterationCount: result.stats.iterationCount
1862
- }
2105
+ stats
1863
2106
  };
1864
2107
  }
1865
2108
  };
@@ -1870,6 +2113,16 @@ EnclaveService = __decorateClass([
1870
2113
  scope: import_sdk3.ProviderScope.GLOBAL
1871
2114
  })
1872
2115
  ], EnclaveService);
2116
+ function createEnclave(options) {
2117
+ try {
2118
+ return new import_core.Enclave(options);
2119
+ } catch (error) {
2120
+ if (!options.toolNamespaces || !(error instanceof TypeError) || !error.message.startsWith("Invalid toolNamespaces")) {
2121
+ throw error;
2122
+ }
2123
+ return new import_core.Enclave({ ...options, toolNamespaces: void 0 });
2124
+ }
2125
+ }
1873
2126
 
1874
2127
  // plugins/plugin-codecall/src/tools/search.tool.ts
1875
2128
  var import_sdk4 = require("@frontmcp/sdk");
@@ -1930,7 +2183,10 @@ var SearchTool = class extends import_sdk4.ToolContext {
1930
2183
  const audit = this.tryGet(AuditLoggerService);
1931
2184
  const executionId = audit ? audit.generateExecutionId() : "";
1932
2185
  const startedAt = Date.now();
1933
- const nonExistentExcludedTools = excludeToolNames.filter((toolName) => !searchService.hasTool(toolName));
2186
+ const surface = (0, import_sdk4.getCallSurface)();
2187
+ const nonExistentExcludedTools = excludeToolNames.filter(
2188
+ (toolName) => !searchService.hasTool(toolName, surface)
2189
+ );
1934
2190
  if (nonExistentExcludedTools.length > 0) {
1935
2191
  warnings.push({
1936
2192
  type: "excluded_tool_not_found",
@@ -1944,7 +2200,8 @@ var SearchTool = class extends import_sdk4.ToolContext {
1944
2200
  const searchResults = await searchService.search(query, {
1945
2201
  topK,
1946
2202
  appIds,
1947
- excludeToolNames
2203
+ excludeToolNames,
2204
+ surface
1948
2205
  });
1949
2206
  for (const result of searchResults) {
1950
2207
  if (result.relevanceScore < minRelevanceScore) {
@@ -1989,7 +2246,7 @@ var SearchTool = class extends import_sdk4.ToolContext {
1989
2246
  return {
1990
2247
  tools,
1991
2248
  warnings,
1992
- totalAvailableTools: searchService.getTotalCount()
2249
+ totalAvailableTools: searchService.getTotalCount(surface)
1993
2250
  };
1994
2251
  }
1995
2252
  };
@@ -2340,11 +2597,10 @@ function getSanitizedMessage(code, toolName, rawMessage) {
2340
2597
  }
2341
2598
  }
2342
2599
  function sanitizeValidationMessage(message) {
2343
- let sanitized = message.replace(/(?:\/[\w.-]+)+|(?:[A-Za-z]:\\[\w\\.-]+)+/g, "[path]");
2600
+ let sanitized = toClientErrorMessage(message);
2344
2601
  sanitized = sanitized.replace(/\bat line \d+/gi, "");
2345
2602
  sanitized = sanitized.replace(/\bline \d+/gi, "");
2346
2603
  sanitized = sanitized.replace(/:\d+:\d+/g, "");
2347
- sanitized = sanitized.replace(/\n\s*at .*/g, "");
2348
2604
  if (sanitized.length > 200) {
2349
2605
  sanitized = sanitized.substring(0, 200) + "...";
2350
2606
  }
@@ -2382,75 +2638,6 @@ function assertNotSelfReference(toolName) {
2382
2638
  }
2383
2639
  }
2384
2640
 
2385
- // plugins/plugin-codecall/src/utils/build-tool-namespaces.ts
2386
- var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2387
- var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2388
- var RESERVED_NAMESPACES = /* @__PURE__ */ new Set([
2389
- "console",
2390
- "Math",
2391
- "JSON",
2392
- "Object",
2393
- "Array",
2394
- "String",
2395
- "Number",
2396
- "Boolean",
2397
- "Promise",
2398
- "Symbol",
2399
- "Map",
2400
- "Set",
2401
- "WeakMap",
2402
- "WeakSet",
2403
- "Date",
2404
- "Error",
2405
- "RegExp",
2406
- "globalThis",
2407
- "global",
2408
- "window",
2409
- "self",
2410
- "undefined",
2411
- "null",
2412
- "true",
2413
- "false",
2414
- "callTool",
2415
- "getTool",
2416
- "mcpLog",
2417
- "mcpNotify"
2418
- ]);
2419
- function buildToolNamespaces(tools, callTool) {
2420
- const namespaces = /* @__PURE__ */ Object.create(null);
2421
- const skipped = [];
2422
- for (const tool of tools) {
2423
- const name = tool?.name;
2424
- if (typeof name !== "string" || name.length === 0) continue;
2425
- const dot = name.indexOf(".");
2426
- if (dot <= 0 || dot === name.length - 1) {
2427
- skipped.push({ name, reason: "no-namespace-prefix" });
2428
- continue;
2429
- }
2430
- const ns = name.slice(0, dot);
2431
- const method = name.slice(dot + 1);
2432
- if (!IDENT_RE.test(ns) || !IDENT_RE.test(method)) {
2433
- skipped.push({ name, reason: "invalid-identifier" });
2434
- continue;
2435
- }
2436
- if (PROTOTYPE_KEYS.has(ns)) {
2437
- skipped.push({ name, reason: "prototype-key" });
2438
- continue;
2439
- }
2440
- if (RESERVED_NAMESPACES.has(ns)) {
2441
- skipped.push({ name, reason: "reserved-namespace" });
2442
- continue;
2443
- }
2444
- const bucket = namespaces[ns] ?? (namespaces[ns] = /* @__PURE__ */ Object.create(null));
2445
- if (Object.prototype.hasOwnProperty.call(bucket, method)) {
2446
- skipped.push({ name, reason: "duplicate-method" });
2447
- continue;
2448
- }
2449
- bucket[method] = (input, options) => callTool(name, input, options);
2450
- }
2451
- return { namespaces, skipped };
2452
- }
2453
-
2454
2641
  // plugins/plugin-codecall/src/utils/describe.utils.ts
2455
2642
  var INTENT_PATTERNS = {
2456
2643
  create: /^(create|add|new|insert|make|append|register|generate|produce|build|construct|provision|instantiate|define|compose|draft)/i,
@@ -2812,12 +2999,13 @@ var DescribeTool = class extends import_sdk7.ToolContext {
2812
2999
  const tools = [];
2813
3000
  const notFound = [];
2814
3001
  const config = this.get(CodeCallConfig);
3002
+ const surface = (0, import_sdk7.getCallSurface)();
2815
3003
  for (const toolName of toolNames) {
2816
3004
  if (isBlockedSelfReference(toolName)) {
2817
3005
  notFound.push(toolName);
2818
3006
  continue;
2819
3007
  }
2820
- const access = checkCodeCallToolAccess(this.scope, config, toolName);
3008
+ const access = checkCodeCallToolAccess(this.scope, config, toolName, { surface });
2821
3009
  if (!access.allowed) {
2822
3010
  audit?.logSecurityAccessDenied(executionId, toolName, access.reason);
2823
3011
  notFound.push(toolName);
@@ -3029,6 +3217,7 @@ var runtimeErrorPayloadSchema = import_lazy_zod7.z.object({
3029
3217
  source: import_lazy_zod7.z.literal("script"),
3030
3218
  message: import_lazy_zod7.z.string(),
3031
3219
  name: import_lazy_zod7.z.string().optional(),
3220
+ /** @deprecated Never set: stack traces name server files, so results don't carry them. */
3032
3221
  stack: import_lazy_zod7.z.string().optional()
3033
3222
  });
3034
3223
  var toolErrorPayloadSchema = import_lazy_zod7.z.object({
@@ -3037,6 +3226,7 @@ var toolErrorPayloadSchema = import_lazy_zod7.z.object({
3037
3226
  toolInput: import_lazy_zod7.z.unknown(),
3038
3227
  message: import_lazy_zod7.z.string(),
3039
3228
  code: import_lazy_zod7.z.string().optional(),
3229
+ /** @deprecated Never set: the enclave's error data is not sanitized for clients. */
3040
3230
  details: import_lazy_zod7.z.unknown().optional()
3041
3231
  });
3042
3232
  var timeoutErrorPayloadSchema = import_lazy_zod7.z.object({
@@ -3101,11 +3291,12 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3101
3291
  * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
3102
3292
  * unexamined.
3103
3293
  */
3104
- checkToolPolicy(name) {
3105
- return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name);
3294
+ checkToolPolicy(name, surface) {
3295
+ return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name, { surface });
3106
3296
  }
3107
3297
  async execute(input) {
3108
3298
  const { script, allowedTools } = input;
3299
+ const callerSurface = (0, import_sdk8.getCallSurface)();
3109
3300
  const audit = this.tryGet(AuditLoggerService);
3110
3301
  const executionId = audit ? audit.generateExecutionId() : "";
3111
3302
  const callDepth = 1;
@@ -3118,7 +3309,7 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3118
3309
  audit?.logSecuritySelfReference(executionId, name);
3119
3310
  }
3120
3311
  assertNotSelfReference(name);
3121
- const decision = this.checkToolPolicy(name);
3312
+ const decision = this.checkToolPolicy(name, callerSurface);
3122
3313
  if (!decision.allowed) {
3123
3314
  audit?.logSecurityAccessDenied(executionId, name, decision.reason);
3124
3315
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
@@ -3147,7 +3338,8 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3147
3338
  }
3148
3339
  };
3149
3340
  const ctx = {
3150
- authInfo: this.authInfo
3341
+ authInfo: this.authInfo,
3342
+ surface: callerSurface
3151
3343
  };
3152
3344
  const mcpResult = await this.scope.runFlow("tools:call-tool", { request, ctx });
3153
3345
  if (!mcpResult) {
@@ -3187,7 +3379,7 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3187
3379
  audit?.logSecuritySelfReference(executionId, name);
3188
3380
  return void 0;
3189
3381
  }
3190
- const introspectionAccess = this.checkToolPolicy(name);
3382
+ const introspectionAccess = this.checkToolPolicy(name, callerSurface);
3191
3383
  if (!introspectionAccess.allowed) {
3192
3384
  audit?.logSecurityAccessDenied(executionId, name, introspectionAccess.reason);
3193
3385
  return void 0;
@@ -3217,8 +3409,8 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3217
3409
  const callableTools = this.scope.tools.getTools(true).filter(
3218
3410
  (tool) => checkCodeCallToolPolicy(toCodeCallPolicyTool(tool, void 0, this.scope), policyConfig).allowed
3219
3411
  );
3220
- const { namespaces, skipped } = buildToolNamespaces(callableTools, environment.callTool);
3221
- environment.namespaces = namespaces;
3412
+ const { namespaces, skipped } = buildToolNamespaces(callableTools);
3413
+ environment.toolNamespaces = namespaces;
3222
3414
  if (skipped.length > 0) {
3223
3415
  this.logger?.debug?.("codecall: tools skipped during namespace generation", {
3224
3416
  count: skipped.length,
@@ -3250,6 +3442,16 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3250
3442
  }
3251
3443
  if (!executionResult.success) {
3252
3444
  const error = executionResult.error;
3445
+ if (error.code === "SYNTAX_ERROR") {
3446
+ audit?.logExecutionFailure(executionId, script, durationMs, error.message);
3447
+ return {
3448
+ status: "syntax_error",
3449
+ error: {
3450
+ message: toClientErrorMessage(error.message) || "Syntax error in script",
3451
+ ...error.location ? { location: error.location } : {}
3452
+ }
3453
+ };
3454
+ }
3253
3455
  if (error.code === "VALIDATION_ERROR" || error.name === "ValidationError") {
3254
3456
  audit?.logSecurityAstBlocked(executionId, error.blockedPatterns?.join(", ") ?? "UNKNOWN", error.message);
3255
3457
  audit?.logExecutionFailure(executionId, script, durationMs, error.message);
@@ -3269,9 +3471,9 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3269
3471
  source: "tool",
3270
3472
  toolName: error.toolName,
3271
3473
  toolInput: error.toolInput,
3272
- message: error.message,
3273
- code: error.code,
3274
- details: error.details
3474
+ message: toClientErrorMessage(error.message),
3475
+ code: error.code
3476
+ // No `details`: the enclave's error data is not sanitized for clients.
3275
3477
  }
3276
3478
  };
3277
3479
  }
@@ -3280,9 +3482,8 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3280
3482
  status: "runtime_error",
3281
3483
  error: {
3282
3484
  source: "script",
3283
- message: error.message,
3284
- name: error.name,
3285
- stack: error.stack
3485
+ message: toClientErrorMessage(error.message),
3486
+ name: error.name
3286
3487
  }
3287
3488
  };
3288
3489
  }
@@ -3296,13 +3497,12 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3296
3497
  const errorName = error instanceof Error ? error.name : "Error";
3297
3498
  const errorMessage = error instanceof Error ? error.message : String(error);
3298
3499
  audit?.logExecutionFailure(executionId, script, elapsed(), errorMessage);
3299
- const errorStack = error instanceof Error ? error.stack : void 0;
3300
3500
  const errorLoc = error.loc;
3301
3501
  if (errorName === "SyntaxError" || errorMessage?.includes("syntax")) {
3302
3502
  return {
3303
3503
  status: "syntax_error",
3304
3504
  error: {
3305
- message: errorMessage || "Syntax error in script",
3505
+ message: toClientErrorMessage(errorMessage) || "Syntax error in script",
3306
3506
  location: errorLoc ? { line: errorLoc.line, column: errorLoc.column } : void 0
3307
3507
  }
3308
3508
  };
@@ -3311,9 +3511,8 @@ var ExecuteTool = class extends import_sdk8.ToolContext {
3311
3511
  status: "runtime_error",
3312
3512
  error: {
3313
3513
  source: "script",
3314
- message: errorMessage || "An unexpected error occurred during script execution",
3315
- name: errorName,
3316
- stack: errorStack
3514
+ message: toClientErrorMessage(errorMessage) || "An unexpected error occurred during script execution",
3515
+ name: errorName
3317
3516
  }
3318
3517
  };
3319
3518
  }
@@ -3382,7 +3581,11 @@ var InvokeTool = class extends import_sdk9.ToolContext {
3382
3581
  `Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
3383
3582
  );
3384
3583
  }
3385
- const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
3584
+ const surface = (0, import_sdk9.getCallSurface)();
3585
+ const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, {
3586
+ directCall: true,
3587
+ surface
3588
+ });
3386
3589
  if (!decision.allowed) {
3387
3590
  audit?.logSecurityAccessDenied(executionId, toolName, decision.reason);
3388
3591
  return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
@@ -3395,7 +3598,8 @@ var InvokeTool = class extends import_sdk9.ToolContext {
3395
3598
  }
3396
3599
  };
3397
3600
  const ctx = {
3398
- authInfo: this.authInfo
3601
+ authInfo: this.authInfo,
3602
+ surface
3399
3603
  };
3400
3604
  const result = await this.scope.runFlow("tools:call-tool", { request, ctx });
3401
3605
  if (!result) {