mcp: add rfc7591 types

This commit is contained in:
Denis Mishin 2025-04-23 13:43:00 -04:00
parent f1a9401ddc
commit 9894531cb8
9 changed files with 2729 additions and 0 deletions

View file

@ -0,0 +1,18 @@
version: v2
inputs:
- directory: .
plugins:
- remote: buf.build/protocolbuffers/go:v1.36.5
out: .
opt:
- paths=source_relative
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/bufbuild/buf-examples/protovalidate/quickstart-go/start/gen
# Don't modify any file option or field option for protovalidate. Without
# this, generated Go will fail to compile.
disable:
- file_option: go_package
module: buf.build/bufbuild/protovalidate

View file

@ -0,0 +1,6 @@
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 7712fb530c574b95bc1d57c0877543c3
digest: b5:b3e9c9428384357e3b73e4d5a4614328b0a4b1595b10163bbe9483fa16204749274c41797bd49b0d716479c855aa35c1172a94f471fa120ba8369637fd138829

11
internal/rfc7591/buf.yaml Normal file
View file

@ -0,0 +1,11 @@
version: v2
modules:
- path: .
deps:
- buf.build/bufbuild/protovalidate
lint:
use:
- STANDARD
breaking:
use:
- FILE

View file

@ -0,0 +1,4 @@
package rfc7591v1
//go:generate go run github.com/bufbuild/buf/cmd/buf@v1.53.0 dep update
//go:generate go run github.com/bufbuild/buf/cmd/buf@v1.53.0 generate

