@@ -14,13 +14,16 @@ import (
1414 "github.com/modelcontextprotocol/registry/internal/config"
1515)
1616
17- // CoreTokenExchangeInput represents the common input structure for token exchange
18- type CoreTokenExchangeInput struct {
17+ // SignatureTokenExchangeInput represents the common input structure for token exchange
18+ type SignatureTokenExchangeInput struct {
1919 Domain string `json:"domain" doc:"Domain name" example:"example.com" required:"true"`
2020 Timestamp string `json:"timestamp" doc:"RFC3339 timestamp" example:"2023-01-01T00:00:00Z" required:"true"`
2121 SignedTimestamp string `json:"signed_timestamp" doc:"Hex-encoded Ed25519 signature of timestamp" example:"abcdef1234567890" required:"true"`
2222}
2323
24+ // KeyFetcher defines a function type for fetching keys from external sources
25+ type KeyFetcher func (ctx context.Context , domain string ) ([]string , error )
26+
2427// CoreAuthHandler represents the common handler structure
2528type CoreAuthHandler struct {
2629 config * config.Config
@@ -90,9 +93,6 @@ func BuildPermissions(domain string, includeSubdomains bool) []auth.Permission {
9093 }
9194
9295 if includeSubdomains {
93- // DNS implies a hierarchy where subdomains are treated as part of the parent domain,
94- // therefore we grant permissions for all subdomains (e.g., com.example.*)
95- // This is in line with other DNS-based authentication methods e.g. ACME DNS-01 challenges
9696 permissions = append (permissions , auth.Permission {
9797 Action : auth .PermissionActionPublish ,
9898 ResourcePattern : fmt .Sprintf ("%s.*" , reverseDomain ),
@@ -120,6 +120,51 @@ func (h *CoreAuthHandler) CreateJWTClaimsAndToken(ctx context.Context, authMetho
120120 return tokenResponse , nil
121121}
122122
123+ // ExchangeToken is a shared method for token exchange that takes a key fetcher function,
124+ // subdomain inclusion flag, and auth method
125+ func (h * CoreAuthHandler ) ExchangeToken (
126+ ctx context.Context ,
127+ domain , timestamp , signedTimestamp string ,
128+ keyFetcher KeyFetcher ,
129+ includeSubdomains bool ,
130+ authMethod auth.Method ) (* auth.TokenResponse , error ) {
131+ _ , err := ValidateDomainAndTimestamp (domain , timestamp )
132+ if err != nil {
133+ return nil , err
134+ }
135+
136+ signature , err := DecodeAndValidateSignature (signedTimestamp )
137+ if err != nil {
138+ return nil , err
139+ }
140+
141+ keyStrings , err := keyFetcher (ctx , domain )
142+ if err != nil {
143+ return nil , fmt .Errorf ("failed to fetch keys: %w" , err )
144+ }
145+
146+ publicKeys := ParseMCPKeysFromStrings (keyStrings )
147+ if len (publicKeys ) == 0 {
148+ switch authMethod {
149+ case auth .MethodHTTP :
150+ return nil , fmt .Errorf ("failed to parse public key" )
151+ case auth .MethodDNS :
152+ return nil , fmt .Errorf ("no valid MCP public keys found in DNS TXT records" )
153+ default :
154+ return nil , fmt .Errorf ("no valid MCP public keys found using %s authentication" , authMethod )
155+ }
156+ }
157+
158+ messageBytes := []byte (timestamp )
159+ if ! VerifySignatureWithKeys (publicKeys , messageBytes , signature ) {
160+ return nil , fmt .Errorf ("signature verification failed" )
161+ }
162+
163+ permissions := BuildPermissions (domain , includeSubdomains )
164+
165+ return h .CreateJWTClaimsAndToken (ctx , authMethod , domain , permissions )
166+ }
167+
123168func ParseMCPKeysFromStrings (inputs []string ) []ed25519.PublicKey {
124169 var publicKeys []ed25519.PublicKey
125170 mcpPattern := regexp .MustCompile (`v=MCPv1;\s*k=ed25519;\s*p=([A-Za-z0-9+/=]+)` )
0 commit comments