mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
fix: keep duplicate indexes on registry fallback and harden the oauth split
Duplication blanked mcpServerNames when the registry was unavailable, because filterAuthorizedTools grandfathers the source's tools without resolving them - the copy kept tools it could no longer resolve. Source names now carry forward for the tools that still point at them. splitToolCallName also treated any oauth_mcp_ prefix as a synthetic OAuth call, so a genuine upstream tool by that name resolved to the wrong server. A configured server name now decides when one matches, since a real key always ends in its server, and the prefix only breaks ties for unconfigured servers.
This commit is contained in:
parent
056152fe35
commit
07404c0cf9
3 changed files with 49 additions and 4 deletions
|
|
@ -948,6 +948,22 @@ const duplicateAgentHandler = async (req, res) => {
|
|||
configServers,
|
||||
resolvedServerNames,
|
||||
});
|
||||
/** When the registry is unavailable, `filterAuthorizedTools` grandfathers the
|
||||
* source's tools without resolving them, so carry forward the source names those
|
||||
* retained tools still point at rather than blanking the index. */
|
||||
const sourceNames = agent.mcpServerNames ?? [];
|
||||
if (sourceNames.length > 0) {
|
||||
const sourceNameSet = new Set(sourceNames);
|
||||
for (const tool of newAgentData.tools ?? []) {
|
||||
if (typeof tool !== 'string' || !tool.includes(Constants.mcp_delimiter)) {
|
||||
continue;
|
||||
}
|
||||
const [, retainedName] = splitMCPToolKey(tool, sourceNames);
|
||||
if (retainedName && sourceNameSet.has(retainedName)) {
|
||||
resolvedServerNames.add(retainedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
newAgentData.mcpServerNames = Array.from(resolvedServerNames);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2809,15 +2809,22 @@ export function splitMCPToolKey(
|
|||
* Splits a tool-call name for display, where the key may be a synthetic MCP OAuth
|
||||
* call (`oauth${mcp_delimiter}${serverName}`) rather than a real tool key.
|
||||
*
|
||||
* The OAuth form is unambiguous because its tool half is always exactly `oauth`,
|
||||
* so everything after the first delimiter is the server even when the server name
|
||||
* itself contains one. Real tool keys go through `splitMCPToolKey`, whose tool half
|
||||
* is untrusted and may carry the delimiter.
|
||||
* A configured server name is authoritative when one matches, because a real tool key
|
||||
* always ends in its server. Only when none matches does the `oauth` prefix decide,
|
||||
* which keeps a genuine upstream tool named `oauth${mcp_delimiter}...` from being read
|
||||
* as a synthetic call while still resolving OAuth prompts for unconfigured servers.
|
||||
*/
|
||||
export function splitToolCallName(
|
||||
toolCallName: string,
|
||||
knownServerNames?: readonly string[],
|
||||
): [string, string | undefined] {
|
||||
if (knownServerNames?.length) {
|
||||
const [toolName, serverName] = splitMCPToolKey(toolCallName, knownServerNames);
|
||||
if (serverName != null && knownServerNames.includes(serverName)) {
|
||||
return [toolName, serverName];
|
||||
}
|
||||
}
|
||||
|
||||
const oauthPrefix = `oauth${Constants.mcp_delimiter}`;
|
||||
if (toolCallName.startsWith(oauthPrefix)) {
|
||||
return ['oauth', toolCallName.slice(oauthPrefix.length)];
|
||||
|
|
|
|||
|
|
@ -69,3 +69,25 @@ describe('splitToolCallName', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('splitToolCallName with configured server names', () => {
|
||||
const d = Constants.mcp_delimiter;
|
||||
|
||||
it('reads a real tool whose own name starts with the oauth prefix', () => {
|
||||
expect(splitToolCallName(`oauth${d}reset${d}github`, ['github'])).toEqual([
|
||||
`oauth${d}reset`,
|
||||
'github',
|
||||
]);
|
||||
});
|
||||
|
||||
it('still resolves a synthetic OAuth call for a configured server', () => {
|
||||
expect(splitToolCallName(`oauth${d}github`, ['github'])).toEqual(['oauth', 'github']);
|
||||
});
|
||||
|
||||
it('resolves a synthetic OAuth call for a delimiter-bearing configured server', () => {
|
||||
expect(splitToolCallName(`oauth${d}foo${d}bar`, [`foo${d}bar`])).toEqual([
|
||||
'oauth',
|
||||
`foo${d}bar`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue