Skip to content

Commit 1224a02

Browse files
authored
Merge branch 'main' into bhsz/fix-char-encoding
2 parents 9ed9289 + d1ef187 commit 1224a02

79 files changed

Lines changed: 5016 additions & 760 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/maven-central-release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@ jobs:
2626
with:
2727
node-version: '20'
2828

29+
# Deploy runs the integration tests, but only with Jackson 3
30+
# We run Jackson 2 IT manually
2931
- name: Jackson 2 Integration Tests
3032
run: mvn -pl mcp-test -am -Pjackson2 test
3133

32-
- name: Build and Test
33-
run: mvn clean verify
34-
34+
# Deploy runs all previous maven goals, including test and verify
3535
- name: Publish to Maven Central
3636
run: |
3737
mvn --batch-mode \
3838
-Prelease \
3939
-Pjavadoc \
40-
deploy
40+
clean deploy
4141
env:
4242
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
4343
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}

MIGRATION-2.0.md

Lines changed: 180 additions & 89 deletions
Large diffs are not rendered by default.

conformance-tests/VALIDATION_RESULTS.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Server Tests (active suite):** 44/44 passed (31 scenarios, 100%)
66
**Server Tests (spec 2025-11-25):** 4/4 passed — SEP-1613 `json-schema-2020-12` scenario ✨
77
**Client Tests:** 3/4 scenarios passed (9/10 checks passed)
8-
**Auth Tests:** 14/15 scenarios fully passing (196 passed, 0 failed, 1 warning, 93.3% scenarios, 99.5% checks)
8+
**Auth Tests:** 15/15 scenarios fully passing (195 passed, 0 failed, 0 warnings, 100% scenarios, 100% checks)
99

1010
## Server Test Results
1111

@@ -46,16 +46,17 @@
4646

4747
## Auth Test Results (Spring HTTP Client)
4848

49-
**Status: 196 passed, 0 failed, 1 warning across 15 scenarios**
49+
**Status: 195 passed, 0 failed, 0 warnings across 15 scenarios**
5050

