@frontmcp/plugin-codecall 1.8.7 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.mjs CHANGED
@@ -14,9 +14,12 @@ import CachePlugin from "@frontmcp/plugin-cache";
14
14
  import {
15
15
  DynamicPlugin,
16
16
  FrontMcpLogger,
17
+ isEntryGatedBy,
17
18
  ListToolsHook,
18
19
  Plugin,
19
- ScopeEntry
20
+ ScopeEntry,
21
+ ToolHook,
22
+ ToolNotFoundError
20
23
  } from "@frontmcp/sdk";
21
24
 
22
25
  // plugins/plugin-codecall/src/codecall.types.ts
@@ -372,8 +375,186 @@ function presetDefaults(preset) {
372
375
  }
373
376
  }
374
377
 
375
- // plugins/plugin-codecall/src/services/audit-logger.service.ts
376
- import { Provider as Provider2, ProviderScope as ProviderScope2 } from "@frontmcp/sdk";
378
+ // plugins/plugin-codecall/src/security/codecall-dispatch.ts
379
+ import { getRunningTool } from "@frontmcp/sdk";
380
+ var CODECALL_DISPATCH = /* @__PURE__ */ Symbol("frontmcp.codecall.dispatch");
381
+ function asCodeCallDispatch(ctx) {
382
+ return Object.defineProperty(ctx, CODECALL_DISPATCH, { value: true, enumerable: false });
383
+ }
384
+ function isInProcessDispatch(ctx) {
385
+ if (getRunningTool()) return true;
386
+ if (typeof ctx !== "object" || ctx === null) return false;
387
+ const marks = ctx;
388
+ return marks[CODECALL_DISPATCH] === true || marks.internalCall === true;
389
+ }
390
+
391
+ // plugins/plugin-codecall/src/security/codecall-tool-policy.ts
392
+ var BLOCKED_NAMESPACE_PATTERNS = Object.freeze([/^system:/, /^internal:/, /^__/]);
393
+ function isOfferedToCaller(availableWhen, surface) {
394
+ const offered = availableWhen?.surface;
395
+ return offered === void 0 || surface === void 0 || offered.includes(surface);
396
+ }
397
+ var ALLOWED = { allowed: true };
398
+ function deny(reason) {
399
+ return { allowed: false, reason };
400
+ }
401
+ function codeCallAppIdOf(scope, entry) {
402
+ const lineage = scope?.tools?.lineageOf?.(entry) ?? [];
403
+ const owners = entry.owner ? [...lineage, entry.owner] : lineage;
404
+ return owners.find((owner) => owner.kind === "app")?.id;
405
+ }
406
+ function toCodeCallPolicyTool(entry, requestedName, scope) {
407
+ const { metadata } = entry;
408
+ return {
409
+ name: entry.name || entry.fullName,
410
+ fullName: entry.fullName,
411
+ aliases: requestedName ? [requestedName] : void 0,
412
+ appId: codeCallAppIdOf(scope, entry),
413
+ description: metadata?.description,
414
+ tags: metadata?.tags,
415
+ // `internal` tools refuse every external tools/call, CodeCall's included, so no surface may offer them.
416
+ hidden: metadata?.hideFromDiscovery === true || metadata?.visibility === "hidden" || metadata?.visibility === "internal",
417
+ codecall: metadata?.codecall,
418
+ annotations: metadata?.annotations,
419
+ metadata
420
+ };
421
+ }
422
+ function isPlainData(value) {
423
+ if (Array.isArray(value)) return true;
424
+ const proto = Object.getPrototypeOf(value);
425
+ return proto === Object.prototype || proto === null;
426
+ }
427
+ function readOnlyCopy(value, copies) {
428
+ if (typeof value !== "object" || value === null || !isPlainData(value)) return value;
429
+ const existing = copies.get(value);
430
+ if (existing !== void 0) return existing;
431
+ const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
432
+ copies.set(value, copy);
433
+ for (const key of Reflect.ownKeys(value)) {
434
+ if (!Object.prototype.propertyIsEnumerable.call(value, key)) continue;
435
+ Object.defineProperty(copy, key, {
436
+ value: readOnlyCopy(Reflect.get(value, key), copies),
437
+ enumerable: true,
438
+ writable: true,
439
+ configurable: true
440
+ });
441
+ }
442
+ return Object.freeze(copy);
443
+ }
444
+ function toToolFilterInfo(tool) {
445
+ const copies = /* @__PURE__ */ new WeakMap();
446
+ const annotations = readOnlyCopy(tool.annotations, copies);
447
+ const metadata = tool.metadata ? readOnlyCopy({ ...tool.metadata, ...annotations ? { annotations: tool.annotations } : {} }, copies) : void 0;
448
+ return {
449
+ name: tool.name,
450
+ fullName: tool.fullName,
451
+ appId: tool.appId,
452
+ source: tool.codecall?.source,
453
+ description: tool.description,
454
+ tags: readOnlyCopy(tool.codecall?.tags ?? tool.tags, copies),
455
+ annotations,
456
+ metadata
457
+ };
458
+ }
459
+ function ownNamesOf(tool) {
460
+ return [tool.name, tool.fullName].filter((candidate) => !!candidate);
461
+ }
462
+ function checkCodeCallToolPolicy(tool, config) {
463
+ const { name } = tool;
464
+ const spellings = [...ownNamesOf(tool), ...tool.aliases ?? []];
465
+ if (spellings.some((candidate) => candidate.startsWith("codecall:"))) {
466
+ return deny("CodeCall meta-tools are not callable from CodeCall");
467
+ }
468
+ for (const candidate of spellings) {
469
+ if (BLOCKED_NAMESPACE_PATTERNS.some((pattern) => pattern.test(candidate))) {
470
+ return deny(`Tool "${candidate}" is in a namespace CodeCall never calls`);
471
+ }
472
+ }
473
+ if (tool.hidden === true) {
474
+ return deny(`Tool "${name}" is hidden from discovery`);
475
+ }
476
+ const enabled = tool.codecall?.enabledInCodeCall;
477
+ switch (config.mode) {
478
+ case "codecall_only":
479
+ case "metadata_driven":
480
+ if (enabled === false) {
481
+ return deny(`Tool "${name}" sets enabledInCodeCall: false`);
482
+ }
483
+ break;
484
+ case "codecall_opt_in":
485
+ if (enabled !== true) {
486
+ return deny(`Tool "${name}" has not opted in to CodeCall`);
487
+ }
488
+ break;
489
+ default:
490
+ return deny(`Unknown CodeCall mode: ${String(config.mode)}`);
491
+ }
492
+ if (config.includeTools && !config.includeTools(toToolFilterInfo(tool))) {
493
+ return deny(`Tool "${name}" is excluded by the includeTools filter`);
494
+ }
495
+ return ALLOWED;
496
+ }
497
+ function denyUnknownTool(name) {
498
+ return deny(`Tool "${name}" is not available through CodeCall`);
499
+ }
500
+ function resolveCodeCallTool(scope, name) {
501
+ const typedScope = scope;
502
+ const alias = /[-_]/.test(name) ? name.includes("_") ? name.replace(/_/g, "-") : name.replace(/-/g, "_") : void 0;
503
+ const candidates = alias ? [name, alias] : [name];
504
+ const matches = (entry) => candidates.includes(entry.fullName) || candidates.includes(entry.name);
505
+ const local = typedScope.tools?.getTools(true)?.find(matches);
506
+ if (local) return local;
507
+ const registries = typedScope.providers?.getRegistries?.("AppRegistry") ?? [];
508
+ for (const registry of registries) {
509
+ for (const app of registry.getApps()) {
510
+ if (!app.isRemote) continue;
511
+ const remote = app.tools.getTools(true).find(matches);
512
+ if (remote) return remote;
513
+ }
514
+ }
515
+ return void 0;
516
+ }
517
+ function checkDirectCallPolicy(tool, directCalls) {
518
+ if (!directCalls) return ALLOWED;
519
+ if (directCalls.enabled === false) {
520
+ return deny("Direct tool invocation is disabled");
521
+ }
522
+ const { allowedTools } = directCalls;
523
+ if (allowedTools && !ownNamesOf(tool).some((ownName) => allowedTools.includes(ownName))) {
524
+ return deny(`Tool "${tool.name}" is not in the direct-call allowlist`);
525
+ }
526
+ if (directCalls.filter && !directCalls.filter(toToolFilterInfo(tool))) {
527
+ return deny(`Tool "${tool.name}" is excluded by the directCalls filter`);
528
+ }
529
+ return ALLOWED;
530
+ }
531
+ function readCodeCallPolicyConfig(config) {
532
+ return {
533
+ // An absent mode means an unparsed/partial config, not a hostile one: fall back to the
534
+ // schema's own documented default rather than denying every call. An unrecognised mode
535
+ // still fails closed inside the policy's switch.
536
+ mode: config.get("mode") ?? "codecall_only",
537
+ includeTools: config.get("includeTools")
538
+ };
539
+ }
540
+ function checkCodeCallToolAccess(scope, config, name, options = {}) {
541
+ const entry = resolveCodeCallTool(scope, name);
542
+ if (!entry) return denyUnknownTool(name);
543
+ if (!isOfferedToCaller(entry.metadata?.availableWhen, options.surface)) {
544
+ return deny(`Tool "${name}" is not offered on the caller's surface "${options.surface}"`);
545
+ }
546
+ const policyTool = toCodeCallPolicyTool(entry, name, scope);
547
+ const baseDecision = checkCodeCallToolPolicy(policyTool, readCodeCallPolicyConfig(config));
548
+ if (!baseDecision.allowed) return baseDecision;
549
+ if (options.directCall) {
550
+ const directDecision = checkDirectCallPolicy(
551
+ policyTool,
552
+ config.get("directCalls")
553
+ );
554
+ if (!directDecision.allowed) return directDecision;
555
+ }
556
+ return { allowed: true, entry };
557
+ }
377
558
 
