Skip to content

Commit 20c5628

Browse files
committed
Add auth/cross-app-access-complete-flow to JDK conformance client (SEP-990)
1 parent 649a426 commit 20c5628

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
<version>2.0.0-SNAPSHOT</version>
3232
</dependency>
3333

34+
<!-- mcp-core for EnterpriseAuth / EnterpriseAuthProvider (SEP-990) -->
35+
<dependency>
36+
<groupId>io.modelcontextprotocol.sdk</groupId>
37+
<artifactId>mcp-core</artifactId>
38+
<version>2.0.0-SNAPSHOT</version>
39+
</dependency>
40+
3441
<!-- Logging -->
3542
<dependency>
3643
<groupId>ch.qos.logback</groupId>

conformance-tests/client-jdk-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceJdkClientMcpClient.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
import java.time.Duration;
44

5+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
6+
import com.fasterxml.jackson.annotation.JsonProperty;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
58
import io.modelcontextprotocol.client.McpClient;
69
import io.modelcontextprotocol.client.McpSyncClient;
10+
import io.modelcontextprotocol.client.auth.DiscoverAndRequestJwtAuthGrantOptions;
11+
import io.modelcontextprotocol.client.auth.EnterpriseAuth;
12+
import io.modelcontextprotocol.client.auth.EnterpriseAuthProvider;
13+
import io.modelcontextprotocol.client.auth.EnterpriseAuthProviderOptions;
714
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
815
import io.modelcontextprotocol.spec.McpSchema;
916

@@ -53,13 +60,17 @@ public static void main(String[] args) {
5360
case "sse-retry":
5461
runSSERetryScenario(serverUrl);
5562
break;
63+
case "auth/cross-app-access-complete-flow":
64+
runCrossAppAccessCompleteFlowScenario(serverUrl);
65+
break;
5666
default:
5767
System.err.println("Unknown scenario: " + scenario);
5868
System.err.println("Available scenarios:");
5969
System.err.println(" - initialize");
6070
System.err.println(" - tools_call");
6171
System.err.println(" - elicitation-sep1034-client-defaults");
6272
System.err.println(" - sse-retry");
73+
System.err.println(" - auth/cross-app-access-complete-flow");
6374
System.exit(1);
6475
}
6576
System.exit(0);
@@ -283,4 +294,82 @@ private static void runSSERetryScenario(String serverUrl) throws Exception {
283294
}
284295
}
285296

297+
/**
298+
* Cross-App Access scenario: Tests SEP-990 Enterprise Managed Authorization flow.
299+
* <p>
300+
* Reads context from {@code MCP_CONFORMANCE_CONTEXT} (JSON) containing:
301+
* {@code client_id}, {@code client_secret}, {@code idp_client_id},
302+
* {@code idp_id_token}, {@code idp_issuer}, {@code idp_token_endpoint}.
303+
* <p>
304+
* Uses {@link EnterpriseAuthProvider} with an assertion callback that performs RFC
305+
* 8693 token exchange at the IdP, then exchanges the ID-JAG for an access token at
306+
* the MCP authorization server via RFC 7523 JWT Bearer grant.
307+
* @param serverUrl the URL of the MCP server
308+
* @throws Exception if any error occurs during execution
309+
*/
310+
private static void runCrossAppAccessCompleteFlowScenario(String serverUrl) throws Exception {
311+
String contextEnv = System.getenv("MCP_CONFORMANCE_CONTEXT");
312+
if (contextEnv == null || contextEnv.isEmpty()) {
313+
System.err.println("Error: MCP_CONFORMANCE_CONTEXT environment variable is not set");
314+
System.exit(1);
315+
}
316+
317+
CrossAppAccessContext ctx = new ObjectMapper().readValue(contextEnv, CrossAppAccessContext.class);
318+
319+
java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient();
320+
321+
EnterpriseAuthProviderOptions options = EnterpriseAuthProviderOptions.builder()
322+
.clientId(ctx.clientId())
323+
.clientSecret(ctx.clientSecret())
324+
.assertionCallback(assertionCtx -> {
325+
// RFC 8693 token exchange at the IdP: ID Token → ID-JAG
326+
DiscoverAndRequestJwtAuthGrantOptions jagOptions = DiscoverAndRequestJwtAuthGrantOptions
327+
.builder()
328+
.idpUrl(ctx.idpIssuer())
329+
.idpTokenEndpoint(ctx.idpTokenEndpoint())
330+
.idToken(ctx.idpIdToken())
331+
.clientId(ctx.idpClientId())
332+
.audience(assertionCtx.getAuthorizationServerUrl().toString())
333+
.resource(assertionCtx.getResourceUrl().toString())
334+
.build();
335+
return EnterpriseAuth.discoverAndRequestJwtAuthorizationGrant(jagOptions, httpClient);
336+
})
337+
.build();
338+
339+
EnterpriseAuthProvider provider = new EnterpriseAuthProvider(options, httpClient);
340+
341+
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl)
342+
.httpRequestCustomizer(provider)
343+
.build();
344+
345+
McpSyncClient client = McpClient.sync(transport)
346+
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
347+
.requestTimeout(Duration.ofSeconds(30))
348+
.build();
349+
350+
try {
351+
client.initialize();
352+
System.out.println("Successfully connected to MCP server");
353+
354+
client.listTools();
355+
System.out.println("Successfully listed tools");
356+
}
357+
finally {
358+
client.close();
359+
System.out.println("Connection closed successfully");
360+
}
361+
}
362+
363+
/**
364+
* Context provided by the conformance suite for the cross-app-access-complete-flow
365+
* scenario via the {@code MCP_CONFORMANCE_CONTEXT} environment variable.
366+
*/
367+
@JsonIgnoreProperties(ignoreUnknown = true)
368+
private record CrossAppAccessContext(@JsonProperty("client_id") String clientId,
369+
@JsonProperty("client_secret") String clientSecret,
370+
@JsonProperty("idp_client_id") String idpClientId,
371+
@JsonProperty("idp_id_token") String idpIdToken, @JsonProperty("idp_issuer") String idpIssuer,
372+
@JsonProperty("idp_token_endpoint") String idpTokenEndpoint) {
373+
}
374+
286375
}

0 commit comments

Comments
 (0)