mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-20 09:09:35 +02:00
fix(mcp-sync): read mcp.servers as a V1 server when it has a V1 type (#599 merge-time fix)
mcpTable took any record at `mcp.servers` as OpenCode's V2 container. OpenCode itself does so only when the value is not a V1 server (`type` local or remote). So a server named "servers" copied from another CLI was written as V1, re-parsed as a container, failed the read-back guard and failed the whole OpenCode target, and an existing V1 "servers" stopped being read. The V2 entry now also wins a name collision, as in OpenCode; sync never writes a name already defined, so the read-back is unaffected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a8b8530722
commit
1f41eacd87
2 files changed
+35
-5
No files matched your search
+5
-5
@@ -413,14 +413,14 @@ function mcpTable(format: McpFormat, text: string | null): Record<string, unknow
|
||||
if (!isRecord(table)) throw new McpConfigError(`"${dialect.key}" is not an object`);
|
||||
if (!dialect.nestedKey) return table;
|
||||
const nested = table[dialect.nestedKey];
|
||||
if (nested === undefined) return table;
|
||||
// OpenCode reads `mcp.servers` as a V1 server named "servers" when it carries a V1 `type`.
|
||||
if (nested === undefined || (isRecord(nested) && (nested.type === 'local' || nested.type === 'remote'))) return table;
|
||||
if (!isRecord(nested)) throw new McpConfigError(`"${dialect.key}.${dialect.nestedKey}" is not an object`);
|
||||
// Merge the nested (V2) table with the direct (V1) one. V1 entries win on a name collision so
|
||||
// the write path — which appends to `doc[key]` and leaves the nested table in place — reads back
|
||||
// identically.
|
||||
// Merge the direct (V1) table with the nested (V2) one. Like OpenCode, the native V2 entry wins a
|
||||
// name collision; sync never writes a name already defined, so this does not affect the read-back.
|
||||
const merged = dict<unknown>();
|
||||
for (const k of safeKeys(nested)) merged[k] = nested[k];
|
||||
for (const k of safeKeys(table)) if (k !== dialect.nestedKey) merged[k] = table[k];
|
||||
for (const k of safeKeys(nested)) merged[k] = nested[k];
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,36 @@ describe('dialect parsing', () => {
|
||||
expect(o.direct).toEqual({ transport: 'http', url: 'https://direct' });
|
||||
});
|
||||
|
||||
it('reads `mcp.servers` as a V1 server named "servers" when it carries a V1 type, as OpenCode does', () => {
|
||||
const o = parseServers(
|
||||
'opencode-json',
|
||||
JSON.stringify({
|
||||
mcp: { servers: { type: 'local', command: ['npx', 'srv'] }, other: { type: 'remote', url: 'https://o' } },
|
||||
})
|
||||
);
|
||||
expect(Object.keys(o).sort()).toEqual(['other', 'servers']);
|
||||
expect(o.servers).toEqual({ transport: 'stdio', command: 'npx', args: ['srv'] });
|
||||
});
|
||||
|
||||
it('writes a server named "servers" into OpenCode, and it reads back as one', () => {
|
||||
const out = addServers('opencode-json', JSON.stringify({ mcp: {} }), {
|
||||
servers: { transport: 'stdio', command: 'npx', args: ['srv'] },
|
||||
fs: { transport: 'stdio', command: 'npx', args: ['fs'] },
|
||||
});
|
||||
const back = parseServers('opencode-json', out);
|
||||
expect(Object.keys(back).sort()).toEqual(['fs', 'servers']);
|
||||
});
|
||||
|
||||
it('lets the native V2 entry win a name collision, like OpenCode', () => {
|
||||
const o = parseServers(
|
||||
'opencode-json',
|
||||
JSON.stringify({
|
||||
mcp: { dup: { type: 'remote', url: 'https://v1' }, servers: { dup: { type: 'remote', url: 'https://v2' } } },
|
||||
})
|
||||
);
|
||||
expect(o.dup).toEqual({ transport: 'http', url: 'https://v2' });
|
||||
});
|
||||
|
||||
it('throws on unparseable files so they are never written', () => {
|
||||
expect(() => parseServers('opencode-json', '{ // jsonc\n}')).toThrow();
|
||||
expect(() => parseServers('codex-toml', '[mcp_servers.a]\ncommand="x"\n[mcp_servers.a]\ncommand="y"\n')).toThrow();
|
||||
|
||||
Reference in new issue
Block a user