Module: Mcp::DiscoveryMetadata

Defined in:
app/services/mcp/discovery_metadata.rb

Overview

Single source of truth for the MCP gateway's OAuth discovery documents.

The gateway (mcp.warmlyyours.com) is WarmlyYours' OAuth 2.1 authorization
server (Doorkeeper: authorization_code + refresh_token grants, forced
PKCE/S256, RFC 7591 dynamic registration, scope mcp). These builders are
shared by OauthMetadataController (the canonical, self-describing
copies served on mcp.*) and OauthDiscoveryController (which surfaces the
same metadata on the public origins a crawler/agent scans). Centralizing them
keeps the two from drifting if the OAuth config changes.

Tokens are opaque (no JWT), so there is intentionally no jwks_uri (OPTIONAL
per RFC 8414 §2), and there is no OpenID Connect layer, so no
/.well-known/openid-configuration.

Class Method Summary collapse

Class Method Details

.authorization_server(issuer:) ⇒ Hash

RFC 8414 OAuth 2.0 Authorization Server Metadata.

Parameters:

  • issuer (String)

    the issuer identifier; also the base for the
    advertised endpoints (the AS hosts them under its own origin).

Returns:

  • (Hash)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/services/mcp/discovery_metadata.rb', line 27

def authorization_server(issuer:)
  {
    issuer: issuer,
    authorization_endpoint: "#{issuer}/oauth/authorize",
    token_endpoint: "#{issuer}/oauth/token",
    revocation_endpoint: "#{issuer}/oauth/revoke",
    # RFC 7591 dynamic client registration — required for Claude Code's
    # auto-OAuth flow; without it every client must be pre-created.
    registration_endpoint: "#{issuer}/oauth/register",
    response_types_supported: %w[code],
    grant_types_supported: %w[authorization_code refresh_token],
    code_challenge_methods_supported: %w[S256],
    token_endpoint_auth_methods_supported: %w[none client_secret_basic client_secret_post],
    scopes_supported: %w[mcp],
    # auth.md agent-registration discovery (https://github.com/workos/auth.md).
    # Tells an autonomous agent how to register + authenticate. `skill` MUST
    # resolve to the /auth.md document itself — isitagentready's authMd check
    # validates that `agent_auth.skill` points at /auth.md ("Missing or unsafe
    # skill URL pointing to /auth.md" otherwise), NOT the Agent Skills index.
    # `register_uri` is the live RFC 7591 endpoint, plus one complete OAuth 2.1
    # method. The check reads this block from the RFC 8414 metadata (not from
    # /auth.md's prose). Registration is genuinely open; tool access is still
    # gated (see the MCP server card's `access` block).
    agent_auth: {
      skill: "#{WEB_URL}/auth.md",
      register_uri: "#{issuer}/oauth/register",
      methods: [
        {
          type: 'oauth2',
          grant_types: %w[authorization_code refresh_token],
          code_challenge_methods: %w[S256],
          registration: 'https://www.rfc-editor.org/rfc/rfc7591',
          token_endpoint_auth_methods: %w[none client_secret_basic client_secret_post],
          scopes: %w[mcp]
        }
      ]
    }
  }
end

.protected_resource(resource:, authorization_servers:) ⇒ Hash

RFC 9728 OAuth 2.0 Protected Resource Metadata.

Parameters:

  • resource (String)

    the protected resource's identifier (its origin).

  • authorization_servers (Array<String>)

    issuer URLs allowed to mint
    tokens for the resource.

Returns:

  • (Hash)


73
74
75
76
77
78
79
80
# File 'app/services/mcp/discovery_metadata.rb', line 73

def protected_resource(resource:, authorization_servers:)
  {
    resource: resource,
    authorization_servers: authorization_servers,
    bearer_methods_supported: %w[header],
    scopes_supported: %w[mcp]
  }
end