Skip to content

Commit 7f7d629

Browse files
committed
Discover MCP at conventional /mcp path (probe + confirm)
Some MCP servers (e.g. mcp.posthog.com/mcp) aren't referenced by a server-card or api-catalog. Probe /mcp with an initialize and confirm via 401+resource_metadata or a jsonrpc 200, then run the onboarding chain for DCR/CIMD.
1 parent c37076c commit 7f7d629

1 file changed

Lines changed: 17 additions & 1 deletion

File tree

src/lib/detect.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
1616

1717
export interface McpDetection {
1818
url: string;
19-
source: "api-catalog" | "server-card";
19+
source: "api-catalog" | "server-card" | "probe";
2020
/** "oauth2" | "none" | undefined (unknown) */
2121
auth?: string;
2222
authorizationServer?: string;
@@ -231,6 +231,20 @@ async function detectMcpOnboarding(fetchImpl: FetchLike, mcpUrl: string): Promis
231231
};
232232
}
233233

234+
/** Probe the conventional /mcp path; confirm it's actually an MCP endpoint. */
235+
async function discoverMcpEndpoint(fetchImpl: FetchLike, domain: string): Promise<string | undefined> {
236+
const url = `https://${domain}/mcp`;
237+
const hit = await get(fetchImpl, url, {
238+
method: "POST",
239+
headers: { "content-type": "application/json", accept: "application/json, text/event-stream" },
240+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "integrations.sh", version: "0" } } }),
241+
});
242+
if (!hit) return undefined;
243+
const wwwAuth = hit.res.headers.get("www-authenticate") ?? "";
244+
const looksMcp = (hit.res.status === 401 && /resource_metadata=/.test(wwwAuth)) || (hit.res.ok && /["']?jsonrpc/.test(hit.text));
245+
return looksMcp ? url : undefined;
246+
}
247+
234248
// ── orchestration ────────────────────────────────────────────────────────────
235249

236250
export async function detect(domain: string, fetchImpl: FetchLike = fetch): Promise<DetectionResult> {
@@ -244,12 +258,14 @@ export async function detect(domain: string, fetchImpl: FetchLike = fetch): Prom
244258
checkApiSchema(fetchImpl, domain).catch(() => undefined),
245259
checkApiOAuth(fetchImpl, domain).catch(() => undefined),
246260
]);
261+
const probedMcp = await discoverMcpEndpoint(fetchImpl, domain).catch(() => undefined);
247262

248263
// Collect MCP endpoints from the server card + api-catalog, then probe each
249264
// for self-onboarding capability.
250265
const mcpSeen = new Map<string, McpDetection>();
251266
if (serverCard) mcpSeen.set(serverCard.url, serverCard);
252267
for (const url of apiCatalog?.mcp ?? []) if (!mcpSeen.has(url)) mcpSeen.set(url, { url, source: "api-catalog" });
268+
if (probedMcp && !mcpSeen.has(probedMcp)) mcpSeen.set(probedMcp, { url: probedMcp, source: "probe" });
253269
const mcp = await Promise.all(
254270
[...mcpSeen.values()].map(async (m) => ({ ...m, ...(await detectMcpOnboarding(fetchImpl, m.url).catch(() => ({}))) })),
255271
);

0 commit comments

Comments
 (0)