378
559
  // plugins/plugin-codecall/src/utils/client-error-message.ts
379
560
  var EMBEDDED_STACK_FRAME_RE = /\n[ \t]*at\s[^\n]*/g;
@@ -413,7 +594,85 @@ function redactAbsolutePaths(text) {
413
594
  return result + redactPaths(text.slice(last));
414
595
  }
415
596
 
597
+ // plugins/plugin-codecall/src/errors/tool-call.errors.ts
598
+ var TOOL_CALL_ERROR_CODES = {
599
+ NOT_FOUND: "NOT_FOUND",
600
+ VALIDATION: "VALIDATION",
601
+ EXECUTION: "EXECUTION",
602
+ TIMEOUT: "TIMEOUT",
603
+ ACCESS_DENIED: "ACCESS_DENIED",
604
+ SELF_REFERENCE: "SELF_REFERENCE"
605
+ };
606
+ function createToolCallError(code, toolName, rawMessage) {
607
+ const sanitizedMessage = getSanitizedMessage(code, toolName, rawMessage);
608
+ return Object.freeze({
609
+ code,
610
+ message: sanitizedMessage,
611
+ toolName
612
+ });
613
+ }
614
+ function getSanitizedMessage(code, toolName, rawMessage) {
615
+ switch (code) {
616
+ case TOOL_CALL_ERROR_CODES.NOT_FOUND:
617
+ return `Tool "${toolName}" was not found`;
618
+ case TOOL_CALL_ERROR_CODES.VALIDATION:
619
+ return rawMessage ? sanitizeValidationMessage(rawMessage) : `Input validation failed for tool "${toolName}"`;
620
+ case TOOL_CALL_ERROR_CODES.EXECUTION:
621
+ return `Tool "${toolName}" execution failed`;
622
+ case TOOL_CALL_ERROR_CODES.TIMEOUT:
623
+ return `Tool "${toolName}" execution timed out`;
624
+ case TOOL_CALL_ERROR_CODES.ACCESS_DENIED:
625
+ return `Access denied for tool "${toolName}"`;
626
+ case TOOL_CALL_ERROR_CODES.SELF_REFERENCE:
627
+ return `Cannot call CodeCall tools from within AgentScript`;
628
+ default:
629
+ return `An error occurred while calling "${toolName}"`;
630
+ }
631
+ }
632
+ function sanitizeValidationMessage(message) {
633
+ let sanitized = toClientErrorMessage(message);
634
+ sanitized = sanitized.replace(/\bat line \d+/gi, "");
635
+ sanitized = sanitized.replace(/\bline \d+/gi, "");
636
+ sanitized = sanitized.replace(/:\d+:\d+/g, "");
637
+ if (sanitized.length > 200) {
638
+ sanitized = sanitized.substring(0, 200) + "...";
639
+ }
640
+ return sanitized.trim();
641
+ }
642
+ var SelfReferenceError = class extends Error {
643
+ code = TOOL_CALL_ERROR_CODES.SELF_REFERENCE;
644
+ toolName;
645
+ constructor(toolName) {
646
+ super(`Self-reference attack: Attempted to call CodeCall tool "${toolName}" from within AgentScript`);
647
+ this.name = "SelfReferenceError";
648
+ this.toolName = toolName;
649
+ Object.freeze(this);
650
+ }
651
+ };
652
+
653
+ // plugins/plugin-codecall/src/security/self-reference-guard.ts
654
+ var CODECALL_TOOL_PREFIX = "codecall:";
655
+ var BLOCKED_CODECALL_TOOLS = Object.freeze(
656
+ /* @__PURE__ */ new Set(["codecall:search", "codecall:describe", "codecall:execute", "codecall:invoke"])
657
+ );
658
+ function isBlockedSelfReference(toolName) {
659
+ const normalizedName = toolName.toLowerCase().trim();
660
+ if (normalizedName.startsWith(CODECALL_TOOL_PREFIX)) {
661
+ return true;
662
+ }
663
+ if (BLOCKED_CODECALL_TOOLS.has(toolName)) {
664
+ return true;
665
+ }
666
+ return false;
667
+ }
668
+ function assertNotSelfReference(toolName) {
669
+ if (isBlockedSelfReference(toolName)) {
670
+ throw new SelfReferenceError(toolName);
671
+ }
672
+ }
673
+
416
674
  // plugins/plugin-codecall/src/services/audit-logger.service.ts
