Skip to content

Commit 19a8c00

Browse files
sheegansrigmtzolov
authored andcommitted
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 <[email protected]>
1 parent a63d32a commit 19a8c00

File tree

6 files changed

+43
-16
lines changed

6 files changed

+43
-16
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import io.modelcontextprotocol.spec.McpServerTransportProviderBase;
3737
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
3838
import io.modelcontextprotocol.util.Assert;
39-
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
39+
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
4040
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
4141
import io.modelcontextprotocol.util.Utils;
4242
import org.slf4j.Logger;
@@ -120,7 +120,7 @@ public class McpAsyncServer {
120120

121121
private List<String> protocolVersions;
122122

123-
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
123+
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
124124

125125
/**
126126
* Create a new McpAsyncServer with the given transport provider and capabilities.

mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
2626
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
2727
import io.modelcontextprotocol.util.Assert;
28-
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
28+
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
2929
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
3030
import reactor.core.publisher.Mono;
3131

@@ -268,7 +268,7 @@ public McpAsyncServer build() {
268268
*/
269269
abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
270270

271-
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
271+
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
272272

273273
McpJsonMapper jsonMapper;
274274

@@ -865,7 +865,7 @@ public McpSyncServer build() {
865865
*/
866866
abstract class SyncSpecification<S extends SyncSpecification<S>> {
867867

868-
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
868+
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
869869

870870
McpJsonMapper jsonMapper;
871871

@@ -1407,7 +1407,7 @@ class StatelessAsyncSpecification {
14071407

14081408
private final McpStatelessServerTransport transport;
14091409

1410-
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
1410+
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
14111411

14121412
McpJsonMapper jsonMapper;
14131413

@@ -1870,7 +1870,7 @@ class StatelessSyncSpecification {
18701870

18711871
boolean immediateExecution = false;
18721872

1873-
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
1873+
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
18741874

18751875
McpJsonMapper jsonMapper;
18761876

mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import io.modelcontextprotocol.spec.McpSchema.Tool;
2121
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
2222
import io.modelcontextprotocol.util.Assert;
23-
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
23+
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
2424
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
2525
import io.modelcontextprotocol.util.Utils;
2626
import org.slf4j.Logger;
@@ -74,7 +74,7 @@ public class McpStatelessAsyncServer {
7474

7575
private List<String> protocolVersions;
7676

77-
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
77+
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
7878

7979
private final JsonSchemaValidator jsonSchemaValidator;
8080

mcp-core/src/main/java/io/modelcontextprotocol/util/DefaultMcpUriTemplateManager.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,30 @@ public boolean matches(String uri) {
141141
return uri.equals(this.uriTemplate);
142142
}
143143

144-
// Convert the pattern to a regex
145-
String regex = this.uriTemplate.replaceAll("\\{[^/]+?\\}", "([^/]+?)");
146-
regex = regex.replace("/", "\\/");
144+
// Convert the URI template into a robust regex pattern that escapes special
145+
// characters like '?'.
146+
StringBuilder patternBuilder = new StringBuilder("^");
147+
Matcher variableMatcher = URI_VARIABLE_PATTERN.matcher(this.uriTemplate);
148+
int lastEnd = 0;
149+
150+
while (variableMatcher.find()) {
151+
// Append the literal part of the template, safely quoted
152+
String textBefore = this.uriTemplate.substring(lastEnd, variableMatcher.start());
153+
patternBuilder.append(Pattern.quote(textBefore));
154+
// Append a capturing group for the variable itself
155+
patternBuilder.append("([^/]+?)");
156+
lastEnd = variableMatcher.end();
157+
}
158+
159+
// Append any remaining literal text after the last variable
160+
if (lastEnd < this.uriTemplate.length()) {
161+
patternBuilder.append(Pattern.quote(this.uriTemplate.substring(lastEnd)));
162+
}
163+
164+
patternBuilder.append("$");
147165

148166
// Check if the URI matches the regex
149-
return Pattern.compile(regex).matcher(uri).matches();
167+
return Pattern.compile(patternBuilder.toString()).matcher(uri).matches();
150168
}
151169

152170
@Override
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* @author Christian Tzolov
99
*/
10-
public class DeafaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory {
10+
public class DefaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory {
1111

1212
/**
1313
* Creates a new instance of {@link McpUriTemplateManager} with the specified URI

mcp-core/src/test/java/io/modelcontextprotocol/McpUriTemplateManagerTests.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import java.util.List;
1313
import java.util.Map;
1414

15-
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
15+
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
1616
import io.modelcontextprotocol.util.McpUriTemplateManager;
1717
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
1818
import org.junit.jupiter.api.BeforeEach;
@@ -29,7 +29,7 @@ public class McpUriTemplateManagerTests {
2929

3030
@BeforeEach
3131
void setUp() {
32-
this.uriTemplateFactory = new DeafaultMcpUriTemplateManagerFactory();
32+
this.uriTemplateFactory = new DefaultMcpUriTemplateManagerFactory();
3333
}
3434

3535
@Test
@@ -94,4 +94,13 @@ void shouldMatchUriAgainstTemplatePattern() {
9494
assertFalse(uriTemplateManager.matches("/api/users/123/comments/456"));
9595
}
9696

97+
@Test
98+
void shouldMatchUriWithQueryParameters() {
99+
String templateWithQuery = "file://name/search?={search}";
100+
var uriTemplateManager = this.uriTemplateFactory.create(templateWithQuery);
101+
102+
assertTrue(uriTemplateManager.matches("file://name/search?=abcd"),
103+
"Should correctly match a URI containing query parameters.");
104+
}
105+
97106
}

0 commit comments

Comments
 (0)