Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ describe('AgentPanelSwitch authorization observers', () => {
expect(mockObserveToolAuthorization).toHaveBeenLastCalledWith(true);
});

it('keeps observers disabled for a hydrated ephemeral agent', () => {
it('enables builder observers while chatting with an ephemeral agent', () => {
renderWithConversation({
conversationId: 'new',
endpoint: EModelEndpoint.agents,
agent_id: 'ephemeral',
} as TConversation);
expect(mockObserveToolAuthorization).toHaveBeenLastCalledWith(false);
expect(mockObserveToolAuthorization).toHaveBeenLastCalledWith(true);
expect(mockSetCurrentAgentId).not.toHaveBeenCalled();
});
});
4 changes: 1 addition & 3 deletions client/src/components/SidePanel/Agents/AgentPanelSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ export default function AgentPanelSwitch() {
const conversation = useRecoilValue(store.conversationByIndex(0));
const agentId = conversation?.agent_id ?? null;
return (
<AgentPanelProvider
observeToolAuthorization={conversation != null && !isEphemeralAgent(agentId)}
>
<AgentPanelProvider observeToolAuthorization={conversation != null}>
<AgentPanelSwitchWithContext agentId={agentId} />
</AgentPanelProvider>
);
Expand Down
7 changes: 7 additions & 0 deletions e2e/config/librechat.e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,13 @@ modelSpecs:
endpoint: 'Mock Provider B'
model: 'mock-model-b'

- name: 'e2e-builder-mcp'
label: 'E2E Builder MCP'
hideBadgeRow: true
preset:
endpoint: 'Mock Provider C'
model: 'mock-model-c'

- name: 'e2e-icon-spec'
label: 'E2E Icon Spec'
iconURL: '/assets/openai.svg'
Expand Down
50 changes: 50 additions & 0 deletions e2e/specs/mock/mcp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
fetchJson,
getAccessToken,
mockReply,
selectModelSpec,
sendMessageAndWaitForCompletion,
} from './helpers';

Expand Down Expand Up @@ -81,6 +82,55 @@ async function expectSelectedMCPServerTools(form: Locator) {
}

test.describe('agent builder MCP tools', () => {
test('discovers builder tools while the current chat uses an ephemeral agent', async ({
page,
}) => {
await page.goto('/c/new');
await selectModelSpec(page, 'E2E Builder MCP');
// Start with a cold query cache and a model spec whose composer does not observe tools.
await page.reload();
await expect(page.getByRole('button', { name: 'Select a model' }).first()).toContainText(
'E2E Builder MCP',
);

const toolResponses: string[] = [];
page.on('response', (response) => {
const pathname = new URL(response.url()).pathname;
if (pathname === '/api/mcp/tools' || pathname === '/api/mcp/connection/status') {
toolResponses.push(pathname);
}
});
const form = await openAgentBuilder(page, { navigate: false });
await form.getByRole('button', { name: 'Add tools' }).click();
const dialog = page.getByRole('dialog', { name: 'Tool Library' });
await dialog.getByRole('textbox', { name: 'Search tools…' }).fill(MCP_SERVER_NAME);
const serverCard = dialog.getByRole('button', { name: new RegExp(MCP_SERVER_NAME) }).first();
await expect(serverCard).toBeVisible();

await serverCard.locator('..').getByRole('button', { name: 'Configure', exact: true }).click();
const details = page.getByRole('dialog', { name: MCP_SERVER_NAME, exact: true });
await expect(details).toBeVisible();
await expect.soft(details.getByText('Connected', { exact: true })).toBeVisible();
await test.info().attach('builder-status', {
body: await page.screenshot({
path: test.info().outputPath('builder-status.png'),
animations: 'disabled',
}),
contentType: 'image/png',
});

// This must be populated by the builder itself; prefetching the catalog through the
// test API or selecting a persistent chat agent would hide the regression.
await expect
.poll(() => toolResponses)
.toEqual(expect.arrayContaining(['/api/mcp/tools', '/api/mcp/connection/status']));
await details.getByRole('button', { name: /^Close( dialog)?$/ }).click();
await serverCard.click();
await expect(serverCard).toHaveAttribute('aria-pressed', 'true');
await dialog.getByRole('button', { name: /^Close( dialog)?$/ }).click();
await expect(form.getByText(MCP_SERVER_NAME, { exact: true })).toBeVisible();
});

test('creates an agent with MCP server tools and persists the selection', async ({ page }) => {
test.setTimeout(120000);

Expand Down
Loading