From d19cdae2e92ae67b2e2461f1917be230c2ebf9f1 Mon Sep 17 00:00:00 2001 From: Sheegan Sri G M Date: Sat, 4 Oct 2025 09:42:58 +0530 Subject: [PATCH] fix: improve URI template matching to properly escape special characters (#599) - Enhanced DefaultMcpUriTemplateManager to use Pattern.quote() for escaping special regex characters like '?' - Replaced simple string replacement regex generation with robust pattern building - Added test case to verify URI matching with query parameters (e.g., "file://name/search?={search}") - Fixed typo: renamed DeafaultMcpUriTemplateManagerFactory to DefaultMcpUriTemplateManagerFactory - Updated all references across server classes and tests Signed-off-by: Christian Tzolov --- .../server/McpAsyncServer.java | 4 +-- .../server/McpServer.java | 10 +++---- .../server/McpStatelessAsyncServer.java | 4 +-- .../util/DefaultMcpUriTemplateManager.java | 26 ++++++++++++++++--- ... DefaultMcpUriTemplateManagerFactory.java} | 2 +- .../McpUriTemplateManagerTests.java | 13 ++++++++-- 6 files changed, 43 insertions(+), 16 deletions(-) rename mcp-core/src/main/java/io/modelcontextprotocol/util/{DeafaultMcpUriTemplateManagerFactory.java => DefaultMcpUriTemplateManagerFactory.java} (86%) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java index dcba3af..ac4b369 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java @@ -36,7 +36,7 @@ import io.modelcontextprotocol.spec.McpServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProviderBase; import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider; import io.modelcontextprotocol.util.Assert; -import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory; +import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory; import io.modelcontextprotocol.util.McpUriTemplateManagerFactory; import io.modelcontextprotocol.util.Utils; import org.slf4j.Logger; @@ -120,7 +120,7 @@ public class McpAsyncServer { private List protocolVersions; - private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory(); + private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory(); /** * Create a new McpAsyncServer with the given transport provider and capabilities. diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java index 8e3ebf9..ecfb74b 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java @@ -25,7 +25,7 @@ import io.modelcontextprotocol.spec.McpServerTransportProvider; import io.modelcontextprotocol.spec.McpStatelessServerTransport; import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider; import io.modelcontextprotocol.util.Assert; -import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory; +import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory; import io.modelcontextprotocol.util.McpUriTemplateManagerFactory; import reactor.core.publisher.Mono; @@ -268,7 +268,7 @@ public interface McpServer { */ abstract class AsyncSpecification> { - McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory(); + McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory(); McpJsonMapper jsonMapper; @@ -865,7 +865,7 @@ public interface McpServer { */ abstract class SyncSpecification> { - McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory(); + McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory(); McpJsonMapper jsonMapper; @@ -1407,7 +1407,7 @@ public interface McpServer { private final McpStatelessServerTransport transport; - McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory(); + McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory(); McpJsonMapper jsonMapper; @@ -1870,7 +1870,7 @@ public interface McpServer { boolean immediateExecution = false; - McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory(); + McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory(); McpJsonMapper jsonMapper; diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java index 823aca4..997df72 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java @@ -20,7 +20,7 @@ import io.modelcontextprotocol.spec.McpSchema.ResourceReference; import io.modelcontextprotocol.spec.McpSchema.Tool; import io.modelcontextprotocol.spec.McpStatelessServerTransport; import io.modelcontextprotocol.util.Assert; -import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory; +import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory; import io.modelcontextprotocol.util.McpUriTemplateManagerFactory; import io.modelcontextprotocol.util.Utils; import org.slf4j.Logger; @@ -74,7 +74,7 @@ public class McpStatelessAsyncServer { private List protocolVersions; - private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory(); + private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory(); private final JsonSchemaValidator jsonSchemaValidator; diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManager.java b/mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManager.java index ef51183..c3b922e 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManager.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManager.java @@ -141,12 +141,30 @@ public class DefaultMcpUriTemplateManager implements McpUriTemplateManager { return uri.equals(this.uriTemplate); } - // Convert the pattern to a regex - String regex = this.uriTemplate.replaceAll("\\{[^/]+?\\}", "([^/]+?)"); - regex = regex.replace("/", "\\/"); + // Convert the URI template into a robust regex pattern that escapes special + // characters like '?'. + StringBuilder patternBuilder = new StringBuilder("^"); + Matcher variableMatcher = URI_VARIABLE_PATTERN.matcher(this.uriTemplate); + int lastEnd = 0; + + while (variableMatcher.find()) { + // Append the literal part of the template, safely quoted + String textBefore = this.uriTemplate.substring(lastEnd, variableMatcher.start()); + patternBuilder.append(Pattern.quote(textBefore)); + // Append a capturing group for the variable itself + patternBuilder.append("([^/]+?)"); + lastEnd = variableMatcher.end(); + } + + // Append any remaining literal text after the last variable + if (lastEnd < this.uriTemplate.length()) { + patternBuilder.append(Pattern.quote(this.uriTemplate.substring(lastEnd))); + } + + patternBuilder.append("$"); // Check if the URI matches the regex - return Pattern.compile(regex).matcher(uri).matches(); + return Pattern.compile(patternBuilder.toString()).matcher(uri).matches(); } @Override diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/util/DeafaultMcpUriTemplateManagerFactory.java b/mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManagerFactory.java similarity index 86% rename from mcp-core/src/main/java/io/modelcontextprotocol/util/DeafaultMcpUriTemplateManagerFactory.java rename to mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManagerFactory.java index 44ea316..fd1a3bd 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/util/DeafaultMcpUriTemplateManagerFactory.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManagerFactory.java @@ -7,7 +7,7 @@ package io.modelcontextprotocol.util; /** * @author Christian Tzolov */ -public class DeafaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory { +public class DefaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory { /** * Creates a new instance of {@link McpUriTemplateManager} with the specified URI diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/McpUriTemplateManagerTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/McpUriTemplateManagerTests.java index 6f041da..8f68f0d 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/McpUriTemplateManagerTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/McpUriTemplateManagerTests.java @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Map; -import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory; +import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory; import io.modelcontextprotocol.util.McpUriTemplateManager; import io.modelcontextprotocol.util.McpUriTemplateManagerFactory; import org.junit.jupiter.api.BeforeEach; @@ -29,7 +29,7 @@ public class McpUriTemplateManagerTests { @BeforeEach void setUp() { - this.uriTemplateFactory = new DeafaultMcpUriTemplateManagerFactory(); + this.uriTemplateFactory = new DefaultMcpUriTemplateManagerFactory(); } @Test @@ -94,4 +94,13 @@ public class McpUriTemplateManagerTests { assertFalse(uriTemplateManager.matches("/api/users/123/comments/456")); } + @Test + void shouldMatchUriWithQueryParameters() { + String templateWithQuery = "file://name/search?={search}"; + var uriTemplateManager = this.uriTemplateFactory.create(templateWithQuery); + + assertTrue(uriTemplateManager.matches("file://name/search?=abcd"), + "Should correctly match a URI containing query parameters."); + } + }