This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit d4d528c488ce3a89756cdb3187aaf65fbd22c342 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Aug 13 15:49:09 2026 +0200 CAMEL-24390: Address review feedback - use simple pattern matching without regex fallback Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/docs/main.adoc | 2 +- .../org/apache/camel/catalog/docs/mcp-server.adoc | 17 +++-- .../component/mcp/server/McpServerBridge.java | 2 +- .../mcp/server/McpServerConfiguration.java | 6 +- .../camel-main-configuration-metadata.json | 2 +- core/camel-main/src/main/docs/main.adoc | 2 +- .../main/HttpServerConfigurationProperties.java | 12 ++-- .../org/apache/camel/support/PatternHelper.java | 48 ++++++++++++++ .../apache/camel/support/PatternHelperTest.java | 73 ++++++++++++++++++++++ 9 files changed, 145 insertions(+), 19 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc index 3fe96305eab4..8ed7976b3865 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc @@ -220,7 +220,7 @@ The camel.server supports 28 options, which are listed below. | *camel.server.mcpServerName* | MCP server name advertised to clients. Defaults to the CamelContext name. | | String | *camel.server.mcpSessionIdleTtl* | Idle TTL in milliseconds for MCP sessions on the Vert.x streamable transport. Sessions with no activity for longer than this interval are evicted. 0 disables idle eviction. | 300000 | long | *camel.server.mcpSessionKeepAliveInterval* | Keep-alive ping interval in milliseconds for MCP sessions on the Vert.x streamable transport. Dead sessions are evicted after consecutive ping failures. 0 disables keep-alive pings. | 30000 | long -| *camel.server.mcpTags* | Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. | | String +| *camel.server.mcpTags* | Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching is case-insensitive and supports exact match, wildcard prefix ( {code foo} ), and {code } to match all tags. Only tools registered under a matching tag are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. | | String | *camel.server.mcpToolTimeout* | Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own. | 20000 | long | *camel.server.path* | Context-path to use for embedded HTTP server | / | String | *camel.server.port* | Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number. | 8080 | int diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc index fdc8fdef6e3a..6b761dc26029 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc @@ -120,6 +120,9 @@ camel.server.mcp-tags = crm,notify camel.server.mcp-server-name = my-integration-app ---- +Tag patterns support wildcards: use `*` to expose all tagged tools, or a +prefix pattern like `crm*` to expose all tags starting with `crm`: + On other runtimes, or when wiring programmatically, add the `McpServerBridge` service to the CamelContext instead: @@ -157,10 +160,11 @@ JBang (see the xref:main.adoc[camel-main] options) or on | `camel.server.mcp-enabled` | Whether to expose ai-tool routes as MCP tools over streamable HTTP. | `false` | bridge -| `camel.server.mcp-tags` | Comma-separated list of ai-tool tags to expose as - MCP tools. Only tools registered under one of these tags are published; the - untagged default pool is never exposed. When not set, no tools are - published. | | bridge +| `camel.server.mcp-tags` | Comma-separated list of ai-tool tag patterns to + expose as MCP tools. Patterns support exact match, wildcard prefix + (`foo*`), and `*` to match all tags. Only tools registered under a matching + tag are published; the untagged default pool is never exposed. When not + set, no tools are published. | | bridge | `camel.server.mcp-tool-timeout` | Per-call tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its @@ -215,8 +219,9 @@ External MCP clients are *untrusted senders* under the xref:manual::security-model.adoc[Camel security model]. The module applies the following rules: -* *Explicit opt-in per tool*: only tools whose tags intersect the configured - `tags` are exposed. The untagged default pool is never exposed implicitly. +* *Explicit opt-in per tool*: only tools whose tags match the configured tag + patterns are exposed. The untagged default pool is never exposed, even when + using the `*` wildcard. * *Flat namespace protection*: a tool whose name collides with an already exposed tool is refused with an ERROR log — never silently replaced. * *Error sanitization*: route exceptions are mapped to a generic error diff --git a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java index 91abc3f11f1c..277833c5b3ec 100644 --- a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java +++ b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java @@ -304,7 +304,7 @@ public class McpServerBridge extends ServiceSupport implements CamelContextAware } private boolean matchesTag(String tag) { - return tag != null && PatternHelper.matchPatterns(tag, tagPatterns); + return tag != null && PatternHelper.matchSimplePatterns(tag, tagPatterns); } private final class RegistryListener implements AiToolRegistryListener { diff --git a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java index e2f69c04d099..09fed0658340 100644 --- a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java +++ b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java @@ -35,9 +35,9 @@ public class McpServerConfiguration { private long sessionIdleTtl = McpServerConstants.DEFAULT_SESSION_IDLE_TTL; /** - * Comma-separated list of ai-tool tag patterns to expose as MCP tools. Patterns support exact match, wildcard - * prefix ({@code foo*}), and {@code *} to match all tags. Only tools registered under a matching tag are published; - * the untagged default pool is never exposed. When not set, no tools are published. + * Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching is case-insensitive and supports + * exact match, wildcard prefix ({@code foo*}), and {@code *} to match all tags. Only tools registered under a + * matching tag are published; the untagged default pool is never exposed. When not set, no tools are published. */ public String getTags() { return tags; diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index 1146c05cd1cc..6287ad28e0ae 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -445,7 +445,7 @@ { "name": "camel.server.mcpServerName", "required": false, "description": "MCP server name advertised to clients. Defaults to the CamelContext name.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.mcpSessionIdleTtl", "required": false, "description": "Idle TTL in milliseconds for MCP sessions on the Vert.x streamable transport. Sessions with no activity for longer than this interval are evicted. 0 disables idle eviction.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "long", "defaultValue": 300000, "secret": false }, { "name": "camel.server.mcpSessionKeepAliveInterval", "required": false, "description": "Keep-alive ping interval in milliseconds for MCP sessions on the Vert.x streamable transport. Dead sessions are evicted after consecutive ping failures. 0 disables keep-alive pings.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "long", "defaultValue": 30000, "secret": false }, - { "name": "camel.server.mcpTags", "required": false, "description": "Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.server.mcpTags", "required": false, "description": "Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching is case-insensitive and supports exact match, wildcard prefix ( {code foo} ), and {code } to match all tags. Only tools registered under a matching tag are exposed; the untagged default pool is never exposed. When not set, no tools are exposed.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "ja [...] { "name": "camel.server.mcpToolTimeout", "required": false, "description": "Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "long", "defaultValue": 20000, "secret": false }, { "name": "camel.server.path", "required": false, "description": "Context-path to use for embedded HTTP server", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/", "secret": false }, { "name": "camel.server.port", "required": false, "description": "Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 8080, "secret": false }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 3fe96305eab4..8ed7976b3865 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -220,7 +220,7 @@ The camel.server supports 28 options, which are listed below. | *camel.server.mcpServerName* | MCP server name advertised to clients. Defaults to the CamelContext name. | | String | *camel.server.mcpSessionIdleTtl* | Idle TTL in milliseconds for MCP sessions on the Vert.x streamable transport. Sessions with no activity for longer than this interval are evicted. 0 disables idle eviction. | 300000 | long | *camel.server.mcpSessionKeepAliveInterval* | Keep-alive ping interval in milliseconds for MCP sessions on the Vert.x streamable transport. Dead sessions are evicted after consecutive ping failures. 0 disables keep-alive pings. | 30000 | long -| *camel.server.mcpTags* | Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. | | String +| *camel.server.mcpTags* | Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching is case-insensitive and supports exact match, wildcard prefix ( {code foo} ), and {code } to match all tags. Only tools registered under a matching tag are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. | | String | *camel.server.mcpToolTimeout* | Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own. | 20000 | long | *camel.server.path* | Context-path to use for embedded HTTP server | / | String | *camel.server.port* | Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number. | 8080 | int diff --git a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java index b319500f6e36..1bf2b23fe5b5 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java @@ -358,9 +358,9 @@ public class HttpServerConfigurationProperties implements BootstrapCloseable { } /** - * Comma-separated list of ai-tool tag patterns to expose as MCP tools. Patterns support exact match, wildcard prefix - * ({@code foo*}), and {@code *} to match all tags. Only tools registered under a matching tag are exposed; the - * untagged default pool is never exposed. When not set, no tools are exposed. + * Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching is case-insensitive and supports + * exact match, wildcard prefix ({@code foo*}), and {@code *} to match all tags. Only tools registered under a + * matching tag are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. */ public void setMcpTags(String mcpTags) { this.mcpTags = mcpTags; @@ -614,9 +614,9 @@ public class HttpServerConfigurationProperties implements BootstrapCloseable { } /** - * Comma-separated list of ai-tool tag patterns to expose as MCP tools. Patterns support exact match, wildcard prefix - * ({@code foo*}), and {@code *} to match all tags. Only tools registered under a matching tag are exposed; the - * untagged default pool is never exposed. When not set, no tools are exposed. + * Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching is case-insensitive and supports + * exact match, wildcard prefix ({@code foo*}), and {@code *} to match all tags. Only tools registered under a + * matching tag are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. */ public HttpServerConfigurationProperties withMcpTags(String mcpTags) { this.mcpTags = mcpTags; diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PatternHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/PatternHelper.java index dd784ae5ae47..04c1e0feb279 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/PatternHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/PatternHelper.java @@ -92,6 +92,54 @@ public final class PatternHelper { return false; } + /** + * Matches the name with the given pattern (case insensitive) using simple matching only (no regular expressions). + * <p/> + * The match rules are applied in this order: + * <ul> + * <li>exact match (case insensitive), returns true</li> + * <li>wildcard match (pattern ends with a * and the name starts with the pattern), returns true</li> + * <li>otherwise returns false</li> + * </ul> + * + * @param name the name + * @param pattern a pattern to match + * @return <tt>true</tt> if match, <tt>false</tt> otherwise. + * @see #matchSimplePatterns(String, String[]) + */ + public static boolean matchSimplePattern(String name, String pattern) { + if (name == null || pattern == null) { + return false; + } + + if (name.equalsIgnoreCase(pattern)) { + return true; + } + + if ("*".equals(pattern)) { + return true; + } + + return matchWildcard(name, pattern); + } + + /** + * Matches the name with the given patterns (case insensitive) using simple matching only (no regular expressions). + * + * @param name the name + * @param patterns pattern(s) to match + * @return <tt>true</tt> if match, <tt>false</tt> otherwise. + * @see #matchSimplePattern(String, String) + */ + public static boolean matchSimplePatterns(String name, String[] patterns) { + for (String pattern : patterns) { + if (PatternHelper.matchSimplePattern(name, pattern)) { + return true; + } + } + return false; + } + /** * Matches the name with the given pattern (case insensitive). * <p/> diff --git a/core/camel-support/src/test/java/org/apache/camel/support/PatternHelperTest.java b/core/camel-support/src/test/java/org/apache/camel/support/PatternHelperTest.java new file mode 100644 index 000000000000..2fceae7208b5 --- /dev/null +++ b/core/camel-support/src/test/java/org/apache/camel/support/PatternHelperTest.java @@ -0,0 +1,73 @@ +/* + * 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.support; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PatternHelperTest { + + @Test + void testSimplePatternExactMatch() { + assertTrue(PatternHelper.matchSimplePattern("crm", "crm")); + assertTrue(PatternHelper.matchSimplePattern("crm", "CRM")); + assertTrue(PatternHelper.matchSimplePattern("CRM", "crm")); + } + + @Test + void testSimplePatternNoMatch() { + assertFalse(PatternHelper.matchSimplePattern("crm", "erp")); + } + + @Test + void testSimplePatternWildcardAll() { + assertTrue(PatternHelper.matchSimplePattern("crm", "*")); + assertTrue(PatternHelper.matchSimplePattern("anything", "*")); + } + + @Test + void testSimplePatternWildcardPrefix() { + assertTrue(PatternHelper.matchSimplePattern("crm-tools", "crm*")); + assertTrue(PatternHelper.matchSimplePattern("CRM-Tools", "crm*")); + assertFalse(PatternHelper.matchSimplePattern("erp-tools", "crm*")); + } + + @Test + void testSimplePatternNoRegexFallback() { + // "a.b" as regex would match "axb" — simple pattern must NOT do this + assertFalse(PatternHelper.matchSimplePattern("axb", "a.b")); + // contrast with the full matchPattern which does fall back to regex + assertTrue(PatternHelper.matchPattern("axb", "a.b")); + } + + @Test + void testSimplePatternNullSafety() { + assertFalse(PatternHelper.matchSimplePattern(null, "crm")); + assertFalse(PatternHelper.matchSimplePattern("crm", null)); + assertFalse(PatternHelper.matchSimplePattern(null, null)); + } + + @Test + void testSimplePatternsMultiple() { + String[] patterns = { "crm", "erp*" }; + assertTrue(PatternHelper.matchSimplePatterns("crm", patterns)); + assertTrue(PatternHelper.matchSimplePatterns("erp-tools", patterns)); + assertFalse(PatternHelper.matchSimplePatterns("billing", patterns)); + } +}