5151
Uses the `client-spring-http-client` module with Spring Security OAuth2 and the [mcp-client-security](https://github.com/springaicommunity/mcp-client-security) library.
5252

53-
### Fully Passing (14/15 scenarios)
53+
### Fully Passing (15/15 scenarios)
5454

5555
- **auth/metadata-default (13/13):** Default metadata discovery
5656
- **auth/metadata-var1 (13/13):** Metadata discovery variant 1
5757
- **auth/metadata-var2 (13/13):** Metadata discovery variant 2
5858
- **auth/metadata-var3 (13/13):** Metadata discovery variant 3
59+
- **auth/basic-cimd (12/12):** Basic Client-Initiated Metadata Discovery
5960
- **auth/scope-from-www-authenticate (14/14):** Scope extraction from WWW-Authenticate header
6061
- **auth/scope-from-scopes-supported (14/14):** Scope extraction from scopes_supported
6162
- **auth/scope-omitted-when-undefined (14/14):** Scope omitted when not defined
@@ -67,14 +68,9 @@ Uses the `client-spring-http-client` module with Spring Security OAuth2 and the
6768
- **auth/resource-mismatch (2/2):** Resource mismatch handling
6869
- **auth/pre-registration (6/6):** Pre-registered client credentials flow
6970

70-
### Partially Passing (1/15 scenarios)
71-
72-
- **auth/basic-cimd (13/13 + 1 warning):** Basic Client-Initiated Metadata Discovery — all checks pass, minor warning
73-
7471
## Known Limitations
7572

7673
1. **Client SSE Retry:** Client doesn't parse or respect the `retry:` field, reconnects immediately, and doesn't send Last-Event-ID header
77-
2. **Auth Basic CIMD:** Minor conformance warning in the basic Client-Initiated Metadata Discovery flow
7874

7975
## Running Tests
8076

@@ -132,4 +128,3 @@ npx @modelcontextprotocol/conformance@0.1.15 client \
132128

133129
### High Priority
134130
1. Fix client SSE retry field handling in `HttpClientStreamableHttpTransport`
135-
2. Implement CIMD

conformance-tests/client-jdk-http-client/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>io.modelcontextprotocol.sdk</groupId>
88
<artifactId>conformance-tests</artifactId>
9-
<version>2.0.0-SNAPSHOT</version>
9+
<version>2.0.1-SNAPSHOT</version>
1010
</parent>
1111
<artifactId>client-jdk-http-client</artifactId>
1212
<packaging>jar</packaging>
@@ -28,7 +28,7 @@
2828
<dependency>
2929
<groupId>io.modelcontextprotocol.sdk</groupId>
3030
<artifactId>mcp</artifactId>
31-
<version>2.0.0-SNAPSHOT</version>
31+
<version>2.0.1-SNAPSHOT</version>
3232
</dependency>
3333

3434
<!-- Logging -->

conformance-tests/client-jdk-http-client/src/main/resources/logback.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
</root>
1313

1414
<!-- More verbose logging for MCP client -->
15-
<logger name="io.modelcontextprotocol" level="DEBUG" />
15+
<logger name="io.modelcontextprotocol" level="INFO" />
1616
</configuration>

conformance-tests/client-spring-http-client/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,24 @@ Test with @modelcontextprotocol/conformance@0.1.15.
1414

1515
## Conformance Test Results
1616

17-
**Status: 178 passed, 1 failed, 1 warning across 14 scenarios**
17+
**Status: 195 passed, 0 failed, 0 warnings across 15 scenarios**
1818

1919
| Scenario | Result | Details |
2020
|---|---|---|
21-
| auth/metadata-default | ✅ Pass | 12/12 |
22-
| auth/metadata-var1 | ✅ Pass | 12/12 |
23-
| auth/metadata-var2 | ✅ Pass | 12/12 |
24-
| auth/metadata-var3 | ✅ Pass | 12/12 |
25-
| auth/basic-cimd | ⚠️ Warning | 12/12 passed, 1 warning |
26-
| auth/scope-from-www-authenticate | ✅ Pass | 13/13 |
27-
| auth/scope-from-scopes-supported | ✅ Pass | 13/13 |
28-
| auth/scope-omitted-when-undefined | ✅ Pass | 13/13 |
29-
| auth/scope-step-up | ✅ Pass | 12/12 |
21+
| auth/metadata-default | ✅ Pass | 13/13 |
22+
| auth/metadata-var1 | ✅ Pass | 13/13 |
23+
| auth/metadata-var2 | ✅ Pass | 13/13 |
24+
| auth/metadata-var3 | ✅ Pass | 13/13 |
25+
| auth/basic-cimd | ✅ Pass | 12/12 |
26+
| auth/scope-from-www-authenticate | ✅ Pass | 14/14 |
27+
| auth/scope-from-scopes-supported | ✅ Pass | 14/14 |
28+
| auth/scope-omitted-when-undefined | ✅ Pass | 14/14 |
29+
| auth/scope-step-up | ✅ Pass | 16/16 |
3030
| auth/scope-retry-limit | ✅ Pass | 11/11 |
31-
| auth/token-endpoint-auth-basic | ✅ Pass | 17/17 |
32-
| auth/token-endpoint-auth-post | ✅ Pass | 17/17 |
33-
| auth/token-endpoint-auth-none | ✅ Pass | 17/17 |
31+
| auth/token-endpoint-auth-basic | ✅ Pass | 18/18 |
32+
| auth/token-endpoint-auth-post | ✅ Pass | 18/18 |
33+
| auth/token-endpoint-auth-none | ✅ Pass | 18/18 |
34+
| auth/resource-mismatch | ✅ Pass | 2/2 |
3435
| auth/pre-registration | ✅ Pass | 6/6 |
3536

3637
See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for the full project validation results.
@@ -113,8 +114,7 @@ java -jar conformance-tests/client-spring-http-client/target/client-spring-http-
113114

114115
## Known Issues
115116

116-
1. **auth/scope-step-up** (1 failure) — The client does not fully handle scope step-up challenges where the server requests additional scopes after initial authorization.
117-
2. **auth/basic-cimd** (1 warning) — Minor conformance warning in the basic Client-Initiated Metadata Discovery flow.
117+
Currently, there are no known issues in the auth suite implementation.
118118

119119
## References
120120

conformance-tests/client-spring-http-client/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>io.modelcontextprotocol.sdk</groupId>
88
<artifactId>conformance-tests</artifactId>
9-
<version>2.0.0-SNAPSHOT</version>
9+
<version>2.0.1-SNAPSHOT</version>
1010
</parent>
1111
<artifactId>client-spring-http-client</artifactId>
1212
<packaging>jar</packaging>
@@ -23,8 +23,8 @@
2323
<properties>
2424
<java.version>17</java.version>
2525
<spring-boot.version>4.0.5</spring-boot.version>
26-
<spring-ai.version>2.0.0-M4</spring-ai.version>
27-
<spring-ai-mcp-security.version>0.1.5</spring-ai-mcp-security.version>
26+
<spring-ai.version>2.0.0-M6</spring-ai.version>
27+
<spring-ai-mcp-security.version>0.1.11</spring-ai-mcp-security.version>
2828
<maven.deploy.skip>true</maven.deploy.skip>
2929
</properties>
3030

conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceSpringClientApplication.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88

99
import io.modelcontextprotocol.conformance.client.scenario.Scenario;
1010
import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
11-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DefaultMcpOAuth2ClientManager;
11+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DefaultMcpOAuth2DcrClientManager;
1212
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DynamicClientRegistrationService;
1313
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.InMemoryMcpClientRegistrationRepository;
1414
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
15-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
15+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2DcrClientManager;
16+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.DefaultMcpOAuth2CimdClientManager;
17+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.McpOAuth2CimdClientManager;
18+
import org.springaicommunity.mcp.security.common.url.DefaultUrlValidator;
1619

1720
import org.springframework.boot.ApplicationArguments;
1821
import org.springframework.boot.ApplicationRunner;
1922
import org.springframework.boot.SpringApplication;
2023
import org.springframework.boot.autoconfigure.SpringBootApplication;
2124
import org.springframework.context.annotation.Bean;
25+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
26+
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
27+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2228

2329
/**
2430
* MCP Conformance Test Client - Spring HTTP Client Implementation.
@@ -42,13 +48,15 @@ public class ConformanceSpringClientApplication {
4248

4349
public static final String REGISTRATION_ID = "default_registration";
4450

51+
private final DefaultUrlValidator URL_VALIDATOR = new DefaultUrlValidator(true);
52+
4553
public static void main(String[] args) {
4654
SpringApplication.run(ConformanceSpringClientApplication.class, args);
4755
}
4856

4957
@Bean
5058
McpMetadataDiscoveryService discovery() {
51-
return new McpMetadataDiscoveryService();
59+
return new McpMetadataDiscoveryService(URL_VALIDATOR);
5260
}
5361

5462
@Bean
@@ -57,10 +65,24 @@ McpClientRegistrationRepository clientRegistrationRepository() {
5765
}
5866

5967
@Bean
60-
McpOAuth2ClientManager mcpOAuth2ClientManager(McpClientRegistrationRepository mcpClientRegistrationRepository,
68+
McpOAuth2DcrClientManager mcpOAuth2ClientManager(McpClientRegistrationRepository mcpClientRegistrationRepository,
6169
McpMetadataDiscoveryService mcpMetadataDiscoveryService) {
62-
return new DefaultMcpOAuth2ClientManager(mcpClientRegistrationRepository,
63-
new DynamicClientRegistrationService(), mcpMetadataDiscoveryService);
70+
return new DefaultMcpOAuth2DcrClientManager(mcpClientRegistrationRepository,
71+
new DynamicClientRegistrationService(URL_VALIDATOR), mcpMetadataDiscoveryService, URL_VALIDATOR);
72+
}
73+
74+
@Bean
75+
McpOAuth2CimdClientManager mcpOAuth2CimdClientManager(McpMetadataDiscoveryService mcpMetadataDiscoveryService,
76+
McpClientRegistrationRepository mcpClientRegistrationRepository) {
77+
return new DefaultMcpOAuth2CimdClientManager(mcpMetadataDiscoveryService, mcpClientRegistrationRepository,
78+
URL_VALIDATOR);
79+
}
80+
81+
@Bean
82+
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager(
83+
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
84+
McpClientRegistrationRepository clientRegistrationRepository) {
85+
return new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientRepository);
6486
}
6587

6688
@Bean

conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/DefaultConfiguration.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,65 @@
44

55
package io.modelcontextprotocol.conformance.client.configuration;
66

7-
import io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication;
7+
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
88
import io.modelcontextprotocol.conformance.client.scenario.DefaultScenario;
99
import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
10+
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2CimdHttpClientTransportCustomizer;
11+
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2DcrHttpClientTransportCustomizer;
1012
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
11-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
13+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2DcrClientManager;
14+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.DefaultMcpOAuth2CimdClientManager;
15+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.McpOAuth2CimdClientManager;
1216

17+
import org.springframework.ai.mcp.customizer.McpClientCustomizer;
18+
import org.springframework.beans.factory.annotation.Value;
1319
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
1420
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
1521
import org.springframework.context.annotation.Bean;
1622
import org.springframework.context.annotation.Configuration;
17-
import org.springframework.security.config.Customizer;
1823
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
19-
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
24+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
25+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2026
import org.springframework.security.web.SecurityFilterChain;
2127

2228
@Configuration
2329
@ConditionalOnExpression("#{environment['MCP_CONFORMANCE_SCENARIO'] != 'auth/pre-registration'}")
2430
public class DefaultConfiguration {
2531

32+
private final String TEST_CLIENT_ID_URL = "https://conformance-test.local/client-metadata.json";
33+
2634
@Bean
27-
DefaultScenario defaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
28-
ServletWebServerApplicationContext serverCtx,
29-
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
30-
McpOAuth2ClientManager mcpOAuth2ClientManager) {
31-
return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository,
32-
mcpOAuth2ClientManager);
35+
DefaultScenario defaultScenario(ServletWebServerApplicationContext serverCtx,
36+
McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer) {
37+
return new DefaultScenario(serverCtx, transportCustomizer);
38+
}
39+
40+
@Bean
41+
McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer(
42+
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager,
43+
McpClientRegistrationRepository clientRegistrationRepository,
44+
McpOAuth2DcrClientManager mcpOAuth2ClientManager, McpOAuth2CimdClientManager mcpOAuth2CimdClientManager,
45+
@Value("${mcp.conformance.scenario}") String scenario) {
46+
if (scenario.equals("auth/basic-cimd")) {
47+
if (mcpOAuth2CimdClientManager instanceof DefaultMcpOAuth2CimdClientManager mgr) {
48+
// Hardcode the client_id
49+
mgr.setClientRegistrationCustomizer(
50+
cr -> ClientRegistration.withClientRegistration(cr).clientId(TEST_CLIENT_ID_URL).build());
51+
}
52+
return new OAuth2CimdHttpClientTransportCustomizer(oAuth2AuthorizedClientManager,
53+
clientRegistrationRepository, mcpOAuth2CimdClientManager);
54+
55+
}
56+
else {
57+
return new OAuth2DcrHttpClientTransportCustomizer(oAuth2AuthorizedClientManager,
58+
clientRegistrationRepository, mcpOAuth2ClientManager);
59+
}
3360
}
3461

3562
@Bean
36-
SecurityFilterChain securityFilterChain(HttpSecurity http, ConformanceSpringClientApplication.ServerUrl serverUrl) {
63+
SecurityFilterChain securityFilterChain(HttpSecurity http) {
3764
return http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
38-
.with(new McpClientOAuth2Configurer(), Customizer.withDefaults())
65+
.with(new McpClientOAuth2Configurer(), mcp -> mcp.cimd(true))
3966
.build();
4067
}
4168

conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/DefaultScenario.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,10 @@
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
1919
import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
20-
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2HttpClientTransportCustomizer;
21-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
22-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
2320

21+
import org.springframework.ai.mcp.customizer.McpClientCustomizer;
2422
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
2523
import org.springframework.http.client.JdkClientHttpRequestFactory;
26-
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
27-
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2824
import org.springframework.web.client.RestClient;
2925
import org.springframework.web.util.UriComponentsBuilder;
3026

@@ -34,23 +30,14 @@ public class DefaultScenario implements Scenario {
3430

3531
private final ServletWebServerApplicationContext serverCtx;
3632

37-
private final DefaultOAuth2AuthorizedClientManager authorizedClientManager;
38-
39-
private final McpClientRegistrationRepository clientRegistrationRepository;
40-
41-
private final McpOAuth2ClientManager mcpOAuth2ClientManager;
33+
private final McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer;
4234

4335
private McpSyncClient client;
4436

45-
public DefaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
46-
ServletWebServerApplicationContext serverCtx,
47-
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
48-
McpOAuth2ClientManager mcpOAuth2ClientManager) {
37+
public DefaultScenario(ServletWebServerApplicationContext serverCtx,
38+
McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer) {
4939
this.serverCtx = serverCtx;
50-
this.clientRegistrationRepository = clientRegistrationRepository;
51-
this.mcpOAuth2ClientManager = mcpOAuth2ClientManager;
52-
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository,
53-
oAuth2AuthorizedClientRepository);
40+
this.transportCustomizer = transportCustomizer;
5441
}
5542

5643
@Override
@@ -59,12 +46,10 @@ public void execute(String serverUrl) {
5946
var testServerUrl = "http://localhost:" + serverCtx.getWebServer().getPort();
6047
var testClient = buildTestClient(testServerUrl);
6148

62-
var customizer = new OAuth2HttpClientTransportCustomizer(authorizedClientManager, clientRegistrationRepository,
63-
mcpOAuth2ClientManager);
6449
var baseUri = UriComponentsBuilder.fromUriString(serverUrl).replacePath(null).toUriString();
6550
var path = UriComponentsBuilder.fromUriString(serverUrl).build().getPath();
6651
var transportBuilder = HttpClientStreamableHttpTransport.builder(baseUri).endpoint(path);
67-
customizer.customize("default-transport", transportBuilder);
52+
transportCustomizer.customize("default-transport", transportBuilder);
6853
HttpClientStreamableHttpTransport transport = transportBuilder.build();
6954

7055
this.client = McpClient.sync(transport)

0 commit comments

Comments
 (0)