gnodet-bot commented on code in PR #29592:
URL: https://github.com/apache/camel/pull/29592#discussion_r4534192742
##########
core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java:
##########
@@ -872,4 +912,20 @@ public HttpServerConfigurationProperties
withMcpAllowedOrigins(String mcpAllowed
return this;
}
+ /**
+ * Name of the camel-oauth profile used to authenticate OAuth bearer
tokens on the MCP HTTP endpoint.
+ */
+ public HttpServerConfigurationProperties withMcpOauthProfile(String
mcpOauthProfile) {
+ this.mcpOauthProfile = mcpOauthProfile;
+ return this;
+ }
+
+ /**
+ * Comma-separated OAuth scopes required to access the MCP HTTP endpoint.
+ */
+ public HttpServerConfigurationProperties withMcpRequiredScopes(String
mcpRequiredScopes) {
+ this.mcpRequiredScopes = mcpRequiredScopes;
+ return this;
+ }
+
}
Review Comment:
**Still open: missing `withMcpResourceUri()` fluent builder.**
`withMcpOauthProfile()` and `withMcpRequiredScopes()` were added but
`withMcpResourceUri()` was not. Since `mcp-resource-uri` is *required* whenever
an OAuth profile is set (enforced by
`DefaultMcpServerFactory.validateOAuthConfiguration`), users of the fluent API
cannot construct a valid OAuth configuration without falling back to the setter.
```suggestion
}
/**
* Canonical public HTTPS resource identifier for the MCP endpoint, used
by RFC 9728 metadata.
* Required when an OAuth profile is configured. Never inferred from
HTTP request headers.
*/
public HttpServerConfigurationProperties withMcpResourceUri(String
mcpResourceUri) {
this.mcpResourceUri = mcpResourceUri;
return this;
}
}
```
##########
components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/McpOAuthProtectedResourceMetadata.java:
##########
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.mcp.server.vertx;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Package-private immutable RFC 9728 metadata for the Camel Main HTTP MCP
protected resource.
+ * <p>
+ * The public resource identifier is explicit configuration. It is never
derived from request {@code Host} or
+ * {@code X-Forwarded-*} headers, which are supplied by the caller unless a
separately trusted proxy contract exists.
+ */
+final class McpOAuthProtectedResourceMetadata {
+
+ private static final String WELL_KNOWN_PREFIX =
"/.well-known/oauth-protected-resource";
+
+ private final String resource;
+ private final String authorizationServer;
+ private final String metadataPath;
+ private final String metadataUri;
+ private final List<String> scopesSupported;
+
+ private McpOAuthProtectedResourceMetadata(
+ String resource, String
authorizationServer, String metadataPath,
+ String metadataUri,
+ List<String> scopesSupported) {
+ this.resource = resource;
+ this.authorizationServer = authorizationServer;
+ this.metadataPath = metadataPath;
+ this.metadataUri = metadataUri;
+ this.scopesSupported = List.copyOf(scopesSupported);
+ }
+
+ /**
+ * Creates and validates the metadata for a resource. The resource URI
must be an absolute HTTPS URI with no
+ * user-info, query, or fragment, and its path must equal the configured
MCP endpoint path.
+ */
+ static McpOAuthProtectedResourceMetadata create(
+ String resourceUri, String mcpPath, String
authorizationServerIssuer, List<String> scopesSupported) {
+ return create(resourceUri, mcpPath, authorizationServerIssuer,
scopesSupported, false);
+ }
+
+ /**
+ * Creates metadata allowing an HTTP authorization-server issuer only when
the OAuth profile explicitly enables
+ * allow-insecure-http (for local development/test IdPs). The public
resource URI remains HTTPS.
+ */
+ static McpOAuthProtectedResourceMetadata create(
+ String resourceUri, String mcpPath, String
authorizationServerIssuer, List<String> scopesSupported,
+ boolean allowInsecureAuthorizationServer) {
+ URI resource = parseHttpsUri(resourceUri,
"camel.server.mcp-resource-uri");
+ URI issuer = parseHttpsUri(authorizationServerIssuer, "OAuth profile
issuer", allowInsecureAuthorizationServer);
+
+ if (mcpPath == null || mcpPath.isBlank() || !mcpPath.startsWith("/")
+ || mcpPath.indexOf('?') >= 0 || mcpPath.indexOf('#') >= 0 ||
mcpPath.indexOf('\\') >= 0) {
+ throw new IllegalArgumentException("MCP HTTP path must be an
absolute path without query or fragment");
+ }
+
+ String resourcePath = resource.getRawPath();
+ if (resourcePath == null || resourcePath.isEmpty()) {
+ resourcePath = "/";
+ }
+ if (!resourcePath.equals(mcpPath)) {
+ throw new IllegalArgumentException(
+ "camel.server.mcp-resource-uri path must match
camel.server.mcp-path");
+ }
+
+ // RFC 9728 inserts the well-known path between the authority and the
resource path.
+ String metadataPath = WELL_KNOWN_PREFIX + ("/".equals(resourcePath) ?
"" : resourcePath);
+ String metadataUri = resource.getScheme() + "://" +
resource.getRawAuthority() + metadataPath;
+
+ return new McpOAuthProtectedResourceMetadata(
+ resource.toASCIIString(), issuer.toASCIIString(),
metadataPath, metadataUri,
+ scopesSupported != null ? scopesSupported : List.of());
+ }
+
+ private static URI parseHttpsUri(String value, String label) {
+ return parseHttpsUri(value, label, false);
+ }
+
+ private static URI parseHttpsUri(String value, String label, boolean
allowInsecureHttp) {
+ if (value == null || value.isBlank()) {
+ throw new IllegalArgumentException(label + " must be configured as
an absolute HTTPS URI");
+ }
+ try {
+ URI uri = new URI(value.trim());
+ boolean https = "https".equalsIgnoreCase(uri.getScheme());
+ boolean permittedHttp = allowInsecureHttp &&
"http".equalsIgnoreCase(uri.getScheme());
+ if (!(https || permittedHttp) || uri.getHost() == null
+ || uri.getRawAuthority() == null || uri.getRawUserInfo()
!= null
+ || uri.getRawQuery() != null || uri.getRawFragment() !=
null
+ || uri.getPort() > 65535) {
Review Comment:
**Still open: port 0 is not rejected.** `uri.getPort() > 65535` allows port
0 through — Java's `URI` accepts `:0` as syntactically valid, but port 0 is an
ephemeral OS-assigned port with no stable meaning as a public resource URI.
```suggestion
|| uri.getPort() > 65535 || uri.getPort() == 0) {
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]