mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095)
* feat: Add conversation ID support to custom endpoint headers
- Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided
- Pass conversation ID to header resolution for dynamic headers
- Add comprehensive test coverage
Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder.
* fix: filter out unresolved placeholders from headers (thanks @MrunmayS)
* feat: add support for request body placeholders in custom endpoint headers
- Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId
- Update tests to reflect new body placeholder functionality
* refactor resolveHeaders
* style: minor styling cleanup
* fix: type error in unit test
* feat: add body to other endpoints
* feat: add body for mcp tool calls
* chore: remove changes that unnecessarily increase scope after clarification of requirements
* refactor: move http.ts to packages/api and have RequestBody intersect with Express request body
* refactor: processMCPEnv now uses single object argument pattern
* refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes
* feat: enhance MCP connection handling with dynamic request headers to pass request body fields
---------
Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1>
Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com>
Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
This commit is contained in:
parent
627f0bffe5
commit
d7d02766ea
25 changed files with 353 additions and 171 deletions
|
|
@ -81,11 +81,21 @@ export class MCPConnection extends EventEmitter {
|
|||
private lastPingTime: number;
|
||||
private lastConnectionCheckAt: number = 0;
|
||||
private oauthTokens?: MCPOAuthTokens | null;
|
||||
private requestHeaders?: Record<string, string> | null;
|
||||
private oauthRequired = false;
|
||||
iconPath?: string;
|
||||
timeout?: number;
|
||||
url?: string;
|
||||
|
||||
setRequestHeaders(headers: Record<string, string> | null): void {
|
||||
logger.debug(`${this.getLogPrefix()} Setting request headers: ${JSON.stringify(headers)}`);
|
||||
this.requestHeaders = headers;
|
||||
}
|
||||
|
||||
getRequestHeaders(): Record<string, string> | null | undefined {
|
||||
return this.requestHeaders;
|
||||
}
|
||||
|
||||
constructor(params: MCPConnectionParams) {
|
||||
super();
|
||||
this.options = params.serverConfig;
|
||||
|
|
@ -116,6 +126,43 @@ export class MCPConnection extends EventEmitter {
|
|||
return `[MCP]${userPart}[${this.serverName}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create fetch functions without capturing the entire `this` context.
|
||||
* This helps prevent memory leaks by only passing necessary dependencies.
|
||||
*
|
||||
* @param getHeaders Function to retrieve request headers
|
||||
* @returns A fetch function that merges headers appropriately
|
||||
*/
|
||||
private createFetchFunction(
|
||||
getHeaders: () => Record<string, string> | null | undefined,
|
||||
): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {
|
||||
return function customFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
const requestHeaders = getHeaders();
|
||||
if (!requestHeaders) {
|
||||
return fetch(input, init);
|
||||
}
|
||||
|
||||
let initHeaders: Record<string, string> = {};
|
||||
if (init?.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
initHeaders = Object.fromEntries(init.headers.entries());
|
||||
} else if (Array.isArray(init.headers)) {
|
||||
initHeaders = Object.fromEntries(init.headers);
|
||||
} else {
|
||||
initHeaders = init.headers as Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(input, {
|
||||
...init,
|
||||
headers: {
|
||||
...initHeaders,
|
||||
...requestHeaders,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private emitError(error: unknown, errorContext: string): void {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`${this.getLogPrefix()} ${errorContext}: ${errorMessage}`);
|
||||
|
|
@ -188,6 +235,7 @@ export class MCPConnection extends EventEmitter {
|
|||
});
|
||||
},
|
||||
},
|
||||
fetch: this.createFetchFunction(this.getRequestHeaders.bind(this)),
|
||||
});
|
||||
|
||||
transport.onclose = () => {
|
||||
|
|
@ -214,7 +262,7 @@ export class MCPConnection extends EventEmitter {
|
|||
);
|
||||
const abortController = new AbortController();
|
||||
|
||||
// Add OAuth token to headers if available
|
||||
/** Add OAuth token to headers if available */
|
||||
const headers = { ...options.headers };
|
||||
if (this.oauthTokens?.access_token) {
|
||||
headers['Authorization'] = `Bearer ${this.oauthTokens.access_token}`;
|
||||
|
|
@ -225,6 +273,7 @@ export class MCPConnection extends EventEmitter {
|
|||
headers,
|
||||
signal: abortController.signal,
|
||||
},
|
||||
fetch: this.createFetchFunction(this.getRequestHeaders.bind(this)),
|
||||
});
|
||||
|
||||
transport.onclose = () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue