@enactprotocol/mcp-server 2.2.2 → 2.3.1
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/dist/index.js +360 -337
- package/package.json +4 -4
- package/src/index.ts +258 -109
- package/tests/secrets.test.ts +455 -0
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enactprotocol/mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "MCP protocol server for Enact tool integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"dev:http": "bun run src/index.ts --http"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@enactprotocol/api": "2.
|
|
28
|
-
"@enactprotocol/execution": "2.
|
|
29
|
-
"@enactprotocol/shared": "2.
|
|
27
|
+
"@enactprotocol/api": "2.3.1",
|
|
28
|
+
"@enactprotocol/execution": "2.3.1",
|
|
29
|
+
"@enactprotocol/shared": "2.3.1",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.10.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -26,14 +26,21 @@ import {
|
|
|
26
26
|
searchTools,
|
|
27
27
|
verifyAllAttestations,
|
|
28
28
|
} from "@enactprotocol/api";
|
|
29
|
-
import { DaggerExecutionProvider } from "@enactprotocol/execution";
|
|
30
29
|
import {
|
|
30
|
+
DaggerExecutionProvider,
|
|
31
|
+
DockerExecutionProvider,
|
|
32
|
+
ExecutionRouter,
|
|
33
|
+
LocalExecutionProvider,
|
|
34
|
+
} from "@enactprotocol/execution";
|
|
35
|
+
import type { ExecutionResult } from "@enactprotocol/execution";
|
|
36
|
+
import { resolveSecret } from "@enactprotocol/secrets";
|
|
37
|
+
import {
|
|
38
|
+
type ActionsManifest,
|
|
31
39
|
type ToolManifest,
|
|
32
40
|
addMcpTool,
|
|
33
41
|
addToolToRegistry,
|
|
34
42
|
applyDefaults,
|
|
35
43
|
getActiveToolset,
|
|
36
|
-
getCacheDir,
|
|
37
44
|
getMcpToolInfo,
|
|
38
45
|
getMinimumAttestations,
|
|
39
46
|
getToolCachePath,
|
|
@@ -41,7 +48,8 @@ import {
|
|
|
41
48
|
isIdentityTrusted,
|
|
42
49
|
listMcpTools,
|
|
43
50
|
loadConfig,
|
|
44
|
-
|
|
51
|
+
loadManifestWithActions,
|
|
52
|
+
parseActionSpecifier,
|
|
45
53
|
pathExists,
|
|
46
54
|
validateInputs,
|
|
47
55
|
} from "@enactprotocol/shared";
|
|
@@ -70,18 +78,30 @@ function fromMcpName(mcpName: string): string {
|
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
/**
|
|
73
|
-
*
|
|
81
|
+
* Resolve secrets from the keyring for a tool's environment variables
|
|
82
|
+
* Only resolves variables marked with secret: true in the manifest
|
|
74
83
|
*/
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
async function resolveManifestSecrets(
|
|
85
|
+
toolName: string,
|
|
86
|
+
manifest: ToolManifest
|
|
87
|
+
): Promise<Record<string, string>> {
|
|
88
|
+
const envOverrides: Record<string, string> = {};
|
|
89
|
+
|
|
90
|
+
if (!manifest.env) {
|
|
91
|
+
return envOverrides;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [envName, envDecl] of Object.entries(manifest.env)) {
|
|
95
|
+
// Only resolve secrets (not regular env vars)
|
|
96
|
+
if (envDecl && typeof envDecl === "object" && envDecl.secret) {
|
|
97
|
+
const result = await resolveSecret(toolName, envName);
|
|
98
|
+
if (result.found && result.value) {
|
|
99
|
+
envOverrides[envName] = result.value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
|
|
84
|
-
return manifest.inputSchema as Tool["inputSchema"];
|
|
104
|
+
return envOverrides;
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
/**
|
|
@@ -110,7 +130,8 @@ function getApiClient() {
|
|
|
110
130
|
* Extract a tar.gz bundle to a directory
|
|
111
131
|
*/
|
|
112
132
|
async function extractBundle(bundleData: ArrayBuffer, destPath: string): Promise<void> {
|
|
113
|
-
const
|
|
133
|
+
const { tmpdir } = await import("node:os");
|
|
134
|
+
const tempFile = join(tmpdir(), `enact-bundle-${Date.now()}.tar.gz`);
|
|
114
135
|
mkdirSync(dirname(tempFile), { recursive: true });
|
|
115
136
|
writeFileSync(tempFile, Buffer.from(bundleData));
|
|
116
137
|
|
|
@@ -295,10 +316,6 @@ async function handleMetaTool(
|
|
|
295
316
|
let response = `# ${toolNameArg}@${targetVersion}\n\n`;
|
|
296
317
|
response += `**Description:** ${versionInfo.description}\n\n`;
|
|
297
318
|
|
|
298
|
-
if (manifest.inputSchema) {
|
|
299
|
-
response += `## Input Schema\n\`\`\`json\n${JSON.stringify(manifest.inputSchema, null, 2)}\n\`\`\`\n\n`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
319
|
if (manifest.outputSchema) {
|
|
303
320
|
response += `## Output Schema\n\`\`\`json\n${JSON.stringify(manifest.outputSchema, null, 2)}\n\`\`\`\n\n`;
|
|
304
321
|
}
|
|
@@ -315,6 +332,27 @@ async function handleMetaTool(
|
|
|
315
332
|
}
|
|
316
333
|
}
|
|
317
334
|
|
|
335
|
+
// Check for actions in the manifest
|
|
336
|
+
const actionsManifest = manifest.actions as ActionsManifest | undefined;
|
|
337
|
+
if (
|
|
338
|
+
actionsManifest &&
|
|
339
|
+
typeof actionsManifest.actions === "object" &&
|
|
340
|
+
Object.keys(actionsManifest.actions).length > 0
|
|
341
|
+
) {
|
|
342
|
+
response += "\n## Available Actions\n\n";
|
|
343
|
+
response += `This tool supports the following actions. Run with \`enact_run\` using \`${toolNameArg}:<action>\` format.\n\n`;
|
|
344
|
+
|
|
345
|
+
for (const [actionName, action] of Object.entries(actionsManifest.actions)) {
|
|
346
|
+
response += `### ${actionName}\n`;
|
|
347
|
+
if (action.description) {
|
|
348
|
+
response += `${action.description}\n\n`;
|
|
349
|
+
}
|
|
350
|
+
if (action.inputSchema) {
|
|
351
|
+
response += `**Input Schema:**\n\`\`\`json\n${JSON.stringify(action.inputSchema, null, 2)}\n\`\`\`\n\n`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
318
356
|
return {
|
|
319
357
|
content: [{ type: "text", text: response }],
|
|
320
358
|
};
|
|
@@ -335,10 +373,14 @@ async function handleMetaTool(
|
|
|
335
373
|
const toolNameArg = args.tool as string;
|
|
336
374
|
const toolArgs = (args.args as Record<string, unknown>) || {};
|
|
337
375
|
|
|
376
|
+
// Parse action specifier (owner/skill/action or owner/skill)
|
|
377
|
+
const { skillName, actionName } = parseActionSpecifier(toolNameArg);
|
|
378
|
+
|
|
338
379
|
try {
|
|
339
380
|
// Check if tool is already installed
|
|
340
|
-
const toolInfo = getMcpToolInfo(
|
|
381
|
+
const toolInfo = getMcpToolInfo(skillName);
|
|
341
382
|
let manifest: ToolManifest;
|
|
383
|
+
let actionsManifest: ActionsManifest | undefined;
|
|
342
384
|
let cachePath: string;
|
|
343
385
|
let bundleHash: string | undefined;
|
|
344
386
|
let toolVersion: string | undefined;
|
|
@@ -347,7 +389,7 @@ async function handleMetaTool(
|
|
|
347
389
|
|
|
348
390
|
if (toolInfo) {
|
|
349
391
|
// Tool is installed, use cached version
|
|
350
|
-
const loaded =
|
|
392
|
+
const loaded = loadManifestWithActions(toolInfo.cachePath);
|
|
351
393
|
if (!loaded) {
|
|
352
394
|
return {
|
|
353
395
|
content: [{ type: "text", text: "Failed to load installed tool manifest" }],
|
|
@@ -355,38 +397,39 @@ async function handleMetaTool(
|
|
|
355
397
|
};
|
|
356
398
|
}
|
|
357
399
|
manifest = loaded.manifest;
|
|
400
|
+
actionsManifest = loaded.actionsManifest;
|
|
358
401
|
cachePath = toolInfo.cachePath;
|
|
359
402
|
toolVersion = toolInfo.version;
|
|
360
403
|
|
|
361
404
|
// Get bundle hash for installed tool from registry
|
|
362
405
|
try {
|
|
363
|
-
const versionInfo = await getToolVersion(apiClient,
|
|
406
|
+
const versionInfo = await getToolVersion(apiClient, skillName, toolVersion);
|
|
364
407
|
bundleHash = versionInfo.bundle.hash;
|
|
365
408
|
} catch {
|
|
366
409
|
// Continue without hash - will skip verification
|
|
367
410
|
}
|
|
368
411
|
} else {
|
|
369
412
|
// Tool not installed - fetch and install temporarily
|
|
370
|
-
const info = await getToolInfo(apiClient,
|
|
413
|
+
const info = await getToolInfo(apiClient, skillName);
|
|
371
414
|
toolVersion = info.latestVersion;
|
|
372
415
|
|
|
373
416
|
// Download bundle
|
|
374
417
|
const bundleResult = await downloadBundle(apiClient, {
|
|
375
|
-
name:
|
|
418
|
+
name: skillName,
|
|
376
419
|
version: info.latestVersion,
|
|
377
420
|
verify: true,
|
|
378
421
|
});
|
|
379
422
|
bundleHash = bundleResult.hash;
|
|
380
423
|
|
|
381
424
|
// Extract to cache
|
|
382
|
-
cachePath = getToolCachePath(
|
|
425
|
+
cachePath = getToolCachePath(skillName, info.latestVersion);
|
|
383
426
|
if (pathExists(cachePath)) {
|
|
384
427
|
rmSync(cachePath, { recursive: true, force: true });
|
|
385
428
|
}
|
|
386
429
|
await extractBundle(bundleResult.data, cachePath);
|
|
387
430
|
|
|
388
|
-
// Load manifest
|
|
389
|
-
const loaded =
|
|
431
|
+
// Load manifest with actions
|
|
432
|
+
const loaded = loadManifestWithActions(cachePath);
|
|
390
433
|
if (!loaded) {
|
|
391
434
|
return {
|
|
392
435
|
content: [{ type: "text", text: "Failed to load downloaded tool manifest" }],
|
|
@@ -394,6 +437,7 @@ async function handleMetaTool(
|
|
|
394
437
|
};
|
|
395
438
|
}
|
|
396
439
|
manifest = loaded.manifest;
|
|
440
|
+
actionsManifest = loaded.actionsManifest;
|
|
397
441
|
}
|
|
398
442
|
|
|
399
443
|
// Verify attestations before execution
|
|
@@ -403,7 +447,7 @@ async function handleMetaTool(
|
|
|
403
447
|
try {
|
|
404
448
|
const verified = await verifyAllAttestations(
|
|
405
449
|
apiClient,
|
|
406
|
-
|
|
450
|
+
skillName,
|
|
407
451
|
toolVersion,
|
|
408
452
|
bundleHash
|
|
409
453
|
);
|
|
@@ -431,7 +475,7 @@ async function handleMetaTool(
|
|
|
431
475
|
try {
|
|
432
476
|
const verified = await verifyAllAttestations(
|
|
433
477
|
apiClient,
|
|
434
|
-
|
|
478
|
+
skillName,
|
|
435
479
|
toolVersion,
|
|
436
480
|
bundleHash
|
|
437
481
|
);
|
|
@@ -469,31 +513,74 @@ async function handleMetaTool(
|
|
|
469
513
|
// policy === 'allow' - continue execution with warning
|
|
470
514
|
}
|
|
471
515
|
|
|
472
|
-
//
|
|
473
|
-
const
|
|
474
|
-
? applyDefaults(toolArgs, manifest.inputSchema)
|
|
475
|
-
: toolArgs;
|
|
476
|
-
|
|
477
|
-
const validation = validateInputs(inputsWithDefaults, manifest.inputSchema);
|
|
478
|
-
if (!validation.valid) {
|
|
479
|
-
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
480
|
-
return {
|
|
481
|
-
content: [{ type: "text", text: `Input validation failed: ${errors}` }],
|
|
482
|
-
isError: true,
|
|
483
|
-
};
|
|
484
|
-
}
|
|
516
|
+
// Resolve secrets from keyring
|
|
517
|
+
const secretOverrides = await resolveManifestSecrets(skillName, manifest);
|
|
485
518
|
|
|
486
|
-
|
|
519
|
+
// Execute the tool or action — select backend via execution router
|
|
520
|
+
const execConfig = loadConfig();
|
|
521
|
+
const router = new ExecutionRouter({
|
|
522
|
+
default: execConfig.execution?.default,
|
|
523
|
+
fallback: execConfig.execution?.fallback,
|
|
524
|
+
trusted_scopes: execConfig.execution?.trusted_scopes,
|
|
525
|
+
});
|
|
526
|
+
router.registerProvider("local", new LocalExecutionProvider({ verbose: false }));
|
|
527
|
+
router.registerProvider("docker", new DockerExecutionProvider({ verbose: false }));
|
|
528
|
+
router.registerProvider("dagger", new DaggerExecutionProvider({ verbose: false }));
|
|
487
529
|
|
|
488
|
-
|
|
489
|
-
const provider = new DaggerExecutionProvider({ verbose: false });
|
|
530
|
+
const provider = await router.selectProvider(skillName);
|
|
490
531
|
await provider.initialize();
|
|
491
532
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
533
|
+
let result: ExecutionResult;
|
|
534
|
+
|
|
535
|
+
// Check if we need to execute an action
|
|
536
|
+
if (actionName && actionsManifest) {
|
|
537
|
+
// Find the action in the manifest (map lookup)
|
|
538
|
+
const action = actionsManifest.actions[actionName];
|
|
539
|
+
if (!action) {
|
|
540
|
+
const availableActions = Object.keys(actionsManifest.actions).join(", ");
|
|
541
|
+
return {
|
|
542
|
+
content: [
|
|
543
|
+
{
|
|
544
|
+
type: "text",
|
|
545
|
+
text: `Action "${actionName}" not found in ${skillName}. Available actions: ${availableActions}`,
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
isError: true,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Use action's inputSchema for validation (optional, default to empty)
|
|
553
|
+
const effectiveSchema = action.inputSchema ?? { type: "object" as const, properties: {} };
|
|
554
|
+
const inputsWithDefaults = applyDefaults(toolArgs, effectiveSchema);
|
|
555
|
+
|
|
556
|
+
const validation = validateInputs(inputsWithDefaults, effectiveSchema);
|
|
557
|
+
if (!validation.valid) {
|
|
558
|
+
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
559
|
+
return {
|
|
560
|
+
content: [{ type: "text", text: `Input validation failed: ${errors}` }],
|
|
561
|
+
isError: true,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const finalInputs = validation.coercedValues ?? inputsWithDefaults;
|
|
566
|
+
|
|
567
|
+
// Execute the action
|
|
568
|
+
result = await provider.executeAction(
|
|
569
|
+
manifest,
|
|
570
|
+
actionsManifest,
|
|
571
|
+
actionName,
|
|
572
|
+
action,
|
|
573
|
+
{ params: finalInputs, envOverrides: secretOverrides },
|
|
574
|
+
{ mountDirs: { [cachePath]: "/workspace" } }
|
|
575
|
+
);
|
|
576
|
+
} else {
|
|
577
|
+
// Execute the tool normally (no per-script schema — pass params through)
|
|
578
|
+
result = await provider.execute(
|
|
579
|
+
manifest,
|
|
580
|
+
{ params: toolArgs, envOverrides: secretOverrides },
|
|
581
|
+
{ mountDirs: { [cachePath]: "/workspace" } }
|
|
582
|
+
);
|
|
583
|
+
}
|
|
497
584
|
|
|
498
585
|
if (result.success) {
|
|
499
586
|
const output = result.output?.stdout || "Tool executed successfully (no output)";
|
|
@@ -605,19 +692,43 @@ function createMcpServer(): Server {
|
|
|
605
692
|
// Start with meta-tools for progressive discovery
|
|
606
693
|
const tools: Tool[] = [...META_TOOLS];
|
|
607
694
|
|
|
608
|
-
// Add installed tools
|
|
695
|
+
// Add installed tools and their actions
|
|
609
696
|
for (const tool of mcpTools) {
|
|
610
|
-
const loaded =
|
|
697
|
+
const loaded = loadManifestWithActions(tool.cachePath);
|
|
611
698
|
const manifest = loaded?.manifest;
|
|
699
|
+
const actionsManifest = loaded?.actionsManifest;
|
|
612
700
|
|
|
613
|
-
const description = manifest?.description || `Enact tool: ${tool.name}`;
|
|
614
701
|
const toolsetNote = activeToolset ? ` [toolset: ${activeToolset}]` : "";
|
|
615
702
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
703
|
+
// If the tool has actions, expose each action as a separate MCP tool
|
|
704
|
+
if (
|
|
705
|
+
actionsManifest &&
|
|
706
|
+
typeof actionsManifest.actions === "object" &&
|
|
707
|
+
Object.keys(actionsManifest.actions).length > 0
|
|
708
|
+
) {
|
|
709
|
+
for (const [actionName, action] of Object.entries(actionsManifest.actions)) {
|
|
710
|
+
// Action tool name format: owner__skill__action (uses colon internally but double underscore for MCP)
|
|
711
|
+
const actionMcpName = toMcpName(`${tool.name}:${actionName}`);
|
|
712
|
+
|
|
713
|
+
tools.push({
|
|
714
|
+
name: actionMcpName,
|
|
715
|
+
description:
|
|
716
|
+
(action.description || `Action ${actionName} of ${tool.name}`) + toolsetNote,
|
|
717
|
+
inputSchema: action.inputSchema
|
|
718
|
+
? (action.inputSchema as Tool["inputSchema"])
|
|
719
|
+
: { type: "object", properties: {} },
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
} else {
|
|
723
|
+
// No actions, expose the tool itself
|
|
724
|
+
const description = manifest?.description || `Enact tool: ${tool.name}`;
|
|
725
|
+
|
|
726
|
+
tools.push({
|
|
727
|
+
name: toMcpName(tool.name),
|
|
728
|
+
description: description + toolsetNote,
|
|
729
|
+
inputSchema: { type: "object", properties: {} },
|
|
730
|
+
});
|
|
731
|
+
}
|
|
621
732
|
}
|
|
622
733
|
|
|
623
734
|
return { tools };
|
|
@@ -635,8 +746,11 @@ function createMcpServer(): Server {
|
|
|
635
746
|
return metaResult;
|
|
636
747
|
}
|
|
637
748
|
|
|
749
|
+
// Parse action specifier (owner/skill/action or owner/skill)
|
|
750
|
+
const { skillName, actionName } = parseActionSpecifier(enactToolName);
|
|
751
|
+
|
|
638
752
|
// Find the tool in MCP registry (respects active toolset)
|
|
639
|
-
const toolInfo = getMcpToolInfo(
|
|
753
|
+
const toolInfo = getMcpToolInfo(skillName);
|
|
640
754
|
|
|
641
755
|
if (!toolInfo) {
|
|
642
756
|
const activeToolset = getActiveToolset();
|
|
@@ -647,21 +761,21 @@ function createMcpServer(): Server {
|
|
|
647
761
|
content: [
|
|
648
762
|
{
|
|
649
763
|
type: "text",
|
|
650
|
-
text: `Error: Tool "${
|
|
764
|
+
text: `Error: Tool "${skillName}" not found.${toolsetHint} Use 'enact mcp add <tool>' to add it.`,
|
|
651
765
|
},
|
|
652
766
|
],
|
|
653
767
|
isError: true,
|
|
654
768
|
};
|
|
655
769
|
}
|
|
656
770
|
|
|
657
|
-
// Load manifest
|
|
658
|
-
const loaded =
|
|
771
|
+
// Load manifest with actions
|
|
772
|
+
const loaded = loadManifestWithActions(toolInfo.cachePath);
|
|
659
773
|
if (!loaded) {
|
|
660
774
|
return {
|
|
661
775
|
content: [
|
|
662
776
|
{
|
|
663
777
|
type: "text",
|
|
664
|
-
text: `Error: Failed to load manifest for "${
|
|
778
|
+
text: `Error: Failed to load manifest for "${skillName}"`,
|
|
665
779
|
},
|
|
666
780
|
],
|
|
667
781
|
isError: true,
|
|
@@ -669,62 +783,97 @@ function createMcpServer(): Server {
|
|
|
669
783
|
}
|
|
670
784
|
|
|
671
785
|
const manifest = loaded.manifest;
|
|
786
|
+
const actionsManifest = loaded.actionsManifest;
|
|
672
787
|
|
|
673
|
-
//
|
|
674
|
-
|
|
675
|
-
// Return the documentation/instructions for LLM interpretation
|
|
676
|
-
const instructions = manifest.doc || manifest.description || "No instructions available.";
|
|
677
|
-
return {
|
|
678
|
-
content: [
|
|
679
|
-
{
|
|
680
|
-
type: "text",
|
|
681
|
-
text: `[Instruction Tool: ${enactToolName}]\n\n${instructions}\n\nInputs provided: ${JSON.stringify(args, null, 2)}`,
|
|
682
|
-
},
|
|
683
|
-
],
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Apply defaults and validate inputs
|
|
688
|
-
const inputsWithDefaults = manifest.inputSchema
|
|
689
|
-
? applyDefaults(args, manifest.inputSchema)
|
|
690
|
-
: args;
|
|
691
|
-
|
|
692
|
-
const validation = validateInputs(inputsWithDefaults, manifest.inputSchema);
|
|
693
|
-
if (!validation.valid) {
|
|
694
|
-
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
695
|
-
return {
|
|
696
|
-
content: [
|
|
697
|
-
{
|
|
698
|
-
type: "text",
|
|
699
|
-
text: `Input validation failed: ${errors}`,
|
|
700
|
-
},
|
|
701
|
-
],
|
|
702
|
-
isError: true,
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const finalInputs = validation.coercedValues ?? inputsWithDefaults;
|
|
788
|
+
// Resolve secrets from keyring
|
|
789
|
+
const secretOverrides = await resolveManifestSecrets(skillName, manifest);
|
|
707
790
|
|
|
708
|
-
// Execute the tool
|
|
709
|
-
const
|
|
710
|
-
|
|
791
|
+
// Execute the tool — select backend via execution router
|
|
792
|
+
const execConfig2 = loadConfig();
|
|
793
|
+
const router2 = new ExecutionRouter({
|
|
794
|
+
default: execConfig2.execution?.default,
|
|
795
|
+
fallback: execConfig2.execution?.fallback,
|
|
796
|
+
trusted_scopes: execConfig2.execution?.trusted_scopes,
|
|
711
797
|
});
|
|
798
|
+
router2.registerProvider("local", new LocalExecutionProvider({ verbose: false }));
|
|
799
|
+
router2.registerProvider("docker", new DockerExecutionProvider({ verbose: false }));
|
|
800
|
+
router2.registerProvider("dagger", new DaggerExecutionProvider({ verbose: false }));
|
|
801
|
+
|
|
802
|
+
const provider = await router2.selectProvider(skillName);
|
|
712
803
|
|
|
713
804
|
try {
|
|
714
805
|
await provider.initialize();
|
|
715
806
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
{
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
807
|
+
let result: ExecutionResult;
|
|
808
|
+
|
|
809
|
+
// Check if we need to execute an action
|
|
810
|
+
if (actionName && actionsManifest) {
|
|
811
|
+
// Find the action in the manifest (map lookup)
|
|
812
|
+
const action = actionsManifest.actions[actionName];
|
|
813
|
+
if (!action) {
|
|
814
|
+
const availableActions = Object.keys(actionsManifest.actions).join(", ");
|
|
815
|
+
return {
|
|
816
|
+
content: [
|
|
817
|
+
{
|
|
818
|
+
type: "text",
|
|
819
|
+
text: `Action "${actionName}" not found in ${skillName}. Available actions: ${availableActions}`,
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
isError: true,
|
|
823
|
+
};
|
|
726
824
|
}
|
|
727
|
-
|
|
825
|
+
|
|
826
|
+
// Use action's inputSchema for validation (optional, default to empty)
|
|
827
|
+
const effectiveSchema = action.inputSchema ?? { type: "object" as const, properties: {} };
|
|
828
|
+
const inputsWithDefaults = applyDefaults(args, effectiveSchema);
|
|
829
|
+
|
|
830
|
+
const validation = validateInputs(inputsWithDefaults, effectiveSchema);
|
|
831
|
+
if (!validation.valid) {
|
|
832
|
+
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
833
|
+
return {
|
|
834
|
+
content: [
|
|
835
|
+
{
|
|
836
|
+
type: "text",
|
|
837
|
+
text: `Input validation failed: ${errors}`,
|
|
838
|
+
},
|
|
839
|
+
],
|
|
840
|
+
isError: true,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const finalInputs = validation.coercedValues ?? inputsWithDefaults;
|
|
845
|
+
|
|
846
|
+
// Execute the action
|
|
847
|
+
result = await provider.executeAction(
|
|
848
|
+
manifest,
|
|
849
|
+
actionsManifest,
|
|
850
|
+
actionName,
|
|
851
|
+
action,
|
|
852
|
+
{ params: finalInputs, envOverrides: secretOverrides },
|
|
853
|
+
{ mountDirs: { [toolInfo.cachePath]: "/workspace" } }
|
|
854
|
+
);
|
|
855
|
+
} else {
|
|
856
|
+
// Check if this is an instruction-based tool (no command)
|
|
857
|
+
if (!manifest.command) {
|
|
858
|
+
// Return the documentation/instructions for LLM interpretation
|
|
859
|
+
const instructions = manifest.doc || manifest.description || "No instructions available.";
|
|
860
|
+
return {
|
|
861
|
+
content: [
|
|
862
|
+
{
|
|
863
|
+
type: "text",
|
|
864
|
+
text: `[Instruction Tool: ${skillName}]\n\n${instructions}\n\nInputs provided: ${JSON.stringify(args, null, 2)}`,
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Execute the tool normally (no per-script schema — pass params through)
|
|
871
|
+
result = await provider.execute(
|
|
872
|
+
manifest,
|
|
873
|
+
{ params: args, envOverrides: secretOverrides },
|
|
874
|
+
{ mountDirs: { [toolInfo.cachePath]: "/workspace" } }
|
|
875
|
+
);
|
|
876
|
+
}
|
|
728
877
|
|
|
729
878
|
if (result.success) {
|
|
730
879
|
return {
|