diff --git a/packages/desktop/packages/shared/src/sources/__tests__/server-builder-url.test.ts b/packages/desktop/packages/shared/src/sources/__tests__/server-builder-url.test.ts new file mode 100644 index 00000000000..3dd6158f69f --- /dev/null +++ b/packages/desktop/packages/shared/src/sources/__tests__/server-builder-url.test.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, test } from 'bun:test'; +import { normalizeMcpUrl } from '../server-builder.ts'; + +describe('normalizeMcpUrl', () => { + test('removes trailing slashes from the URL pathname', () => { + expect(normalizeMcpUrl('https://api.example.com/mcp/')).toBe( + 'https://api.example.com/mcp', + ); + expect(normalizeMcpUrl('https://api.example.com/mcp///')).toBe( + 'https://api.example.com/mcp', + ); + }); + + test('preserves trailing slashes inside query parameters', () => { + expect(normalizeMcpUrl('https://api.example.com/mcp?key=/')).toBe( + 'https://api.example.com/mcp?key=/', + ); + expect(normalizeMcpUrl('https://api.example.com/mcp/?key=value/')).toBe( + 'https://api.example.com/mcp?key=value/', + ); + }); + + test('preserves trailing slashes inside URL fragments', () => { + expect(normalizeMcpUrl('https://api.example.com/mcp#/')).toBe( + 'https://api.example.com/mcp#/', + ); + expect(normalizeMcpUrl('https://api.example.com/mcp/#/route/')).toBe( + 'https://api.example.com/mcp#/route/', + ); + }); + + test('keeps legacy fallback behavior for non-parseable URLs', () => { + expect(normalizeMcpUrl('not a url///')).toBe('not a url'); + }); +}); diff --git a/packages/desktop/packages/shared/src/sources/server-builder.ts b/packages/desktop/packages/shared/src/sources/server-builder.ts index 79b0814d71c..2384562ac40 100644 --- a/packages/desktop/packages/shared/src/sources/server-builder.ts +++ b/packages/desktop/packages/shared/src/sources/server-builder.ts @@ -334,11 +334,19 @@ export class SourceServerBuilder { /** * Normalize MCP URL to standard format - * - Removes trailing slashes + * - Removes trailing slashes from the path portion only + * - Preserves query strings and fragments unchanged * - Preserves the user-configured path as-is (no /mcp suffix appended) */ export function normalizeMcpUrl(url: string): string { - return url.replace(/\/+$/, ''); + const suffixIndex = url.search(/[?#]/); + if (suffixIndex === -1) { + return url.replace(/\/+$/, ''); + } + + return ( + url.slice(0, suffixIndex).replace(/\/+$/, '') + url.slice(suffixIndex) + ); } // Singleton instance