mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🔁 feat: Allow "http" as Alias for "streamable-http" in MCP Options (#8624)
- Updated StreamableHTTPOptionsSchema to accept "http" alongside "streamable-http". - Enhanced isStreamableHTTPOptions function to handle both types and validate URLs accordingly. - Added tests to ensure correct processing of "http" type options and rejection of websocket URLs.
This commit is contained in:
parent
a01536ddb7
commit
365e3bca95
3 changed files with 65 additions and 4 deletions
|
|
@ -45,9 +45,12 @@ function isSSEOptions(options: t.MCPOptions): options is t.SSEOptions {
|
||||||
* @returns True if options are for a streamable HTTP transport
|
* @returns True if options are for a streamable HTTP transport
|
||||||
*/
|
*/
|
||||||
function isStreamableHTTPOptions(options: t.MCPOptions): options is t.StreamableHTTPOptions {
|
function isStreamableHTTPOptions(options: t.MCPOptions): options is t.StreamableHTTPOptions {
|
||||||
if ('url' in options && options.type === 'streamable-http') {
|
if ('url' in options && 'type' in options) {
|
||||||
const protocol = new URL(options.url).protocol;
|
const optionType = options.type as string;
|
||||||
return protocol !== 'ws:' && protocol !== 'wss:';
|
if (optionType === 'streamable-http' || optionType === 'http') {
|
||||||
|
const protocol = new URL(options.url).protocol;
|
||||||
|
return protocol !== 'ws:' && protocol !== 'wss:';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +145,7 @@ export class MCPConnection extends EventEmitter {
|
||||||
} else if (isWebSocketOptions(options)) {
|
} else if (isWebSocketOptions(options)) {
|
||||||
type = 'websocket';
|
type = 'websocket';
|
||||||
} else if (isStreamableHTTPOptions(options)) {
|
} else if (isStreamableHTTPOptions(options)) {
|
||||||
|
// Could be either 'streamable-http' or 'http', normalize to 'streamable-http'
|
||||||
type = 'streamable-http';
|
type = 'streamable-http';
|
||||||
} else if (isSSEOptions(options)) {
|
} else if (isSSEOptions(options)) {
|
||||||
type = 'sse';
|
type = 'sse';
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,31 @@ describe('Environment Variable Extraction (MCP)', () => {
|
||||||
|
|
||||||
expect(result.headers).toEqual(options.headers);
|
expect(result.headers).toEqual(options.headers);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should accept "http" as an alias for "streamable-http"', () => {
|
||||||
|
const options = {
|
||||||
|
type: 'http',
|
||||||
|
url: 'https://example.com/api',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer token',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = StreamableHTTPOptionsSchema.parse(options);
|
||||||
|
|
||||||
|
expect(result.type).toBe('http');
|
||||||
|
expect(result.url).toBe('https://example.com/api');
|
||||||
|
expect(result.headers).toEqual(options.headers);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject websocket URLs with "http" type', () => {
|
||||||
|
const options = {
|
||||||
|
type: 'http',
|
||||||
|
url: 'ws://example.com/socket',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => StreamableHTTPOptionsSchema.parse(options)).toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('processMCPEnv', () => {
|
describe('processMCPEnv', () => {
|
||||||
|
|
@ -298,6 +323,38 @@ describe('Environment Variable Extraction (MCP)', () => {
|
||||||
expect(result.type).toBe('streamable-http');
|
expect(result.type).toBe('streamable-http');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should maintain http type in processed options', () => {
|
||||||
|
const obj = {
|
||||||
|
type: 'http' as const,
|
||||||
|
url: 'https://example.com/api',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = processMCPEnv(obj as unknown as MCPOptions);
|
||||||
|
|
||||||
|
expect(result.type).toBe('http');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process headers in http options', () => {
|
||||||
|
const user = createTestUser({ id: 'test-user-123' });
|
||||||
|
const obj = {
|
||||||
|
type: 'http' as const,
|
||||||
|
url: 'https://example.com',
|
||||||
|
headers: {
|
||||||
|
Authorization: '${TEST_API_KEY}',
|
||||||
|
'User-Id': '{{LIBRECHAT_USER_ID}}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = processMCPEnv(obj as unknown as MCPOptions, user);
|
||||||
|
|
||||||
|
expect('headers' in result && result.headers).toEqual({
|
||||||
|
Authorization: 'test-api-key-value',
|
||||||
|
'User-Id': 'test-user-123',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should process dynamic user fields in headers', () => {
|
it('should process dynamic user fields in headers', () => {
|
||||||
const user = createTestUser({
|
const user = createTestUser({
|
||||||
id: 'user-123',
|
id: 'user-123',
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export const SSEOptionsSchema = BaseOptionsSchema.extend({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StreamableHTTPOptionsSchema = BaseOptionsSchema.extend({
|
export const StreamableHTTPOptionsSchema = BaseOptionsSchema.extend({
|
||||||
type: z.literal('streamable-http'),
|
type: z.union([z.literal('streamable-http'), z.literal('http')]),
|
||||||
headers: z.record(z.string(), z.string()).optional(),
|
headers: z.record(z.string(), z.string()).optional(),
|
||||||
url: z
|
url: z
|
||||||
.string()
|
.string()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue