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
98 changes: 98 additions & 0 deletions src/lib/discover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,104 @@ describe("discover llms.txt seed facts", () => {
});

describe("discover MCP onboarding overrides", () => {
test("binds detected MCP OAuth to the credential declared for that surface", async () => {
const source = "https://samva.dev/.well-known/integrations.json";
const detect: DetectionResult = {
...mcpDetection({
url: "https://mcp.samva.dev/mcp",
authorizationServer: "https://mcp.samva.dev",
authorizationServerMetadataFetched: true,
cimd: true,
}),
domain: "samva.dev",
integrationsJson: {
url: source,
result: {
version: 3,
credentials: {
samva_mcp_oauth: {
type: "oauth2",
label: "Samva MCP OAuth",
setup: "Connect an OAuth-capable MCP client to the hosted MCP URL.",
},
samva_cli_oauth: {
type: "oauth2",
label: "Samva CLI login",
setup: "Run `samva login` to complete the device flow.",
},
},
surfaces: [
{
slug: "samva-mcp",
name: "Samva hosted MCP",
type: "mcp",
url: "https://mcp.samva.dev/mcp",
basis: { via: "declared", source },
auth: {
status: "required",
entries: [
{
use: [{ id: "samva_mcp_oauth", mechanics: { source: "well-known" } }],
basis: { via: "declared", source },
},
],
},
},
{
slug: "samva-cli",
name: "Samva CLI",
type: "cli",
command: "samva",
basis: { via: "declared", source },
auth: {
status: "required",
entries: [
{
use: [{ id: "samva_cli_oauth", mechanics: { source: "cli", command: "samva login" } }],
basis: { via: "declared", source },
},
],
},
},
],
},
},
};
const chat: ChatFn = async () => ({
message: { role: "assistant", content: null },
toolCalls: [
{
id: "cred-1",
name: "record_credential",
arguments: {
id: "samva_cli_oauth",
type: "oauth2",
label: "Samva CLI login",
setup: "Run `samva login` to complete the device flow.",
},
},
{
id: "finish-1",
name: "finish",
arguments: {
summary: "Samva exposes MCP and CLI surfaces.",
description: "Samva is an email API.",
},
},
],
});

const result = await discover(detect.domain, detect, chat, web);
if (!result) throw new Error("discover returned null");
const mcp = result.surfaces.find((surface) => surface.type === "mcp");

expect(mcp?.auth.status).toBe("required");
if (mcp?.auth.status !== "required") throw new Error("MCP auth was not required");
expect(mcp.auth.entries[0]?.use[0]?.id).toBe("samva_mcp_oauth");
expect(result.credentials.samva_mcp_oauth?.setup).toContain("Client ID Metadata Document");
expect(result.credentials.samva_cli_oauth?.setup).toBe("Run `samva login` to complete the device flow.");
});

test("rewrites Slack no-DCR MCP setup to manual registration with the manifest deep link", async () => {
const result = await discoverMcp(mcpDetection({ authorizationServerMetadataFetched: true, dcr: false, cimd: false }));
const setup = setupFrom(result);
Expand Down
35 changes: 30 additions & 5 deletions src/lib/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,8 +838,33 @@ function merge(r: DiscoveryResult, detect: DetectionResult, emit?: Emit): Discov
const oauth = detect.auth?.oauth;
const hasDetOauth = oauth && (oauth.authorizationEndpoint || oauth.tokenEndpoint || oauth.registrationEndpoint || oauth.scopes?.length);

// Ensure a detected OAuth credential exists (referenced by detected MCP auth).
const ensureOauthCred = (): string => {
const declaredMcpOauthCredential = (mcpUrl: string): readonly [string, Credential] | undefined => {
const declared = detect.integrationsJson?.result;
const surface = declared?.surfaces?.find((candidate) => candidate.type === "mcp" && candidate.url === mcpUrl);
if (surface?.auth.status !== "required") return undefined;
for (const entry of surface.auth.entries) {
for (const use of entry.use) {
const credential = declared?.credentials?.[use.id];
if (credential && (/oauth/i.test(credential.type) || /oauth/i.test(credential.label))) {
return [use.id, credential];
}
}
}
return undefined;
};

// Preserve the OAuth identity that the owner bound to this MCP surface before
// falling back to a model-discovered, domain-wide OAuth credential.
const ensureOauthCred = (mcpUrl: string): string => {
const declared = declaredMcpOauthCredential(mcpUrl);
if (declared) {
const [id, credential] = declared;
if (!r.credentials[id]) {
r.credentials[id] = cloneJson(credential);
emit?.({ kind: "credential", id, credential: r.credentials[id] });
}
return id;
}
const existing = Object.entries(r.credentials).find(([, c]) => /oauth/i.test(c.type) || /oauth/i.test(c.label));
if (existing) return existing[0];
const id = "oauth";
Expand All @@ -864,7 +889,7 @@ function merge(r: DiscoveryResult, detect: DetectionResult, emit?: Emit): Discov
// developer portal, copy client_id/client_secret" story the model wrote from
// docs — and bind the surface to it as a detected signal.
const bindMcpSelfOnboard = (surface: Surface, mcp: McpDetection): void => {
const id = ensureOauthCred();
const id = ensureOauthCred(mcp.url);
const c = r.credentials[id];
c.type = "oauth2";
c.label = "OAuth 2.0";
Expand Down Expand Up @@ -895,7 +920,7 @@ function merge(r: DiscoveryResult, detect: DetectionResult, emit?: Emit): Discov
const isMcpManualOnboard = (mcp: McpDetection): boolean => mcp.authorizationServerMetadataFetched === true && !mcp.dcr && !mcp.cimd;

const bindMcpManualOnboard = (surface: Surface, mcp: McpDetection): void => {
const id = ensureOauthCred();
const id = ensureOauthCred(mcp.url);
const c = r.credentials[id];
const hostKnowledge = mcpHostKnowledge(mcp.url);
c.type = "oauth2";
Expand All @@ -922,7 +947,7 @@ function merge(r: DiscoveryResult, detect: DetectionResult, emit?: Emit): Discov
}
const auth: AuthStatus =
hasDetOauth || mcp.auth
? { status: "required", entries: [{ use: [{ id: ensureOauthCred(), mechanics: { source: "well-known" } }], basis: { via: "detected", signal: "oauth-protected-resource", verifiedAt } }] }
? { status: "required", entries: [{ use: [{ id: ensureOauthCred(mcp.url), mechanics: { source: "well-known" } }], basis: { via: "detected", signal: "oauth-protected-resource", verifiedAt } }] }
: { status: "unknown" };
const s: Surface = { slug: assignSlug("MCP server", r.surfaces), name: "MCP server", type: "mcp", url: mcp.url, basis: { via: "detected", signal: "mcp:initialize", verifiedAt }, auth, notes: mcp.dcr || mcp.cimd ? "Self-onboarding (DCR/CIMD)" : undefined };
r.surfaces.unshift(s);
Expand Down