Skip to content

Commit e4235ca

Browse files
tmchowclaude
andcommitted
Address PR review feedback (#479)
- Copilot/Qwen: treat all existing MCP servers as plugin-managed when _compound_managed_mcp tracking key is absent (legacy migration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b26a02a commit e4235ca

4 files changed

Lines changed: 76 additions & 8 deletions

File tree

src/targets/copilot.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,12 @@ async function mergeCopilotMcpConfig(
6161
? { ...(existing.mcpServers as Record<string, unknown>) }
6262
: {}
6363

64-
// Remove previously-managed plugin servers that are no longer in the bundle
65-
const prevManaged = Array.isArray(existing[MANAGED_KEY]) ? existing[MANAGED_KEY] as string[] : []
64+
// Remove previously-managed plugin servers that are no longer in the bundle.
65+
// Legacy migration: if no tracking key exists, assume all existing servers are plugin-managed
66+
// (the old writer overwrote the entire file, so there are no user servers to preserve).
67+
const prevManaged = Array.isArray(existing[MANAGED_KEY])
68+
? existing[MANAGED_KEY] as string[]
69+
: Object.keys(existingMcp)
6670
for (const name of prevManaged) {
6771
if (!(name in incoming)) {
6872
delete existingMcp[name]

src/targets/qwen.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@ async function mergeQwenConfig(
6868
? { ...(existing.mcpServers as Record<string, unknown>) }
6969
: {}
7070

71-
// Remove previously-managed plugin servers that are no longer in the bundle
72-
const prevManaged = Array.isArray(existing[MANAGED_KEY]) ? existing[MANAGED_KEY] as string[] : []
71+
// Remove previously-managed plugin servers that are no longer in the bundle.
72+
// Legacy migration: if no tracking key exists, assume all existing servers are plugin-managed
73+
// (the old writer overwrote the entire file, so there are no user servers to preserve).
74+
const prevManaged = Array.isArray(existing[MANAGED_KEY])
75+
? existing[MANAGED_KEY] as string[]
76+
: Object.keys(existingMcp)
7377
const incomingMcp = incoming.mcpServers ?? {}
7478
for (const name of prevManaged) {
7579
if (!(name in incomingMcp)) {

tests/copilot-writer.test.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,13 @@ Run these research agents:
258258
const githubRoot = path.join(tempRoot, ".github")
259259
await fs.mkdir(githubRoot, { recursive: true })
260260

261-
// User has their own MCP server (not managed by the plugin)
261+
// User has their own MCP server alongside plugin-managed ones (tracking key present)
262262
await fs.writeFile(
263263
path.join(githubRoot, "copilot-mcp-config.json"),
264-
JSON.stringify({ mcpServers: { "user-tool": { type: "local", command: "my-tool", tools: ["*"] } } }),
264+
JSON.stringify({
265+
mcpServers: { "user-tool": { type: "local", command: "my-tool", tools: ["*"] } },
266+
_compound_managed_mcp: [],
267+
}),
265268
)
266269

267270
const bundle: CopilotBundle = {
@@ -278,6 +281,38 @@ Run these research agents:
278281
expect(result.mcpServers.plugin).toBeDefined()
279282
})
280283

284+
test("prunes stale servers from legacy config without tracking key", async () => {
285+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "copilot-legacy-"))
286+
const githubRoot = path.join(tempRoot, ".github")
287+
await fs.mkdir(githubRoot, { recursive: true })
288+
289+
// Simulate old writer output: has mcpServers but no _compound_managed_mcp
290+
await fs.writeFile(
291+
path.join(githubRoot, "copilot-mcp-config.json"),
292+
JSON.stringify({
293+
mcpServers: {
294+
old: { type: "local", command: "old-server", tools: ["*"] },
295+
renamed: { type: "local", command: "renamed-server", tools: ["*"] },
296+
},
297+
}),
298+
)
299+
300+
const bundle: CopilotBundle = {
301+
agents: [],
302+
generatedSkills: [],
303+
skillDirs: [],
304+
mcpConfig: { fresh: { type: "local", command: "new-server", tools: ["*"] } },
305+
}
306+
307+
await writeCopilotBundle(githubRoot, bundle)
308+
309+
const result = JSON.parse(await fs.readFile(path.join(githubRoot, "copilot-mcp-config.json"), "utf8"))
310+
expect(result.mcpServers.fresh).toBeDefined()
311+
expect(result.mcpServers.old).toBeUndefined()
312+
expect(result.mcpServers.renamed).toBeUndefined()
313+
expect(result._compound_managed_mcp).toEqual(["fresh"])
314+
})
315+
281316
test("creates skill directories with SKILL.md", async () => {
282317
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "copilot-genskill-"))
283318
const bundle: CopilotBundle = {

tests/qwen-writer.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ describe("writeQwenBundle", () => {
3636
test("preserves user-added MCP servers across re-installs", async () => {
3737
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwen-user-mcp-"))
3838

39-
// User has their own MCP server (not managed by the plugin)
39+
// User has their own MCP server alongside plugin-managed ones (tracking key present)
4040
await fs.writeFile(
4141
path.join(tempRoot, "qwen-extension.json"),
42-
JSON.stringify({ name: "user-project", mcpServers: { "user-tool": { command: "my-tool" } } }),
42+
JSON.stringify({
43+
name: "user-project",
44+
mcpServers: { "user-tool": { command: "my-tool" } },
45+
_compound_managed_mcp: [],
46+
}),
4347
)
4448

4549
await writeQwenBundle(tempRoot, makeBundle({ plugin: { command: "plugin-server" } }))
@@ -63,6 +67,27 @@ describe("writeQwenBundle", () => {
6367
expect(result.customField).toBe("should-survive")
6468
})
6569

70+
test("prunes stale servers from legacy config without tracking key", async () => {
71+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwen-legacy-"))
72+
73+
// Simulate old writer output: has mcpServers but no _compound_managed_mcp
74+
await fs.writeFile(
75+
path.join(tempRoot, "qwen-extension.json"),
76+
JSON.stringify({
77+
name: "old-project",
78+
mcpServers: { old: { command: "old-server" }, renamed: { command: "renamed-server" } },
79+
}),
80+
)
81+
82+
await writeQwenBundle(tempRoot, makeBundle({ fresh: { command: "new-server" } }))
83+
84+
const result = JSON.parse(await fs.readFile(path.join(tempRoot, "qwen-extension.json"), "utf8"))
85+
expect(result.mcpServers.fresh).toBeDefined()
86+
expect(result.mcpServers.old).toBeUndefined()
87+
expect(result.mcpServers.renamed).toBeUndefined()
88+
expect(result._compound_managed_mcp).toEqual(["fresh"])
89+
})
90+
6691
test("cleans up all plugin MCP servers when bundle has none", async () => {
6792
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwen-zero-"))
6893

0 commit comments

Comments
 (0)