Skip to content

Commit 170df60

Browse files
committed
refactor(auth): address code review feedback for enterprise managed auth
Addresses code review comments for SEP-990 enterprise auth implementation: 1. Removed non-existing scenarios from client.py. 2. Removed run-enterprise-auth-conformance.sh script. 3. Remove redundant CI job, use conformance v0.1.14 with --suite all 4. Document specific use cases for manual token exchange flow 5. Added client_credentials support to the __init__.py 6. Rename TokenExchangeResponse to IDJAGTokenExchangeResponse for clarity 7. Fix IdP credential usage - separate idp_client_id/secret from MCP credentials 8. Add parameter discovery example demonstrating OAuth metadata discovery
1 parent f01bf16 commit 170df60

File tree

8 files changed

+203
-344
lines changed

8 files changed

+203
-344
lines changed

.github/actions/conformance/client.py

Lines changed: 0 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
auth/client-credentials-basic - Client credentials with client_secret_basic
1919
auth/cross-app-access-complete-flow - Enterprise managed OAuth (SEP-990) - v0.1.14+
2020
auth/enterprise-token-exchange - Enterprise auth with OIDC ID token (legacy name)
21-
auth/enterprise-saml-exchange - Enterprise auth with SAML assertion (legacy name)
22-
auth/enterprise-id-jag-validation - Validate ID-JAG token structure (legacy name)
2321
auth/* - Authorization code flow (default for auth scenarios)
2422
2523
Enterprise Auth (SEP-990):
@@ -415,177 +413,6 @@ async def run_cross_app_access_complete_flow(server_url: str) -> None:
415413
await _run_auth_session(server_url, enterprise_auth)
416414

417415

418-
@register("auth/enterprise-token-exchange")
419-
async def run_enterprise_token_exchange(server_url: str) -> None:
420-
"""Enterprise managed auth: Token exchange flow (RFC 8693) with OIDC ID token."""
421-
from mcp.client.auth.extensions.enterprise_managed_auth import (
422-
EnterpriseAuthOAuthClientProvider,
423-
TokenExchangeParameters,
424-
)
425-
426-
context = get_conformance_context()
427-
id_token = context.get("id_token")
428-
idp_token_endpoint = context.get("idp_token_endpoint")
429-
mcp_server_auth_issuer = context.get("mcp_server_auth_issuer")
430-
mcp_server_resource_id = context.get("mcp_server_resource_id")
431-
scope = context.get("scope")
432-
433-
if not id_token:
434-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'id_token'")
435-
if not idp_token_endpoint:
436-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'idp_token_endpoint'")
437-
if not mcp_server_auth_issuer:
438-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'mcp_server_auth_issuer'")
439-
if not mcp_server_resource_id:
440-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'mcp_server_resource_id'")
441-
442-
# Create token exchange parameters
443-
token_exchange_params = TokenExchangeParameters.from_id_token(
444-
id_token=id_token,
445-
mcp_server_auth_issuer=mcp_server_auth_issuer,
446-
mcp_server_resource_id=mcp_server_resource_id,
447-
scope=scope,
448-
)
449-
450-
# Create enterprise auth provider
451-
enterprise_auth = EnterpriseAuthOAuthClientProvider(
452-
server_url=server_url,
453-
client_metadata=OAuthClientMetadata(
454-
client_name="conformance-enterprise-client",
455-
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
456-
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
457-
response_types=["token"],
458-
),
459-
storage=InMemoryTokenStorage(),
460-
idp_token_endpoint=idp_token_endpoint,
461-
token_exchange_params=token_exchange_params,
462-
)
463-
464-
await _run_auth_session(server_url, enterprise_auth)
465-
466-
467-
@register("auth/enterprise-saml-exchange")
468-
async def run_enterprise_saml_exchange(server_url: str) -> None:
469-
"""Enterprise managed auth: SAML assertion exchange flow (RFC 8693)."""
470-
from mcp.client.auth.extensions.enterprise_managed_auth import (
471-
EnterpriseAuthOAuthClientProvider,
472-
TokenExchangeParameters,
473-
)
474-
475-
context = get_conformance_context()
476-
saml_assertion = context.get("saml_assertion")
477-
idp_token_endpoint = context.get("idp_token_endpoint")
478-
mcp_server_auth_issuer = context.get("mcp_server_auth_issuer")
479-
mcp_server_resource_id = context.get("mcp_server_resource_id")
480-
scope = context.get("scope")
481-
482-
if not saml_assertion:
483-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'saml_assertion'")
484-
if not idp_token_endpoint:
485-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'idp_token_endpoint'")
486-
if not mcp_server_auth_issuer:
487-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'mcp_server_auth_issuer'")
488-
if not mcp_server_resource_id:
489-
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'mcp_server_resource_id'")
490-
491-
# Create token exchange parameters for SAML
492-
token_exchange_params = TokenExchangeParameters.from_saml_assertion(
493-
saml_assertion=saml_assertion,
494-
mcp_server_auth_issuer=mcp_server_auth_issuer,
495-
mcp_server_resource_id=mcp_server_resource_id,
496-
scope=scope,
497-
)
498-
499-
# Create enterprise auth provider
500-
enterprise_auth = EnterpriseAuthOAuthClientProvider(
501-
server_url=server_url,
502-
client_metadata=OAuthClientMetadata(
503-
client_name="conformance-enterprise-saml-client",
504-
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
505-
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
506-
response_types=["token"],
507-
),
508-
storage=InMemoryTokenStorage(),
509-
idp_token_endpoint=idp_token_endpoint,
510-
token_exchange_params=token_exchange_params,
511-
)
512-
513-
await _run_auth_session(server_url, enterprise_auth)
514-
515-
516-
@register("auth/enterprise-id-jag-validation")
517-
async def run_id_jag_validation(server_url: str) -> None:
518-
"""Validate ID-JAG token structure and claims (SEP-990)."""
519-
from mcp.client.auth.extensions.enterprise_managed_auth import (
520-
EnterpriseAuthOAuthClientProvider,
521-
TokenExchangeParameters,
522-
decode_id_jag,
523-
validate_token_exchange_params,
524-
)
525-
526-
context = get_conformance_context()
527-
id_token = context.get("id_token")
528-
idp_token_endpoint = context.get("idp_token_endpoint")
529-
mcp_server_auth_issuer = context.get("mcp_server_auth_issuer")
530-
mcp_server_resource_id = context.get("mcp_server_resource_id")
531-
532-
if not all([id_token, idp_token_endpoint, mcp_server_auth_issuer, mcp_server_resource_id]):
533-
raise RuntimeError("Missing required context parameters for ID-JAG validation")
534-
535-
# Create and validate token exchange parameters
536-
token_exchange_params = TokenExchangeParameters.from_id_token(
537-
id_token=id_token,
538-
mcp_server_auth_issuer=mcp_server_auth_issuer,
539-
mcp_server_resource_id=mcp_server_resource_id,
540-
)
541-
542-
logger.debug("Validating token exchange parameters")
543-
validate_token_exchange_params(token_exchange_params)
544-
logger.debug("Token exchange parameters validated successfully")
545-
546-
# Create enterprise auth provider
547-
enterprise_auth = EnterpriseAuthOAuthClientProvider(
548-
server_url=server_url,
549-
client_metadata=OAuthClientMetadata(
550-
client_name="conformance-validation-client",
551-
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
552-
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
553-
response_types=["token"],
554-
),
555-
storage=InMemoryTokenStorage(),
556-
idp_token_endpoint=idp_token_endpoint,
557-
token_exchange_params=token_exchange_params,
558-
)
559-
560-
async with httpx.AsyncClient() as client:
561-
# Get ID-JAG
562-
id_jag = await enterprise_auth.exchange_token_for_id_jag(client)
563-
logger.debug(f"Obtained ID-JAG for validation: {id_jag[:50]}...")
564-
565-
# Decode and validate ID-JAG claims
566-
logger.debug("Decoding ID-JAG token")
567-
claims = decode_id_jag(id_jag)
568-
569-
# Validate required claims
570-
assert claims.typ == "oauth-id-jag+jwt", f"Invalid typ: {claims.typ}"
571-
assert claims.jti, "Missing jti claim"
572-
assert claims.iss, "Missing iss claim"
573-
assert claims.sub, "Missing sub claim"
574-
assert claims.aud, "Missing aud claim"
575-
assert claims.resource == mcp_server_resource_id, f"Invalid resource: {claims.resource}"
576-
assert claims.client_id, "Missing client_id claim"
577-
assert claims.exp > claims.iat, "Invalid expiration"
578-
579-
logger.debug("ID-JAG validated successfully:")
580-
logger.debug(f" Subject: {claims.sub}")
581-
logger.debug(f" Issuer: {claims.iss}")
582-
logger.debug(f" Audience: {claims.aud}")
583-
logger.debug(f" Resource: {claims.resource}")
584-
logger.debug(f" Client ID: {claims.client_id}")
585-
586-
logger.debug("ID-JAG validation completed successfully")
587-
588-
589416
async def _run_auth_session(server_url: str, oauth_auth: OAuthClientProvider) -> None:
590417
"""Common session logic for all OAuth flows."""
591418
client = httpx.AsyncClient(auth=oauth_auth, timeout=30.0)

.github/actions/conformance/run-enterprise-auth-conformance.sh

Lines changed: 0 additions & 89 deletions
This file was deleted.

.github/workflows/conformance.yml

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,6 @@ jobs:
3030
- run: ./.github/actions/conformance/run-server.sh
3131

3232
client-conformance:
33-
runs-on: ubuntu-latest
34-
continue-on-error: true
35-
steps:
36-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
37-
- uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7.2.1
38-
with:
39-
enable-cache: true
40-
version: 0.9.5
41-
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
42-
with:
43-
node-version: 24
44-
- run: uv sync --frozen --all-extras --package mcp
45-
- run: npx @modelcontextprotocol/conformance@0.1.13 client --command 'uv run --frozen python .github/actions/conformance/client.py' --suite all
46-
47-
enterprise-auth-conformance:
4833
runs-on: ubuntu-latest
4934
continue-on-error: true
5035
steps:
@@ -57,4 +42,4 @@ jobs:
5742
with:
5843
node-version: 24
5944
- run: uv sync --frozen --all-extras --package mcp
60-
- run: npx @modelcontextprotocol/conformance@0.1.14 client --command 'uv run --frozen python .github/actions/conformance/client.py' --scenario auth/cross-app-access-complete-flow
45+
- run: npx @modelcontextprotocol/conformance@0.1.14 client --command 'uv run --frozen python .github/actions/conformance/client.py' --suite all

0 commit comments

Comments
 (0)