From 810a242319f3381a5de2d20e9f9825433bd898f0 Mon Sep 17 00:00:00 2001 From: Q <2857503667@qq.com> Date: Tue, 19 May 2026 15:23:58 +0800 Subject: [PATCH] Fix nested path URI resolution --- .../io/modelcontextprotocol/util/Utils.java | 36 +++++++++++++++++-- .../modelcontextprotocol/util/UtilsTests.java | 6 ++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/util/Utils.java b/mcp-core/src/main/java/io/modelcontextprotocol/util/Utils.java index cd420100c..26d30673c 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/util/Utils.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/util/Utils.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.util; import java.net.URI; +import java.net.URISyntaxException; import java.util.Collection; import java.util.Map; @@ -76,9 +77,40 @@ public static URI resolveUri(URI baseUrl, String endpointUrl) { if (endpointUri.isAbsolute() && !isUnderBaseUri(baseUrl, endpointUri)) { throw new IllegalArgumentException("Absolute endpoint URL does not match the base URL."); } - else { - return baseUrl.resolve(endpointUri); + else if (endpointUri.isAbsolute()) { + return endpointUri; } + + return baseUrlWithTrailingSlash(baseUrl).resolve(removeLeadingSlash(endpointUrl)); + } + + private static URI baseUrlWithTrailingSlash(URI baseUrl) { + String path = baseUrl.getPath(); + if (path == null || path.isEmpty()) { + return createUriWithPath(baseUrl, "/"); + } + if (path.endsWith("/")) { + return baseUrl; + } + return createUriWithPath(baseUrl, path + "/"); + } + + private static URI createUriWithPath(URI baseUrl, String path) { + try { + return new URI(baseUrl.getScheme(), baseUrl.getAuthority(), path, baseUrl.getQuery(), + baseUrl.getFragment()); + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Invalid base URL.", ex); + } + } + + private static String removeLeadingSlash(String endpointUrl) { + String result = endpointUrl; + while (result.startsWith("/")) { + result = result.substring(1); + } + return result; } /** diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/util/UtilsTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/util/UtilsTests.java index 0f2e689b5..fb79cff9f 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/util/UtilsTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/util/UtilsTests.java @@ -45,9 +45,11 @@ void testMapIsEmpty() { @ParameterizedTest @CsvSource({ // relative endpoints - "http://localhost:8080/root, /api/v1, http://localhost:8080/api/v1", + "http://localhost:8080/root, /api/v1, http://localhost:8080/root/api/v1", + "http://localhost:8080/root, api/v1, http://localhost:8080/root/api/v1", "http://localhost:8080/root/, api, http://localhost:8080/root/api", "http://localhost:8080, /api, http://localhost:8080/api", + "http://localhost:8080/root, /api/v1?key=value, http://localhost:8080/root/api/v1?key=value", // absolute endpoints matching base "http://localhost:8080/root, http://localhost:8080/root/api/v1, http://localhost:8080/root/api/v1", "http://localhost:8080/root, http://localhost:8080/root, http://localhost:8080/root" }) @@ -66,4 +68,4 @@ void testAbsoluteUriNotMatchingBase(String baseUrl, String endpoint) { .hasMessageContaining("does not match the base URL"); } -} \ No newline at end of file +}