respond(SessionMcpOauthRespondParams params) {
return caller.invoke("session.mcp.oauth.respond", _p, Void.class);
}
+ /**
+ * Pending MCP OAuth request ID and host-provided token or cancellation response.
+ *
+ * Note: the {@code sessionId} field in the params record is overridden
+ * by the session-scoped wrapper; any value provided is ignored.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture handlePendingRequest(SessionMcpOauthHandlePendingRequestParams params) {
+ com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);
+ _p.put("sessionId", this.sessionId);
+ return caller.invoke("session.mcp.oauth.handlePendingRequest", _p, SessionMcpOauthHandlePendingRequestResult.class);
+ }
+
/**
* Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, and the callback success-page copy.
*
diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java
new file mode 100644
index 000000000..403bd548a
--- /dev/null
+++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Pending MCP OAuth request ID and host-provided token or cancellation response.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionMcpOauthHandlePendingRequestParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId,
+ /** OAuth request identifier from the mcp.oauth_required event */
+ @JsonProperty("requestId") String requestId,
+ /** Host response to the pending OAuth request. */
+ @JsonProperty("result") Object result
+) {
+}
diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java
new file mode 100644
index 000000000..a7bca646e
--- /dev/null
+++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Indicates whether the pending MCP OAuth response was accepted.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionMcpOauthHandlePendingRequestResult(
+ /** Whether the response was accepted. False if the request was unknown, timed out, or already resolved. */
+ @JsonProperty("success") Boolean success
+) {
+}
diff --git a/java/src/test/java/com/github/copilot/CapiProxy.java b/java/src/test/java/com/github/copilot/CapiProxy.java
index 90c2dd0a7..6484f0581 100644
--- a/java/src/test/java/com/github/copilot/CapiProxy.java
+++ b/java/src/test/java/com/github/copilot/CapiProxy.java
@@ -290,6 +290,53 @@ public void setCopilotUserByToken(String token, String login, String copilotPlan
}
}
+ /**
+ * Registers a raw Copilot user response for a given token on the
+ * {@code /copilot_internal/user} endpoint.
+ *
+ *
+ * Unlike
+ * {@link #setCopilotUserByToken(String, String, String, String, String, String)},
+ * this posts the response object verbatim, so callers control the exact field
+ * names the proxy returns to the CLI. This matters because the CLI reads
+ * snake_case fields (e.g. {@code copilot_plan}, {@code is_mcp_enabled}) from
+ * the raw user JSON to gate MCP enablement. Use this to register the default
+ * e2e user with the same snake_case shape the Go, Node, Python, and .NET
+ * harnesses post, keeping MCP behavior hermetic and consistent across SDKs.
+ *
+ *
+ * @param token
+ * the GitHub token to configure
+ * @param response
+ * the raw user response object to return for the token (field names
+ * are sent verbatim)
+ * @throws IOException
+ * if the request fails
+ * @throws InterruptedException
+ * if the request is interrupted
+ */
+ public void setCopilotUserByToken(String token, Map response)
+ throws IOException, InterruptedException {
+ if (proxyUrl == null) {
+ throw new IllegalStateException("Proxy not started");
+ }
+
+ Map payload = new java.util.HashMap<>();
+ payload.put("token", token);
+ payload.put("response", response);
+
+ String body = MAPPER.writeValueAsString(payload);
+
+ HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/copilot-user-config"))
+ .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build();
+
+ HttpResponse response2 = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response2.statusCode() != 200) {
+ throw new IOException(
+ "Failed to set copilot user config: " + response2.statusCode() + ": " + response2.body());
+ }
+ }
+
/**
* Stops the proxy server gracefully.
*
diff --git a/java/src/test/java/com/github/copilot/E2ETestContext.java b/java/src/test/java/com/github/copilot/E2ETestContext.java
index 2bc139d94..4089e10ff 100644
--- a/java/src/test/java/com/github/copilot/E2ETestContext.java
+++ b/java/src/test/java/com/github/copilot/E2ETestContext.java
@@ -55,6 +55,12 @@
public class E2ETestContext implements AutoCloseable {
private static final Logger LOG = Logger.getLogger(E2ETestContext.class.getName());
+
+ /**
+ * The default GitHub token used by the CLI in e2e tests. The proxy resolves
+ * this token to the default Copilot user registered at context creation.
+ */
+ private static final String DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests";
private static final Pattern SNAKE_CASE = Pattern.compile("[^a-zA-Z0-9]");
private static final Pattern USER_CONTENT_PATTERN = Pattern
.compile("^\\s+-\\s+role:\\s+user\\s*$\\s+content:\\s*(.+?)$", Pattern.MULTILINE);
@@ -97,6 +103,23 @@ public static E2ETestContext create() throws IOException, InterruptedException {
CapiProxy proxy = new CapiProxy();
String proxyUrl = proxy.start();
+ // Register a default Copilot user for the CLI's default token so the proxy's
+ // /copilot_internal/user endpoint returns a valid user (HTTP 200) instead of
+ // 401 "Bad credentials". CLI 1.0.64-1 gates MCP enablement on this user:
+ // `is_mcp_enabled` (added by the proxy) is the global gate, and snake_case
+ // `copilot_plan` makes the third-party MCP policy resolver early-return
+ // allow-all for non-org plans (anything other than business/enterprise),
+ // avoiding a /copilot/mcp_registry network call the proxy does not serve.
+ // Without this, MCP servers never reach CONNECTED. This mirrors the Go,
+ // Node, Python, and .NET harnesses, which all register the same default
+ // individual_pro user at context creation.
+ Map defaultUser = new HashMap<>();
+ defaultUser.put("login", "e2e-test-user");
+ defaultUser.put("copilot_plan", "individual_pro");
+ defaultUser.put("endpoints", Map.of("api", proxyUrl, "telemetry", "https://localhost:1/telemetry"));
+ defaultUser.put("analytics_tracking_id", "e2e-test-tracking-id");
+ proxy.setCopilotUserByToken(DEFAULT_GITHUB_TOKEN, defaultUser);
+
return new E2ETestContext(cliPath, homeDir, workDir, proxyUrl, proxy, repoRoot);
}
@@ -256,6 +279,11 @@ public List