From 4e17be57053fb0ce2561d92e05b6e38ef789b2d4 Mon Sep 17 00:00:00 2001 From: Kaoru0822 Date: Tue, 6 Oct 2026 22:20:02 -0700 Subject: [PATCH] fix(setup): read an OpenCode V2 server named type as a table entry (#866) Only a string type is the V1 entry discriminator; a native mcp.servers table may hold a server object named type. Co-authored-by: Claude Opus 5.5 --- .../ClientConfigurationDocument.ts | 8 ++++-- .../clientConfigurationDialects.test.ts | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/application/ClientConfigurationDocument.ts b/src/application/ClientConfigurationDocument.ts index 9af15be084..f12db600b6 100644 --- a/src/application/ClientConfigurationDocument.ts +++ b/src/application/ClientConfigurationDocument.ts @@ -108,11 +108,15 @@ export const parseClientConfiguration = ( const value = document[serversKey]; const servers = value === undefined ? {} : objectSchema.parse(value); const native = servers.servers; - // A V1 server named `servers` is an entry with a `type`; the V2 table is not. + // A V1 server named `servers` has a string `type` discriminator; the V2 + // table may instead hold a server object named `type`. if ( format !== "opencode" || native === undefined || - (typeof native === "object" && native !== null && "type" in native) + (typeof native === "object" && + native !== null && + "type" in native && + typeof native.type === "string") ) return { document, diff --git a/tests/boundary/filesystem/clientConfigurationDialects.test.ts b/tests/boundary/filesystem/clientConfigurationDialects.test.ts index 106aba2f09..ea45ad59b8 100644 --- a/tests/boundary/filesystem/clientConfigurationDialects.test.ts +++ b/tests/boundary/filesystem/clientConfigurationDialects.test.ts @@ -322,6 +322,33 @@ describe("OpenCode V2 native server table", () => { }); }); + it("treats a V2 table holding a server named type as native", async () => { + const { home, client } = await openCodeClient("rea-opencode-type-server-"); + const typeServer = { type: "local", command: ["node", "type.js"] }; + await writeFile( + client.configPath, + `${JSON.stringify({ mcp: { servers: { type: typeServer, rea: { type: "local", command } } } })}\n`, + ); + + expect(await readClientRegistrationStatuses(home)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + client: "opencode", + state: "aligned", + command, + }), + ]), + ); + expect((await systemUninstallHost(home).removeClient(client)).status).toBe( + "removed", + ); + expect( + parseOpenCode(await readFile(client.configPath, "utf8")).mcp, + ).toEqual({ + servers: { type: typeServer }, + }); + }); + it("keeps a V1 server named servers in the V1 table", async () => { const { client } = await openCodeClient("rea-opencode-v1-servers-"); const servers = { type: "local", command: ["node", "servers.js"] };