@codemcp/ade 0.4.0 → 0.5.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/package.json +1 -1
- package/packages/cli/dist/index.js +67 -17
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/catalog/facets/process.ts +10 -1
- package/packages/core/src/registry.ts +3 -2
- package/packages/core/src/resolver.spec.ts +29 -0
- package/packages/core/src/writers/mcp-server.spec.ts +62 -0
- package/packages/core/src/writers/mcp-server.ts +25 -0
- package/packages/core/src/writers/workflows.spec.ts +22 -0
- package/packages/core/src/writers/workflows.ts +5 -2
- package/packages/harnesses/package.json +1 -1
- package/packages/harnesses/src/writers/copilot.spec.ts +2 -6
- package/packages/harnesses/src/writers/copilot.ts +2 -9
- package/packages/harnesses/src/writers/kiro.spec.ts +32 -0
- package/packages/harnesses/src/writers/kiro.ts +22 -5
- package/packages/harnesses/src/writers/opencode.spec.ts +66 -0
- package/packages/harnesses/src/writers/opencode.ts +30 -3
package/package.json
CHANGED
|
@@ -11805,14 +11805,34 @@ var instructionWriter = {
|
|
|
11805
11805
|
var workflowsWriter = {
|
|
11806
11806
|
id: "workflows",
|
|
11807
11807
|
async write(config) {
|
|
11808
|
-
const { package: pkg, ref, env: env2 } = config;
|
|
11808
|
+
const { package: pkg, ref, env: env2, allowedTools } = config;
|
|
11809
11809
|
return {
|
|
11810
11810
|
mcp_servers: [
|
|
11811
11811
|
{
|
|
11812
11812
|
ref: ref ?? pkg,
|
|
11813
11813
|
command: "npx",
|
|
11814
11814
|
args: [pkg],
|
|
11815
|
-
env: env2 ?? {}
|
|
11815
|
+
env: env2 ?? {},
|
|
11816
|
+
...allowedTools !== void 0 ? { allowedTools } : {}
|
|
11817
|
+
}
|
|
11818
|
+
]
|
|
11819
|
+
};
|
|
11820
|
+
}
|
|
11821
|
+
};
|
|
11822
|
+
|
|
11823
|
+
// ../core/dist/writers/mcp-server.js
|
|
11824
|
+
var mcpServerWriter = {
|
|
11825
|
+
id: "mcp-server",
|
|
11826
|
+
async write(config) {
|
|
11827
|
+
const { ref, command: command2, args: args2, env: env2, allowedTools } = config;
|
|
11828
|
+
return {
|
|
11829
|
+
mcp_servers: [
|
|
11830
|
+
{
|
|
11831
|
+
ref,
|
|
11832
|
+
command: command2,
|
|
11833
|
+
args: args2,
|
|
11834
|
+
env: env2 ?? {},
|
|
11835
|
+
...allowedTools !== void 0 ? { allowedTools } : {}
|
|
11816
11836
|
}
|
|
11817
11837
|
]
|
|
11818
11838
|
};
|
|
@@ -11882,12 +11902,13 @@ function createDefaultRegistry() {
|
|
|
11882
11902
|
const registry2 = createRegistry();
|
|
11883
11903
|
registerProvisionWriter(registry2, instructionWriter);
|
|
11884
11904
|
registerProvisionWriter(registry2, workflowsWriter);
|
|
11905
|
+
registerProvisionWriter(registry2, mcpServerWriter);
|
|
11885
11906
|
registerProvisionWriter(registry2, skillsWriter);
|
|
11886
11907
|
registerProvisionWriter(registry2, knowledgeWriter);
|
|
11887
11908
|
registerProvisionWriter(registry2, gitHooksWriter);
|
|
11888
11909
|
registerProvisionWriter(registry2, setupNoteWriter);
|
|
11889
11910
|
registerProvisionWriter(registry2, permissionPolicyWriter);
|
|
11890
|
-
for (const id of ["
|
|
11911
|
+
for (const id of ["installable"]) {
|
|
11891
11912
|
registerProvisionWriter(registry2, {
|
|
11892
11913
|
id,
|
|
11893
11914
|
write: async () => ({})
|
|
@@ -11912,7 +11933,16 @@ var processFacet = {
|
|
|
11912
11933
|
writer: "workflows",
|
|
11913
11934
|
config: {
|
|
11914
11935
|
package: "@codemcp/workflows-server@latest",
|
|
11915
|
-
ref: "workflows"
|
|
11936
|
+
ref: "workflows",
|
|
11937
|
+
env: {
|
|
11938
|
+
VIBE_WORKFLOW_DOMAINS: "skilled"
|
|
11939
|
+
},
|
|
11940
|
+
allowedTools: [
|
|
11941
|
+
"whats_next",
|
|
11942
|
+
"conduct_review",
|
|
11943
|
+
"list_workflows",
|
|
11944
|
+
"get_tool_info"
|
|
11945
|
+
]
|
|
11916
11946
|
}
|
|
11917
11947
|
},
|
|
11918
11948
|
{
|
|
@@ -22295,13 +22325,7 @@ function getBuiltInTools(profile) {
|
|
|
22295
22325
|
}
|
|
22296
22326
|
}
|
|
22297
22327
|
function getForwardedMcpTools(servers) {
|
|
22298
|
-
return servers.
|
|
22299
|
-
const allowedTools = server.allowedTools ?? ["*"];
|
|
22300
|
-
if (allowedTools.includes("*")) {
|
|
22301
|
-
return [`${server.ref}/*`];
|
|
22302
|
-
}
|
|
22303
|
-
return allowedTools.map((tool) => `${server.ref}/${tool}`);
|
|
22304
|
-
});
|
|
22328
|
+
return servers.map((server) => `${server.ref}/*`);
|
|
22305
22329
|
}
|
|
22306
22330
|
function renderCopilotAgentMcpServers(servers) {
|
|
22307
22331
|
if (servers.length === 0) {
|
|
@@ -22313,7 +22337,7 @@ function renderCopilotAgentMcpServers(servers) {
|
|
|
22313
22337
|
lines.push(" type: stdio");
|
|
22314
22338
|
lines.push(` command: ${JSON.stringify(server.command)}`);
|
|
22315
22339
|
lines.push(` args: ${JSON.stringify(server.args)}`);
|
|
22316
|
-
lines.push(` tools: ${JSON.stringify(
|
|
22340
|
+
lines.push(` tools: ${JSON.stringify(["*"])}`);
|
|
22317
22341
|
if (Object.keys(server.env).length > 0) {
|
|
22318
22342
|
lines.push(" env:");
|
|
22319
22343
|
for (const [key, value] of Object.entries(server.env)) {
|
|
@@ -22460,20 +22484,21 @@ var kiroWriter = {
|
|
|
22460
22484
|
})
|
|
22461
22485
|
});
|
|
22462
22486
|
const tools = getKiroTools(getAutonomyProfile(config), config.mcp_servers);
|
|
22487
|
+
const allowedTools = getKiroAllowedTools(getAutonomyProfile(config), config.mcp_servers);
|
|
22463
22488
|
await writeJson(join17(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
22464
22489
|
name: "ade",
|
|
22465
22490
|
description: "ADE \u2014 Agentic Development Environment agent with project conventions and tools.",
|
|
22466
22491
|
prompt: config.instructions.join("\n\n") || "ADE \u2014 Agentic Development Environment agent.",
|
|
22467
22492
|
mcpServers: getKiroAgentMcpServers(config.mcp_servers),
|
|
22468
22493
|
tools,
|
|
22469
|
-
allowedTools
|
|
22494
|
+
allowedTools,
|
|
22470
22495
|
useLegacyMcpJson: true
|
|
22471
22496
|
});
|
|
22472
22497
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
22473
22498
|
}
|
|
22474
22499
|
};
|
|
22475
22500
|
function getKiroTools(profile, servers) {
|
|
22476
|
-
const mcpTools =
|
|
22501
|
+
const mcpTools = servers.map((server) => `@${server.ref}/*`);
|
|
22477
22502
|
switch (profile) {
|
|
22478
22503
|
case "rigid":
|
|
22479
22504
|
return ["read", "shell", "spec", ...mcpTools];
|
|
@@ -22485,14 +22510,24 @@ function getKiroTools(profile, servers) {
|
|
|
22485
22510
|
return ["read", "write", "shell", "spec", ...mcpTools];
|
|
22486
22511
|
}
|
|
22487
22512
|
}
|
|
22488
|
-
function
|
|
22489
|
-
|
|
22513
|
+
function getKiroAllowedTools(profile, servers) {
|
|
22514
|
+
const mcpAllowedTools = servers.flatMap((server) => {
|
|
22490
22515
|
const allowedTools = server.allowedTools ?? ["*"];
|
|
22491
22516
|
if (allowedTools.includes("*")) {
|
|
22492
22517
|
return [`@${server.ref}/*`];
|
|
22493
22518
|
}
|
|
22494
22519
|
return allowedTools.map((tool) => `@${server.ref}/${tool}`);
|
|
22495
22520
|
});
|
|
22521
|
+
switch (profile) {
|
|
22522
|
+
case "rigid":
|
|
22523
|
+
return ["read", "shell", "spec", ...mcpAllowedTools];
|
|
22524
|
+
case "sensible-defaults":
|
|
22525
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
22526
|
+
case "max-autonomy":
|
|
22527
|
+
return ["read", "write", "shell(*)", "spec", ...mcpAllowedTools];
|
|
22528
|
+
default:
|
|
22529
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
22530
|
+
}
|
|
22496
22531
|
}
|
|
22497
22532
|
function getKiroAgentMcpServers(servers) {
|
|
22498
22533
|
return Object.fromEntries(servers.map((server) => [
|
|
@@ -22630,6 +22665,19 @@ var MAX_AUTONOMY_RULES = {
|
|
|
22630
22665
|
codesearch: "ask",
|
|
22631
22666
|
doom_loop: "deny"
|
|
22632
22667
|
};
|
|
22668
|
+
function getMcpPermissions(servers) {
|
|
22669
|
+
const entries = servers.flatMap((server) => {
|
|
22670
|
+
const allowedTools = server.allowedTools ?? ["*"];
|
|
22671
|
+
if (allowedTools.includes("*")) {
|
|
22672
|
+
return [[`${server.ref}*`, "allow"]];
|
|
22673
|
+
}
|
|
22674
|
+
return [
|
|
22675
|
+
[`${server.ref}*`, "ask"],
|
|
22676
|
+
...allowedTools.map((tool) => [`${server.ref}_${tool}`, "allow"])
|
|
22677
|
+
];
|
|
22678
|
+
});
|
|
22679
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
22680
|
+
}
|
|
22633
22681
|
function getPermissionRules(profile) {
|
|
22634
22682
|
switch (profile) {
|
|
22635
22683
|
case "rigid":
|
|
@@ -22658,9 +22706,11 @@ var opencodeWriter = {
|
|
|
22658
22706
|
defaults: { $schema: "https://opencode.ai/config.json" }
|
|
22659
22707
|
});
|
|
22660
22708
|
const permission = getPermissionRules(getAutonomyProfile(config));
|
|
22709
|
+
const mcpPermissions = getMcpPermissions(config.mcp_servers);
|
|
22710
|
+
const mergedPermission = permission || mcpPermissions ? { ...mcpPermissions ?? {}, ...permission ?? {} } : void 0;
|
|
22661
22711
|
await writeAgentMd(config, {
|
|
22662
22712
|
path: join18(projectRoot, ".opencode", "agents", "ade.md"),
|
|
22663
|
-
extraFrontmatter:
|
|
22713
|
+
extraFrontmatter: mergedPermission ? renderYamlMapping("permission", mergedPermission) : void 0,
|
|
22664
22714
|
fallbackBody: "ADE \u2014 Agentic Development Environment agent with project conventions and tools."
|
|
22665
22715
|
});
|
|
22666
22716
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
@@ -16,7 +16,16 @@ export const processFacet: Facet = {
|
|
|
16
16
|
writer: "workflows",
|
|
17
17
|
config: {
|
|
18
18
|
package: "@codemcp/workflows-server@latest",
|
|
19
|
-
ref: "workflows"
|
|
19
|
+
ref: "workflows",
|
|
20
|
+
env: {
|
|
21
|
+
VIBE_WORKFLOW_DOMAINS: "skilled"
|
|
22
|
+
},
|
|
23
|
+
allowedTools: [
|
|
24
|
+
"whats_next",
|
|
25
|
+
"conduct_review",
|
|
26
|
+
"list_workflows",
|
|
27
|
+
"get_tool_info"
|
|
28
|
+
]
|
|
20
29
|
}
|
|
21
30
|
},
|
|
22
31
|
{
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from "./types.js";
|
|
6
6
|
import { instructionWriter } from "./writers/instruction.js";
|
|
7
7
|
import { workflowsWriter } from "./writers/workflows.js";
|
|
8
|
+
import { mcpServerWriter } from "./writers/mcp-server.js";
|
|
8
9
|
import { skillsWriter } from "./writers/skills.js";
|
|
9
10
|
import { knowledgeWriter } from "./writers/knowledge.js";
|
|
10
11
|
import { gitHooksWriter } from "./writers/git-hooks.js";
|
|
@@ -51,15 +52,15 @@ export function createDefaultRegistry(): WriterRegistry {
|
|
|
51
52
|
|
|
52
53
|
registerProvisionWriter(registry, instructionWriter);
|
|
53
54
|
registerProvisionWriter(registry, workflowsWriter);
|
|
55
|
+
registerProvisionWriter(registry, mcpServerWriter);
|
|
54
56
|
registerProvisionWriter(registry, skillsWriter);
|
|
55
|
-
|
|
56
57
|
registerProvisionWriter(registry, knowledgeWriter);
|
|
57
58
|
registerProvisionWriter(registry, gitHooksWriter);
|
|
58
59
|
registerProvisionWriter(registry, setupNoteWriter);
|
|
59
60
|
registerProvisionWriter(registry, permissionPolicyWriter);
|
|
60
61
|
|
|
61
62
|
// Stub writers for types not yet implemented
|
|
62
|
-
for (const id of ["
|
|
63
|
+
for (const id of ["installable"]) {
|
|
63
64
|
registerProvisionWriter(registry, {
|
|
64
65
|
id,
|
|
65
66
|
write: async () => ({})
|
|
@@ -120,6 +120,35 @@ describe("resolve", () => {
|
|
|
120
120
|
expect(result.mcp_servers.length).toBeGreaterThanOrEqual(1);
|
|
121
121
|
expect(result.instructions).toContain("Extra instruction");
|
|
122
122
|
});
|
|
123
|
+
|
|
124
|
+
it("env set in the catalog option config is forwarded to the resolved mcp_server entry", async () => {
|
|
125
|
+
const userConfig: UserConfig = {
|
|
126
|
+
choices: { process: "codemcp-workflows" }
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Patch the catalog option's provision config to include env
|
|
130
|
+
const processFacet = catalog.facets.find((f) => f.id === "process")!;
|
|
131
|
+
const option = processFacet.options.find(
|
|
132
|
+
(o) => o.id === "codemcp-workflows"
|
|
133
|
+
)!;
|
|
134
|
+
const workflowsProvision = option.recipe.find(
|
|
135
|
+
(p) => p.writer === "workflows"
|
|
136
|
+
)!;
|
|
137
|
+
workflowsProvision.config = {
|
|
138
|
+
...workflowsProvision.config,
|
|
139
|
+
env: { VIBE_WORKFLOWS_DOMAIN: "skilled" }
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = await resolve(userConfig, catalog, registry);
|
|
143
|
+
|
|
144
|
+
const workflowsServer = result.mcp_servers.find(
|
|
145
|
+
(s) => s.ref === "workflows"
|
|
146
|
+
);
|
|
147
|
+
expect(workflowsServer).toBeDefined();
|
|
148
|
+
expect(workflowsServer!.env).toEqual({
|
|
149
|
+
VIBE_WORKFLOWS_DOMAIN: "skilled"
|
|
150
|
+
});
|
|
151
|
+
});
|
|
123
152
|
});
|
|
124
153
|
|
|
125
154
|
describe("unknown facet in choices", () => {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mcpServerWriter } from "./mcp-server.js";
|
|
3
|
+
import type { ResolutionContext } from "../types.js";
|
|
4
|
+
|
|
5
|
+
describe("mcpServerWriter", () => {
|
|
6
|
+
const context: ResolutionContext = { resolved: {} };
|
|
7
|
+
|
|
8
|
+
it("has id 'mcp-server'", () => {
|
|
9
|
+
expect(mcpServerWriter.id).toBe("mcp-server");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns mcp_servers with correct ref, command, args, and env", async () => {
|
|
13
|
+
const result = await mcpServerWriter.write(
|
|
14
|
+
{
|
|
15
|
+
ref: "my-server",
|
|
16
|
+
command: "npx",
|
|
17
|
+
args: ["my-mcp-package"],
|
|
18
|
+
env: { KEY: "value" }
|
|
19
|
+
},
|
|
20
|
+
context
|
|
21
|
+
);
|
|
22
|
+
expect(result).toEqual({
|
|
23
|
+
mcp_servers: [
|
|
24
|
+
{
|
|
25
|
+
ref: "my-server",
|
|
26
|
+
command: "npx",
|
|
27
|
+
args: ["my-mcp-package"],
|
|
28
|
+
env: { KEY: "value" }
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("defaults env to an empty object when not specified", async () => {
|
|
35
|
+
const result = await mcpServerWriter.write(
|
|
36
|
+
{ ref: "my-server", command: "npx", args: ["my-mcp-package"] },
|
|
37
|
+
context
|
|
38
|
+
);
|
|
39
|
+
expect(result.mcp_servers![0].env).toEqual({});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("includes allowedTools when specified", async () => {
|
|
43
|
+
const result = await mcpServerWriter.write(
|
|
44
|
+
{
|
|
45
|
+
ref: "my-server",
|
|
46
|
+
command: "npx",
|
|
47
|
+
args: ["my-mcp-package"],
|
|
48
|
+
allowedTools: ["tool_a", "tool_b"]
|
|
49
|
+
},
|
|
50
|
+
context
|
|
51
|
+
);
|
|
52
|
+
expect(result.mcp_servers![0].allowedTools).toEqual(["tool_a", "tool_b"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("omits allowedTools from entry when not specified", async () => {
|
|
56
|
+
const result = await mcpServerWriter.write(
|
|
57
|
+
{ ref: "my-server", command: "npx", args: ["my-mcp-package"] },
|
|
58
|
+
context
|
|
59
|
+
);
|
|
60
|
+
expect(result.mcp_servers![0]).not.toHaveProperty("allowedTools");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ProvisionWriterDef } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export const mcpServerWriter: ProvisionWriterDef = {
|
|
4
|
+
id: "mcp-server",
|
|
5
|
+
async write(config) {
|
|
6
|
+
const { ref, command, args, env, allowedTools } = config as {
|
|
7
|
+
ref: string;
|
|
8
|
+
command: string;
|
|
9
|
+
args: string[];
|
|
10
|
+
env?: Record<string, string>;
|
|
11
|
+
allowedTools?: string[];
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
mcp_servers: [
|
|
15
|
+
{
|
|
16
|
+
ref,
|
|
17
|
+
command,
|
|
18
|
+
args,
|
|
19
|
+
env: env ?? {},
|
|
20
|
+
...(allowedTools !== undefined ? { allowedTools } : {})
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -59,6 +59,28 @@ describe("workflowsWriter", () => {
|
|
|
59
59
|
expect(result.mcp_servers![0].env).toEqual({});
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
it("includes allowedTools in the entry when specified", async () => {
|
|
63
|
+
const result = await workflowsWriter.write(
|
|
64
|
+
{
|
|
65
|
+
package: "@codemcp/workflows-server",
|
|
66
|
+
allowedTools: ["whats_next", "conduct_review"]
|
|
67
|
+
},
|
|
68
|
+
context
|
|
69
|
+
);
|
|
70
|
+
expect(result.mcp_servers![0].allowedTools).toEqual([
|
|
71
|
+
"whats_next",
|
|
72
|
+
"conduct_review"
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("omits allowedTools from entry when not specified", async () => {
|
|
77
|
+
const result = await workflowsWriter.write(
|
|
78
|
+
{ package: "@codemcp/workflows-server" },
|
|
79
|
+
context
|
|
80
|
+
);
|
|
81
|
+
expect(result.mcp_servers![0]).not.toHaveProperty("allowedTools");
|
|
82
|
+
});
|
|
83
|
+
|
|
62
84
|
it("only returns mcp_servers, not other LogicalConfig keys", async () => {
|
|
63
85
|
const result = await workflowsWriter.write(
|
|
64
86
|
{ package: "@codemcp/workflows-server" },
|
|
@@ -6,11 +6,13 @@ export const workflowsWriter: ProvisionWriterDef = {
|
|
|
6
6
|
const {
|
|
7
7
|
package: pkg,
|
|
8
8
|
ref,
|
|
9
|
-
env
|
|
9
|
+
env,
|
|
10
|
+
allowedTools
|
|
10
11
|
} = config as {
|
|
11
12
|
package: string;
|
|
12
13
|
ref?: string;
|
|
13
14
|
env?: Record<string, string>;
|
|
15
|
+
allowedTools?: string[];
|
|
14
16
|
};
|
|
15
17
|
return {
|
|
16
18
|
mcp_servers: [
|
|
@@ -18,7 +20,8 @@ export const workflowsWriter: ProvisionWriterDef = {
|
|
|
18
20
|
ref: ref ?? pkg,
|
|
19
21
|
command: "npx",
|
|
20
22
|
args: [pkg],
|
|
21
|
-
env: env ?? {}
|
|
23
|
+
env: env ?? {},
|
|
24
|
+
...(allowedTools !== undefined ? { allowedTools } : {})
|
|
22
25
|
}
|
|
23
26
|
]
|
|
24
27
|
};
|
|
@@ -198,12 +198,8 @@ describe("copilotWriter", () => {
|
|
|
198
198
|
expect(sensibleAgent).not.toContain(" - execute");
|
|
199
199
|
expect(sensibleAgent).not.toContain(" - todo");
|
|
200
200
|
expect(sensibleAgent).not.toContain(" - web");
|
|
201
|
-
expect(sensibleAgent).toContain(" - workflows
|
|
202
|
-
expect(sensibleAgent).toContain(
|
|
203
|
-
expect(sensibleAgent).not.toContain(" - workflows/*");
|
|
204
|
-
expect(sensibleAgent).toContain(
|
|
205
|
-
' tools: ["whats_next","proceed_to_phase"]'
|
|
206
|
-
);
|
|
201
|
+
expect(sensibleAgent).toContain(" - workflows/*");
|
|
202
|
+
expect(sensibleAgent).toContain(' tools: ["*"]');
|
|
207
203
|
|
|
208
204
|
expect(maxAgent).toContain(" - read");
|
|
209
205
|
expect(maxAgent).toContain(" - edit");
|
|
@@ -56,14 +56,7 @@ function getBuiltInTools(profile: AutonomyProfile | undefined): string[] {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function getForwardedMcpTools(servers: McpServerEntry[]): string[] {
|
|
59
|
-
return servers.
|
|
60
|
-
const allowedTools = server.allowedTools ?? ["*"];
|
|
61
|
-
if (allowedTools.includes("*")) {
|
|
62
|
-
return [`${server.ref}/*`];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return allowedTools.map((tool) => `${server.ref}/${tool}`);
|
|
66
|
-
});
|
|
59
|
+
return servers.map((server) => `${server.ref}/*`);
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
function renderCopilotAgentMcpServers(servers: McpServerEntry[]): string[] {
|
|
@@ -78,7 +71,7 @@ function renderCopilotAgentMcpServers(servers: McpServerEntry[]): string[] {
|
|
|
78
71
|
lines.push(" type: stdio");
|
|
79
72
|
lines.push(` command: ${JSON.stringify(server.command)}`);
|
|
80
73
|
lines.push(` args: ${JSON.stringify(server.args)}`);
|
|
81
|
-
lines.push(` tools: ${JSON.stringify(
|
|
74
|
+
lines.push(` tools: ${JSON.stringify(["*"])}`);
|
|
82
75
|
|
|
83
76
|
if (Object.keys(server.env).length > 0) {
|
|
84
77
|
lines.push(" env:");
|
|
@@ -186,4 +186,36 @@ describe("kiroWriter", () => {
|
|
|
186
186
|
expect(rigidMcp.mcpServers.workflows.autoApprove).toEqual(["*"]);
|
|
187
187
|
expect(maxMcp.mcpServers.workflows.autoApprove).toEqual(["*"]);
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
it("uses wildcard in tools but restricted names in allowedTools when allowedTools is set", async () => {
|
|
191
|
+
const config: LogicalConfig = {
|
|
192
|
+
mcp_servers: [
|
|
193
|
+
{
|
|
194
|
+
ref: "workflows",
|
|
195
|
+
command: "npx",
|
|
196
|
+
args: ["-y", "@codemcp/workflows"],
|
|
197
|
+
env: {},
|
|
198
|
+
allowedTools: ["whats_next", "conduct_review"]
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
instructions: [],
|
|
202
|
+
cli_actions: [],
|
|
203
|
+
knowledge_sources: [],
|
|
204
|
+
skills: [],
|
|
205
|
+
git_hooks: [],
|
|
206
|
+
setup_notes: []
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
await kiroWriter.install(config, dir);
|
|
210
|
+
|
|
211
|
+
const agent = JSON.parse(
|
|
212
|
+
await readFile(join(dir, ".kiro", "agents", "ade.json"), "utf-8")
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(agent.tools).toContain("@workflows/*");
|
|
216
|
+
expect(agent.tools).not.toContain("@workflows/whats_next");
|
|
217
|
+
expect(agent.allowedTools).toContain("@workflows/whats_next");
|
|
218
|
+
expect(agent.allowedTools).toContain("@workflows/conduct_review");
|
|
219
|
+
expect(agent.allowedTools).not.toContain("@workflows/*");
|
|
220
|
+
});
|
|
189
221
|
});
|
|
@@ -27,6 +27,10 @@ export const kiroWriter: HarnessWriter = {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const tools = getKiroTools(getAutonomyProfile(config), config.mcp_servers);
|
|
30
|
+
const allowedTools = getKiroAllowedTools(
|
|
31
|
+
getAutonomyProfile(config),
|
|
32
|
+
config.mcp_servers
|
|
33
|
+
);
|
|
30
34
|
await writeJson(join(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
31
35
|
name: "ade",
|
|
32
36
|
description:
|
|
@@ -36,7 +40,7 @@ export const kiroWriter: HarnessWriter = {
|
|
|
36
40
|
"ADE — Agentic Development Environment agent.",
|
|
37
41
|
mcpServers: getKiroAgentMcpServers(config.mcp_servers),
|
|
38
42
|
tools,
|
|
39
|
-
allowedTools
|
|
43
|
+
allowedTools,
|
|
40
44
|
useLegacyMcpJson: true
|
|
41
45
|
});
|
|
42
46
|
|
|
@@ -48,7 +52,7 @@ function getKiroTools(
|
|
|
48
52
|
profile: AutonomyProfile | undefined,
|
|
49
53
|
servers: McpServerEntry[]
|
|
50
54
|
): string[] {
|
|
51
|
-
const mcpTools =
|
|
55
|
+
const mcpTools = servers.map((server) => `@${server.ref}/*`);
|
|
52
56
|
|
|
53
57
|
switch (profile) {
|
|
54
58
|
case "rigid":
|
|
@@ -62,15 +66,28 @@ function getKiroTools(
|
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function
|
|
66
|
-
|
|
69
|
+
function getKiroAllowedTools(
|
|
70
|
+
profile: AutonomyProfile | undefined,
|
|
71
|
+
servers: McpServerEntry[]
|
|
72
|
+
): string[] {
|
|
73
|
+
const mcpAllowedTools = servers.flatMap((server) => {
|
|
67
74
|
const allowedTools = server.allowedTools ?? ["*"];
|
|
68
75
|
if (allowedTools.includes("*")) {
|
|
69
76
|
return [`@${server.ref}/*`];
|
|
70
77
|
}
|
|
71
|
-
|
|
72
78
|
return allowedTools.map((tool) => `@${server.ref}/${tool}`);
|
|
73
79
|
});
|
|
80
|
+
|
|
81
|
+
switch (profile) {
|
|
82
|
+
case "rigid":
|
|
83
|
+
return ["read", "shell", "spec", ...mcpAllowedTools];
|
|
84
|
+
case "sensible-defaults":
|
|
85
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
86
|
+
case "max-autonomy":
|
|
87
|
+
return ["read", "write", "shell(*)", "spec", ...mcpAllowedTools];
|
|
88
|
+
default:
|
|
89
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
90
|
+
}
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
function getKiroAgentMcpServers(
|
|
@@ -148,6 +148,72 @@ describe("opencodeWriter", () => {
|
|
|
148
148
|
expect(rigidAgent).not.toContain("tools:");
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it("writes allowed MCP tools into the permission block of the agent frontmatter", async () => {
|
|
152
|
+
const projectRoot = join(dir, "mcp-tools");
|
|
153
|
+
const config: LogicalConfig = {
|
|
154
|
+
mcp_servers: [
|
|
155
|
+
{
|
|
156
|
+
ref: "workflows",
|
|
157
|
+
command: "npx",
|
|
158
|
+
args: ["@codemcp/workflows-server@latest"],
|
|
159
|
+
env: {},
|
|
160
|
+
allowedTools: ["whats_next", "conduct_review"]
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
instructions: ["Follow project rules."],
|
|
164
|
+
cli_actions: [],
|
|
165
|
+
knowledge_sources: [],
|
|
166
|
+
skills: [],
|
|
167
|
+
git_hooks: [],
|
|
168
|
+
setup_notes: []
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
await opencodeWriter.install(config, projectRoot);
|
|
172
|
+
|
|
173
|
+
const agent = await readFile(
|
|
174
|
+
join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
175
|
+
"utf-8"
|
|
176
|
+
);
|
|
177
|
+
const frontmatter = parseFrontmatter(agent);
|
|
178
|
+
const permission = frontmatter.permission as Record<string, string>;
|
|
179
|
+
|
|
180
|
+
expect(permission["workflows*"]).toBe("ask");
|
|
181
|
+
expect(permission["workflows_whats_next"]).toBe("allow");
|
|
182
|
+
expect(permission["workflows_conduct_review"]).toBe("allow");
|
|
183
|
+
expect(agent).not.toContain("tools:");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("writes wildcard MCP permission when allowedTools is not restricted", async () => {
|
|
187
|
+
const projectRoot = join(dir, "mcp-wildcard");
|
|
188
|
+
const config: LogicalConfig = {
|
|
189
|
+
mcp_servers: [
|
|
190
|
+
{
|
|
191
|
+
ref: "workflows",
|
|
192
|
+
command: "npx",
|
|
193
|
+
args: ["@codemcp/workflows-server@latest"],
|
|
194
|
+
env: {}
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
instructions: ["Follow project rules."],
|
|
198
|
+
cli_actions: [],
|
|
199
|
+
knowledge_sources: [],
|
|
200
|
+
skills: [],
|
|
201
|
+
git_hooks: [],
|
|
202
|
+
setup_notes: []
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
await opencodeWriter.install(config, projectRoot);
|
|
206
|
+
|
|
207
|
+
const agent = await readFile(
|
|
208
|
+
join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
209
|
+
"utf-8"
|
|
210
|
+
);
|
|
211
|
+
const frontmatter = parseFrontmatter(agent);
|
|
212
|
+
const permission = frontmatter.permission as Record<string, string>;
|
|
213
|
+
|
|
214
|
+
expect(permission["workflows*"]).toBe("allow");
|
|
215
|
+
});
|
|
216
|
+
|
|
151
217
|
it("keeps MCP servers in project config and writes documented environment fields", async () => {
|
|
152
218
|
const projectRoot = join(dir, "mcp");
|
|
153
219
|
const config = {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AutonomyProfile,
|
|
4
|
+
LogicalConfig,
|
|
5
|
+
McpServerEntry
|
|
6
|
+
} from "@codemcp/ade-core";
|
|
3
7
|
import type { HarnessWriter } from "../types.js";
|
|
4
8
|
import {
|
|
5
9
|
writeAgentMd,
|
|
@@ -138,6 +142,24 @@ const MAX_AUTONOMY_RULES: Record<string, PermissionRule> = {
|
|
|
138
142
|
doom_loop: "deny"
|
|
139
143
|
};
|
|
140
144
|
|
|
145
|
+
function getMcpPermissions(
|
|
146
|
+
servers: McpServerEntry[]
|
|
147
|
+
): Record<string, PermissionRule> | undefined {
|
|
148
|
+
const entries: [string, PermissionRule][] = servers.flatMap((server) => {
|
|
149
|
+
const allowedTools = server.allowedTools ?? ["*"];
|
|
150
|
+
if (allowedTools.includes("*")) {
|
|
151
|
+
return [[`${server.ref}*`, "allow"]] as [string, PermissionRule][];
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
[`${server.ref}*`, "ask"] as [string, PermissionRule],
|
|
155
|
+
...allowedTools.map(
|
|
156
|
+
(tool) => [`${server.ref}_${tool}`, "allow"] as [string, PermissionRule]
|
|
157
|
+
)
|
|
158
|
+
];
|
|
159
|
+
});
|
|
160
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
141
163
|
function getPermissionRules(
|
|
142
164
|
profile: AutonomyProfile | undefined
|
|
143
165
|
): Record<string, PermissionRule> | undefined {
|
|
@@ -170,11 +192,16 @@ export const opencodeWriter: HarnessWriter = {
|
|
|
170
192
|
});
|
|
171
193
|
|
|
172
194
|
const permission = getPermissionRules(getAutonomyProfile(config));
|
|
195
|
+
const mcpPermissions = getMcpPermissions(config.mcp_servers);
|
|
196
|
+
const mergedPermission =
|
|
197
|
+
permission || mcpPermissions
|
|
198
|
+
? { ...(mcpPermissions ?? {}), ...(permission ?? {}) }
|
|
199
|
+
: undefined;
|
|
173
200
|
|
|
174
201
|
await writeAgentMd(config, {
|
|
175
202
|
path: join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
176
|
-
extraFrontmatter:
|
|
177
|
-
? renderYamlMapping("permission",
|
|
203
|
+
extraFrontmatter: mergedPermission
|
|
204
|
+
? renderYamlMapping("permission", mergedPermission)
|
|
178
205
|
: undefined,
|
|
179
206
|
fallbackBody:
|
|
180
207
|
"ADE — Agentic Development Environment agent with project conventions and tools."
|