UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

85 lines • 4.25 kB
/** * The OAuth discovery surface of the MCP server (MCP Authorization spec, RFC 9728, RFC 6750 §3): the * protected-resource metadata document, its well-known URL, and the `WWW-Authenticate` challenge a * `401`/`403` carries so an MCP client knows where to start the login. * * Pure functions over strings — the provider decides which origin to feed them (the configured * `publicUrl`, or the request's own protocol and `Host` as a fallback). */ /** Normalize a route prefix the way the provider mounts it: no leading/trailing slashes. */ export function normalizeMcpPath(path) { return (path ?? 'mcp').replace(/^\/+|\/+$/g, ''); } /** * Validate `publicUrl` from `config/mcp.ts` and reduce it to its origin (`https://app.example.com`). * Only an origin is meaningful: the MCP endpoint and its well-known document are mounted at fixed * paths on this app, so a path, query, or fragment would advertise URLs the app does not serve. */ export function publicOrigin(publicUrl) { let url; try { url = new URL(publicUrl); } catch { throw new Error(`[@adonis-agora/agent] \`publicUrl\` in config/mcp.ts is not an absolute URL: ${JSON.stringify(publicUrl)}`); } if (url.protocol !== 'http:' && url.protocol !== 'https:') { throw new Error(`[@adonis-agora/agent] \`publicUrl\` in config/mcp.ts must be http(s), got ${JSON.stringify(publicUrl)}`); } if (url.pathname !== '/' || url.search !== '' || url.hash !== '') { throw new Error(`[@adonis-agora/agent] \`publicUrl\` in config/mcp.ts must be an origin only (e.g. "https://app.example.com"), got ${JSON.stringify(publicUrl)}`); } return url.origin; } /** The protected resource (RFC 9728 `resource`): the URL an MCP client points at, e.g. `https://app.example.com/mcp`. */ export function mcpResourceUrl(origin, path) { return `${origin}/${normalizeMcpPath(path)}`; } /** Where the protected-resource metadata lives (RFC 9728 §3.1): the well-known segment goes between host and path. */ export function protectedResourceMetadataUrl(origin, path) { return `${origin}/.well-known/oauth-protected-resource/${normalizeMcpPath(path)}`; } /** * The Poppy issuer `@adonis-agora/authkit-server` announces in `Symbol.for('@adonis-agora/poppy:issuer')` * when Personal Agent Protocol is on — Poppy §6 requires a listed MCP server's `authorization_servers` * to include it, so a Poppy Session Token's issuer is one this server names. */ export function announcedPoppyIssuer() { const value = globalThis[Symbol.for('@adonis-agora/poppy:issuer')]; return typeof value === 'string' && value !== '' ? value : undefined; } /** * Build the RFC 9728 document for the MCP endpoint at `origin` + `path`. Its `authorization_servers` * is the configured issuer, plus the Poppy issuer when one is announced (and different). */ export function protectedResourceMetadata(meta, origin, path) { const poppyIssuer = announcedPoppyIssuer(); return { resource: mcpResourceUrl(origin, path), authorization_servers: [...new Set([meta.issuer, ...(poppyIssuer ? [poppyIssuer] : [])])], scopes_supported: meta.scopesSupported, bearer_methods_supported: ['header'], resource_name: meta.resourceName, }; } /** * The `WWW-Authenticate` value for a `401`/`403` from the MCP endpoint — e.g. * `Bearer error="invalid_token", resource_metadata="https://app.example.com/.well-known/oauth-protected-resource/mcp"`. * `resource_metadata` is what makes an MCP client discover the authorization server and open the login. */ export function wwwAuthenticateChallenge(options = {}) { const params = []; if (options.error !== undefined) params.push(`error="${options.error}"`); if (options.scope !== undefined) params.push(`scope="${quote(options.scope)}"`); if (options.resourceMetadataUrl !== undefined) { params.push(`resource_metadata="${quote(options.resourceMetadataUrl)}"`); } return params.length === 0 ? 'Bearer' : `Bearer ${params.join(', ')}`; } /** Escape a value for an RFC 7230 quoted-string. */ function quote(value) { return value.replace(/[\\"]/g, (char) => `\\${char}`); } //# sourceMappingURL=discovery.js.map