Skip to content

Commit 28ca23d

Browse files
HusneShabbirHusneShabbir
andauthored
test(lightspeed): e2e for MCP tool calling in chat UI (#2748)
Add SSE fixture with tool_call/tool_result and a Playwright test that asserts the tool response header renders after a streamed mcp_list_tools call. Made-with: Cursor Co-authored-by: HusneShabbir <husneshabbir447@gmail.com>
1 parent 5394da1 commit 28ca23d

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

workspaces/lightspeed/packages/app-legacy/e2e-tests/fixtures/responses.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,56 @@ export const generateQueryResponse = (conversationId: string) => {
162162
.map(({ event, data }) => `data: ${JSON.stringify({ event, data })}\n\n`)
163163
.join('')}\n`;
164164
};
165+
166+
const e2eMcpToolCallId = 'mcp_list_e2e-00000000-0000-4000-8000-000000000001';
167+
168+
/** SSE with `tool_call` / `tool_result` then assistant tokens (same wire format as {@link generateQueryResponse}). */
169+
export function generateQueryResponseWithMcpToolCall(
170+
conversationId: string,
171+
): string {
172+
const events: {
173+
event: string;
174+
data?: Record<string, any>;
175+
done?: boolean;
176+
}[] = [];
177+
178+
events.push({
179+
event: 'start',
180+
data: {
181+
conversation_id: conversationId,
182+
request_id: mockStreamRequestId,
183+
},
184+
});
185+
events.push({
186+
event: 'tool_call',
187+
data: {
188+
id: e2eMcpToolCallId,
189+
name: 'mcp_list_tools',
190+
args: { server_label: 'mcp-integration-tools' },
191+
type: 'mcp_list_tools',
192+
},
193+
});
194+
events.push({
195+
event: 'tool_result',
196+
data: {
197+
id: e2eMcpToolCallId,
198+
status: 'success',
199+
content: '{"server_label":"mcp-integration-tools","tools":[]}',
200+
},
201+
});
202+
203+
const tokens = assistantResponse.match(/(\s+|[^\s]+)/g) || [
204+
assistantResponse,
205+
];
206+
tokens.forEach((token, index) => {
207+
events.push({
208+
event: 'token',
209+
data: { id: index, token, role: 'inference' },
210+
});
211+
});
212+
events.push({ event: 'end', done: true });
213+
214+
return `${events
215+
.map(({ event, data }) => `data: ${JSON.stringify({ event, data })}\n\n`)
216+
.join('')}\n`;
217+
}

workspaces/lightspeed/packages/app-legacy/e2e-tests/lightspeed.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import {
2727
type McpServersListMock,
2828
thinkingContent,
2929
assistantResponse,
30+
generateQueryResponseWithMcpToolCall,
31+
modelBaseUrl,
3032
} from './fixtures/responses';
3133
import {
3234
openLightspeed,
@@ -687,4 +689,39 @@ test.describe('Lightspeed tests', () => {
687689
});
688690
});
689691
});
692+
693+
test('MCP tool calling renders in UI', async () => {
694+
const mcpToolCallPrompt = 'test mcp tool call';
695+
696+
await mockConversations(sharedPage, conversations, true);
697+
await mockChatHistory(sharedPage, []);
698+
await openLightspeed(sharedPage);
699+
700+
await sharedPage.unroute(`${modelBaseUrl}/v1/query`);
701+
await sharedPage.route(`${modelBaseUrl}/v1/query`, async route => {
702+
const payload = route.request().postDataJSON();
703+
if (payload.conversation_id) {
704+
conversations[1].conversation_id = payload.conversation_id;
705+
}
706+
const conversationId =
707+
conversations[1].conversation_id ?? conversations[0].conversation_id;
708+
await route.fulfill({
709+
body: generateQueryResponseWithMcpToolCall(conversationId),
710+
});
711+
});
712+
713+
await sendMessage(mcpToolCallPrompt, sharedPage, translations);
714+
715+
await expect(
716+
sharedPage.getByRole('button', {
717+
name: evaluateMessage(
718+
translations['toolCall.header'],
719+
'mcp_list_tools',
720+
),
721+
}),
722+
).toBeVisible();
723+
724+
await sharedPage.unroute(`${modelBaseUrl}/v1/query`);
725+
await mockQuery(sharedPage, botQuery, conversations);
726+
});
690727
});

0 commit comments

Comments
 (0)