@frontmcp/plugin-codecall 1.8.1 → 1.8.3

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
  /**
@@ -1651,6 +1735,135 @@ var ToolSearchService = class _ToolSearchService {
1651
1735
  // plugins/plugin-codecall/src/services/enclave.service.ts
1652
1736
  import { Enclave } from "@enclave-vm/core";
1653
1737
  import { Provider as Provider3, ProviderScope as ProviderScope3 } from "@frontmcp/sdk";
1738
+
1739
+ // plugins/plugin-codecall/src/utils/build-tool-namespaces.ts
1740
+ var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1741
+ var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1742
+ var RESERVED_NAMESPACES = /* @__PURE__ */ new Set([
1743
+ "console",
1744
+ "Math",
1745
+ "JSON",
1746
+ "Object",
1747
+ "Array",
1748
+ "String",
1749
+ "Number",
1750
+ "Boolean",
1751
+ "Promise",
1752
+ "Symbol",
1753
+ "Map",
1754
+ "Set",
1755
+ "WeakMap",
1756
+ "WeakSet",
1757
+ "Date",
1758
+ "Error",
1759
+ "RegExp",
1760
+ "globalThis",
1761
+ "global",
1762
+ "window",
1763
+ "self",
1764
+ "undefined",
1765
+ "null",
1766
+ "true",
1767
+ "false",
1768
+ "callTool",
1769
+ "getTool",
1770
+ "mcpLog",
1771
+ "mcpNotify"
1772
+ ]);
1773
+ var UNBINDABLE_NAMESPACES = new Set(
1774
+ [
1775
+ // Reserved words and strict-mode restricted bindings
1776
+ "break case catch class const continue debugger default delete do else enum export extends finally for",
1777
+ "function if import in instanceof new return super switch this throw try typeof var void while with yield",
1778
+ "let static implements interface package private protected public await eval arguments NaN Infinity",
1779
+ // AgentScript globals
1780
+ "parallel isNaN isFinite parseInt parseFloat encodeURI encodeURIComponent decodeURI decodeURIComponent",
1781
+ // Identifiers the AgentScript validator refuses
1782
+ "process require module exports Buffer Function AsyncFunction GeneratorFunction Reflect Proxy TypeError",
1783
+ "ReferenceError SyntaxError RangeError URIError EvalError AggregateError fetch XMLHttpRequest WebSocket",
1784
+ "localStorage sessionStorage indexedDB crypto performance structuredClone AbortController AbortSignal",
1785
+ "MessageChannel MessagePort BroadcastChannel TextEncoder TextDecoder Intl setTimeout setInterval",
1786
+ "setImmediate clearTimeout clearInterval clearImmediate queueMicrotask WebAssembly Worker SharedWorker",
1787
+ "ServiceWorker WeakRef FinalizationRegistry BigInt Atomics SharedArrayBuffer ArrayBuffer DataView"
1788
+ ].join(" ").split(" ")
1789
+ );
1790
+ function buildToolNamespaces(tools) {
1791
+ const namespaces = /* @__PURE__ */ Object.create(null);
1792
+ const skipped = [];
1793
+ for (const tool of tools) {
1794
+ const name = tool?.name;
1795
+ if (typeof name !== "string" || name.length === 0) continue;
1796
+ const dot = name.indexOf(".");
1797
+ if (dot <= 0 || dot === name.length - 1) {
1798
+ skipped.push({ name, reason: "no-namespace-prefix" });
1799
+ continue;
1800
+ }
1801
+ const ns = name.slice(0, dot);
1802
+ const method = name.slice(dot + 1);
1803
+ if (!IDENT_RE.test(ns) || !IDENT_RE.test(method)) {
1804
+ skipped.push({ name, reason: "invalid-identifier" });
1805
+ continue;
1806
+ }
1807
+ if (PROTOTYPE_KEYS.has(ns) || PROTOTYPE_KEYS.has(method)) {
1808
+ skipped.push({ name, reason: "prototype-key" });
1809
+ continue;
1810
+ }
1811
+ if (RESERVED_NAMESPACES.has(ns) || UNBINDABLE_NAMESPACES.has(ns) || ns.startsWith("__")) {
1812
+ skipped.push({ name, reason: "reserved-namespace" });
1813
+ continue;
1814
+ }
1815
+ const bucket = namespaces[ns] ?? (namespaces[ns] = /* @__PURE__ */ Object.create(null));
1816
+ if (Object.prototype.hasOwnProperty.call(bucket, method)) {
1817
+ skipped.push({ name, reason: "duplicate-method" });
1818
+ continue;
1819
+ }
1820
+ bucket[method] = name;
1821
+ }
1822
+ return { namespaces, skipped };
1823
+ }
1824
+ var NAMESPACE_CALL_HELPER = "__codecallNamespaceCall";
1825
+ var NAMESPACE_CALL_HELPER_SOURCE = `const ${NAMESPACE_CALL_HELPER} = async (toolName, run, options) => { if (options && options.throwOnError === false) { try { return { success: true, data: await run() }; } catch (error) { return { success: false, error: { message: error && error.message, toolName: toolName } }; } } return await run(); };`;
1826
+ function mentionsIdentifier(script, identifier) {
1827
+ let from = 0;
1828
+ for (; ; ) {
1829
+ const at = script.indexOf(identifier, from);
1830
+ if (at === -1) return false;
1831
+ from = at + 1;
1832
+ const before = at > 0 ? script[at - 1] : "";
1833
+ const after = script[at + identifier.length] ?? "";
1834
+ if (/[A-Za-z0-9_$'"`]/.test(before) || /[A-Za-z0-9_$]/.test(after)) continue;
1835
+ if (before === "." && script.slice(Math.max(0, at - 3), at) !== "...") continue;
1836
+ return true;
1837
+ }
1838
+ }
1839
+ function wrapScriptWithToolNamespaces(script, namespaces) {
1840
+ if (!namespaces) return script;
1841
+ const declarations = [];
1842
+ for (const ns of Object.keys(namespaces)) {
1843
+ if (!IDENT_RE.test(ns) || RESERVED_NAMESPACES.has(ns) || UNBINDABLE_NAMESPACES.has(ns) || ns.startsWith("__")) {
1844
+ continue;
1845
+ }
1846
+ if (PROTOTYPE_KEYS.has(ns) || !mentionsIdentifier(script, ns)) continue;
1847
+ const methods = namespaces[ns];
1848
+ const entries = [];
1849
+ for (const method of Object.keys(methods)) {
1850
+ const toolName = methods[method];
1851
+ if (!IDENT_RE.test(method) || PROTOTYPE_KEYS.has(method) || toolName !== `${ns}.${method}`) continue;
1852
+ const literal = JSON.stringify(toolName);
1853
+ entries.push(
1854
+ `${JSON.stringify(method)}: (input, options) => ${NAMESPACE_CALL_HELPER}(${literal}, () => callTool(${literal}, input === undefined ? {} : input), options)`
1855
+ );
1856
+ }
1857
+ if (entries.length > 0) {
1858
+ declarations.push(`const ${ns} = { ${entries.join(", ")} };`);
1859
+ }
1860
+ }
1861
+ if (declarations.length === 0) return script;
1862
+ return `${NAMESPACE_CALL_HELPER_SOURCE} ${declarations.join(" ")} return await (async () => {${script}
1863
+ })();`;
1864
+ }
1865
+
1866
+ // plugins/plugin-codecall/src/services/enclave.service.ts
1654
1867
  var ScriptTooLargeError = class extends Error {
1655
1868
  code = "SCRIPT_TOO_LARGE";
1656
1869
  scriptLength;
@@ -1699,10 +1912,7 @@ var EnclaveService = class {
1699
1912
  maxResolvedSize: this.sidecarOptions.maxResolvedSize,
1700
1913
  allowComposites: this.sidecarOptions.allowComposites
1701
1914
  } : 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
- );
1915
+ const source = wrapScriptWithToolNamespaces(code, environment.toolNamespaces);
1706
1916
  const enclave = new Enclave({
1707
1917
  timeout: this.vmOptions.timeoutMs,
1708
1918
  maxToolCalls: this.vmOptions.maxSteps || 100,
@@ -1716,11 +1926,6 @@ var EnclaveService = class {
1716
1926
  // Allow functions in globals since we intentionally provide getTool, mcpLog, mcpNotify, and console
1717
1927
  allowFunctionsInGlobals: true,
1718
1928
  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
1929
  // Provide getTool as a custom global
1725
1930
  getTool: environment.getTool,
1726
1931
  // Provide logging functions if available
@@ -1743,7 +1948,7 @@ var EnclaveService = class {
1743
1948
  }
1744
1949
  });
1745
1950
  try {
1746
- const result = await enclave.run(code);
1951
+ const result = await enclave.run(source);
1747
1952
  return this.mapEnclaveResult(result, logs);
1748
1953
  } finally {
1749
1954
  enclave.dispose();
@@ -1833,7 +2038,7 @@ EnclaveService = __decorateClass([
1833
2038
  ], EnclaveService);
1834
2039
 
1835
2040
  // plugins/plugin-codecall/src/tools/search.tool.ts
1836
- import { Tool, ToolContext } from "@frontmcp/sdk";
2041
+ import { getCallSurface, Tool, ToolContext } from "@frontmcp/sdk";
1837
2042
 
1838
2043
  // plugins/plugin-codecall/src/tools/search.schema.ts
1839
2044
  import { z as z2 } from "@frontmcp/lazy-zod";
@@ -1891,7 +2096,10 @@ var SearchTool = class extends ToolContext {
1891
2096
  const audit = this.tryGet(AuditLoggerService);
1892
2097
  const executionId = audit ? audit.generateExecutionId() : "";
1893
2098
  const startedAt = Date.now();
1894
- const nonExistentExcludedTools = excludeToolNames.filter((toolName) => !searchService.hasTool(toolName));
2099
+ const surface = getCallSurface();
2100
+ const nonExistentExcludedTools = excludeToolNames.filter(
2101
+ (toolName) => !searchService.hasTool(toolName, surface)
2102
+ );
1895
2103
  if (nonExistentExcludedTools.length > 0) {
1896
2104
  warnings.push({
1897
2105
  type: "excluded_tool_not_found",
@@ -1905,7 +2113,8 @@ var SearchTool = class extends ToolContext {
1905
2113
  const searchResults = await searchService.search(query, {
1906
2114
  topK,
1907
2115
  appIds,
1908
- excludeToolNames
2116
+ excludeToolNames,
2117
+ surface
1909
2118
  });
1910
2119
  for (const result of searchResults) {
1911
2120
  if (result.relevanceScore < minRelevanceScore) {
@@ -1950,7 +2159,7 @@ var SearchTool = class extends ToolContext {
1950
2159
  return {
1951
2160
  tools,
1952
2161
  warnings,
1953
- totalAvailableTools: searchService.getTotalCount()
2162
+ totalAvailableTools: searchService.getTotalCount(surface)
1954
2163
  };
1955
2164
  }
1956
2165
  };
@@ -2263,7 +2472,7 @@ SearchKnowledgeTool = __decorateClass([
2263
2472
 
2264
2473
  // plugins/plugin-codecall/src/tools/describe.tool.ts
2265
2474
  import { toJSONSchema, z as z6, ZodType } from "@frontmcp/lazy-zod";
2266
- import { Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
2475
+ import { getCallSurface as getCallSurface2, Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
2267
2476
 
2268
2477
  // plugins/plugin-codecall/src/errors/tool-call.errors.ts
2269
2478
  var TOOL_CALL_ERROR_CODES = {
@@ -2301,11 +2510,10 @@ function getSanitizedMessage(code, toolName, rawMessage) {
2301
2510
  }
2302
2511
  }
2303
2512
  function sanitizeValidationMessage(message) {
2304
- let sanitized = message.replace(/(?:\/[\w.-]+)+|(?:[A-Za-z]:\\[\w\\.-]+)+/g, "[path]");
2513
+ let sanitized = toClientErrorMessage(message);
2305
2514
  sanitized = sanitized.replace(/\bat line \d+/gi, "");
2306
2515
  sanitized = sanitized.replace(/\bline \d+/gi, "");
2307
2516
  sanitized = sanitized.replace(/:\d+:\d+/g, "");
2308
- sanitized = sanitized.replace(/\n\s*at .*/g, "");
2309
2517
  if (sanitized.length > 200) {
2310
2518
  sanitized = sanitized.substring(0, 200) + "...";
2311
2519
  }
@@ -2343,75 +2551,6 @@ function assertNotSelfReference(toolName) {
2343
2551
  }
2344
2552
  }
2345
2553
 
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
2554
  // plugins/plugin-codecall/src/utils/describe.utils.ts
2416
2555
  var INTENT_PATTERNS = {
2417
2556
  create: /^(create|add|new|insert|make|append|register|generate|produce|build|construct|provision|instantiate|define|compose|draft)/i,
@@ -2773,12 +2912,13 @@ var DescribeTool = class extends ToolContext4 {
2773
2912
  const tools = [];
2774
2913
  const notFound = [];
2775
2914
  const config = this.get(CodeCallConfig);
2915
+ const surface = getCallSurface2();
2776
2916
  for (const toolName of toolNames) {
2777
2917
  if (isBlockedSelfReference(toolName)) {
2778
2918
  notFound.push(toolName);
2779
2919
  continue;
2780
2920
  }
2781
- const access = checkCodeCallToolAccess(this.scope, config, toolName);
2921
+ const access = checkCodeCallToolAccess(this.scope, config, toolName, { surface });
2782
2922
  if (!access.allowed) {
2783
2923
  audit?.logSecurityAccessDenied(executionId, toolName, access.reason);
2784
2924
  notFound.push(toolName);
@@ -2936,7 +3076,7 @@ DescribeTool = __decorateClass([
2936
3076
  ], DescribeTool);
2937
3077
 
2938
3078
  // plugins/plugin-codecall/src/tools/execute.tool.ts
2939
- import { Tool as Tool5, ToolContext as ToolContext5 } from "@frontmcp/sdk";
3079
+ import { getCallSurface as getCallSurface3, Tool as Tool5, ToolContext as ToolContext5 } from "@frontmcp/sdk";
2940
3080
 
2941
3081
  // plugins/plugin-codecall/src/tools/execute.schema.ts
2942
3082
  import { z as z7 } from "@frontmcp/lazy-zod";
@@ -2990,6 +3130,7 @@ var runtimeErrorPayloadSchema = z7.object({
2990
3130
  source: z7.literal("script"),
2991
3131
  message: z7.string(),
2992
3132
  name: z7.string().optional(),
3133
+ /** @deprecated Never set: stack traces name server files, so results don't carry them. */
2993
3134
  stack: z7.string().optional()
2994
3135
  });
2995
3136
  var toolErrorPayloadSchema = z7.object({
@@ -2998,6 +3139,7 @@ var toolErrorPayloadSchema = z7.object({
2998
3139
  toolInput: z7.unknown(),
2999
3140
  message: z7.string(),
3000
3141
  code: z7.string().optional(),
3142
+ /** @deprecated Never set: the enclave's error data is not sanitized for clients. */
3001
3143
  details: z7.unknown().optional()
3002
3144
  });
3003
3145
  var timeoutErrorPayloadSchema = z7.object({
@@ -3062,11 +3204,12 @@ var ExecuteTool = class extends ToolContext5 {
3062
3204
  * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
3063
3205
  * unexamined.
3064
3206
  */
3065
- checkToolPolicy(name) {
3066
- return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name);
3207
+ checkToolPolicy(name, surface) {
3208
+ return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name, { surface });
3067
3209
  }
3068
3210
  async execute(input) {
3069
3211
  const { script, allowedTools } = input;
3212
+ const callerSurface = getCallSurface3();
3070
3213
  const audit = this.tryGet(AuditLoggerService);
3071
3214
  const executionId = audit ? audit.generateExecutionId() : "";
3072
3215
  const callDepth = 1;
@@ -3079,7 +3222,7 @@ var ExecuteTool = class extends ToolContext5 {
3079
3222
  audit?.logSecuritySelfReference(executionId, name);
3080
3223
  }
3081
3224
  assertNotSelfReference(name);
3082
- const decision = this.checkToolPolicy(name);
3225
+ const decision = this.checkToolPolicy(name, callerSurface);
3083
3226
  if (!decision.allowed) {
3084
3227
  audit?.logSecurityAccessDenied(executionId, name, decision.reason);
3085
3228
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
@@ -3108,7 +3251,8 @@ var ExecuteTool = class extends ToolContext5 {
3108
3251
  }
3109
3252
  };
3110
3253
  const ctx = {
3111
- authInfo: this.authInfo
3254
+ authInfo: this.authInfo,
3255
+ surface: callerSurface
3112
3256
  };
3113
3257
  const mcpResult = await this.scope.runFlow("tools:call-tool", { request, ctx });
3114
3258
  if (!mcpResult) {
@@ -3148,7 +3292,7 @@ var ExecuteTool = class extends ToolContext5 {
3148
3292
  audit?.logSecuritySelfReference(executionId, name);
3149
3293
  return void 0;
3150
3294
  }
3151
- const introspectionAccess = this.checkToolPolicy(name);
3295
+ const introspectionAccess = this.checkToolPolicy(name, callerSurface);
3152
3296
  if (!introspectionAccess.allowed) {
3153
3297
  audit?.logSecurityAccessDenied(executionId, name, introspectionAccess.reason);
3154
3298
  return void 0;
@@ -3178,8 +3322,8 @@ var ExecuteTool = class extends ToolContext5 {
3178
3322
  const callableTools = this.scope.tools.getTools(true).filter(
3179
3323
  (tool) => checkCodeCallToolPolicy(toCodeCallPolicyTool(tool, void 0, this.scope), policyConfig).allowed
3180
3324
  );
3181
- const { namespaces, skipped } = buildToolNamespaces(callableTools, environment.callTool);
3182
- environment.namespaces = namespaces;
3325
+ const { namespaces, skipped } = buildToolNamespaces(callableTools);
3326
+ environment.toolNamespaces = namespaces;
3183
3327
  if (skipped.length > 0) {
3184
3328
  this.logger?.debug?.("codecall: tools skipped during namespace generation", {
3185
3329
  count: skipped.length,
@@ -3230,9 +3374,9 @@ var ExecuteTool = class extends ToolContext5 {
3230
3374
  source: "tool",
3231
3375
  toolName: error.toolName,
3232
3376
  toolInput: error.toolInput,
3233
- message: error.message,
3234
- code: error.code,
3235
- details: error.details
3377
+ message: toClientErrorMessage(error.message),
3378
+ code: error.code
3379
+ // No `details`: the enclave's error data is not sanitized for clients.
3236
3380
  }
3237
3381
  };
3238
3382
  }
@@ -3241,9 +3385,8 @@ var ExecuteTool = class extends ToolContext5 {
3241
3385
  status: "runtime_error",
3242
3386
  error: {
3243
3387
  source: "script",
3244
- message: error.message,
3245
- name: error.name,
3246
- stack: error.stack
3388
+ message: toClientErrorMessage(error.message),
3389
+ name: error.name
3247
3390
  }
3248
3391
  };
3249
3392
  }
@@ -3257,13 +3400,12 @@ var ExecuteTool = class extends ToolContext5 {
3257
3400
  const errorName = error instanceof Error ? error.name : "Error";
3258
3401
  const errorMessage = error instanceof Error ? error.message : String(error);
3259
3402
  audit?.logExecutionFailure(executionId, script, elapsed(), errorMessage);
3260
- const errorStack = error instanceof Error ? error.stack : void 0;
3261
3403
  const errorLoc = error.loc;
3262
3404
  if (errorName === "SyntaxError" || errorMessage?.includes("syntax")) {
3263
3405
  return {
3264
3406
  status: "syntax_error",
3265
3407
  error: {
3266
- message: errorMessage || "Syntax error in script",
3408
+ message: toClientErrorMessage(errorMessage) || "Syntax error in script",
3267
3409
  location: errorLoc ? { line: errorLoc.line, column: errorLoc.column } : void 0
3268
3410
  }
3269
3411
  };
@@ -3272,9 +3414,8 @@ var ExecuteTool = class extends ToolContext5 {
3272
3414
  status: "runtime_error",
3273
3415
  error: {
3274
3416
  source: "script",
3275
- message: errorMessage || "An unexpected error occurred during script execution",
3276
- name: errorName,
3277
- stack: errorStack
3417
+ message: toClientErrorMessage(errorMessage) || "An unexpected error occurred during script execution",
3418
+ name: errorName
3278
3419
  }
3279
3420
  };
3280
3421
  }
@@ -3299,7 +3440,7 @@ ExecuteTool = __decorateClass([
3299
3440
  ], ExecuteTool);
3300
3441
 
3301
3442
  // plugins/plugin-codecall/src/tools/invoke.tool.ts
3302
- import { Tool as Tool6, ToolContext as ToolContext6 } from "@frontmcp/sdk";
3443
+ import { getCallSurface as getCallSurface4, Tool as Tool6, ToolContext as ToolContext6 } from "@frontmcp/sdk";
3303
3444
 
3304
3445
  // plugins/plugin-codecall/src/tools/invoke.schema.ts
3305
3446
  import { z as z8 } from "@frontmcp/lazy-zod";
@@ -3343,7 +3484,11 @@ var InvokeTool = class extends ToolContext6 {
3343
3484
  `Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
3344
3485
  );
3345
3486
  }
3346
- const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
3487
+ const surface = getCallSurface4();
3488
+ const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, {
3489
+ directCall: true,
3490
+ surface
3491
+ });
3347
3492
  if (!decision.allowed) {
3348
3493
  audit?.logSecurityAccessDenied(executionId, toolName, decision.reason);
3349
3494
  return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
@@ -3356,7 +3501,8 @@ var InvokeTool = class extends ToolContext6 {
3356
3501
  }
3357
3502
  };
3358
3503
  const ctx = {
3359
- authInfo: this.authInfo
3504
+ authInfo: this.authInfo,
3505
+ surface
3360
3506
  };
3361
3507
  const result = await this.scope.runFlow("tools:call-tool", { request, ctx });
3362
3508
  if (!result) {