Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/gateways/VirtualServerDetailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function VirtualServerDetailsPanel({
onAddTag?: (serverId: string, tags: string[]) => Promise<void>;
}) {
const intl = useIntl();
const endpoint = server ? getVirtualServerEndpoint(server.id) : "";
const endpoint = server ? getVirtualServerEndpoint(server) : "";
const tagFallback = intl.formatMessage({ id: "gateways.details.tagFallback" });
const notSyncedYet = intl.formatMessage({ id: "gateways.card.notSyncedYet" });
const tags = (server?.tags ?? []).map((tag, index) => getTagDisplay(tag, index, tagFallback));
Expand Down
24 changes: 20 additions & 4 deletions src/components/gateways/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,37 @@ describe("Gateways Utils", () => {
});

describe("getVirtualServerEndpoint", () => {
it("returns relative path if window or origin is not available", () => {
it("prefers the gateway-supplied url over window.location.origin", () => {
// mcp-context-forge#6632: window.location.origin is the web UI's own
// origin, not necessarily where the gateway serves this endpoint in a
// split deployment -- server.url (APP_DOMAIN-derived, server-side)
// must win whenever the gateway supplies it.
expect(
getVirtualServerEndpoint({
id: "test-id",
url: "https://gateway.example.com/servers/test-id/mcp",
}),
).toBe("https://gateway.example.com/servers/test-id/mcp");
});

it("falls back to a relative path if window or origin is not available and no url is supplied", () => {
// Mock window.location to simulate missing origin
const originalLocation = window.location;
// @ts-expect-error - invalid url - testing missing window.location
delete window.location;

expect(getVirtualServerEndpoint("test-id")).toBe("/servers/test-id/mcp");
expect(getVirtualServerEndpoint({ id: "test-id" })).toBe("/servers/test-id/mcp");

// Restore window.location
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.location = originalLocation as any;
});

it("returns absolute URL if window.location.origin is available", () => {
expect(getVirtualServerEndpoint("test-id")).toBe("http://localhost:3000/servers/test-id/mcp");
it("falls back to an absolute URL built from window.location.origin when no url is supplied", () => {
// Covers gateways older than mcp-context-forge#6632's url field.
expect(getVirtualServerEndpoint({ id: "test-id" })).toBe(
"http://localhost:3000/servers/test-id/mcp",
);
});
});

Expand Down
11 changes: 9 additions & 2 deletions src/components/gateways/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ export function truncateMiddle(value: string, maxLength = 24) {
return `${value.slice(0, edgeLength)}...${value.slice(-edgeLength)}`;
}

export function getVirtualServerEndpoint(serverId: string) {
const encodedServerId = encodeURIComponent(serverId);
// Prefers the gateway-supplied `url` (APP_DOMAIN-derived server-side,
// mcp-context-forge#6632) over window.location.origin, which is the web
// UI's own origin and not necessarily where the gateway serves this
// endpoint in a split deployment. The origin-based construction remains
// only as a fallback for gateways older than the `url` field.
export function getVirtualServerEndpoint(server: Pick<VirtualServer, "id" | "url">) {
if (server.url) return server.url;

const encodedServerId = encodeURIComponent(server.id);
if (typeof window === "undefined" || !window.location?.origin) {
return `/servers/${encodedServerId}/mcp`;
}
Expand Down
7 changes: 7 additions & 0 deletions src/types/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ export interface VirtualServerTag {
export interface VirtualServer extends Omit<BaseServer, "team_id" | "owner_email"> {
description: string;
icon: string;
/**
* Fully-qualified MCP endpoint URL, derived server-side from the gateway's
* own APP_DOMAIN (mcp-context-forge#6632). Absent on gateways older than
* that field — see getVirtualServerEndpoint's window.location.origin
* fallback for that case.
*/
url?: string;
createdAt: string;
updatedAt: string;
associatedTools?: string[];
Expand Down
Loading