fix: apply SSE auth headers to initial GET, redact URL credentials, warn on malformed headers

This commit is contained in:
dhananjai1729
2026-03-19 18:09:52 +05:30
committed by Peter Steinberger
parent 6fda8b4e9a
commit 32b7c00f90
2 changed files with 56 additions and 4 deletions

View File

@@ -35,7 +35,10 @@ function toStringRecord(
export function resolveSseMcpServerLaunchConfig(
raw: unknown,
options?: { onDroppedHeader?: (key: string, value: unknown) => void },
options?: {
onDroppedHeader?: (key: string, value: unknown) => void;
onMalformedHeaders?: (value: unknown) => void;
},
): SseMcpServerLaunchResult {
if (!isRecord(raw)) {
return { ok: false, reason: "server config must be an object" };
@@ -56,17 +59,49 @@ export function resolveSseMcpServerLaunchConfig(
reason: `only http and https URLs are supported, got ${parsed.protocol}`,
};
}
// Warn if headers is present but not an object (e.g. a string or array).
let headers: Record<string, string> | undefined;
if (raw.headers !== undefined && raw.headers !== null) {
if (!isRecord(raw.headers)) {
options?.onMalformedHeaders?.(raw.headers);
} else {
headers = toStringRecord(raw.headers, options?.onDroppedHeader);
}
}
return {
ok: true,
config: {
url,
headers: toStringRecord(raw.headers, options?.onDroppedHeader),
headers,
},
};
}
export function describeSseMcpServerLaunchConfig(config: SseMcpServerLaunchConfig): string {
return config.url;
try {
const parsed = new URL(config.url);
// Redact embedded credentials and query-token auth from log/description output.
if (parsed.username || parsed.password) {
parsed.username = parsed.username ? "***" : "";
parsed.password = parsed.password ? "***" : "";
}
for (const key of parsed.searchParams.keys()) {
const lower = key.toLowerCase();
if (
lower === "token" ||
lower === "key" ||
lower === "api_key" ||
lower === "apikey" ||
lower === "secret" ||
lower === "access_token"
) {
parsed.searchParams.set(key, "***");
}
}
return parsed.toString();
} catch {
return config.url;
}
}
export type { SseMcpServerLaunchConfig, SseMcpServerLaunchResult };

View File

@@ -155,13 +155,30 @@ function resolveTransport(
`bundle-mcp: server "${serverName}": header "${key}" has an unsupported value type and was ignored.`,
);
},
onMalformedHeaders: () => {
logWarn(
`bundle-mcp: server "${serverName}": "headers" must be a JSON object; the value was ignored.`,
);
},
});
if (sseLaunch.ok) {
const headers: Record<string, string> = {
...sseLaunch.config.headers,
};
const hasHeaders = Object.keys(headers).length > 0;
const transport = new SSEClientTransport(new URL(sseLaunch.config.url), {
requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
// Apply headers to POST requests (tool calls, listTools, etc.).
requestInit: hasHeaders ? { headers } : undefined,
// Apply headers to the initial SSE GET handshake (required for auth).
eventSourceInit: hasHeaders
? {
fetch: (url, init) =>
fetch(url, {
...init,
headers: { ...headers, ...(init?.headers as Record<string, string>) },
}),
}
: undefined,
});
return {
transport,