CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
well-known.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 369ad65 | 1 | /** |
| 2 | * OAuth / agent discovery metadata under `/.well-known/*`. | |
| 3 | * | |
| 4 | * These endpoints let an AI agent (claude.ai remote connectors, Cursor, | |
| 5 | * Copilot, etc.) auto-discover how to authenticate against Gluecron without | |
| 6 | * any manual configuration. They advertise ONLY endpoints that actually | |
| 7 | * exist today: | |
| 8 | * | |
| 9 | * - RFC 8414 Authorization Server Metadata | |
| 10 | * GET /.well-known/oauth-authorization-server | |
| 11 | * - RFC 9728 Protected Resource Metadata | |
| 12 | * GET /.well-known/oauth-protected-resource | |
| 13 | * GET /.well-known/oauth-protected-resource/mcp (resource-specific) | |
| 14 | * | |
| 4c27627 | 15 | * `registration_endpoint` (RFC 7591 dynamic client registration) is now live |
| 16 | * at POST /oauth/register and advertised below. `introspection_endpoint` | |
| 17 | * (RFC 7662) is still omitted until it ships — advertising a 404 would break | |
| 18 | * clients that probe it. Add it here in the same change that builds it. | |
| 369ad65 | 19 | * |
| 20 | * Pure/read-only, no auth, no DB. Safe to serve to anyone. | |
| 21 | */ | |
| 22 | ||
| 23 | import { Hono } from "hono"; | |
| 8ed88f2 | 24 | import type { Context } from "hono"; |
| 369ad65 | 25 | import { config } from "../lib/config"; |
| 26 | import { SUPPORTED_SCOPES } from "../lib/oauth"; | |
| 27 | ||
| 28 | const wellKnown = new Hono(); | |
| 29 | ||
| 30 | /** Absolute URL on this deployment's public origin (no trailing slash). */ | |
| 31 | function base(): string { | |
| 32 | // config.appBaseUrl already strips a trailing slash. | |
| 33 | return config.appBaseUrl; | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * RFC 8414 — OAuth 2.0 Authorization Server Metadata. | |
| 38 | * The document a client reads to learn where /authorize and /token live, | |
| 39 | * which PKCE methods are supported, and which scopes exist. | |
| 40 | */ | |
| 41 | wellKnown.get("/.well-known/oauth-authorization-server", (c) => { | |
| 42 | const b = base(); | |
| 43 | return c.json( | |
| 44 | { | |
| 45 | issuer: b, | |
| 46 | authorization_endpoint: `${b}/oauth/authorize`, | |
| 47 | token_endpoint: `${b}/oauth/token`, | |
| 48 | revocation_endpoint: `${b}/oauth/revoke`, | |
| 4c27627 | 49 | // RFC 7591 dynamic client registration — lets agents self-register. |
| 50 | registration_endpoint: `${b}/oauth/register`, | |
| 369ad65 | 51 | scopes_supported: SUPPORTED_SCOPES, |
| 52 | response_types_supported: ["code"], | |
| 53 | grant_types_supported: ["authorization_code", "refresh_token"], | |
| 54 | // PKCE (RFC 7636) — the provider verifies S256 and plain. | |
| 55 | code_challenge_methods_supported: ["S256", "plain"], | |
| 56 | token_endpoint_auth_methods_supported: [ | |
| 57 | "client_secret_basic", | |
| 58 | "client_secret_post", | |
| 59 | "none", // public clients (PKCE, no secret) | |
| 60 | ], | |
| 61 | revocation_endpoint_auth_methods_supported: [ | |
| 62 | "client_secret_basic", | |
| 63 | "client_secret_post", | |
| 64 | "none", | |
| 65 | ], | |
| 66 | service_documentation: `${b}/api/docs`, | |
| 67 | }, | |
| 68 | 200, | |
| 69 | { "cache-control": "public, max-age=3600" } | |
| 70 | ); | |
| 71 | }); | |
| 72 | ||
| 73 | /** | |
| 74 | * RFC 9728 — OAuth 2.0 Protected Resource Metadata. | |
| 75 | * | |
| 76 | * Served both at the root path and at `/mcp` (resource-specific). A remote | |
| 77 | * MCP connector reads this to learn which authorization server guards the | |
| 78 | * `/mcp` resource and which scopes it accepts. | |
| 79 | */ | |
| 8ed88f2 | 80 | function protectedResourceDoc(c: Context) { |
| 369ad65 | 81 | const b = base(); |
| 82 | return c.json( | |
| 83 | { | |
| 84 | resource: `${b}/mcp`, | |
| 85 | authorization_servers: [b], | |
| 86 | scopes_supported: SUPPORTED_SCOPES, | |
| 87 | bearer_methods_supported: ["header"], | |
| 88 | resource_documentation: `${b}/api/docs`, | |
| 89 | }, | |
| 90 | 200, | |
| 91 | { "cache-control": "public, max-age=3600" } | |
| 92 | ); | |
| 93 | } | |
| 94 | ||
| 95 | wellKnown.get("/.well-known/oauth-protected-resource", protectedResourceDoc); | |
| 96 | // Resource-specific form (RFC 9728 §3.1) — some clients append the resource | |
| 97 | // path when the WWW-Authenticate challenge points at `<origin>/mcp`. | |
| 98 | wellKnown.get("/.well-known/oauth-protected-resource/mcp", protectedResourceDoc); | |
| 99 | ||
| cb65da8 | 100 | /** |
| 101 | * Official MCP Registry domain verification (HTTP method). | |
| 102 | * | |
| 103 | * registry.modelcontextprotocol.io verifies control of the `com.gluecron` | |
| 104 | * namespace by fetching this file and checking the signature made with the | |
| 105 | * matching PRIVATE key during `mcp-publisher login http`. The value below is | |
| 106 | * the PUBLIC half of the publishing keypair — safe to serve to anyone. | |
| 107 | * Override via MCP_REGISTRY_AUTH env when the keypair is rotated. | |
| 108 | * Registry entry lives in ./server.json at the repo root. | |
| 109 | */ | |
| 110 | wellKnown.get("/.well-known/mcp-registry-auth", (c) => | |
| 111 | c.text( | |
| 112 | process.env.MCP_REGISTRY_AUTH || | |
| 113 | "v=MCPv1; k=ed25519; p=7a15jFzQv2INI1Lc9Yg8+v2Dv3QNK+r83ZfpotOOOdw=", | |
| 114 | 200, | |
| 115 | { "cache-control": "public, max-age=300" } | |
| 116 | ) | |
| 117 | ); | |
| 118 | ||
| 369ad65 | 119 | export default wellKnown; |