Module: MCP::Client::OAuth::JWTClientAssertion

Defined in:
lib/mcp/client/oauth/jwt_client_assertion.rb

Overview

Builds RFC 7523 Section 2.2 JWT client assertions for the private_key_jwt client authentication method used by the client_credentials grant (MCP extension io.modelcontextprotocol/oauth-client-credentials, SEP-1046).

The JWS is assembled with openssl so the SDK stays free of a JWT gem dependency (the TypeScript and Python SDKs use jose and PyJWT for the same assertion). Claims follow SEP-1046 and RFC 7523: iss and sub carry the client_id, aud carries the authorization server's issuer identifier, plus exp, iat, and a unique jti.

Defined Under Namespace

Classes: InvalidKeyError, UnsupportedAlgorithmError

Constant Summary collapse

ASSERTION_TYPE =

RFC 7523 Section 2.2 client_assertion_type value.

"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
DEFAULT_LIFETIME =

Assertion lifetime in seconds; matches the TypeScript SDK's jwtLifetimeSeconds and the Python SDK's lifetime_seconds default.

300
SUPPORTED_ALGORITHMS =
["ES256", "RS256"].freeze

Class Method Summary collapse

Class Method Details

.generate(client_id:, audience:, private_key:, signing_algorithm:, lifetime: DEFAULT_LIFETIME) ⇒ Object

Returns a signed compact-serialization JWT (header.payload.signature).

Parameters:

  • client_id (String)

    The pre-registered OAuth client identifier.

  • audience (String)

    The authorization server's issuer identifier.

  • private_key (String, OpenSSL::PKey::PKey)

    PEM string (PKCS#8 or traditional encoding) or an already-parsed key.

  • signing_algorithm (String)

    "ES256" (prime256v1 EC key) or "RS256" (RSA key).

  • lifetime (Integer) (defaults to: DEFAULT_LIFETIME)

    Seconds until the exp claim expires.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/mcp/client/oauth/jwt_client_assertion.rb', line 54

def generate(client_id:, audience:, private_key:, signing_algorithm:, lifetime: DEFAULT_LIFETIME)
  unless SUPPORTED_ALGORITHMS.include?(signing_algorithm)
    raise UnsupportedAlgorithmError,
      "signing_algorithm must be one of #{SUPPORTED_ALGORITHMS.inspect} (got #{signing_algorithm.inspect})."
  end

  key = parse_key(private_key)
  validate_key!(key, signing_algorithm)

  now = Time.now.to_i
  header = { alg: signing_algorithm, typ: "JWT" }
  claims = {
    iss: client_id,
    sub: client_id,
    aud: audience,
    exp: now + lifetime,
    iat: now,
    jti: SecureRandom.uuid,
  }

  signing_input = "#{base64url(JSON.generate(header))}.#{base64url(JSON.generate(claims))}"
  "#{signing_input}.#{base64url(sign(key, signing_algorithm, signing_input))}"
end