@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/codecall.plugin.d.ts +22 -0
- package/codecall.plugin.d.ts.map +1 -1
- package/codecall.types.d.ts +3 -0
- package/codecall.types.d.ts.map +1 -1
- package/esm/index.mjs +296 -255
- package/esm/package.json +5 -5
- package/index.js +400 -362
- package/package.json +5 -5
- package/security/codecall-dispatch.d.ts +13 -0
- package/security/codecall-dispatch.d.ts.map +1 -0
- package/security/index.d.ts +1 -0
- package/security/index.d.ts.map +1 -1
- package/tools/execute.tool.d.ts.map +1 -1
- package/tools/invoke.tool.d.ts +1 -1
- package/tools/invoke.tool.d.ts.map +1 -1
- package/tools/search.tool.d.ts +1 -1
- package/tools/search.tool.d.ts.map +1 -1
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/
|
|
376
|
-
import {
|
|
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
|
|
3906
|
-
|
|
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.
|
|
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.
|
|
54
|
-
"@frontmcp/sdk": "1.
|
|
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.
|
|
60
|
-
"@frontmcp/plugin-cache": "1.
|
|
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"
|