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
61f7be222b
commit
d19cdae2e9
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.McpServerTransportProviderBase;
|
||||||
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
|
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
|
||||||
import io.modelcontextprotocol.util.Assert;
|
import io.modelcontextprotocol.util.Assert;
|
||||||
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
|
||||||
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
||||||
import io.modelcontextprotocol.util.Utils;
|
import io.modelcontextprotocol.util.Utils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -120,7 +120,7 @@ public class McpAsyncServer {
|
||||||
|
|
||||||
private List<String> protocolVersions;
|
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.
|
* 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.McpStatelessServerTransport;
|
||||||
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
|
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
|
||||||
import io.modelcontextprotocol.util.Assert;
|
import io.modelcontextprotocol.util.Assert;
|
||||||
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
|
||||||
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
|
@ -268,7 +268,7 @@ public interface McpServer {
|
||||||
*/
|
*/
|
||||||
abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
|
abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
|
||||||
|
|
||||||
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
McpJsonMapper jsonMapper;
|
McpJsonMapper jsonMapper;
|
||||||
|
|
||||||
|
|
@ -865,7 +865,7 @@ public interface McpServer {
|
||||||
*/
|
*/
|
||||||
abstract class SyncSpecification<S extends SyncSpecification<S>> {
|
abstract class SyncSpecification<S extends SyncSpecification<S>> {
|
||||||
|
|
||||||
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
McpJsonMapper jsonMapper;
|
McpJsonMapper jsonMapper;
|
||||||
|
|
||||||
|
|
@ -1407,7 +1407,7 @@ public interface McpServer {
|
||||||
|
|
||||||
private final McpStatelessServerTransport transport;
|
private final McpStatelessServerTransport transport;
|
||||||
|
|
||||||
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
McpJsonMapper jsonMapper;
|
McpJsonMapper jsonMapper;
|
||||||
|
|
||||||
|
|
@ -1870,7 +1870,7 @@ public interface McpServer {
|
||||||
|
|
||||||
boolean immediateExecution = false;
|
boolean immediateExecution = false;
|
||||||
|
|
||||||
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
McpJsonMapper jsonMapper;
|
McpJsonMapper jsonMapper;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||||
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
|
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
|
||||||
import io.modelcontextprotocol.util.Assert;
|
import io.modelcontextprotocol.util.Assert;
|
||||||
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
|
||||||
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
||||||
import io.modelcontextprotocol.util.Utils;
|
import io.modelcontextprotocol.util.Utils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -74,7 +74,7 @@ public class McpStatelessAsyncServer {
|
||||||
|
|
||||||
private List<String> protocolVersions;
|
private List<String> protocolVersions;
|
||||||
|
|
||||||
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
|
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||||
|
|
||||||
private final JsonSchemaValidator jsonSchemaValidator;
|
private final JsonSchemaValidator jsonSchemaValidator;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,12 +141,30 @@ public class DefaultMcpUriTemplateManager implements McpUriTemplateManager {
|
||||||
return uri.equals(this.uriTemplate);
|
return uri.equals(this.uriTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the pattern to a regex
|
// Convert the URI template into a robust regex pattern that escapes special
|
||||||
String regex = this.uriTemplate.replaceAll("\\{[^/]+?\\}", "([^/]+?)");
|
// characters like '?'.
|
||||||
regex = regex.replace("/", "\\/");
|
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
|
// Check if the URI matches the regex
|
||||||
return Pattern.compile(regex).matcher(uri).matches();
|
return Pattern.compile(patternBuilder.toString()).matcher(uri).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
|
||||||
import io.modelcontextprotocol.util.McpUriTemplateManager;
|
import io.modelcontextprotocol.util.McpUriTemplateManager;
|
||||||
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
@ -29,7 +29,7 @@ public class McpUriTemplateManagerTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
this.uriTemplateFactory = new DeafaultMcpUriTemplateManagerFactory();
|
this.uriTemplateFactory = new DefaultMcpUriTemplateManagerFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -94,4 +94,13 @@ public class McpUriTemplateManagerTests {
|
||||||
assertFalse(uriTemplateManager.matches("/api/users/123/comments/456"));
|
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