mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-27 14:30:44 -07:00
121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { EventEmitter } from 'node:events';
|
|
import { XcodeMcpBridgeFixTransport } from './xcode-mcp-fix-transport.js';
|
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
|
|
// Mock Transport that simulates the mcpbridge behavior
|
|
class MockBadMcpBridgeTransport extends EventEmitter implements Transport {
|
|
onclose?: () => void;
|
|
onerror?: (error: Error) => void;
|
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
|
|
async start() {}
|
|
async close() {}
|
|
async send(_message: JSONRPCMessage) {}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
emitMessage(msg: any) {
|
|
this.onmessage?.(msg);
|
|
}
|
|
}
|
|
|
|
describe('Xcode MCP Bridge Fix', () => {
|
|
it('intercepts and fixes the non-compliant mcpbridge response', async () => {
|
|
const mockTransport = new MockBadMcpBridgeTransport();
|
|
const fixTransport = new XcodeMcpBridgeFixTransport(mockTransport);
|
|
|
|
// We need to capture what the fixTransport emits to its listeners
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const messages: any[] = [];
|
|
fixTransport.onmessage = (msg) => {
|
|
messages.push(msg);
|
|
};
|
|
|
|
await fixTransport.start();
|
|
|
|
// SCENARIO 1: Bad response from Xcode
|
|
// It has `content` stringified JSON, but misses `structuredContent`
|
|
const badPayload = {
|
|
jsonrpc: '2.0',
|
|
id: 1,
|
|
result: {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
windows: [{ title: 'HelloWorld', path: '/path/to/project' }],
|
|
}),
|
|
},
|
|
],
|
|
// Missing: structuredContent
|
|
},
|
|
};
|
|
|
|
mockTransport.emitMessage(badPayload);
|
|
|
|
// Verify the message received by the client (listener of fixTransport)
|
|
const fixedMsg = messages.find((m) => m.id === 1);
|
|
expect(fixedMsg).toBeDefined();
|
|
expect(fixedMsg.result.structuredContent).toBeDefined();
|
|
expect(fixedMsg.result.structuredContent.windows[0].title).toBe(
|
|
'HelloWorld',
|
|
);
|
|
|
|
// SCENARIO 2: Good response (should be untouched)
|
|
const goodPayload = {
|
|
jsonrpc: '2.0',
|
|
id: 2,
|
|
result: {
|
|
content: [{ type: 'text', text: 'normal text' }],
|
|
structuredContent: { some: 'data' },
|
|
},
|
|
};
|
|
mockTransport.emitMessage(goodPayload);
|
|
|
|
const goodMsg = messages.find((m) => m.id === 2);
|
|
expect(goodMsg).toBeDefined();
|
|
expect(goodMsg.result.structuredContent).toEqual({ some: 'data' });
|
|
});
|
|
|
|
it('ignores responses that cannot be parsed as JSON', async () => {
|
|
const mockTransport = new MockBadMcpBridgeTransport();
|
|
const fixTransport = new XcodeMcpBridgeFixTransport(mockTransport);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const messages: any[] = [];
|
|
fixTransport.onmessage = (msg) => {
|
|
messages.push(msg);
|
|
};
|
|
|
|
await fixTransport.start();
|
|
|
|
const nonJsonPayload = {
|
|
jsonrpc: '2.0',
|
|
id: 3,
|
|
result: {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: "Just some plain text that isn't JSON",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
mockTransport.emitMessage(nonJsonPayload);
|
|
|
|
const msg = messages.find((m) => m.id === 3);
|
|
expect(msg).toBeDefined();
|
|
expect(msg.result.structuredContent).toBeUndefined();
|
|
expect(msg.result.content[0].text).toBe(
|
|
"Just some plain text that isn't JSON",
|
|
);
|
|
});
|
|
});
|