fix(mcp): import opencode mcp config snippets (#1532)
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
|
import { parseImportedMcpSnippet } from './mcpImport';
|
||||||
|
|
||||||
|
describe('parseImportedMcpSnippet', () => {
|
||||||
|
test('imports OpenCode mcp wrapper config', () => {
|
||||||
|
const result = parseImportedMcpSnippet(JSON.stringify({
|
||||||
|
$schema: 'https://opencode.ai/config.json',
|
||||||
|
mcp: {
|
||||||
|
stitch: {
|
||||||
|
type: 'remote',
|
||||||
|
url: 'https://stitch.googleapis.com/mcp',
|
||||||
|
enabled: true,
|
||||||
|
headers: {
|
||||||
|
'X-Goog-Api-Key': 'test-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
if (!result.ok) throw new Error(result.error);
|
||||||
|
expect(result.name).toBe('stitch');
|
||||||
|
expect(result.type).toBe('remote');
|
||||||
|
expect(result.url).toBe('https://stitch.googleapis.com/mcp');
|
||||||
|
expect(result.enabled).toBe(true);
|
||||||
|
expect(result.headers).toEqual([{ key: 'X-Goog-Api-Key', value: 'test-key' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps existing mcpServers wrapper support', () => {
|
||||||
|
const result = parseImportedMcpSnippet(JSON.stringify({
|
||||||
|
mcpServers: {
|
||||||
|
localTool: {
|
||||||
|
command: 'node server.js',
|
||||||
|
args: ['--stdio'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
if (!result.ok) throw new Error(result.error);
|
||||||
|
expect(result.name).toBe('localTool');
|
||||||
|
expect(result.type).toBe('local');
|
||||||
|
expect(result.command).toEqual(['node', 'server.js', '--stdio']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects multiple OpenCode mcp wrapper entries', () => {
|
||||||
|
const result = parseImportedMcpSnippet(JSON.stringify({
|
||||||
|
mcp: {
|
||||||
|
one: { type: 'remote', url: 'https://one.example/mcp' },
|
||||||
|
two: { type: 'remote', url: 'https://two.example/mcp' },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) throw new Error('Expected import to fail');
|
||||||
|
expect(result.error).toContain('Paste one server at a time');
|
||||||
|
expect(result.error).toContain('servers in mcp');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -221,6 +221,7 @@ function isServerConfig(val: Record<string, unknown>): boolean {
|
|||||||
*
|
*
|
||||||
* Supported shapes:
|
* Supported shapes:
|
||||||
* { "mcpServers": { "name": { ... } } }
|
* { "mcpServers": { "name": { ... } } }
|
||||||
|
* { "mcp": { "name": { ... } } }
|
||||||
* { "name": { ... } }
|
* { "name": { ... } }
|
||||||
* { ...serverConfig }
|
* { ...serverConfig }
|
||||||
*/
|
*/
|
||||||
@@ -270,6 +271,29 @@ export function parseImportedMcpSnippet(
|
|||||||
return buildResult(serverName, inferType(entry as Record<string, unknown>), entry as Record<string, unknown>);
|
return buildResult(serverName, inferType(entry as Record<string, unknown>), entry as Record<string, unknown>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect OpenCode config shape { "mcp": { "name": { ... } } }
|
||||||
|
const mcp = obj.mcp;
|
||||||
|
if (isObject(mcp)) {
|
||||||
|
const keys = Object.keys(mcp);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return buildError('mcp object is empty', parsed);
|
||||||
|
}
|
||||||
|
if (keys.length > 1) {
|
||||||
|
return buildError(
|
||||||
|
'Paste one server at a time. Found ' +
|
||||||
|
keys.length +
|
||||||
|
' servers in mcp',
|
||||||
|
parsed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const serverName = keys[0]!;
|
||||||
|
const entry = mcp[serverName];
|
||||||
|
if (!isObject(entry)) {
|
||||||
|
return buildError('Server entry in mcp is not a valid object', parsed);
|
||||||
|
}
|
||||||
|
return buildResult(serverName, inferType(entry as Record<string, unknown>), entry as Record<string, unknown>);
|
||||||
|
}
|
||||||
|
|
||||||
// Detect single named entry { "serverName": { ... } }
|
// Detect single named entry { "serverName": { ... } }
|
||||||
const single = extractSingleServer(obj);
|
const single = extractSingleServer(obj);
|
||||||
if (single) {
|
if (single) {
|
||||||
@@ -336,4 +360,4 @@ export function applyImportedMcpToDraft(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user