Skip to content

Commit 8708e26

Browse files
authored
mcp-server: added servlet context to resource_metadata header (#22)
1 parent 0596aaa commit 8708e26

File tree

4 files changed

+127
-2
lines changed

4 files changed

+127
-2
lines changed

mcp-server-security/src/main/java/org/springaicommunity/mcp/security/server/oauth2/authentication/BearerResourceMetadataTokenAuthenticationEntryPoint.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public void commence(HttpServletRequest request, HttpServletResponse response,
6363

6464
private String buildResourceMetadataPath(HttpServletRequest request, ResourceIdentifier resourceIdentifier) {
6565
return UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))
66-
.replacePath("/.well-known/oauth-protected-resource" + resourceIdentifier.getPath())
66+
.replacePath(
67+
request.getContextPath() + "/.well-known/oauth-protected-resource" + resourceIdentifier.getPath())
6768
.replaceQuery(null)
6869
.fragment(null)
6970
.toUriString();

mcp-server-security/src/main/java/org/springaicommunity/mcp/security/server/oauth2/metadata/ResourceIdentifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public String getResource() {
4343
var request = requestAttributes.getRequest();
4444

4545
return UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))
46-
.replacePath(this.getPath())
46+
.replacePath(request.getContextPath() + this.getPath())
4747
.replaceQuery(null)
4848
.fragment(null)
4949
.toUriString();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.springaicommunity.mcp.security.server.oauth2.authentication;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.mockito.Mockito.mock;
5+
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
import org.springaicommunity.mcp.security.server.oauth2.metadata.ResourceIdentifier;
9+
import org.springframework.http.HttpHeaders;
10+
import org.springframework.mock.web.MockHttpServletRequest;
11+
import org.springframework.mock.web.MockHttpServletResponse;
12+
import org.springframework.security.core.AuthenticationException;
13+
14+
class BearerResourceMetadataTokenAuthenticationEntryPointTest {
15+
16+
private final MockHttpServletRequest request = new MockHttpServletRequest();
17+
18+
private final MockHttpServletResponse response = new MockHttpServletResponse();
19+
20+
private final ResourceIdentifier resourceIdentifier = new ResourceIdentifier("/mcp");
21+
22+
private final BearerResourceMetadataTokenAuthenticationEntryPoint entryPoint = new BearerResourceMetadataTokenAuthenticationEntryPoint(
23+
resourceIdentifier);
24+
25+
@Test
26+
void commence_ShouldAddCustomContextPath() throws Exception {
27+
request.setContextPath("/foo");
28+
request.setScheme("https");
29+
request.setServerName("my.host.com");
30+
request.setServerPort(443);
31+
request.setRequestURI("/foo/some/endpoint");
32+
33+
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer");
34+
35+
AuthenticationException authException = mock(AuthenticationException.class);
36+
37+
entryPoint.commence(request, response, authException);
38+
39+
String headerValue = response.getHeader(HttpHeaders.WWW_AUTHENTICATE);
40+
41+
assertThat(headerValue)
42+
.contains("Bearer resource_metadata=https://my.host.com/foo/.well-known/oauth-protected-resource/mcp");
43+
44+
}
45+
46+
@Test
47+
void commence_ShouldAddDefaultContextPath() throws Exception {
48+
request.setContextPath("");
49+
request.setScheme("https");
50+
request.setServerName("my.host.com");
51+
request.setServerPort(443);
52+
request.setRequestURI("/some/endpoint");
53+
54+
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer");
55+
56+
AuthenticationException authException = mock(AuthenticationException.class);
57+
58+
entryPoint.commence(request, response, authException);
59+
60+
String headerValue = response.getHeader(HttpHeaders.WWW_AUTHENTICATE);
61+
62+
assertThat(headerValue)
63+
.contains("Bearer resource_metadata=https://my.host.com/.well-known/oauth-protected-resource/mcp");
64+
}
65+
66+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.springaicommunity.mcp.security.server.oauth2.metadata;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.BDDAssertions.catchThrowable;
5+
6+
import org.junit.jupiter.api.AfterEach;
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.mock.web.MockHttpServletRequest;
9+
import org.springframework.web.context.request.RequestContextHolder;
10+
import org.springframework.web.context.request.ServletRequestAttributes;
11+
12+
class ResourceIdentifierTest {
13+
14+
@AfterEach
15+
void tearDown() {
16+
RequestContextHolder.resetRequestAttributes();
17+
}
18+
19+
@Test
20+
void constructor_ShouldThrowException_WhenPathIsEmpty() {
21+
// when
22+
Throwable thrown = catchThrowable(() -> new ResourceIdentifier(""));
23+
24+
// then
25+
assertThat(thrown).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("path cannot be empty");
26+
}
27+
28+
@Test
29+
void getPath_ShouldReturnGivenPath() {
30+
// given
31+
var identifier = new ResourceIdentifier("/my-resource");
32+
33+
// then
34+
assertThat(identifier.getPath()).isEqualTo("/my-resource");
35+
}
36+
37+
@Test
38+
void getResource_ShouldBuildFullUrlBasedOnCurrentRequest() {
39+
// given
40+
MockHttpServletRequest request = new MockHttpServletRequest();
41+
request.setScheme("https");
42+
request.setServerName("my.host.com");
43+
request.setServerPort(8443);
44+
request.setContextPath("/foo");
45+
request.setRequestURI("/foo/other/path");
46+
47+
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
48+
49+
var identifier = new ResourceIdentifier("/mcp");
50+
51+
// when
52+
String result = identifier.getResource();
53+
54+
// then
55+
assertThat(result).isEqualTo("https://my.host.com:8443/foo/mcp");
56+
}
57+
58+
}

0 commit comments

Comments
 (0)