mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-06 02:28:51 +01:00
🗝️ feat: User Provided Credentials for MCP Servers (#7980)
* 🗝️ feat: Per-User Credentials for MCP Servers
chore: add aider to gitignore
feat: fill custom variables to MCP server
feat: replace placeholders with custom user MCP variables
feat: handle MCP install/uninstall (uses pluginauths)
feat: add MCP custom variables dialog to MCPSelect
feat: add MCP custom variables dialog to the side panel
feat: do not require to fill MCP credentials for in tools dialog
feat: add translations keys (en+cs) for custom MCP variables
fix: handle LIBRECHAT_USER_ID correctly during MCP var replacement
style: remove unused MCP translation keys
style: fix eslint for MCP custom vars
chore: move aider gitignore to AI section
* feat: Add Plugin Authentication Methods to data-schemas
* refactor: Replace PluginAuth model methods with new utility functions for improved code organization and maintainability
* refactor: Move IPluginAuth interface to types directory for better organization and update pluginAuth schema to use the new import
* refactor: Remove unused getUsersPluginsAuthValuesMap function and streamline PluginService.js; add new getPluginAuthMap function for improved plugin authentication handling
* chore: fix typing for optional tools property with GenericTool[] type
* chore: update librechat-data-provider version to 0.7.88
* refactor: optimize getUserMCPAuthMap function by reducing variable usage and improving server key collection logic
* refactor: streamline MCP tool creation by removing customUserVars parameter and enhancing user-specific authentication handling to avoid closure encapsulation
* refactor: extract processSingleValue function to streamline MCP environment variable processing and enhance readability
* refactor: enhance MCP tool processing logic by simplifying conditions and improving authentication handling for custom user variables
* ci: fix action tests
* chore: fix imports, remove comments
* chore: remove non-english translations
* fix: remove newline at end of translation.json file
---------
Co-authored-by: Aleš Kůtek <kutekales@gmail.com>
This commit is contained in:
parent
8b15bb2ed6
commit
3e4b01de82
36 changed files with 1536 additions and 166 deletions
93
packages/api/src/agents/auth.ts
Normal file
93
packages/api/src/agents/auth.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { logger } from '@librechat/data-schemas';
|
||||
import type { IPluginAuth, PluginAuthMethods } from '@librechat/data-schemas';
|
||||
import { decrypt } from '../crypto/encryption';
|
||||
|
||||
export interface GetPluginAuthMapParams {
|
||||
userId: string;
|
||||
pluginKeys: string[];
|
||||
throwError?: boolean;
|
||||
findPluginAuthsByKeys: PluginAuthMethods['findPluginAuthsByKeys'];
|
||||
}
|
||||
|
||||
export type PluginAuthMap = Record<string, Record<string, string>>;
|
||||
|
||||
/**
|
||||
* Retrieves and decrypts authentication values for multiple plugins
|
||||
* @returns A map where keys are pluginKeys and values are objects of authField:decryptedValue pairs
|
||||
*/
|
||||
export async function getPluginAuthMap({
|
||||
userId,
|
||||
pluginKeys,
|
||||
throwError = true,
|
||||
findPluginAuthsByKeys,
|
||||
}: GetPluginAuthMapParams): Promise<PluginAuthMap> {
|
||||
try {
|
||||
/** Early return for empty plugin keys */
|
||||
if (!pluginKeys?.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/** All plugin auths for current user query */
|
||||
const pluginAuths = await findPluginAuthsByKeys({ userId, pluginKeys });
|
||||
|
||||
/** Group auth records by pluginKey for efficient lookup */
|
||||
const authsByPlugin = new Map<string, IPluginAuth[]>();
|
||||
for (const auth of pluginAuths) {
|
||||
if (!auth.pluginKey) {
|
||||
logger.warn(`[getPluginAuthMap] Missing pluginKey for userId ${userId}`);
|
||||
continue;
|
||||
}
|
||||
const existing = authsByPlugin.get(auth.pluginKey) || [];
|
||||
existing.push(auth);
|
||||
authsByPlugin.set(auth.pluginKey, existing);
|
||||
}
|
||||
|
||||
const authMap: PluginAuthMap = {};
|
||||
const decryptionPromises: Promise<void>[] = [];
|
||||
|
||||
/** Single loop through requested pluginKeys */
|
||||
for (const pluginKey of pluginKeys) {
|
||||
authMap[pluginKey] = {};
|
||||
const auths = authsByPlugin.get(pluginKey) || [];
|
||||
|
||||
for (const auth of auths) {
|
||||
decryptionPromises.push(
|
||||
(async () => {
|
||||
try {
|
||||
const decryptedValue = await decrypt(auth.value);
|
||||
authMap[pluginKey][auth.authField] = decryptedValue;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error(
|
||||
`[getPluginAuthMap] Decryption failed for userId ${userId}, plugin ${pluginKey}, field ${auth.authField}: ${message}`,
|
||||
);
|
||||
|
||||
if (throwError) {
|
||||
throw new Error(
|
||||
`Decryption failed for plugin ${pluginKey}, field ${auth.authField}: ${message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
})(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(decryptionPromises);
|
||||
return authMap;
|
||||
} catch (error) {
|
||||
if (!throwError) {
|
||||
/** Empty objects for each plugin key on error */
|
||||
return pluginKeys.reduce((acc, key) => {
|
||||
acc[key] = {};
|
||||
return acc;
|
||||
}, {} as PluginAuthMap);
|
||||
}
|
||||
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error(
|
||||
`[getPluginAuthMap] Failed to fetch auth values for userId ${userId}, plugins: ${pluginKeys.join(', ')}: ${message}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
import { Run, Providers } from '@librechat/agents';
|
||||
import { providerEndpointMap, KnownEndpoints } from 'librechat-data-provider';
|
||||
import type { StandardGraphConfig, EventHandler, GraphEvents, IState } from '@librechat/agents';
|
||||
import type {
|
||||
StandardGraphConfig,
|
||||
EventHandler,
|
||||
GenericTool,
|
||||
GraphEvents,
|
||||
IState,
|
||||
} from '@librechat/agents';
|
||||
import type { Agent } from 'librechat-data-provider';
|
||||
import type * as t from '~/types';
|
||||
|
||||
|
|
@ -32,7 +38,7 @@ export async function createRun({
|
|||
streaming = true,
|
||||
streamUsage = true,
|
||||
}: {
|
||||
agent: Agent;
|
||||
agent: Omit<Agent, 'tools'> & { tools?: GenericTool[] };
|
||||
signal: AbortSignal;
|
||||
runId?: string;
|
||||
streaming?: boolean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue