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 <christian.tzolov@broadcom.com>
This commit is contained in:
parent
a63d32a5ea
commit
19a8c00a47
6 changed files with 43 additions and 16 deletions
|
|
@ -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<String> protocolVersions;
|
||||
|
||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||
|
||||
/**
|
||||
* Create a new McpAsyncServer with the given transport provider and capabilities.
|
||||
|
|
|
|||
|
|
@ -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<S extends AsyncSpecification<S>> {
|
||||
|
||||
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
||||
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||
|
||||
McpJsonMapper jsonMapper;
|
||||
|
||||
|
|
@ -865,7 +865,7 @@ public interface McpServer {
|
|||
*/
|
||||
abstract class SyncSpecification<S extends SyncSpecification<S>> {
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String> protocolVersions;
|
||||
|
||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||
|
||||
private final JsonSchemaValidator jsonSchemaValidator;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2025 - 2025 the original author or authors.
|
||||
*/
|
||||
|
||||
package io.modelcontextprotocol.util;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class DefaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory {
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link McpUriTemplateManager} with the specified URI
|
||||
* template.
|
||||
* @param uriTemplate The URI template to be used for variable extraction
|
||||
* @return A new instance of {@link McpUriTemplateManager}
|
||||
* @throws IllegalArgumentException if the URI template is null or empty
|
||||
*/
|
||||
@Override
|
||||
public McpUriTemplateManager create(String uriTemplate) {
|
||||
return new DefaultMcpUriTemplateManager(uriTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue