@codemcp/ade 0.4.0 → 0.6.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/.beads/issues.jsonl +14 -0
- package/.beads/last-touched +1 -1
- package/.vibe/beads-state-ade-main-iazal7.json +29 -0
- package/.vibe/development-plan-extensibility.md +169 -0
- package/ade.extensions.mjs +66 -0
- package/docs/adr/0002-extension-file-type-safety.md +97 -0
- package/docs/guide/extensions.md +187 -0
- package/package.json +3 -2
- package/packages/cli/dist/index.js +166 -32
- package/packages/cli/package.json +4 -2
- package/packages/cli/src/commands/extensions.integration.spec.ts +122 -0
- package/packages/cli/src/commands/install.spec.ts +21 -1
- package/packages/cli/src/commands/install.ts +10 -5
- package/packages/cli/src/commands/setup.ts +8 -4
- package/packages/cli/src/extensions.spec.ts +128 -0
- package/packages/cli/src/extensions.ts +71 -0
- package/packages/cli/src/index.ts +10 -5
- package/packages/core/package.json +3 -2
- package/packages/core/src/catalog/facets/process.ts +10 -1
- package/packages/core/src/catalog/index.ts +38 -1
- package/packages/core/src/extensions.spec.ts +169 -0
- package/packages/core/src/index.ts +3 -1
- package/packages/core/src/registry.ts +3 -2
- package/packages/core/src/resolver.spec.ts +29 -0
- package/packages/core/src/types.ts +71 -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/index.spec.ts +48 -1
- package/packages/harnesses/src/index.ts +10 -0
- 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/pnpm-workspace.yaml +2 -0
- /package/docs/{adrs → adr}/0001-tui-framework-selection.md +0 -0
|
@@ -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
|
};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
allHarnessWriters,
|
|
4
|
+
getHarnessWriter,
|
|
5
|
+
getHarnessIds,
|
|
6
|
+
buildHarnessWriters
|
|
7
|
+
} from "./index.js";
|
|
8
|
+
import type { HarnessWriter } from "./types.js";
|
|
3
9
|
|
|
4
10
|
describe("harness registry", () => {
|
|
5
11
|
it("exports all harness writers", () => {
|
|
@@ -43,3 +49,44 @@ describe("harness registry", () => {
|
|
|
43
49
|
}
|
|
44
50
|
});
|
|
45
51
|
});
|
|
52
|
+
|
|
53
|
+
describe("buildHarnessWriters", () => {
|
|
54
|
+
it("returns all built-in writers when no extensions provided", () => {
|
|
55
|
+
const writers = buildHarnessWriters({});
|
|
56
|
+
expect(writers).toHaveLength(allHarnessWriters.length);
|
|
57
|
+
expect(writers.map((w) => w.id)).toEqual(
|
|
58
|
+
allHarnessWriters.map((w) => w.id)
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("appends extension harness writers after built-ins", () => {
|
|
63
|
+
const customWriter: HarnessWriter = {
|
|
64
|
+
id: "sap-copilot",
|
|
65
|
+
label: "SAP Copilot",
|
|
66
|
+
description: "SAP internal Copilot harness",
|
|
67
|
+
install: async () => {}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const writers = buildHarnessWriters({ harnessWriters: [customWriter] });
|
|
71
|
+
expect(writers).toHaveLength(allHarnessWriters.length + 1);
|
|
72
|
+
expect(writers.map((w) => w.id)).toContain("sap-copilot");
|
|
73
|
+
// built-ins come first
|
|
74
|
+
expect(writers[0].id).toBe("universal");
|
|
75
|
+
expect(writers[writers.length - 1].id).toBe("sap-copilot");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("does not mutate allHarnessWriters", () => {
|
|
79
|
+
const originalLength = allHarnessWriters.length;
|
|
80
|
+
buildHarnessWriters({
|
|
81
|
+
harnessWriters: [
|
|
82
|
+
{
|
|
83
|
+
id: "ephemeral",
|
|
84
|
+
label: "Ephemeral",
|
|
85
|
+
description: "Should not persist",
|
|
86
|
+
install: async () => {}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
});
|
|
90
|
+
expect(allHarnessWriters).toHaveLength(originalLength);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -45,3 +45,13 @@ export function getHarnessWriter(id: string): HarnessWriter | undefined {
|
|
|
45
45
|
export function getHarnessIds(): string[] {
|
|
46
46
|
return allHarnessWriters.map((w) => w.id);
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the full list of harness writers: built-ins first, then any
|
|
51
|
+
* additional writers contributed via extensions. Does not mutate allHarnessWriters.
|
|
52
|
+
*/
|
|
53
|
+
export function buildHarnessWriters(extensions: {
|
|
54
|
+
harnessWriters?: HarnessWriter[];
|
|
55
|
+
}): HarnessWriter[] {
|
|
56
|
+
return [...allHarnessWriters, ...(extensions.harnessWriters ?? [])];
|
|
57
|
+
}
|
|
@@ -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."
|
package/pnpm-workspace.yaml
CHANGED
|
File without changes
|