675
+ import { Provider as Provider2, ProviderScope as ProviderScope2 } from "@frontmcp/sdk";
417
676
  var AUDIT_EVENT_TYPES = {
418
677
  /** Script execution started */
419
678
  EXECUTION_START: "codecall:execution:start",
@@ -708,174 +967,6 @@ AuditLoggerService = __decorateClass([
708
967
  // plugins/plugin-codecall/src/services/tool-search.service.ts
709
968
  import { TFIDFVectoria, VectoriaDB } from "vectoriadb";
710
969
 
711
- // plugins/plugin-codecall/src/security/codecall-tool-policy.ts
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
- }
717
- var ALLOWED = { allowed: true };
718
- function deny(reason) {
719
- return { allowed: false, reason };
720
- }
721
- function codeCallAppIdOf(scope, entry) {
722
- const lineage = scope?.tools?.lineageOf?.(entry) ?? [];
723
- const owners = entry.owner ? [...lineage, entry.owner] : lineage;
724
- return owners.find((owner) => owner.kind === "app")?.id;
725
- }
726
- function toCodeCallPolicyTool(entry, requestedName, scope) {
727
- const { metadata } = entry;
728
- return {
729
- name: entry.name || entry.fullName,
730
- fullName: entry.fullName,
731
- aliases: requestedName ? [requestedName] : void 0,
732
- appId: codeCallAppIdOf(scope, entry),
733
- description: metadata?.description,
734
- tags: metadata?.tags,
735
- // `internal` tools refuse every external tools/call, CodeCall's included, so no surface may offer them.
736
- hidden: metadata?.hideFromDiscovery === true || metadata?.visibility === "hidden" || metadata?.visibility === "internal",
737
- codecall: metadata?.codecall,
738
- annotations: metadata?.annotations,
739
- metadata
740
- };
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
- }
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;
768
- return {
769
- name: tool.name,
770
- fullName: tool.fullName,
771
- appId: tool.appId,
772
- source: tool.codecall?.source,
773
- description: tool.description,
774
- tags: readOnlyCopy(tool.codecall?.tags ?? tool.tags, copies),
775
- annotations,
776
- metadata
777
- };
778
- }
779
- function ownNamesOf(tool) {
780
- return [tool.name, tool.fullName].filter((candidate) => !!candidate);
781
- }
782
- function checkCodeCallToolPolicy(tool, config) {
783
- const { name } = tool;
784
- const spellings = [...ownNamesOf(tool), ...tool.aliases ?? []];
785
- if (spellings.some((candidate) => candidate.startsWith("codecall:"))) {
786
- return deny("CodeCall meta-tools are not callable from CodeCall");
787
- }
788
- for (const candidate of spellings) {
789
- if (BLOCKED_NAMESPACE_PATTERNS.some((pattern) => pattern.test(candidate))) {
790
- return deny(`Tool "${candidate}" is in a namespace CodeCall never calls`);
791
- }
792
- }
793
- if (tool.hidden === true) {
794
- return deny(`Tool "${name}" is hidden from discovery`);
795
- }
796
- const enabled = tool.codecall?.enabledInCodeCall;
797
- switch (config.mode) {
798
- case "codecall_only":
799
- case "metadata_driven":
800
- if (enabled === false) {
801
- return deny(`Tool "${name}" sets enabledInCodeCall: false`);
802
- }
803
- break;
804
- case "codecall_opt_in":
805
- if (enabled !== true) {
806
- return deny(`Tool "${name}" has not opted in to CodeCall`);
807
- }
808
- break;
809
- default:
810
- return deny(`Unknown CodeCall mode: ${String(config.mode)}`);
811
- }
812
- if (config.includeTools && !config.includeTools(toToolFilterInfo(tool))) {
813
- return deny(`Tool "${name}" is excluded by the includeTools filter`);
814
- }
815
- return ALLOWED;
816
- }
817
- function denyUnknownTool(name) {
818
- return deny(`Tool "${name}" is not available through CodeCall`);
819
- }
820
- function resolveCodeCallTool(scope, name) {
821
- const typedScope = scope;
822
- const alias = /[-_]/.test(name) ? name.includes("_") ? name.replace(/_/g, "-") : name.replace(/-/g, "_") : void 0;
823
- const candidates = alias ? [name, alias] : [name];
824
- const matches = (entry) => candidates.includes(entry.fullName) || candidates.includes(entry.name);
825
- const local = typedScope.tools?.getTools(true)?.find(matches);
826
- if (local) return local;
827
- const registries = typedScope.providers?.getRegistries?.("AppRegistry") ?? [];
828
- for (const registry of registries) {
829
- for (const app of registry.getApps()) {
830
- if (!app.isRemote) continue;
831
- const remote = app.tools.getTools(true).find(matches);
832
- if (remote) return remote;
833
- }
834
- }
835
- return void 0;
836
- }
837
- function checkDirectCallPolicy(tool, directCalls) {
838
- if (!directCalls) return ALLOWED;
839
- if (directCalls.enabled === false) {
840
- return deny("Direct tool invocation is disabled");
841
- }
842
- const { allowedTools } = directCalls;
843
- if (allowedTools && !ownNamesOf(tool).some((ownName) => allowedTools.includes(ownName))) {
844
- return deny(`Tool "${tool.name}" is not in the direct-call allowlist`);
845
- }
846
- if (directCalls.filter && !directCalls.filter(toToolFilterInfo(tool))) {
847
- return deny(`Tool "${tool.name}" is excluded by the directCalls filter`);
848
- }
849
- return ALLOWED;
850
- }
851
- function readCodeCallPolicyConfig(config) {
852
- return {
853
- // An absent mode means an unparsed/partial config, not a hostile one: fall back to the
854
- // schema's own documented default rather than denying every call. An unrecognised mode
855
- // still fails closed inside the policy's switch.
856
- mode: config.get("mode") ?? "codecall_only",
857
- includeTools: config.get("includeTools")
858
- };
859
- }
860
- function checkCodeCallToolAccess(scope, config, name, options = {}) {
861
- const entry = resolveCodeCallTool(scope, name);
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
- }
866
- const policyTool = toCodeCallPolicyTool(entry, name, scope);
867
- const baseDecision = checkCodeCallToolPolicy(policyTool, readCodeCallPolicyConfig(config));
868
- if (!baseDecision.allowed) return baseDecision;
869
- if (options.directCall) {
870
- const directDecision = checkDirectCallPolicy(
871
- policyTool,
872
- config.get("directCalls")
873
- );
874
- if (!directDecision.allowed) return directDecision;
875
- }
876
- return { allowed: true, entry };
877
- }
878
-
879
970
  // plugins/plugin-codecall/src/services/synonym-expansion.service.ts
