mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-31 20:00:00 -05:00
feat: add passthrough headers support for OpenAPI client and MCP protocol (#345)
This commit is contained in:
@@ -18,6 +18,7 @@ import { getGroup } from './sseService.js';
|
||||
import { getServersInGroup, getServerConfigInGroup } from './groupService.js';
|
||||
import { saveToolsAsVectorEmbeddings, searchToolsByVector } from './vectorSearchService.js';
|
||||
import { OpenAPIClient } from '../clients/openapi.js';
|
||||
import { RequestContextService } from './requestContextService.js';
|
||||
import { getDataService } from './services.js';
|
||||
import { getServerDao, ServerConfigWithName } from '../dao/index.js';
|
||||
|
||||
@@ -403,6 +404,7 @@ export const initializeClientsFromSettings = async (
|
||||
prompts: [],
|
||||
createTime: Date.now(),
|
||||
enabled: conf.enabled === undefined ? true : conf.enabled,
|
||||
config: conf, // Store reference to original config for OpenAPI passthrough headers
|
||||
};
|
||||
serverInfos.push(serverInfo);
|
||||
|
||||
@@ -487,6 +489,7 @@ export const initializeClientsFromSettings = async (
|
||||
transport,
|
||||
options: requestOptions,
|
||||
createTime: Date.now(),
|
||||
config: conf, // Store reference to original config
|
||||
};
|
||||
serverInfos.push(serverInfo);
|
||||
|
||||
@@ -1036,7 +1039,34 @@ export const handleCallToolRequest = async (request: any, extra: any) => {
|
||||
? toolName.replace(`${targetServerInfo.name}-`, '')
|
||||
: toolName;
|
||||
|
||||
const result = await openApiClient.callTool(cleanToolName, finalArgs);
|
||||
// Extract passthrough headers from extra or request context
|
||||
let passthroughHeaders: Record<string, string> | undefined;
|
||||
let requestHeaders: Record<string, string | string[] | undefined> | null = null;
|
||||
|
||||
// Try to get headers from extra parameter first (if available)
|
||||
if (extra?.headers) {
|
||||
requestHeaders = extra.headers;
|
||||
} else {
|
||||
// Fallback to request context service
|
||||
const requestContextService = RequestContextService.getInstance();
|
||||
requestHeaders = requestContextService.getHeaders();
|
||||
}
|
||||
|
||||
if (requestHeaders && targetServerInfo.config?.openapi?.passthroughHeaders) {
|
||||
passthroughHeaders = {};
|
||||
for (const headerName of targetServerInfo.config.openapi.passthroughHeaders) {
|
||||
// Handle different header name cases (Express normalizes headers to lowercase)
|
||||
const headerValue =
|
||||
requestHeaders[headerName] || requestHeaders[headerName.toLowerCase()];
|
||||
if (headerValue) {
|
||||
passthroughHeaders[headerName] = Array.isArray(headerValue)
|
||||
? headerValue[0]
|
||||
: String(headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await openApiClient.callTool(cleanToolName, finalArgs, passthroughHeaders);
|
||||
|
||||
console.log(`OpenAPI tool invocation result: ${JSON.stringify(result)}`);
|
||||
return {
|
||||
@@ -1099,7 +1129,38 @@ export const handleCallToolRequest = async (request: any, extra: any) => {
|
||||
`Invoking OpenAPI tool '${cleanToolName}' on server '${serverInfo.name}' with arguments: ${JSON.stringify(request.params.arguments)}`,
|
||||
);
|
||||
|
||||
const result = await openApiClient.callTool(cleanToolName, request.params.arguments || {});
|
||||
// Extract passthrough headers from extra or request context
|
||||
let passthroughHeaders: Record<string, string> | undefined;
|
||||
let requestHeaders: Record<string, string | string[] | undefined> | null = null;
|
||||
|
||||
// Try to get headers from extra parameter first (if available)
|
||||
if (extra?.headers) {
|
||||
requestHeaders = extra.headers;
|
||||
} else {
|
||||
// Fallback to request context service
|
||||
const requestContextService = RequestContextService.getInstance();
|
||||
requestHeaders = requestContextService.getHeaders();
|
||||
}
|
||||
|
||||
if (requestHeaders && serverInfo.config?.openapi?.passthroughHeaders) {
|
||||
passthroughHeaders = {};
|
||||
for (const headerName of serverInfo.config.openapi.passthroughHeaders) {
|
||||
// Handle different header name cases (Express normalizes headers to lowercase)
|
||||
const headerValue =
|
||||
requestHeaders[headerName] || requestHeaders[headerName.toLowerCase()];
|
||||
if (headerValue) {
|
||||
passthroughHeaders[headerName] = Array.isArray(headerValue)
|
||||
? headerValue[0]
|
||||
: String(headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await openApiClient.callTool(
|
||||
cleanToolName,
|
||||
request.params.arguments || {},
|
||||
passthroughHeaders,
|
||||
);
|
||||
|
||||
console.log(`OpenAPI tool invocation result: ${JSON.stringify(result)}`);
|
||||
return {
|
||||
|
||||
105
src/services/requestContextService.ts
Normal file
105
src/services/requestContextService.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
/**
|
||||
* Request context interface for MCP request handling
|
||||
*/
|
||||
export interface RequestContext {
|
||||
headers: Record<string, string | string[] | undefined>;
|
||||
sessionId?: string;
|
||||
userAgent?: string;
|
||||
remoteAddress?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for managing request context during MCP request processing
|
||||
* This allows MCP request handlers to access HTTP headers and other request metadata
|
||||
*/
|
||||
export class RequestContextService {
|
||||
private static instance: RequestContextService;
|
||||
private requestContext: RequestContext | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): RequestContextService {
|
||||
if (!RequestContextService.instance) {
|
||||
RequestContextService.instance = new RequestContextService();
|
||||
}
|
||||
return RequestContextService.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current request context from Express request
|
||||
*/
|
||||
public setRequestContext(req: Request): void {
|
||||
this.requestContext = {
|
||||
headers: req.headers,
|
||||
sessionId: (req.headers['mcp-session-id'] as string) || undefined,
|
||||
userAgent: req.headers['user-agent'] as string,
|
||||
remoteAddress: req.ip || req.socket?.remoteAddress,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request context from custom data
|
||||
*/
|
||||
public setCustomRequestContext(context: RequestContext): void {
|
||||
this.requestContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current request context
|
||||
*/
|
||||
public getRequestContext(): RequestContext | null {
|
||||
return this.requestContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers from the current request context
|
||||
*/
|
||||
public getHeaders(): Record<string, string | string[] | undefined> | null {
|
||||
return this.requestContext?.headers || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific header value (case-insensitive)
|
||||
*/
|
||||
public getHeader(name: string): string | string[] | undefined {
|
||||
if (!this.requestContext?.headers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try exact match first
|
||||
if (this.requestContext.headers[name]) {
|
||||
return this.requestContext.headers[name];
|
||||
}
|
||||
|
||||
// Try lowercase match (Express normalizes headers to lowercase)
|
||||
const lowerName = name.toLowerCase();
|
||||
if (this.requestContext.headers[lowerName]) {
|
||||
return this.requestContext.headers[lowerName];
|
||||
}
|
||||
|
||||
// Try case-insensitive search
|
||||
for (const [key, value] of Object.entries(this.requestContext.headers)) {
|
||||
if (key.toLowerCase() === lowerName) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current request context
|
||||
*/
|
||||
public clearRequestContext(): void {
|
||||
this.requestContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session ID from current request context
|
||||
*/
|
||||
public getSessionId(): string | undefined {
|
||||
return this.requestContext?.sessionId;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { deleteMcpServer, getMcpServer } from './mcpService.js';
|
||||
import { loadSettings } from '../config/index.js';
|
||||
import config from '../config/index.js';
|
||||
import { UserContextService } from './userContextService.js';
|
||||
import { RequestContextService } from './requestContextService.js';
|
||||
|
||||
const transports: { [sessionId: string]: { transport: Transport; group: string } } = {};
|
||||
|
||||
@@ -131,7 +132,16 @@ export const handleSseMessage = async (req: Request, res: Response): Promise<voi
|
||||
`Received message for sessionId: ${sessionId} in group: ${group}${username ? ` for user: ${username}` : ''}`,
|
||||
);
|
||||
|
||||
await (transport as SSEServerTransport).handlePostMessage(req, res);
|
||||
// Set request context for MCP handlers to access HTTP headers
|
||||
const requestContextService = RequestContextService.getInstance();
|
||||
requestContextService.setRequestContext(req);
|
||||
|
||||
try {
|
||||
await (transport as SSEServerTransport).handlePostMessage(req, res);
|
||||
} finally {
|
||||
// Clean up request context after handling
|
||||
requestContextService.clearRequestContext();
|
||||
}
|
||||
};
|
||||
|
||||
export const handleMcpPostRequest = async (req: Request, res: Response): Promise<void> => {
|
||||
@@ -202,7 +212,17 @@ export const handleMcpPostRequest = async (req: Request, res: Response): Promise
|
||||
}
|
||||
|
||||
console.log(`Handling request using transport with type ${transport.constructor.name}`);
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
|
||||
// Set request context for MCP handlers to access HTTP headers
|
||||
const requestContextService = RequestContextService.getInstance();
|
||||
requestContextService.setRequestContext(req);
|
||||
|
||||
try {
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
} finally {
|
||||
// Clean up request context after handling
|
||||
requestContextService.clearRequestContext();
|
||||
}
|
||||
};
|
||||
|
||||
export const handleMcpOtherRequest = async (req: Request, res: Response) => {
|
||||
|
||||
Reference in New Issue
Block a user