Conversation
|
Hi @joar - thanks for filing this PR. What issue does it resolve, and can you share how to reproduce please? Please change to "Ready for review" as well when we should review & merge this PR. Thank you! |
|
Thanks for this PR @joar! Here's my understanding of what this fixes: SummaryThis PR addresses two OAuth/DCR compatibility issues when MCP Gateway acts as an OAuth client registering with upstream authorization servers: 1. Trailing Slash Normalization for OAuth DiscoveryProblem: The MCP Python SDK (used by FastMCP) has a bug where Pydantic's Per RFC 8414 §3.3 and RFC 9728 §3, the issuer URL in metadata responses must exactly match the URL used for discovery. This mismatch causes OAuth discovery to fail in clients like Google's ADK and this gateway. Fix: Strip trailing slashes before constructing discovery_base_url = issuer.rstrip("/")
rfc8414_url = f"{discovery_base_url}/.well-known/oauth-authorization-server"This works around the upstream SDK issue: modelcontextprotocol/python-sdk#1919 2. Refresh Token Grant TypeAdds I'll rebase, and address the rest of the findings, leave it with me as this requires alembic migration. |
The original trailing slash fix introduced a bug where the issuer validation would fail when the server returned an issuer without trailing slash but the client passed one (or vice versa). Changes: - Normalize both the input issuer and metadata issuer for comparison - Use normalized issuer as cache key for consistent cache lookup - Add tests for trailing slash normalization scenarios - Update test to expect refresh_token in grant_types Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Address review feedback: 1. Normalize issuer consistently across the entire DCR flow: - Allowlist validation uses normalized comparison - Storage uses normalized issuer - Lookup uses normalized issuer 2. Make refresh_token conditional on AS support: - Check grant_types_supported in AS metadata - Only request refresh_token if AS advertises support 3. Fix grant_types fallback: - Use requested grant_types as fallback when AS response omits them - Previously hardcoded to ["authorization_code"] which dropped refresh_token 4. Add comprehensive tests: - Test refresh_token inclusion when AS supports it - Test grant_types fallback behavior - Test allowlist trailing slash normalization Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
8126f30 to
342586c
Compare
Rebased and ExtendedThanks for the initial fix @joar! I've rebased your branch onto main and extended it to address additional edge cases discovered during review. Commits Added
Changes Summary1. Issuer Normalization (Complete)
2. Refresh Token Handling (Improved)
3. Bug Fixes
4. Database Migration
New Tests Added (7 total)
Test ResultsReady for final review! |
…igration
Address additional review findings:
1. Fix TypeError when grant_types_supported is explicit null:
- Use `metadata.get("grant_types_supported") or []` instead of
`metadata.get("grant_types_supported", [])`
- The latter returns None when key exists with null value
2. Add configurable permissive refresh_token mode:
- New setting: dcr_request_refresh_token_when_unsupported
- Default: False (strict mode - only request if AS advertises support)
- When True: request refresh_token if AS omits grant_types_supported
3. Add Alembic migration to normalize legacy issuer values:
- Strips trailing slashes from registered_oauth_clients.issuer
- Idempotent and works with SQLite and PostgreSQL
- Prevents duplicate registrations from legacy rows
4. Add comprehensive tests:
- Test explicit null grant_types_supported handling
- Test permissive refresh_token mode
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
342586c to
0549e0e
Compare
Update documentation for new DCR refresh token configuration option: - README.md: Add to DCR settings table - charts/mcp-stack/values.yaml: Add with comment - charts/mcp-stack/README.md: Regenerated via helm-docs - docs/docs/manage/dcr.md: Add env var and behavior note - docs/docs/config.schema.json: Add schema definition Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix: FastMCP compatibility
* fix: normalize issuer URL for metadata validation and caching
The original trailing slash fix introduced a bug where the issuer
validation would fail when the server returned an issuer without
trailing slash but the client passed one (or vice versa).
Changes:
- Normalize both the input issuer and metadata issuer for comparison
- Use normalized issuer as cache key for consistent cache lookup
- Add tests for trailing slash normalization scenarios
- Update test to expect refresh_token in grant_types
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix: complete issuer normalization and conditional refresh_token
Address review feedback:
1. Normalize issuer consistently across the entire DCR flow:
- Allowlist validation uses normalized comparison
- Storage uses normalized issuer
- Lookup uses normalized issuer
2. Make refresh_token conditional on AS support:
- Check grant_types_supported in AS metadata
- Only request refresh_token if AS advertises support
3. Fix grant_types fallback:
- Use requested grant_types as fallback when AS response omits them
- Previously hardcoded to ["authorization_code"] which dropped refresh_token
4. Add comprehensive tests:
- Test refresh_token inclusion when AS supports it
- Test grant_types fallback behavior
- Test allowlist trailing slash normalization
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix: handle null grant_types_supported and add issuer normalization migration
Address additional review findings:
1. Fix TypeError when grant_types_supported is explicit null:
- Use `metadata.get("grant_types_supported") or []` instead of
`metadata.get("grant_types_supported", [])`
- The latter returns None when key exists with null value
2. Add configurable permissive refresh_token mode:
- New setting: dcr_request_refresh_token_when_unsupported
- Default: False (strict mode - only request if AS advertises support)
- When True: request refresh_token if AS omits grant_types_supported
3. Add Alembic migration to normalize legacy issuer values:
- Strips trailing slashes from registered_oauth_clients.issuer
- Idempotent and works with SQLite and PostgreSQL
- Prevents duplicate registrations from legacy rows
4. Add comprehensive tests:
- Test explicit null grant_types_supported handling
- Test permissive refresh_token mode
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* docs: add DCR_REQUEST_REFRESH_TOKEN_WHEN_UNSUPPORTED to documentation
Update documentation for new DCR refresh token configuration option:
- README.md: Add to DCR settings table
- charts/mcp-stack/values.yaml: Add with comment
- charts/mcp-stack/README.md: Regenerated via helm-docs
- docs/docs/manage/dcr.md: Add env var and behavior note
- docs/docs/config.schema.json: Add schema definition
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
---------
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
🐛 Bug-fix PR
Before opening this PR please:
make lint- passesruff,mypy,pylintmake test- all unit + integration tests greenmake coverage- ≥ 90 %make docker docker-run-sslormake podman podman-run-ssl📌 Summary
What problem does this PR fix and why?
🔁 Reproduction Steps
Link the issue and minimal steps to reproduce the bug.
🐞 Root Cause
What was wrong and where?
💡 Fix Description
How did you solve it? Key design points.
🧪 Verification
make lintmake testmake coverage📐 MCP Compliance (if relevant)
✅ Checklist
make black isort pre-commit)