mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-21 23:55:23 +00:00
* fix: resolve customUserVars before first post-OAuth-callback MCP reconnect
The OAuth callback route reconnects the user's MCP connection immediately
after storing new tokens, but never resolves customUserVars before doing
so - unlike the /reinitialize route a few hundred lines below, which does.
As a result, headers/oauth_headers templates like `{{MY_KEY}}` are sent
to the MCP server literally, unsubstituted, on this first connection
attempt, even though the user's value is already saved. The upstream
server rejects it as an invalid credential.
Fixes #14537
* refactor: share getServerCustomUserVars reader from @librechat/api
The mcp_-prefixed key shape was built by getUserMCPAuthMap but re-derived
by hand at each read site (a private helper in services/MCP.js, and the
new callback-route extraction). Export a reader from the same module that
owns the writer and reuse it at both sites, so the key shape has a single
source of truth.
* chore: sort destructured require members in routes/mcp.js
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
import { logger } from '@librechat/data-schemas';
|
|
import { Constants } from 'librechat-data-provider';
|
|
import type { PluginAuthMethods } from '@librechat/data-schemas';
|
|
import type { GenericTool } from '@librechat/agents';
|
|
import { getPluginAuthMap } from '~/agents/auth';
|
|
import { splitMCPToolKey } from './utils';
|
|
|
|
/** Reads one server's customUserVars from a `getUserMCPAuthMap` result */
|
|
export function getServerCustomUserVars(
|
|
userMCPAuthMap: Record<string, Record<string, string>> | undefined,
|
|
serverName: string,
|
|
): Record<string, string> | undefined {
|
|
return userMCPAuthMap?.[`${Constants.mcp_prefix}${serverName}`];
|
|
}
|
|
|
|
export async function getUserMCPAuthMap({
|
|
userId,
|
|
tools,
|
|
servers,
|
|
toolInstances,
|
|
serverNames,
|
|
findPluginAuthsByKeys,
|
|
}: {
|
|
userId: string;
|
|
tools?: (string | undefined)[];
|
|
servers?: (string | undefined)[];
|
|
toolInstances?: (GenericTool | null)[];
|
|
/** Configured server names, used to resolve the tool-key boundary exactly */
|
|
serverNames?: readonly string[];
|
|
findPluginAuthsByKeys: PluginAuthMethods['findPluginAuthsByKeys'];
|
|
}): Promise<Record<string, Record<string, string>>> {
|
|
let allMcpCustomUserVars: Record<string, Record<string, string>> = {};
|
|
let mcpPluginKeysToFetch: string[] = [];
|
|
try {
|
|
const uniqueMcpServers = new Set<string>();
|
|
|
|
if (servers != null && servers.length) {
|
|
for (const serverName of servers) {
|
|
if (!serverName) {
|
|
continue;
|
|
}
|
|
uniqueMcpServers.add(`${Constants.mcp_prefix}${serverName}`);
|
|
}
|
|
} else if (tools != null && tools.length) {
|
|
for (const toolName of tools) {
|
|
if (!toolName) {
|
|
continue;
|
|
}
|
|
const [, mcpServer] = splitMCPToolKey(toolName, serverNames);
|
|
if (!mcpServer) continue;
|
|
uniqueMcpServers.add(`${Constants.mcp_prefix}${mcpServer}`);
|
|
}
|
|
} else if (toolInstances != null && toolInstances.length) {
|
|
for (const tool of toolInstances) {
|
|
if (!tool) {
|
|
continue;
|
|
}
|
|
const mcpTool = tool as GenericTool & { mcpRawServerName?: string };
|
|
if (mcpTool.mcpRawServerName) {
|
|
uniqueMcpServers.add(`${Constants.mcp_prefix}${mcpTool.mcpRawServerName}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uniqueMcpServers.size === 0) {
|
|
return {};
|
|
}
|
|
|
|
mcpPluginKeysToFetch = Array.from(uniqueMcpServers);
|
|
allMcpCustomUserVars = await getPluginAuthMap({
|
|
userId,
|
|
pluginKeys: mcpPluginKeysToFetch,
|
|
throwError: false,
|
|
findPluginAuthsByKeys,
|
|
});
|
|
} catch (err) {
|
|
logger.error(
|
|
`[handleTools] Error batch fetching customUserVars for MCP tools (keys: ${mcpPluginKeysToFetch.join(
|
|
', ',
|
|
)}), user ${userId}: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
err,
|
|
);
|
|
}
|
|
|
|
return allMcpCustomUserVars;
|
|
}
|