880
971
  var DEFAULT_SYNONYM_GROUPS = [
881
972
  // ===========================================================================
@@ -2641,83 +2732,6 @@ SearchKnowledgeTool = __decorateClass([
2641
2732
  import { toJSONSchema, z as z6, ZodType } from "@frontmcp/lazy-zod";
2642
2733
  import { getCallSurface as getCallSurface2, Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
2643
2734
 
2644
- // plugins/plugin-codecall/src/errors/tool-call.errors.ts
2645
- var TOOL_CALL_ERROR_CODES = {
2646
- NOT_FOUND: "NOT_FOUND",
2647
- VALIDATION: "VALIDATION",
2648
- EXECUTION: "EXECUTION",
2649
- TIMEOUT: "TIMEOUT",
2650
- ACCESS_DENIED: "ACCESS_DENIED",
2651
- SELF_REFERENCE: "SELF_REFERENCE"
2652
- };
2653
- function createToolCallError(code, toolName, rawMessage) {
2654
- const sanitizedMessage = getSanitizedMessage(code, toolName, rawMessage);
2655
- return Object.freeze({
2656
- code,
2657
- message: sanitizedMessage,
2658
- toolName
2659
- });
2660
- }
2661
- function getSanitizedMessage(code, toolName, rawMessage) {
2662
- switch (code) {
2663
- case TOOL_CALL_ERROR_CODES.NOT_FOUND:
2664
- return `Tool "${toolName}" was not found`;
2665
- case TOOL_CALL_ERROR_CODES.VALIDATION:
2666
- return rawMessage ? sanitizeValidationMessage(rawMessage) : `Input validation failed for tool "${toolName}"`;
2667
- case TOOL_CALL_ERROR_CODES.EXECUTION:
2668
- return `Tool "${toolName}" execution failed`;
2669
- case TOOL_CALL_ERROR_CODES.TIMEOUT:
2670
- return `Tool "${toolName}" execution timed out`;
2671
- case TOOL_CALL_ERROR_CODES.ACCESS_DENIED:
2672
- return `Access denied for tool "${toolName}"`;
2673
- case TOOL_CALL_ERROR_CODES.SELF_REFERENCE:
2674
- return `Cannot call CodeCall tools from within AgentScript`;
2675
- default:
2676
- return `An error occurred while calling "${toolName}"`;
2677
- }
2678
- }
2679
- function sanitizeValidationMessage(message) {
2680
- let sanitized = toClientErrorMessage(message);
2681
- sanitized = sanitized.replace(/\bat line \d+/gi, "");
2682
- sanitized = sanitized.replace(/\bline \d+/gi, "");
2683
- sanitized = sanitized.replace(/:\d+:\d+/g, "");
2684
- if (sanitized.length > 200) {
2685
- sanitized = sanitized.substring(0, 200) + "...";
2686
- }
2687
- return sanitized.trim();
2688
- }
2689
- var SelfReferenceError = class extends Error {
2690
- code = TOOL_CALL_ERROR_CODES.SELF_REFERENCE;
2691
- toolName;
2692
- constructor(toolName) {
2693
- super(`Self-reference attack: Attempted to call CodeCall tool "${toolName}" from within AgentScript`);
2694
- this.name = "SelfReferenceError";
2695
- this.toolName = toolName;
2696
- Object.freeze(this);
2697
- }
2698
- };
2699
-
2700
- // plugins/plugin-codecall/src/security/self-reference-guard.ts
2701
- var CODECALL_TOOL_PREFIX = "codecall:";
2702
- var BLOCKED_CODECALL_TOOLS = Object.freeze(
2703
- /* @__PURE__ */ new Set(["codecall:search", "codecall:describe", "codecall:execute", "codecall:invoke"])
2704
- );
2705
- function isBlockedSelfReference(toolName) {
2706
- const normalizedName = toolName.toLowerCase().trim();
2707
- if (normalizedName.startsWith(CODECALL_TOOL_PREFIX)) {
2708
- return true;
2709
- }
2710
- if (BLOCKED_CODECALL_TOOLS.has(toolName)) {
2711
- return true;
2712
- }
2713
- return false;
2714
- }
2715
- function assertNotSelfReference(toolName) {
2716
- if (isBlockedSelfReference(toolName)) {
2717
- throw new SelfReferenceError(toolName);
2718
- }
2719
- }
2720
-
2721
2735
  // plugins/plugin-codecall/src/utils/describe.utils.ts
2722
2736
  var INTENT_PATTERNS = {
2723
2737
  create: /^(create|add|new|insert|make|append|register|generate|produce|build|construct|provision|instantiate|define|compose|draft)/i,
@@ -3531,10 +3545,10 @@ var ExecuteTool = class extends ToolContext5 {
3531
3545
  arguments: toolInput
3532
3546
  }
3533
3547
  };
3534
- const ctx = {
3548
+ const ctx = asCodeCallDispatch({
3535
3549
  authInfo: this.authInfo,
3536
3550
  surface: callerSurface
3537
- };
3551
+ });
3538
3552
  const mcpResult = await this.scope.runFlow("tools:call-tool", { request, ctx });
3539
3553
  if (!mcpResult) {
3540
3554
  audit?.logToolCallFailure(
@@ -3791,10 +3805,10 @@ var InvokeTool = class extends ToolContext6 {
3791
3805
  arguments: toolInput
3792
3806
  }
3793
3807
  };
3794
- const ctx = {
3808
+ const ctx = asCodeCallDispatch({
3795
3809
  authInfo: this.authInfo,
3796
3810
  surface
3797
- };
3811
+ });
3798
3812
  const result = await this.scope.runFlow("tools:call-tool", { request, ctx });
3799
3813
  if (!result) {
3800
3814
  audit?.logInvoke(executionId, toolName, false, Date.now() - startedAt);
@@ -3827,6 +3841,10 @@ function auditLogFields(event) {
3827
3841
  const { executionId, durationMs, data } = event;
3828
3842
  return durationMs === void 0 ? { executionId, ...data } : { executionId, durationMs, ...data };
3829
3843
  }
3844
+ function callContextOf(flowCtx) {
3845
+ const rawInput = flowCtx.rawInput;
3846
+ return typeof rawInput === "object" && rawInput !== null ? rawInput.ctx : void 0;
3847
+ }
3830
3848
  var CodeCallPlugin = class extends DynamicPlugin {
3831
3849
  options;
3832
3850
  cachedLogger;
@@ -3902,12 +3920,32 @@ var CodeCallPlugin = class extends DynamicPlugin {
3902
3920
  logger.verbose("adjustListTools: no tools to filter, returning early");
3903
3921
  return;
3904
3922
  }
3905
- const filteredTools = resolvedTools.filter(({ tool }) => {
3906
- return this.shouldShowInListTools(tool, this.options.mode);
3907
- });
3923
+ const scope = this.tryGetScope();
3924
+ const filteredTools = resolvedTools.filter(
3925
+ ({ tool }) => !this.judges(scope, { tool }) || this.shouldShowInListTools(tool, this.options.mode)
3926
+ );
3908
3927
  logger.verbose("adjustListTools: tools after filter", { count: filteredTools.length });
3909
3928
  flowCtx.state.set("resolvedTools", filteredTools);
3910
3929
  }
3930
+ async refuseDirectCallOfHiddenTool(flowCtx) {
3931
+ const { tool } = flowCtx.state;
3932
+ if (!tool || tool.owner?.kind === "scope") return;
3933
+ if (this.shouldShowInListTools(tool, this.options.mode)) return;
3934
+ if (isInProcessDispatch(callContextOf(flowCtx))) return;
3935
+ this.getLogger().verbose("refused a direct tools/call of a tool CodeCall hides", { tool: tool.fullName });
3936
+ throw new ToolNotFoundError(flowCtx.state.input?.name ?? tool.name);
3937
+ }
3938
+ /** Whether this instance's direct-call gate runs for the tool, so the listing hides only what that gate refuses. */
3939
+ judges(scope, entry) {
3940
+ return !scope?.hooks || isEntryGatedBy(scope, entry, this);
3941
+ }
3942
+ tryGetScope() {
3943
+ try {
3944
+ return this.get(ScopeEntry);
3945
+ } catch {
3946
+ return void 0;
3947
+ }
3948
+ }
3911
3949
  /**
3912
3950
  * Determine if a tool should be visible in list_tools based on mode.
3913
3951
  *
@@ -3963,6 +4001,9 @@ var CodeCallPlugin = class extends DynamicPlugin {
3963
4001
  __decorateClass([
3964
4002
  ListToolsHook.Did("resolveConflicts", { priority: 1e3 })
3965
4003
  ], CodeCallPlugin.prototype, "adjustListTools", 1);
4004
+ __decorateClass([
4005
+ ToolHook.Did("findTool", { priority: 10, appliesTo: "uncovered-apps" })
4006
+ ], CodeCallPlugin.prototype, "refuseDirectCallOfHiddenTool", 1);
3966
4007
  CodeCallPlugin = __decorateClass([
3967
4008
  Plugin({
3968
4009
  name: "codecall",
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-codecall",
3
- "version": "1.8.7",
3
+ "version": "1.9.0",
4
4
  "description": "CodeCall plugin for FrontMCP - AgentScript-based meta-tools for orchestrating MCP tools",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -50,14 +50,14 @@
50
50
  "dependencies": {
51
51
  "@enclave-vm/ast": "^2.15.3",
52
52
  "@enclave-vm/core": "^2.15.3",
53
- "@frontmcp/protocol": "1.8.7",
54
- "@frontmcp/sdk": "1.8.7",
53
+ "@frontmcp/protocol": "1.9.0",
54
+ "@frontmcp/sdk": "1.9.0",
55
55
  "acorn": "^8.15.0",
56
56
  "vectoriadb": "^2.3.2"
57
57
  },
58
58
  "peerDependencies": {
59
- "@frontmcp/lazy-zod": "1.8.7",
60
- "@frontmcp/plugin-cache": "1.8.7"
59
+ "@frontmcp/lazy-zod": "1.9.0",
60
+ "@frontmcp/plugin-cache": "1.9.0"
61
61
  },
62
62
  "devDependencies": {
63
63
  "reflect-metadata": "^0.2.2"