2079
internal/rfc7591/types.pb.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,547 @@
syntax = "proto3";
package ietf.rfc7591.v1;
import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";
option go_package = "github.com/pomerium/pomerium/internal/rfc7591";
// Represents the JSON Web Key Set (JWK Set) structure defined in RFC 7517.
// This contains a set of JWKs.
message JsonWebKeySet {
// REQUIRED. The value of the "keys" parameter is an array of JWK values.
repeated JsonWebKey keys = 1 [
(buf.validate.field).required = true,
(buf.validate.field).repeated .min_items = 1
];
}
// Represents a JSON Web Key (JWK) structure defined in RFC 7517.
// A JWK is a JSON object that represents a cryptographic key.
message JsonWebKey {
// REQUIRED. The "kty" (key type) parameter identifies the cryptographic
// algorithm family used with the key, such as "RSA" or "EC".
string kty = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string = {
in : [ "RSA", "EC", "oct", "OKP" ],
min_len : 1
}
];
// OPTIONAL. The "use" (public key use) parameter identifies the intended
// use of the public key. Values are "sig" (signature) or "enc" (encryption).
optional string use = 2
[ (buf.validate.field).string = {in : [ "sig", "enc" ]} ];
// OPTIONAL. The "key_ops" (key operations) parameter identifies the
// operation(s) for which the key is intended to be used.
repeated string key_ops = 3
[ (buf.validate.field).repeated .items.string.min_len = 1 ];
// OPTIONAL. The "alg" (algorithm) parameter identifies the algorithm
// intended for use with the key.
optional string alg = 4 [ (buf.validate.field).string.min_len = 1 ];
// OPTIONAL. The "kid" (key ID) parameter is used to match a specific key.
optional string kid = 5 [ (buf.validate.field).string.min_len = 1 ];
// Parameters specific to the key type.
oneof key_type_parameters {
option (buf.validate.oneof).required =
false; // Only required if kty demands it, checked by message rules
// RSA key specific parameters.
RsaKeyParameters rsa_params = 6;
// Elliptic Curve key specific parameters.
EcKeyParameters ec_params = 7;
// Symmetric key specific parameters.
SymmetricKeyParameters symmetric_params = 8;
// Octet Key Pair specific parameters (e.g., Ed25519).
OkpKeyParameters okp_params = 9;
}
// Message level validation to ensure specific parameters are present based on
// kty.
option (buf.validate.message).cel = {
id : "jwk.kty_params.rsa",
expression : "this.kty != 'RSA' || has(this.rsa_params)",
message : "rsa_params are required for kty 'RSA'"
};
option (buf.validate.message).cel = {
id : "jwk.kty_params.ec",
expression : "this.kty != 'EC' || has(this.ec_params)",
message : "ec_params are required for kty 'EC'"
};
option (buf.validate.message).cel = {
id : "jwk.kty_params.oct",
expression : "this.kty != 'oct' || has(this.symmetric_params)",
message : "symmetric_params are required for kty 'oct'"
};
option (buf.validate.message).cel = {
id : "jwk.kty_params.okp",
expression : "this.kty != 'OKP' || has(this.okp_params)",
message : "okp_params are required for kty 'OKP'"
};
}
// RSA specific key parameters (RFC 7518 Section 6.3).
message RsaKeyParameters {
// REQUIRED. The "n" (modulus) parameter contains the modulus value for the
// RSA public key.
string n = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
// REQUIRED. The "e" (exponent) parameter contains the exponent value for the
// RSA public key.
string e = 2 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
}
// Elliptic Curve specific key parameters (RFC 7518 Section 6.2).
message EcKeyParameters {
// REQUIRED. The "crv" (curve) parameter identifies the cryptographic curve
// used with the key.
string crv = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string = {in : [ "P-256", "P-384", "P-521" ]}
];
// REQUIRED. The "x" (x coordinate) parameter contains the x coordinate for
// the Elliptic Curve point.
string x = 2 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
// REQUIRED. The "y" (y coordinate) parameter contains the y coordinate for
// the Elliptic Curve point.
string y = 3 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
}
// Symmetric key specific parameters (RFC 7518 Section 6.4).
message SymmetricKeyParameters {
// REQUIRED. The "k" (key value) parameter contains the value of the symmetric
// key.
string k = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
}
// Octet Key Pair (OKP) specific parameters (RFC 8037 Section 2).
message OkpKeyParameters {
// REQUIRED. The "crv" (curve) parameter identifies the cryptographic curve
// used with the key.
string crv = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string = {
in : [ "Ed25519", "Ed448", "X25519", "X448" ]
}
];
// REQUIRED. The "x" (x coordinate) parameter contains the public key.
string x = 2 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
}
// Represents the client metadata fields defined in RFC 7591 Section 2.
// These values are used both as input to registration requests and output in
// registration responses.
message ClientMetadata {
// Array of redirection URI strings. REQUIRED for clients using flows with
// redirection.
repeated string redirect_uris = 1 [ (buf.validate.field).repeated = {
min_items : 1,
items : {string : {uri : true, min_len : 1}}
} ];
// OPTIONAL. String indicator of the requested authentication method for the
// token endpoint. Default is "client_secret_basic".
optional string token_endpoint_auth_method = 2
[ (buf.validate.field).string = {
in : [ "none", "client_secret_post", "client_secret_basic" ],
} ];
// OPTIONAL. Array of OAuth 2.0 grant type strings that the client can use.
// If omitted, defaults to ["authorization_code"].
repeated string grant_types = 3
[ (buf.validate.field).repeated .items.string = {
in : [
"authorization_code",
"implicit",
"password",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:jwt-bearer",
"urn:ietf:params:oauth:grant-type:saml2-bearer"
],
} ];
// OPTIONAL. Array of the OAuth 2.0 response type strings that the client can
// use. If omitted, defaults to ["code"].
repeated string response_types = 4
[ (buf.validate.field).repeated .items.string = {
in : [ "code", "token" ],
} ];
// OPTIONAL. Human-readable string name of the client. RECOMMENDED.
optional string client_name = 5
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL. Map for localized client names.
map<string, string> client_name_localized = 6 [ (buf.validate.field).map = {
keys : {
string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}
}, // BCP 47 pattern
values : {string : {min_len : 1, max_len : 255}}
} ];
// OPTIONAL. URL string of a web page providing information about the client.
// RECOMMENDED.
optional string client_uri = 7 [ (buf.validate.field).string.uri = true ];
// OPTIONAL. Map for localized client URIs.
map<string, string> client_uri_localized = 8 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL. URL string that references a logo for the client.
optional string logo_uri = 9 [ (buf.validate.field).string.uri = true ];
// OPTIONAL. Map for localized logo URIs.
map<string, string> logo_uri_localized = 10 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL. String containing a space-separated list of scope values.
optional string scope = 11 [
(buf.validate.field).string = {pattern : "^\\S+( \\S+)*$", min_len : 1}
];
// OPTIONAL. Array of strings representing ways to contact people responsible
// for this client.
repeated string contacts = 12
[ (buf.validate.field).repeated .items.string.email = true ];
// OPTIONAL. URL string pointing to terms of service.
optional string tos_uri = 13 [ (buf.validate.field).string.uri = true ];
// OPTIONAL. Map for localized terms of service URIs.
map<string, string> tos_uri_localized = 14 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL. URL string pointing to privacy policy.
optional string policy_uri = 15 [ (buf.validate.field).string.uri = true ];
// OPTIONAL. Map for localized policy URIs.
map<string, string> policy_uri_localized = 16 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL. URL string referencing the client's JWK Set document. Mutually
// exclusive with `jwks`.
optional string jwks_uri = 17 [ (buf.validate.field).string.uri = true ];
// OPTIONAL. Client's JWK Set document value. Mutually exclusive with
// `jwks_uri`.
optional JsonWebKeySet jwks = 18;
// OPTIONAL. Unique identifier string assigned by the client
// developer/publisher.
optional string software_id = 19
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL. Version identifier string for the client software.
optional string software_version = 20
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// Message level validation to ensure mutual exclusion of jwks and jwks_uri.
option (buf.validate.message).cel = {
id : "client_metadata.jwks_mutual_exclusion",
expression : "!has(this.jwks_uri) || !has(this.jwks)",
message : "jwks_uri and jwks are mutually exclusive"
};
// Validation rule to ensure redirect_uris is present if needed (e.g., for
// specific grant types). This might be better handled by application logic or
// more complex CEL based on grant_types/response_types. Example (adjust logic
// based on actual requirements): option (buf.validate.message).cel = {
// id: "client_metadata.redirect_uris_required",
// expression: "!(('authorization_code' in this.grant_types) || ('implicit'
// in this.grant_types)) || size(this.redirect_uris) > 0", message:
// "redirect_uris are required for authorization_code or implicit grant
// types"
// };
}
// Represents the request sent to the Client Registration Endpoint (RFC 7591
// Section 3.1).
message ClientRegistrationRequest {
// Fields correspond to ClientMetadata, indicating requested values.
// REQUIRED for redirect flows.
repeated string redirect_uris = 1 [ (buf.validate.field).repeated = {
min_items : 1,
items : {string : {uri : true, min_len : 1}}
} ];
// OPTIONAL. Default is "client_secret_basic".
optional string token_endpoint_auth_method = 2
[ (buf.validate.field).string = {
in : [ "none", "client_secret_post", "client_secret_basic" ]
} ];
// OPTIONAL. Default is ["authorization_code"].
repeated string grant_types = 3
[ (buf.validate.field).repeated .items.string = {
in : [
"authorization_code",
"implicit",
"password",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:jwt-bearer",
"urn:ietf:params:oauth:grant-type:saml2-bearer"
]
} ];
// OPTIONAL. Default is ["code"].
repeated string response_types = 4 [
(buf.validate.field).repeated .items.string = {in : [ "code", "token" ]}
];
// OPTIONAL. RECOMMENDED.
optional string client_name = 5
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL.
map<string, string> client_name_localized = 6 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {min_len : 1, max_len : 255}}
} ];
// OPTIONAL. RECOMMENDED.
optional string client_uri = 7 [ (buf.validate.field).string.uri = true ];
// OPTIONAL.
map<string, string> client_uri_localized = 8 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL.
optional string logo_uri = 9 [ (buf.validate.field).string.uri = true ];
// OPTIONAL.
map<string, string> logo_uri_localized = 10 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL.
optional string scope = 11 [
(buf.validate.field).string = {pattern : "^\\S+( \\S+)*$", min_len : 1}
];
// OPTIONAL.
repeated string contacts = 12
[ (buf.validate.field).repeated .items.string.email = true ];
// OPTIONAL.
optional string tos_uri = 13 [ (buf.validate.field).string.uri = true ];
// OPTIONAL.
map<string, string> tos_uri_localized = 14 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL.
optional string policy_uri = 15 [ (buf.validate.field).string.uri = true ];
// OPTIONAL.
map<string, string> policy_uri_localized = 16 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL. Mutually exclusive with `jwks`.
optional string jwks_uri = 17 [ (buf.validate.field).string.uri = true ];
// OPTIONAL. Mutually exclusive with `jwks_uri`.
optional JsonWebKeySet jwks = 18;
// OPTIONAL.
optional string software_id = 19
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL.
optional string software_version = 20
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL. A software statement containing client metadata values about the
// client software as claims.
optional string software_statement = 21 [ (buf.validate.field).string = {
min_len : 1,
pattern : "^[a-zA-Z0-9\\-_]+\\.[a-zA-Z0-9\\-_]+\\.[a-zA-Z0-9\\-_]*$"
} ];
// Message level validation to ensure mutual exclusion of jwks and jwks_uri.
option (buf.validate.message).cel = {
id : "client_registration_request.jwks_mutual_exclusion",
expression : "!has(this.jwks_uri) || !has(this.jwks)",
message : "jwks_uri and jwks are mutually exclusive"
};
}
// Represents the successful response from the Client Registration Endpoint (RFC
// 7591 Section 3.2.1).
message ClientInformationResponse {
// REQUIRED. OAuth 2.0 client identifier string issued by the authorization
// server.
string client_id = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1
];
// OPTIONAL. OAuth 2.0 client secret string. Only issued for confidential
// clients.
optional string client_secret = 2 [ (buf.validate.field).string.min_len = 1 ];
// OPTIONAL. Time at which the client identifier was issued (Unix timestamp,
// seconds since epoch).
optional int64 client_id_issued_at = 3 [ (buf.validate.field).int64.gt = 0 ];
// REQUIRED if "client_secret" is issued, OPTIONAL otherwise. Time at which
// the client secret will expire (Unix timestamp, seconds since epoch), or 0
// if it will not expire.
optional int64 client_secret_expires_at = 4
[ (buf.validate.field).int64.gte = 0 ];
// Contains all registered metadata about this client, reflecting server
// state. REQUIRED if applicable to the client registration.
repeated string redirect_uris = 5 [ (buf.validate.field).repeated = {
min_items : 1,
items : {string : {uri : true, min_len : 1}}
} ];
// OPTIONAL (reflects registered value, may have default).
optional string token_endpoint_auth_method = 6
[ (buf.validate.field).string = {
in : [ "none", "client_secret_post", "client_secret_basic" ]
} ];
// OPTIONAL (reflects registered value, may have default).
repeated string grant_types = 7
[ (buf.validate.field).repeated .items.string = {
in : [
"authorization_code",
"implicit",
"password",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:jwt-bearer",
"urn:ietf:params:oauth:grant-type:saml2-bearer"
]
} ];
// OPTIONAL (reflects registered value, may have default).
repeated string response_types = 8 [
(buf.validate.field).repeated .items.string = {in : [ "code", "token" ]}
];
// OPTIONAL (reflects registered value).
optional string client_name = 9
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL (reflects registered value).
map<string, string> client_name_localized = 10 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {min_len : 1, max_len : 255}}
} ];
// OPTIONAL (reflects registered value).
optional string client_uri = 11 [ (buf.validate.field).string.uri = true ];
// OPTIONAL (reflects registered value).
map<string, string> client_uri_localized = 12 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL (reflects registered value).
optional string logo_uri = 13 [ (buf.validate.field).string.uri = true ];
// OPTIONAL (reflects registered value).
map<string, string> logo_uri_localized = 14 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL (reflects registered value).
optional string scope = 15 [
(buf.validate.field).string = {pattern : "^\\S+( \\S+)*$", min_len : 1}
];
// OPTIONAL (reflects registered value).
repeated string contacts = 16
[ (buf.validate.field).repeated .items.string.email = true ];
// OPTIONAL (reflects registered value).
optional string tos_uri = 17 [ (buf.validate.field).string.uri = true ];
// OPTIONAL (reflects registered value).
map<string, string> tos_uri_localized = 18 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL (reflects registered value).
optional string policy_uri = 19 [ (buf.validate.field).string.uri = true ];
// OPTIONAL (reflects registered value).
map<string, string> policy_uri_localized = 20 [ (buf.validate.field).map = {
keys : {string : {pattern : "^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$"}},
values : {string : {uri : true}}
} ];
// OPTIONAL (reflects registered value). Mutually exclusive with `jwks`.
optional string jwks_uri = 21 [ (buf.validate.field).string.uri = true ];
// OPTIONAL (reflects registered value). Mutually exclusive with `jwks_uri`.
optional JsonWebKeySet jwks = 22;
// OPTIONAL (reflects registered value).
optional string software_id = 23
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL (reflects registered value).
optional string software_version = 24
[ (buf.validate.field).string = {min_len : 1, max_len : 255} ];
// OPTIONAL. If a software statement was used in the request, it MUST be
// returned unmodified.
optional string software_statement = 25 [ (buf.validate.field).string = {
min_len : 1,
pattern : "^[a-zA-Z0-9\\-_]+\\.[a-zA-Z0-9\\-_]+\\.[a-zA-Z0-9\\-_]*$"
} ];
// Message level validation
option (buf.validate.message).cel = {
id : "client_info_response.secret_expiry",
// client_secret_expires_at MUST be present if client_secret is present and
// non-empty
expression : "(!has(this.client_secret) || this.client_secret == '') || "
"has(this.client_secret_expires_at)",
message : "client_secret_expires_at is required when client_secret is "
"issued"
};
option (buf.validate.message).cel = {
id : "client_info_response.jwks_mutual_exclusion",
expression : "!has(this.jwks_uri) || !has(this.jwks)",
message : "jwks_uri and jwks fields are mutually exclusive in the response"
};
}
// Standard error codes for client registration errors (RFC 7591 Section 3.2.2).
enum ErrorCode {
ERROR_CODE_UNSPECIFIED = 0;
// The value of one or more redirection URIs is invalid.
ERROR_CODE_INVALID_REDIRECT_URI = 1;
// The value of one of the client metadata fields is invalid.
ERROR_CODE_INVALID_CLIENT_METADATA = 2;
// The software statement presented is invalid.
ERROR_CODE_INVALID_SOFTWARE_STATEMENT = 3;
// The software statement presented is not approved for use by this server.
ERROR_CODE_UNAPPROVED_SOFTWARE_STATEMENT = 4;
}
// Represents the error response from the Client Registration Endpoint (RFC 7591
// Section 3.2.2).
message ClientRegistrationErrorResponse {
// REQUIRED. Single ASCII error code string from the ErrorCode enum.
ErrorCode error = 1 [
(buf.validate.field).required = true,
(buf.validate.field).enum = {
defined_only : true,
not_in : [ 0 ]
}
];
// OPTIONAL. Human-readable ASCII text description of the error.
optional string error_description = 2
[ (buf.validate.field).string = {min_len : 1, max_len : 1024} ];
}

