@enactprotocol/mcp-server 2.2.4 → 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 +334 -321
- package/package.json +4 -4
- package/src/index.ts +234 -119
- package/tests/secrets.test.ts +4 -14
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,15 +26,21 @@ import {
|
|
|
26
26
|
searchTools,
|
|
27
27
|
verifyAllAttestations,
|
|
28
28
|
} from "@enactprotocol/api";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
DaggerExecutionProvider,
|
|
31
|
+
DockerExecutionProvider,
|
|
32
|
+
ExecutionRouter,
|
|
33
|
+
LocalExecutionProvider,
|
|
34
|
+
} from "@enactprotocol/execution";
|
|
35
|
+
import type { ExecutionResult } from "@enactprotocol/execution";
|
|
30
36
|
import { resolveSecret } from "@enactprotocol/secrets";
|
|
31
37
|
import {
|
|
38
|
+
type ActionsManifest,
|
|
32
39
|
type ToolManifest,
|
|
33
40
|
addMcpTool,
|
|
34
41
|
addToolToRegistry,
|
|
35
42
|
applyDefaults,
|
|
36
43
|
getActiveToolset,
|
|
37
|
-
getCacheDir,
|
|
38
44
|
getMcpToolInfo,
|
|
39
45
|
getMinimumAttestations,
|
|
40
46
|
getToolCachePath,
|
|
@@ -42,7 +48,8 @@ import {
|
|
|
42
48
|
isIdentityTrusted,
|
|
43
49
|
listMcpTools,
|
|
44
50
|
loadConfig,
|
|
45
|
-
|
|
51
|
+
loadManifestWithActions,
|
|
52
|
+
parseActionSpecifier,
|
|
46
53
|
pathExists,
|
|
47
54
|
validateInputs,
|
|
48
55
|
} from "@enactprotocol/shared";
|
|
@@ -97,21 +104,6 @@ async function resolveManifestSecrets(
|
|
|
97
104
|
return envOverrides;
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
/**
|
|
101
|
-
* Convert Enact JSON Schema to MCP tool input schema
|
|
102
|
-
*/
|
|
103
|
-
function convertInputSchema(manifest: ToolManifest): Tool["inputSchema"] {
|
|
104
|
-
if (!manifest.inputSchema) {
|
|
105
|
-
return {
|
|
106
|
-
type: "object",
|
|
107
|
-
properties: {},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Return the inputSchema directly - it should already be JSON Schema compatible
|
|
112
|
-
return manifest.inputSchema as Tool["inputSchema"];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
107
|
/**
|
|
116
108
|
* Get API client for registry access
|
|
117
109
|
*/
|
|
@@ -138,7 +130,8 @@ function getApiClient() {
|
|
|
138
130
|
* Extract a tar.gz bundle to a directory
|
|
139
131
|
*/
|
|
140
132
|
async function extractBundle(bundleData: ArrayBuffer, destPath: string): Promise<void> {
|
|
141
|
-
const
|
|
133
|
+
const { tmpdir } = await import("node:os");
|
|
134
|
+
const tempFile = join(tmpdir(), `enact-bundle-${Date.now()}.tar.gz`);
|
|
142
135
|
mkdirSync(dirname(tempFile), { recursive: true });
|
|
143
136
|
writeFileSync(tempFile, Buffer.from(bundleData));
|
|
144
137
|
|
|
@@ -323,10 +316,6 @@ async function handleMetaTool(
|
|
|
323
316
|
let response = `# ${toolNameArg}@${targetVersion}\n\n`;
|
|
324
317
|
response += `**Description:** ${versionInfo.description}\n\n`;
|
|
325
318
|
|
|
326
|
-
if (manifest.inputSchema) {
|
|
327
|
-
response += `## Input Schema\n\`\`\`json\n${JSON.stringify(manifest.inputSchema, null, 2)}\n\`\`\`\n\n`;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
319
|
if (manifest.outputSchema) {
|
|
331
320
|
response += `## Output Schema\n\`\`\`json\n${JSON.stringify(manifest.outputSchema, null, 2)}\n\`\`\`\n\n`;
|
|
332
321
|
}
|
|
@@ -343,6 +332,27 @@ async function handleMetaTool(
|
|
|
343
332
|
}
|
|
344
333
|
}
|
|
345
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
|
+
|
|
346
356
|
return {
|
|
347
357
|
content: [{ type: "text", text: response }],
|
|
348
358
|
};
|
|
@@ -363,10 +373,14 @@ async function handleMetaTool(
|
|
|
363
373
|
const toolNameArg = args.tool as string;
|
|
364
374
|
const toolArgs = (args.args as Record<string, unknown>) || {};
|
|
365
375
|
|
|
376
|
+
// Parse action specifier (owner/skill/action or owner/skill)
|
|
377
|
+
const { skillName, actionName } = parseActionSpecifier(toolNameArg);
|
|
378
|
+
|
|
366
379
|
try {
|
|
367
380
|
// Check if tool is already installed
|
|
368
|
-
const toolInfo = getMcpToolInfo(
|
|
381
|
+
const toolInfo = getMcpToolInfo(skillName);
|
|
369
382
|
let manifest: ToolManifest;
|
|
383
|
+
let actionsManifest: ActionsManifest | undefined;
|
|
370
384
|
let cachePath: string;
|
|
371
385
|
let bundleHash: string | undefined;
|
|
372
386
|
let toolVersion: string | undefined;
|
|
@@ -375,7 +389,7 @@ async function handleMetaTool(
|
|
|
375
389
|
|
|
376
390
|
if (toolInfo) {
|
|
377
391
|
// Tool is installed, use cached version
|
|
378
|
-
const loaded =
|
|
392
|
+
const loaded = loadManifestWithActions(toolInfo.cachePath);
|
|
379
393
|
if (!loaded) {
|
|
380
394
|
return {
|
|
381
395
|
content: [{ type: "text", text: "Failed to load installed tool manifest" }],
|
|
@@ -383,38 +397,39 @@ async function handleMetaTool(
|
|
|
383
397
|
};
|
|
384
398
|
}
|
|
385
399
|
manifest = loaded.manifest;
|
|
400
|
+
actionsManifest = loaded.actionsManifest;
|
|
386
401
|
cachePath = toolInfo.cachePath;
|
|
387
402
|
toolVersion = toolInfo.version;
|
|
388
403
|
|
|
389
404
|
// Get bundle hash for installed tool from registry
|
|
390
405
|
try {
|
|
391
|
-
const versionInfo = await getToolVersion(apiClient,
|
|
406
|
+
const versionInfo = await getToolVersion(apiClient, skillName, toolVersion);
|
|
392
407
|
bundleHash = versionInfo.bundle.hash;
|
|
393
408
|
} catch {
|
|
394
409
|
// Continue without hash - will skip verification
|
|
395
410
|
}
|
|
396
411
|
} else {
|
|
397
412
|
// Tool not installed - fetch and install temporarily
|
|
398
|
-
const info = await getToolInfo(apiClient,
|
|
413
|
+
const info = await getToolInfo(apiClient, skillName);
|
|
399
414
|
toolVersion = info.latestVersion;
|
|
400
415
|
|
|
401
416
|
// Download bundle
|
|
402
417
|
const bundleResult = await downloadBundle(apiClient, {
|
|
403
|
-
name:
|
|
418
|
+
name: skillName,
|
|
404
419
|
version: info.latestVersion,
|
|
405
420
|
verify: true,
|
|
406
421
|
});
|
|
407
422
|
bundleHash = bundleResult.hash;
|
|
408
423
|
|
|
409
424
|
// Extract to cache
|
|
410
|
-
cachePath = getToolCachePath(
|
|
425
|
+
cachePath = getToolCachePath(skillName, info.latestVersion);
|
|
411
426
|
if (pathExists(cachePath)) {
|
|
412
427
|
rmSync(cachePath, { recursive: true, force: true });
|
|
413
428
|
}
|
|
414
429
|
await extractBundle(bundleResult.data, cachePath);
|
|
415
430
|
|
|
416
|
-
// Load manifest
|
|
417
|
-
const loaded =
|
|
431
|
+
// Load manifest with actions
|
|
432
|
+
const loaded = loadManifestWithActions(cachePath);
|
|
418
433
|
if (!loaded) {
|
|
419
434
|
return {
|
|
420
435
|
content: [{ type: "text", text: "Failed to load downloaded tool manifest" }],
|
|
@@ -422,6 +437,7 @@ async function handleMetaTool(
|
|
|
422
437
|
};
|
|
423
438
|
}
|
|
424
439
|
manifest = loaded.manifest;
|
|
440
|
+
actionsManifest = loaded.actionsManifest;
|
|
425
441
|
}
|
|
426
442
|
|
|
427
443
|
// Verify attestations before execution
|
|
@@ -431,7 +447,7 @@ async function handleMetaTool(
|
|
|
431
447
|
try {
|
|
432
448
|
const verified = await verifyAllAttestations(
|
|
433
449
|
apiClient,
|
|
434
|
-
|
|
450
|
+
skillName,
|
|
435
451
|
toolVersion,
|
|
436
452
|
bundleHash
|
|
437
453
|
);
|
|
@@ -459,7 +475,7 @@ async function handleMetaTool(
|
|
|
459
475
|
try {
|
|
460
476
|
const verified = await verifyAllAttestations(
|
|
461
477
|
apiClient,
|
|
462
|
-
|
|
478
|
+
skillName,
|
|
463
479
|
toolVersion,
|
|
464
480
|
bundleHash
|
|
465
481
|
);
|
|
@@ -497,34 +513,74 @@ async function handleMetaTool(
|
|
|
497
513
|
// policy === 'allow' - continue execution with warning
|
|
498
514
|
}
|
|
499
515
|
|
|
500
|
-
//
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
516
|
+
// Resolve secrets from keyring
|
|
517
|
+
const secretOverrides = await resolveManifestSecrets(skillName, manifest);
|
|
518
|
+
|
|
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 }));
|
|
504
529
|
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
508
|
-
return {
|
|
509
|
-
content: [{ type: "text", text: `Input validation failed: ${errors}` }],
|
|
510
|
-
isError: true,
|
|
511
|
-
};
|
|
512
|
-
}
|
|
530
|
+
const provider = await router.selectProvider(skillName);
|
|
531
|
+
await provider.initialize();
|
|
513
532
|
|
|
514
|
-
|
|
533
|
+
let result: ExecutionResult;
|
|
515
534
|
|
|
516
|
-
//
|
|
517
|
-
|
|
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
|
+
}
|
|
518
551
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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);
|
|
522
555
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
+
}
|
|
528
584
|
|
|
529
585
|
if (result.success) {
|
|
530
586
|
const output = result.output?.stdout || "Tool executed successfully (no output)";
|
|
@@ -636,19 +692,43 @@ function createMcpServer(): Server {
|
|
|
636
692
|
// Start with meta-tools for progressive discovery
|
|
637
693
|
const tools: Tool[] = [...META_TOOLS];
|
|
638
694
|
|
|
639
|
-
// Add installed tools
|
|
695
|
+
// Add installed tools and their actions
|
|
640
696
|
for (const tool of mcpTools) {
|
|
641
|
-
const loaded =
|
|
697
|
+
const loaded = loadManifestWithActions(tool.cachePath);
|
|
642
698
|
const manifest = loaded?.manifest;
|
|
699
|
+
const actionsManifest = loaded?.actionsManifest;
|
|
643
700
|
|
|
644
|
-
const description = manifest?.description || `Enact tool: ${tool.name}`;
|
|
645
701
|
const toolsetNote = activeToolset ? ` [toolset: ${activeToolset}]` : "";
|
|
646
702
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
+
}
|
|
652
732
|
}
|
|
653
733
|
|
|
654
734
|
return { tools };
|
|
@@ -666,8 +746,11 @@ function createMcpServer(): Server {
|
|
|
666
746
|
return metaResult;
|
|
667
747
|
}
|
|
668
748
|
|
|
749
|
+
// Parse action specifier (owner/skill/action or owner/skill)
|
|
750
|
+
const { skillName, actionName } = parseActionSpecifier(enactToolName);
|
|
751
|
+
|
|
669
752
|
// Find the tool in MCP registry (respects active toolset)
|
|
670
|
-
const toolInfo = getMcpToolInfo(
|
|
753
|
+
const toolInfo = getMcpToolInfo(skillName);
|
|
671
754
|
|
|
672
755
|
if (!toolInfo) {
|
|
673
756
|
const activeToolset = getActiveToolset();
|
|
@@ -678,21 +761,21 @@ function createMcpServer(): Server {
|
|
|
678
761
|
content: [
|
|
679
762
|
{
|
|
680
763
|
type: "text",
|
|
681
|
-
text: `Error: Tool "${
|
|
764
|
+
text: `Error: Tool "${skillName}" not found.${toolsetHint} Use 'enact mcp add <tool>' to add it.`,
|
|
682
765
|
},
|
|
683
766
|
],
|
|
684
767
|
isError: true,
|
|
685
768
|
};
|
|
686
769
|
}
|
|
687
770
|
|
|
688
|
-
// Load manifest
|
|
689
|
-
const loaded =
|
|
771
|
+
// Load manifest with actions
|
|
772
|
+
const loaded = loadManifestWithActions(toolInfo.cachePath);
|
|
690
773
|
if (!loaded) {
|
|
691
774
|
return {
|
|
692
775
|
content: [
|
|
693
776
|
{
|
|
694
777
|
type: "text",
|
|
695
|
-
text: `Error: Failed to load manifest for "${
|
|
778
|
+
text: `Error: Failed to load manifest for "${skillName}"`,
|
|
696
779
|
},
|
|
697
780
|
],
|
|
698
781
|
isError: true,
|
|
@@ -700,65 +783,97 @@ function createMcpServer(): Server {
|
|
|
700
783
|
}
|
|
701
784
|
|
|
702
785
|
const manifest = loaded.manifest;
|
|
786
|
+
const actionsManifest = loaded.actionsManifest;
|
|
703
787
|
|
|
704
|
-
//
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
788
|
+
// Resolve secrets from keyring
|
|
789
|
+
const secretOverrides = await resolveManifestSecrets(skillName, manifest);
|
|
790
|
+
|
|
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,
|
|
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 }));
|
|
717
801
|
|
|
718
|
-
|
|
719
|
-
const inputsWithDefaults = manifest.inputSchema
|
|
720
|
-
? applyDefaults(args, manifest.inputSchema)
|
|
721
|
-
: args;
|
|
802
|
+
const provider = await router2.selectProvider(skillName);
|
|
722
803
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
726
|
-
return {
|
|
727
|
-
content: [
|
|
728
|
-
{
|
|
729
|
-
type: "text",
|
|
730
|
-
text: `Input validation failed: ${errors}`,
|
|
731
|
-
},
|
|
732
|
-
],
|
|
733
|
-
isError: true,
|
|
734
|
-
};
|
|
735
|
-
}
|
|
804
|
+
try {
|
|
805
|
+
await provider.initialize();
|
|
736
806
|
|
|
737
|
-
|
|
807
|
+
let result: ExecutionResult;
|
|
738
808
|
|
|
739
|
-
|
|
740
|
-
|
|
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
|
+
};
|
|
824
|
+
}
|
|
741
825
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
});
|
|
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);
|
|
746
829
|
|
|
747
|
-
|
|
748
|
-
|
|
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
|
+
}
|
|
749
843
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
}
|
|
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
|
+
};
|
|
760
868
|
}
|
|
761
|
-
|
|
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
|
+
}
|
|
762
877
|
|
|
763
878
|
if (result.success) {
|
|
764
879
|
return {
|
package/tests/secrets.test.ts
CHANGED
|
@@ -417,20 +417,14 @@ describe("Secret Resolution Integration", () => {
|
|
|
417
417
|
version: "1.2.1",
|
|
418
418
|
description: "Scrape websites using Firecrawl API",
|
|
419
419
|
from: "node:20",
|
|
420
|
-
command: "node /work/firecrawl.js",
|
|
421
420
|
env: {
|
|
422
421
|
FIRECRAWL_API_KEY: {
|
|
423
422
|
description: "Your Firecrawl API key from firecrawl.dev",
|
|
424
423
|
secret: true,
|
|
425
424
|
},
|
|
426
425
|
},
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
properties: {
|
|
430
|
-
url: { type: "string" },
|
|
431
|
-
action: { type: "string" },
|
|
432
|
-
},
|
|
433
|
-
required: ["url"],
|
|
426
|
+
scripts: {
|
|
427
|
+
scrape: "node /work/firecrawl.js {{url}}",
|
|
434
428
|
},
|
|
435
429
|
};
|
|
436
430
|
|
|
@@ -448,12 +442,8 @@ describe("Secret Resolution Integration", () => {
|
|
|
448
442
|
version: "1.0.0",
|
|
449
443
|
description: "A simple greeting tool",
|
|
450
444
|
from: "node:20",
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
type: "object",
|
|
454
|
-
properties: {
|
|
455
|
-
name: { type: "string" },
|
|
456
|
-
},
|
|
445
|
+
scripts: {
|
|
446
|
+
greet: "node /work/greet.js {{name}}",
|
|
457
447
|
},
|
|
458
448
|
};
|
|
459
449
|
|