|
18 | 18 | auth/client-credentials-basic - Client credentials with client_secret_basic |
19 | 19 | auth/cross-app-access-complete-flow - Enterprise managed OAuth (SEP-990) - v0.1.14+ |
20 | 20 | 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) |
23 | 21 | auth/* - Authorization code flow (default for auth scenarios) |
24 | 22 |
|
25 | 23 | Enterprise Auth (SEP-990): |
@@ -415,177 +413,6 @@ async def run_cross_app_access_complete_flow(server_url: str) -> None: |
415 | 413 | await _run_auth_session(server_url, enterprise_auth) |
416 | 414 |
|
417 | 415 |
|
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 | | - |
589 | 416 | async def _run_auth_session(server_url: str, oauth_auth: OAuthClientProvider) -> None: |
590 | 417 | """Common session logic for all OAuth flows.""" |
591 | 418 | client = httpx.AsyncClient(auth=oauth_auth, timeout=30.0) |
|
0 commit comments