View file

@ -0,0 +1,49 @@
package rfc7591v1_test
import (
"testing"
"github.com/bufbuild/protovalidate-go"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
rfc7591 "github.com/pomerium/pomerium/internal/rfc7591"
)
func TestValidation(t *testing.T) {
v := &rfc7591.JsonWebKey{Kty: "Invalid"}
require.ErrorContains(t, protovalidate.Validate(v), `kty: value must be in list ["RSA", "EC", "oct", "OKP"] [string.in]`)
}
func TestJSONMarshal(t *testing.T) {
data := `
{
"redirect_uris": [
"http://localhost:8002/oauth/callback"
],
"token_endpoint_auth_method": "none",
"grant_types": [
"authorization_code",
"refresh_token"
],
"response_types": [
"code"
],
"client_name": "MCP Inspector",
"client_uri": "https://github.com/modelcontextprotocol/inspector"
}`
v := &rfc7591.ClientMetadata{}
require.NoError(t, protojson.Unmarshal([]byte(data), v))
diff := cmp.Diff(&rfc7591.ClientMetadata{
RedirectUris: []string{"http://localhost:8002/oauth/callback"},
TokenEndpointAuthMethod: proto.String("none"),
GrantTypes: []string{"authorization_code", "refresh_token"},
ResponseTypes: []string{"code"},
ClientName: proto.String("MCP Inspector"),
ClientUri: proto.String("https://github.com/modelcontextprotocol/inspector"),
}, v, protocmp.Transform())
require.Empty(t, diff)
}