LibreChat/e2e/setup/fake-mcp-server.js
Danny Avila 1bccc2bc18
📡 fix: Refresh MCP Tools After List-Changed Notifications (#14686)
* fix(mcp): handle dynamic tool list changes

Co-authored-by: Pascal Garber <pascal@artandcode.studio>

* test(mcp): fix CI validation

* fix(mcp): keep dynamic tool catalogs live

* fix(mcp): harden dynamic catalog lifecycle

* test(mcp): use typed startup connection

* test(mcp): isolate dynamic e2e fixtures

* fix(mcp): refresh tools after reconnect

* fix(mcp): close dynamic catalog cache gaps

* test(mcp): update OAuth connection mocks

* fix(mcp): preserve app snapshot ownership

* style(mcp): sort connection imports

* fix(mcp): close review race conditions

* fix(mcp): preserve cache ownership edges

* fix(mcp): harden recovery lifecycle

* fix(mcp): guard tool-less app refresh

* fix(mcp): fence distributed cache races

* fix(mcp): retire stale connection state

* fix(mcp): keep tool snapshots authoritative

* fix(mcp): fence stale app tool publications

* style(mcp): sort repository test imports

* test(mcp): mock empty startup publication

* fix(mcp): preserve app publication generations

* fix(mcp): harden publication recovery races

* fix(mcp): address tool catalogs by runtime config

* fix(mcp): load scoped catalogs for assistant writes

* fix(mcp): harden catalog publication recovery

* fix(mcp): serialize forced connection replacement

* fix(mcp): serialize ordinary creation with replacements

* fix(mcp): harden catalog fallback boundaries

* fix(mcp): close lifecycle fencing gaps

* fix(mcp): preserve catalog authority on failures

* fix(mcp): compensate failed catalog mutations

* fix(mcp): fence catalog refresh ordering

* style(mcp): sort agent loader imports

* fix(mcp): cancel stale connection creation

* fix(mcp): fence catalog coordination

* fix(mcp): close catalog race windows

* fix(mcp): harden cross-pod catalog fencing

* fix(mcp): close catalog lifecycle edges

* style(mcp): sort assistant imports

* fix(mcp): reject stale recovery authority

* fix(mcp): restore static catalog on every startup

* fix(mcp): order app catalog publications

* style(mcp): sort catalog revision imports

* fix(mcp): separate catalog allocation and commit fences

---------

Co-authored-by: Pascal Garber <pascal@artandcode.studio>
2026-08-08 13:50:21 -04:00

123 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const z = require('zod/v4');
const { watchDynamicTool } = require('./dynamic-mcp-tools');
const APPROVAL_AUDIT_DIR = path.join('/tmp', 'librechat-e2e-approval-audit');
function recordApprovalInvocation(value) {
fs.mkdirSync(APPROVAL_AUDIT_DIR, { recursive: true });
const filename = Buffer.from(value).toString('base64url');
fs.appendFileSync(path.join(APPROVAL_AUDIT_DIR, filename), `${value}\n`);
}
const server = new McpServer({
name: 'e2e-memory',
version: '1.0.0',
});
server.registerTool(
'remember_fact',
{
description: 'Stores a deterministic fact for LibreChat mock end-to-end tests.',
inputSchema: {
fact: z.string().optional(),
},
},
async ({ fact = 'LibreChat MCP e2e fact' }) => ({
content: [
{
type: 'text',
text: `E2E MCP memory noted: ${fact}`,
},
],
}),
);
server.registerTool(
'recall_fact',
{
description: 'Returns a deterministic fact for LibreChat mock end-to-end tests.',
inputSchema: {},
},
async () => ({
content: [
{
type: 'text',
text: 'E2E MCP memory recalled: LibreChat can persist MCP tools on agents.',
},
],
}),
);
server.registerTool(
'slow_echo',
{
description:
'Echoes text after a delay; used to verify background tool dispatch in mock e2e tests.',
inputSchema: {
text: z.string(),
delay_ms: z.number().optional(),
},
},
async ({ text, delay_ms = 1500 }) => {
await new Promise((resolve) => setTimeout(resolve, delay_ms));
return {
content: [
{
type: 'text',
text: `E2E slow echo: ${text}`,
},
],
};
},
);
server.registerTool(
'approval_probe',
{
description:
'Echoes reviewed input so LibreChat mock end-to-end tests can verify tool approval decisions.',
inputSchema: {
value: z.string(),
review: z.string().optional(),
},
},
async ({ value }) => {
recordApprovalInvocation(value);
return {
content: [
{
type: 'text',
text: `E2E approval probe executed: ${value}`,
},
],
};
},
);
if (process.env.E2E_MCP_LIST_CHANGED === 'true') {
server.registerTool(
'transport_probe',
{
description: 'Confirms that the real stdio MCP transport is connected.',
inputSchema: {},
},
async () => ({ content: [{ type: 'text', text: 'stdio connected' }] }),
);
watchDynamicTool(server);
}
async function main() {
await server.connect(new StdioServerTransport());
}
main().catch((error) => {
console.error('[fake-mcp-server] failed to start', error);
process.exit(1);
});