Fix MCP Auth issues in API to MCP usecase#13574
Fix MCP Auth issues in API to MCP usecase#13574AnuGayan wants to merge 7 commits intowso2:masterfrom
Conversation
- Updated handleRequest to handle MCP no-auth requests. - Implemented getDcrEndpoint helper to retrieve the DCR endpoint from the associated Key Manager. - Updated handleAuthFailure to include the dcr parameter in the WWW-Authenticate header for MCP APIs. - Added test cases to verify the new logic. Co-authored-by: AnuGayan <19290404+AnuGayan@users.noreply.github.com>
…845' into master-mcp # Conflicts: # components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/handlers/security/APIAuthenticationHandler.java # components/apimgt/org.wso2.carbon.apimgt.gateway/src/test/java/org/wso2/carbon/apimgt/gateway/handlers/security/APIAuthenticationHandlerTestCase.java
📝 WalkthroughWalkthroughCentralizes MCP_HTTP_METHOD constant and updates MCP handling across gateway: request initialization, auth flow (including DCR endpoint lookup and WWW-Authenticate header construction), validation relaxation, scope aggregation, and added/updated tests for MCP no-auth and DCR scenarios. Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Auth as APIAuthenticationHandler
participant KMHolder as KeyManagerHolder
participant KMDto as KeyManagerDto
participant Ext as ExtensionListenerUtil
Client->>Auth: Send MCP request
Auth->>Auth: Detect MCP path and MCP_HTTP_METHOD
alt No-Auth or auth failure
Auth->>Auth: handleNoAuthentication(messageContext)
Auth->>Auth: setAPIParametersToMessageContext()
Auth->>KMHolder: lookup KeyManager for apiUUID
KMHolder-->>KMDto: return KeyManagerDto (may include DCR endpoint)
Auth->>Auth: build WWW-Authenticate header (resource_metadata + optional dcr)
Auth->>Ext: postProcessRequest(messageContext, type)
Ext-->>Auth: post-processing complete
Auth-->>Client: return response (with WWW-Authenticate header if applicable)
else Authenticated
Auth-->>Client: continue normal auth flow
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| @@ -159,6 +161,7 @@ private String buildMCPRequest(MessageContext messageContext) throws McpExceptio | |||
|
|
|||
| method = request.getMethod(); | |||
There was a problem hiding this comment.
Log Improvement Suggestion No: 1
| method = request.getMethod(); | |
| method = request.getMethod(); | |
| log.info("MCP request received with method: " + method); |
| @@ -183,10 +186,14 @@ private String buildMCPRequest(MessageContext messageContext) throws McpExceptio | |||
| } | |||
| } | |||
| if (backendOperation != null) { | |||
There was a problem hiding this comment.
Log Improvement Suggestion No: 2
| if (backendOperation != null) { | |
| if (backendOperation != null) { | |
| log.debug("Backend operation found for tool: " + toolName + ", HTTP method: " + backendOperation.getVerb() + ", target: " + backendOperation.getTarget()); |
| handleNoAuthentication(messageContext); | ||
| setAPIParametersToMessageContext(messageContext); | ||
| return ExtensionListenerUtil.postProcessRequest(messageContext, type); |
There was a problem hiding this comment.
Log Improvement Suggestion No: 3
| handleNoAuthentication(messageContext); | |
| setAPIParametersToMessageContext(messageContext); | |
| return ExtensionListenerUtil.postProcessRequest(messageContext, type); | |
| handleNoAuthentication(messageContext); | |
| setAPIParametersToMessageContext(messageContext); | |
| log.info("Authentication skipped for MCP no-auth request"); | |
| return ExtensionListenerUtil.postProcessRequest(messageContext, type); |
|
|
||
| if (keyManagerDto != null && keyManagerDto.getKeyManager() != null) { | ||
| try { | ||
| org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration config = | ||
| keyManagerDto.getKeyManager().getKeyManagerConfiguration(); | ||
| return (String) config.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT); | ||
| } catch (APIManagementException e) { |
There was a problem hiding this comment.
Log Improvement Suggestion No: 4
| if (keyManagerDto != null && keyManagerDto.getKeyManager() != null) { | |
| try { | |
| org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration config = | |
| keyManagerDto.getKeyManager().getKeyManagerConfiguration(); | |
| return (String) config.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT); | |
| } catch (APIManagementException e) { | |
| if (keyManagerDto != null && keyManagerDto.getKeyManager() != null) { | |
| try { | |
| if (log.isDebugEnabled()) { | |
| log.debug("Retrieving DCR endpoint from key manager: " + keyManagerDto.getName()); | |
| } | |
| org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration config = | |
| keyManagerDto.getKeyManager().getKeyManagerConfiguration(); | |
| return (String) config.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT); | |
| } catch (APIManagementException e) { | |
| log.error("Error while retrieving key manager configuration for MCP DCR support", e); |
| String apiType = (String) synCtx.getProperty(APIMgtGatewayConstants.API_TYPE); | ||
| if (org.apache.commons.lang3.StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) { | ||
| Object mcpMethodProperty = synCtx.getProperty("MCP_HTTP_METHOD"); | ||
| Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD); |
There was a problem hiding this comment.
Log Improvement Suggestion No: 5
| String apiType = (String) synCtx.getProperty(APIMgtGatewayConstants.API_TYPE); | |
| if (org.apache.commons.lang3.StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) { | |
| Object mcpMethodProperty = synCtx.getProperty("MCP_HTTP_METHOD"); | |
| Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD); | |
| String apiType = (String) synCtx.getProperty(APIMgtGatewayConstants.API_TYPE); | |
| if (org.apache.commons.lang3.StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) { | |
| log.debug("Processing MCP API type request"); |
| Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD); | ||
| if (mcpMethodProperty != null) { | ||
| httpMethod = mcpMethodProperty.toString(); | ||
| } |
There was a problem hiding this comment.
Log Improvement Suggestion No: 6
| Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD); | |
| if (mcpMethodProperty != null) { | |
| httpMethod = mcpMethodProperty.toString(); | |
| } | |
| Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD); | |
| if (mcpMethodProperty != null) { | |
| httpMethod = mcpMethodProperty.toString(); | |
| log.debug("MCP HTTP method set to: " + httpMethod); |
| if (StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) { | ||
| httpMethod = synCtx.getProperty("MCP_HTTP_METHOD").toString(); | ||
| httpMethod = synCtx.getProperty(MCP_HTTP_METHOD).toString(); | ||
| matchingResource = (String) synCtx.getProperty("MCP_API_ELECTED_RESOURCE"); | ||
| } |
There was a problem hiding this comment.
Log Improvement Suggestion No: 7
| if (StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) { | |
| httpMethod = synCtx.getProperty("MCP_HTTP_METHOD").toString(); | |
| httpMethod = synCtx.getProperty(MCP_HTTP_METHOD).toString(); | |
| matchingResource = (String) synCtx.getProperty("MCP_API_ELECTED_RESOURCE"); | |
| } | |
| if (StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) { | |
| httpMethod = synCtx.getProperty(MCP_HTTP_METHOD).toString(); | |
| matchingResource = (String) synCtx.getProperty("MCP_API_ELECTED_RESOURCE"); | |
| log.debug("Processing MCP API request with HTTP method: " + httpMethod + " and resource: " + matchingResource); |
| if (StringUtils.isEmpty(protocolVersion)) { | ||
| throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE, | ||
| APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field"); | ||
| } |
There was a problem hiding this comment.
Log Improvement Suggestion No: 9
| if (StringUtils.isEmpty(protocolVersion)) { | |
| throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE, | |
| APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field"); | |
| } | |
| if (StringUtils.isEmpty(protocolVersion)) { | |
| log.error("Validation failed: Missing protocolVersion field in initialize request"); | |
| throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE, | |
| APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field"); | |
| } |
There was a problem hiding this comment.
AI Agent Log Improvement Checklist
- The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
- Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.
✅ Before merging this pull request:
- Review all AI-generated comments for accuracy and relevance.
- Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/mediators/McpMediator.java (1)
331-343:⚠️ Potential issue | 🟠 Major
"default"is added multiple times — once per scoped URLMapping and once more unconditionally.Line 337 adds
"default"inside the loop for every URLMapping that has scopes, and line 341 adds it again after the loop. For an API with N scoped resources, the returned list will contain"default"N+1 times. Since line 341 already guarantees"default"is always present, the in-loop addition on line 337 is redundant and introduces duplicates into thescopes_supportedmetadata.Additionally, the method never deduplicates scopes in general — if two URLMappings share a scope, it will appear multiple times.
Proposed fix: remove the in-loop add and deduplicate
public static List<String> getAllScopes(API api) { - List<String> allScopes = new ArrayList<>(); + Set<String> allScopes = new LinkedHashSet<>(); if (api != null && api.getResources() != null) { for (URLMapping urlMapping : api.getResources()) { if (urlMapping.getScopes() != null) { allScopes.addAll(urlMapping.getScopes()); - allScopes.add("default"); } } } allScopes.add("default"); - return allScopes; + return new ArrayList<>(allScopes); }This requires adding
import java.util.LinkedHashSet;andimport java.util.Set;.
🧹 Nitpick comments (1)
components/apimgt/org.wso2.carbon.apimgt.gateway/src/test/java/org/wso2/carbon/apimgt/gateway/handlers/security/APIAuthenticationHandlerTestCase.java (1)
276-318: Good end-to-end test for MCP auth failure with DCR endpoint.The test correctly mocks the full chain:
DataHolder→ key manager list →KeyManagerHolder→KeyManagerDto→KeyManagerConfiguration→ DCR endpoint, and validates theWWW-Authenticateheader contains bothdcrandresource_metadataparameters.One thing to watch:
GatewayUtils.getTenantDomain()(called bygetDcrEndpoint) isn't explicitly mocked, so it returnsnullfrom the static mock. Line 293 usesMockito.anyString()to match the tenant domain parameter — this works with Mockito 1.x (which matchesnullforanyString()), but would break if the project migrates to Mockito 2+. Consider explicitly stubbingGatewayUtils.getTenantDomain()for clarity and forward-compatibility.Proposed improvement for forward-compatibility
Mockito.when(dataHolder.getKeyManagersFromUUID(apiUUID)).thenReturn(keyManagers); PowerMockito.mockStatic(KeyManagerHolder.class); + PowerMockito.when(GatewayUtils.getTenantDomain()).thenReturn("carbon.super"); KeyManagerDto keyManagerDto = Mockito.mock(KeyManagerDto.class); - Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.anyString(), Mockito.eq("default"))).thenReturn(keyManagerDto); + Mockito.when(KeyManagerHolder.getKeyManagerByName("carbon.super", "default")).thenReturn(keyManagerDto);
There was a problem hiding this comment.
Pull request overview
This PR improves MCP (Model Context Protocol) request handling in the API Gateway by standardizing MCP method propagation via a shared constant, refining MCP no-auth authentication flow, and enhancing MCP auth failure responses with better WWW-Authenticate metadata (including optional DCR endpoint discovery).
Changes:
- Introduces
APIConstants.MCP_HTTP_METHODand updates MCP/JWT/OAuth flows to use it consistently. - Updates MCP auth/no-auth handling and MCP auth-failure
WWW-Authenticateheader construction (adds optional DCR endpoint). - Adjusts MCP request validation and MCP protected resource metadata scope population; adds/updates related tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java | Adds MCP_HTTP_METHOD constant for consistent MCP “HTTP method” property usage. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/test/java/org/wso2/carbon/apimgt/gateway/handlers/security/APIAuthenticationHandlerTestCase.java | Adds MCP no-auth and MCP DCR-related auth-failure tests; adjusts metric timer assertions. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/utils/MCPUtils.java | Removes allowed-method validation and relaxes initialize protocol version validation. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/mediators/McpMediator.java | Ensures “default” scope is included in scopes_supported generation. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/handlers/security/oauth/OAuthAuthenticator.java | Uses MCP_HTTP_METHOD constant instead of hard-coded string. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/handlers/security/jwt/JWTValidator.java | Uses MCP_HTTP_METHOD constant instead of hard-coded string. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/handlers/security/APIAuthenticationHandler.java | Aligns MCP no-auth handling with REST no-auth flow; improves MCP WWW-Authenticate header and adds DCR endpoint lookup. |
| components/apimgt/org.wso2.carbon.apimgt.gateway/src/main/java/org/wso2/carbon/apimgt/gateway/handlers/mcp/McpInitHandler.java | Sets MCP_HTTP_METHOD consistently and removes MCP session header on initialize. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Map<String, String> transportHeaderMap = (Map<String, String>) axis2MC.getProperty | ||
| (org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); |
There was a problem hiding this comment.
In METHOD_INITIALIZE branch, transportHeaderMap can be null if TRANSPORT_HEADERS property is absent, causing a NullPointerException on remove(...). Please null-check (and initialize a new map + set it back) before removing the session header, or guard the removal when headers are missing.
| Map<String, String> transportHeaderMap = (Map<String, String>) axis2MC.getProperty | |
| (org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); | |
| Map<String, String> transportHeaderMap = (Map<String, String>) axis2MC.getProperty( | |
| org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); | |
| if (transportHeaderMap == null) { | |
| transportHeaderMap = new HashMap<>(); | |
| axis2MC.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, transportHeaderMap); | |
| } |
| if (StringUtils.isEmpty(protocolVersion)) { | ||
| throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE, | ||
| APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field"); | ||
| } |
There was a problem hiding this comment.
validateInitializeRequest no longer validates protocolVersion against APIConstants.MCP.SUPPORTED_PROTOCOL_VERSIONS, so unsupported protocol versions will now be accepted. If protocol negotiation is required, reintroduce the supported-version check (and return the existing protocol-mismatch error payload) rather than only checking for non-empty.
| } | |
| } | |
| if (!APIConstants.MCP.SUPPORTED_PROTOCOL_VERSIONS.contains(protocolVersion)) { | |
| throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE, | |
| APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, | |
| "Unsupported protocolVersion: " + protocolVersion); | |
| } |
| allScopes.addAll(urlMapping.getScopes()); | ||
| allScopes.add("default"); | ||
| } | ||
| } | ||
| } | ||
| allScopes.add("default"); | ||
| return allScopes; |
There was a problem hiding this comment.
getAllScopes now unconditionally adds the literal "default" inside the loop and again after the loop, which can create duplicates (and repeats it once per resource that has scopes). Consider using a Set or only adding "default" once to avoid duplicating values in the published scopes_supported list.
| if (log.isDebugEnabled()) { | ||
| log.debug("Skipping authentication for MCP request" | ||
| + ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD)); | ||
| } | ||
| handleNoAuthentication(messageContext); | ||
| setAPIParametersToMessageContext(messageContext); | ||
| return ExtensionListenerUtil.postProcessRequest(messageContext, type); |
There was a problem hiding this comment.
The MCP no-auth short-circuit returns postProcessRequest(...) without invoking ExtensionListenerUtil.preProcessRequest(...), unlike the normal request path. This changes extension listener behavior for MCP no-auth requests; consider calling preProcessRequest (and honoring its return) before handleNoAuthentication(...) to keep the listener lifecycle consistent.
| if (log.isDebugEnabled()) { | |
| log.debug("Skipping authentication for MCP request" | |
| + ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD)); | |
| } | |
| handleNoAuthentication(messageContext); | |
| setAPIParametersToMessageContext(messageContext); | |
| return ExtensionListenerUtil.postProcessRequest(messageContext, type); | |
| if (ExtensionListenerUtil.preProcessRequest(messageContext, type)) { | |
| if (log.isDebugEnabled()) { | |
| log.debug("Skipping authentication for MCP request" | |
| + ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD)); | |
| } | |
| handleNoAuthentication(messageContext); | |
| setAPIParametersToMessageContext(messageContext); | |
| return ExtensionListenerUtil.postProcessRequest(messageContext, type); | |
| } | |
| return false; |
| String resourceMetadata = APIConstants.HTTPS_PROTOCOL + APIConstants.URL_SCHEME_SEPARATOR + | ||
| hostHeader + contextPath + APIMgtGatewayConstants.MCP_WELL_KNOWN_RESOURCE; | ||
| String dcrEndpoint = getDcrEndpoint(); | ||
| String wwwAuthenticate = "Bearer resource_metadata=\"" + resourceMetadata + "\""; | ||
| if (StringUtils.isNotEmpty(dcrEndpoint)) { |
There was a problem hiding this comment.
resource_metadata URL construction now hardcodes https:// and uses hostHeader directly, which can omit the gateway port when the Host header is missing/invalid and you fall back to APIUtil.getHostAddress(). Previously MCPUtils.getGatewayServerURL(...) used vhost httpsUrl (including port). Consider restoring vhost-based resolution or appending the configured HTTPS port/offset when building the URL.
| import org.wso2.carbon.utils.multitenancy.MultitenantUtils; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; |
There was a problem hiding this comment.
Unused import java.util.Collections will fail compilation (unused imports are errors in Java). Please remove it or use it.
| import java.util.Collections; |
|
|
||
| PowerMockito.mockStatic(KeyManagerHolder.class); | ||
| KeyManagerDto keyManagerDto = Mockito.mock(KeyManagerDto.class); | ||
| Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.anyString(), Mockito.eq("default"))).thenReturn(keyManagerDto); |
There was a problem hiding this comment.
This test stubs KeyManagerHolder.getKeyManagerByName(anyString(), "default"), but GatewayUtils is statically mocked in setup() so GatewayUtils.getTenantDomain() returns null; anyString() won’t match null and the stub won’t apply, causing getDcrEndpoint() to return null and the assertion to fail. Either stub GatewayUtils.getTenantDomain() to a non-null tenant domain, or change the matcher to accept null (e.g., Mockito.any() for the first arg).
| Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.anyString(), Mockito.eq("default"))).thenReturn(keyManagerDto); | |
| Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.any(), Mockito.eq("default"))).thenReturn(keyManagerDto); |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@components/apimgt/org.wso2.carbon.apimgt.gateway/src/test/java/org/wso2/carbon/apimgt/gateway/handlers/security/APIAuthenticationHandlerTestCase.java`:
- Around line 276-318: The test method testHandleAuthFailureForMCPWithDCR sets
up a DCR endpoint in the kmConfig.dcrEndpoint variable but never asserts it
appears in the WWW-Authenticate header; update the test (in
testHandleAuthFailureForMCPWithDCR) to add an assertion that the computed header
(from transportHeaders "WWW-Authenticate") contains the dcrEndpoint string (use
the existing dcrEndpoint local variable) so the DCR-related header construction
is actually validated.
This pull request introduces several improvements and fixes related to MCP (Multi-Channel Protocol) request handling, authentication, and protocol validation in the API Gateway. The changes primarily focus on consistent usage of constants, improving authentication flows for MCP requests, refining error responses, and enhancing protocol version validation. Below are the most important changes grouped by theme:
MCP Request Handling and Protocol Validation:
MCP_HTTP_METHODconstant across the codebase for setting and retrieving the MCP HTTP method, improving maintainability and reducing hard-coded string usage. [1] [2] [3] [4] [5] [6] [7]protocolVersionfield is present and non-empty, and removing redundant protocol version checks.MCPUtils.validateRequest, deferring method validation to other parts of the code.INITIALIZEmethods, ensuring session management is handled correctly.isNoAuthMCPRequestmethod to returnfalsefor unsupported methods instead of throwing an exception, making the flow more robust.Authentication Flow and Error Handling:
handleNoAuthentication, setting API parameters, and calling extension listeners.WWW-Authenticateheader for authentication failures by directly building the resource metadata URL and optionally including a DCR (Dynamic Client Registration) endpoint if available. This provides better guidance to clients on how to register and obtain tokens.Scopes and Resource Management:
Test Improvements:
These changes collectively improve the reliability, maintainability, and interoperability of MCP request handling and authentication in the API Gateway.