🔁 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:
Danny Avila 2025-07-23 10:26:40 -04:00 committed by GitHub
parent a01536ddb7
commit 365e3bca95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 4 deletions

View file

@ -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';

View file

@ -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',

View file

@ -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()