Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1b027da
Forward OAuth client metadata URL
SamMorrowDrums Aug 4, 2026
2003b23
chore: merge SDK main into CIMD forwarding
SamMorrowDrums Aug 13, 2026
ee12bad
Merge origin/main into sammorrowdrums-auth-client-metadata-url
SamMorrowDrums Aug 13, 2026
6450d14
Merge concurrent PR branch update
SamMorrowDrums Aug 13, 2026
a9162f9
Merge origin/main into sammorrowdrums-auth-client-metadata-url
SamMorrowDrums Aug 13, 2026
760304b
test(rust): cover omitted OAuth metadata URL
SamMorrowDrums Aug 13, 2026
90e39d3
Merge remote-tracking branch 'origin/main' into sammorrowdrums-auth-c…
SamMorrowDrums Aug 17, 2026
dcb0d17
Merge remote-tracking branch 'origin/main' into sammorrowdrums-auth-c…
SamMorrowDrums Aug 19, 2026
38ab2b5
docs(rust): clarify OAuth client identity defaults
SamMorrowDrums Aug 19, 2026
cc1dd27
Merge remote-tracking branch 'origin/main' into sammorrowdrums-auth-c…
SamMorrowDrums Aug 27, 2026
fbb7821
feat: forward OAuth client metadata URL in all SDKs
SamMorrowDrums Aug 27, 2026
0998f3e
test(java): use supported session request builders
SamMorrowDrums Aug 27, 2026
143f910
Merge origin/main into sammorrowdrums-auth-client-metadata-url
SamMorrowDrums Sep 3, 2026
cf3bbe0
ci: upgrade consistency workflow compiler
SamMorrowDrums Sep 3, 2026
7e60244
ci: upgrade agentic workflows to v0.85.4
SamMorrowDrums Sep 3, 2026
5de023d
revert: remove unrelated workflow upgrades
SamMorrowDrums Sep 3, 2026
ca2fdeb
test: cover MCP CIMD OAuth behavior
SteveSandersonMS Sep 3, 2026
c082ddb
Merge origin/main into CIMD session forwarding
SamMorrowDrums Sep 10, 2026
5ab2eb6
test: fix CIMD coverage after main merge
SamMorrowDrums Sep 10, 2026
d5d0d2e
test(dotnet): support CIMD E2E on net472
SamMorrowDrums Sep 10, 2026
4bfe242
test(rust): document loopback OAuth transport
SamMorrowDrums Sep 10, 2026
db96886
test(rust): attach loopback suppression to sink
SamMorrowDrums Sep 10, 2026
24efca7
Merge remote-tracking branch 'origin/main' into sammorrowdrums-auth-c…
SamMorrowDrums Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
config.IncludeSubAgentStreamingEvents,
config.McpServers,
config.McpOAuthTokenStorage,
config.AuthClientIdMetadataUrl,
"direct",
config.CustomAgents,
config.DefaultAgent,
Expand Down Expand Up @@ -1457,6 +1458,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
config.IncludeSubAgentStreamingEvents,
config.McpServers,
config.McpOAuthTokenStorage,
config.AuthClientIdMetadataUrl,
"direct",
config.CustomAgents,
config.DefaultAgent,
Expand Down Expand Up @@ -2885,6 +2887,7 @@ internal record CreateSessionRequest(
bool? IncludeSubAgentStreamingEvents,
IDictionary<string, McpServerConfig>? McpServers,
McpOAuthTokenStorageMode? McpOAuthTokenStorage,
string? AuthClientIdMetadataUrl,
string? EnvValueMode,
IList<CustomAgentConfig>? CustomAgents,
DefaultAgentConfig? DefaultAgent,
Expand Down Expand Up @@ -3012,6 +3015,7 @@ internal record ResumeSessionRequest(
bool? IncludeSubAgentStreamingEvents,
IDictionary<string, McpServerConfig>? McpServers,
McpOAuthTokenStorageMode? McpOAuthTokenStorage,
string? AuthClientIdMetadataUrl,
string? EnvValueMode,
IList<CustomAgentConfig>? CustomAgents,
DefaultAgentConfig? DefaultAgent,
Expand Down
7 changes: 7 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3183,6 +3183,7 @@ protected SessionConfigBase(SessionConfigBase? other)
: new Dictionary<string, McpServerConfig>(other.McpServers))
: null;
McpOAuthTokenStorage = other.McpOAuthTokenStorage;
AuthClientIdMetadataUrl = other.AuthClientIdMetadataUrl;
Model = other.Model;
ModelCapabilities = other.ModelCapabilities;
OnAutoModeSwitchRequest = other.OnAutoModeSwitchRequest;
Expand Down Expand Up @@ -3560,6 +3561,12 @@ protected SessionConfigBase(SessionConfigBase? other)
/// </summary>
public McpOAuthTokenStorageMode? McpOAuthTokenStorage { get; set; }

/// <summary>
/// OAuth Client ID Metadata Document URL identifying the host for MCP authorization.
/// When unset, no host identity is supplied.
/// </summary>
public string? AuthClientIdMetadataUrl { get; set; }

/// <summary>Custom agent configurations for the session.</summary>
public IList<CustomAgentConfig>? CustomAgents { get; set; }

Expand Down
26 changes: 26 additions & 0 deletions dotnet/test/Unit/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,32 @@ public void SessionRequests_OmitExpAssignments_WhenUnset()
Assert.False(resumeDocument.RootElement.TryGetProperty("expAssignments", out _));
}

[Fact]
public void SessionRequests_SerializeAuthClientIdMetadataUrl_AndOmitWhenUnset()
{
var options = GetSerializerOptions();
const string url = "https://example.com/oauth/client-metadata.json";

foreach (var requestName in new[] { "CreateSessionRequest", "ResumeSessionRequest" })
{
var requestType = GetNestedType(typeof(CopilotClient), requestName);
var configured = CreateInternalRequest(
requestType,
("SessionId", "session-id"),
("AuthClientIdMetadataUrl", url));
using var configuredDocument = JsonDocument.Parse(
JsonSerializer.Serialize(configured, requestType, options));
Assert.Equal(
url,
configuredDocument.RootElement.GetProperty("authClientIdMetadataUrl").GetString());

var unset = CreateInternalRequest(requestType, ("SessionId", "session-id"));
using var unsetDocument = JsonDocument.Parse(
JsonSerializer.Serialize(unset, requestType, options));
Assert.False(unsetDocument.RootElement.TryGetProperty("authClientIdMetadataUrl", out _));
}
}

[Fact]
public void SessionConfigClone_PreservesExpAssignments()
{
Expand Down
2 changes: 2 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
req.AdditionalDirectories = config.AdditionalDirectories
req.MCPServers = config.MCPServers
req.MCPOAuthTokenStorage = config.MCPOAuthTokenStorage
req.AuthClientIDMetadataURL = config.AuthClientIDMetadataURL
req.EnvValueMode = "direct"
req.CustomAgents = config.CustomAgents
req.DefaultAgent = config.DefaultAgent
Expand Down Expand Up @@ -1262,6 +1263,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.ContinuePendingWork = config.ContinuePendingWork
req.MCPServers = config.MCPServers
req.MCPOAuthTokenStorage = config.MCPOAuthTokenStorage
req.AuthClientIDMetadataURL = config.AuthClientIDMetadataURL
req.EnvValueMode = "direct"
req.CustomAgents = config.CustomAgents
req.DefaultAgent = config.DefaultAgent
Expand Down
63 changes: 63 additions & 0 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,69 @@ func TestResumeSessionRequest_MCPOAuthTokenStorage(t *testing.T) {
})
}

func TestSessionRequests_AuthClientIDMetadataURL(t *testing.T) {
url := "https://example.com/oauth/client-metadata.json"

t.Run("create includes configured URL", func(t *testing.T) {
data, err := json.Marshal(createSessionRequest{AuthClientIDMetadataURL: url})
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if payload["authClientIdMetadataUrl"] != url {
t.Errorf("Expected authClientIdMetadataUrl to be %q, got %v", url, payload["authClientIdMetadataUrl"])
}
})

t.Run("create omits empty URL", func(t *testing.T) {
data, err := json.Marshal(createSessionRequest{})
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if _, ok := payload["authClientIdMetadataUrl"]; ok {
t.Error("Expected authClientIdMetadataUrl to be omitted when empty")
}
})

t.Run("resume includes configured URL", func(t *testing.T) {
data, err := json.Marshal(resumeSessionRequest{
SessionID: "s1",
AuthClientIDMetadataURL: url,
})
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if payload["authClientIdMetadataUrl"] != url {
t.Errorf("Expected authClientIdMetadataUrl to be %q, got %v", url, payload["authClientIdMetadataUrl"])
}
})

t.Run("resume omits empty URL", func(t *testing.T) {
data, err := json.Marshal(resumeSessionRequest{SessionID: "s1"})
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if _, ok := payload["authClientIdMetadataUrl"]; ok {
t.Error("Expected authClientIdMetadataUrl to be omitted when empty")
}
})
}

func TestOverridesBuiltInTool(t *testing.T) {
t.Run("OverridesBuiltInTool is serialized in tool definition", func(t *testing.T) {
tool := Tool{
Expand Down
8 changes: 8 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,9 @@ type SessionConfig struct {
// MCPOAuthTokenStorage controls how MCP OAuth tokens are stored for this session.
// When empty, the runtime default ("in-memory") is used.
MCPOAuthTokenStorage string
// AuthClientIDMetadataURL identifies the host for MCP OAuth authorization.
// When empty, no host identity is supplied.
AuthClientIDMetadataURL string
// CustomAgents configures custom agents for the session
CustomAgents []CustomAgentConfig
// DefaultAgent configures the default agent (the built-in agent that handles turns when no custom agent is selected).
Expand Down Expand Up @@ -1973,6 +1976,9 @@ type ResumeSessionConfig struct {
// MCPOAuthTokenStorage controls how MCP OAuth tokens are stored for this session.
// When empty, the runtime default ("in-memory") is used.
MCPOAuthTokenStorage string
// AuthClientIDMetadataURL identifies the host for MCP OAuth authorization.
// When empty, no host identity is supplied.
AuthClientIDMetadataURL string
// CustomAgents configures custom agents for the session
CustomAgents []CustomAgentConfig
// DefaultAgent configures the default agent (the built-in agent that handles turns when no custom agent is selected).
Expand Down Expand Up @@ -2519,6 +2525,7 @@ type createSessionRequest struct {
EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"`
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"`
AuthClientIDMetadataURL string `json:"authClientIdMetadataUrl,omitempty"`
EnvValueMode string `json:"envValueMode,omitempty"`
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"`
Expand Down Expand Up @@ -2628,6 +2635,7 @@ type resumeSessionRequest struct {
EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"`
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"`
AuthClientIDMetadataURL string `json:"authClientIdMetadataUrl,omitempty"`
EnvValueMode string `json:"envValueMode,omitempty"`
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
config.getIncludeSubAgentStreamingEvents().ifPresent(request::setIncludeSubAgentStreamingEvents);
request.setMcpServers(config.getMcpServers());
request.setMcpOAuthTokenStorage(config.getMcpOAuthTokenStorage());
request.setAuthClientIdMetadataUrl(config.getAuthClientIdMetadataUrl());
request.setCustomAgents(config.getCustomAgents());
request.setCustomAgentsLocalOnly(
resolveCustomAgentsLocalOnly(config.getCustomAgentsLocalOnly().orElse(null), mode));
Expand Down Expand Up @@ -301,6 +302,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
config.getIncludeSubAgentStreamingEvents().ifPresent(request::setIncludeSubAgentStreamingEvents);
request.setMcpServers(config.getMcpServers());
request.setMcpOAuthTokenStorage(config.getMcpOAuthTokenStorage());
request.setAuthClientIdMetadataUrl(config.getAuthClientIdMetadataUrl());
request.setCustomAgents(config.getCustomAgents());
request.setCustomAgentsLocalOnly(
resolveCustomAgentsLocalOnly(config.getCustomAgentsLocalOnly().orElse(null), mode));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ public final class CreateSessionRequest {
@JsonProperty("mcpOAuthTokenStorage")
private String mcpOAuthTokenStorage;

@JsonProperty("authClientIdMetadataUrl")
private String authClientIdMetadataUrl;

@JsonProperty("envValueMode")
private String envValueMode;

Expand Down Expand Up @@ -588,6 +591,16 @@ public void setMcpOAuthTokenStorage(String mcpOAuthTokenStorage) {
this.mcpOAuthTokenStorage = mcpOAuthTokenStorage;
}

/** Gets the host OAuth client metadata URL. @return the metadata URL */
public String getAuthClientIdMetadataUrl() {
return authClientIdMetadataUrl;
}

/** Sets the host OAuth client metadata URL. @param url the metadata URL */
public void setAuthClientIdMetadataUrl(String url) {
this.authClientIdMetadataUrl = url;
}

/** Gets MCP environment variable value mode. @return the mode */
public String getEnvValueMode() {
return envValueMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class ResumeSessionConfig {
private Boolean includeSubAgentStreamingEvents;
private Map<String, McpServerConfig> mcpServers;
private String mcpOAuthTokenStorage;
private String authClientIdMetadataUrl;
private List<CustomAgentConfig> customAgents;
private DefaultAgentConfig defaultAgent;
private String agent;
Expand Down Expand Up @@ -1401,6 +1402,29 @@ public ResumeSessionConfig setMcpOAuthTokenStorage(String mcpOAuthTokenStorage)
return this;
}

/**
* Gets the OAuth Client ID Metadata Document URL identifying the host.
*
* @return the metadata URL, or {@code null} if not set
*/
public String getAuthClientIdMetadataUrl() {
return authClientIdMetadataUrl;
}

/**
* Sets the OAuth Client ID Metadata Document URL identifying the host for MCP
* authorization. Re-supply the same host identity used when the session was
* created.
*
* @param authClientIdMetadataUrl
* the metadata URL
* @return this config for method chaining
*/
public ResumeSessionConfig setAuthClientIdMetadataUrl(String authClientIdMetadataUrl) {
this.authClientIdMetadataUrl = authClientIdMetadataUrl;
return this;
}

/**
* Gets the custom agent configurations.
*
Expand Down Expand Up @@ -2074,6 +2098,8 @@ public ResumeSessionConfig clone() {
copy.streaming = this.streaming;
copy.includeSubAgentStreamingEvents = this.includeSubAgentStreamingEvents;
copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null;
copy.mcpOAuthTokenStorage = this.mcpOAuthTokenStorage;
copy.authClientIdMetadataUrl = this.authClientIdMetadataUrl;
copy.customAgents = this.customAgents != null ? new ArrayList<>(this.customAgents) : null;
copy.defaultAgent = this.defaultAgent;
copy.agent = this.agent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ public final class ResumeSessionRequest {
@JsonProperty("mcpOAuthTokenStorage")
private String mcpOAuthTokenStorage;

@JsonProperty("authClientIdMetadataUrl")
private String authClientIdMetadataUrl;

@JsonProperty("envValueMode")
private String envValueMode;

Expand Down Expand Up @@ -814,6 +817,16 @@ public void setMcpOAuthTokenStorage(String mcpOAuthTokenStorage) {
this.mcpOAuthTokenStorage = mcpOAuthTokenStorage;
}

/** Gets the host OAuth client metadata URL. @return the metadata URL */
public String getAuthClientIdMetadataUrl() {
return authClientIdMetadataUrl;
}

/** Sets the host OAuth client metadata URL. @param url the metadata URL */
public void setAuthClientIdMetadataUrl(String url) {
this.authClientIdMetadataUrl = url;
}

/** Gets MCP environment variable value mode. @return the mode */
public String getEnvValueMode() {
return envValueMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class SessionConfig {
private Boolean includeSubAgentStreamingEvents;
private Map<String, McpServerConfig> mcpServers;
private String mcpOAuthTokenStorage;
private String authClientIdMetadataUrl;
private List<CustomAgentConfig> customAgents;
private DefaultAgentConfig defaultAgent;
private String agent;
Expand Down Expand Up @@ -1053,6 +1054,28 @@ public SessionConfig setMcpOAuthTokenStorage(String mcpOAuthTokenStorage) {
return this;
}

/**
* Gets the OAuth Client ID Metadata Document URL identifying the host.
*
* @return the metadata URL, or {@code null} if not set
*/
public String getAuthClientIdMetadataUrl() {
return authClientIdMetadataUrl;
}

/**
* Sets the OAuth Client ID Metadata Document URL identifying the host for MCP
* authorization. When unset, no host identity is supplied.
*
* @param authClientIdMetadataUrl
* the metadata URL
* @return this config instance for method chaining
*/
public SessionConfig setAuthClientIdMetadataUrl(String authClientIdMetadataUrl) {
this.authClientIdMetadataUrl = authClientIdMetadataUrl;
return this;
}

/**
* Gets the custom agent configurations.
*
Expand Down Expand Up @@ -2203,6 +2226,8 @@ public SessionConfig clone() {
copy.streaming = this.streaming;
copy.includeSubAgentStreamingEvents = this.includeSubAgentStreamingEvents;
copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null;
copy.mcpOAuthTokenStorage = this.mcpOAuthTokenStorage;
copy.authClientIdMetadataUrl = this.authClientIdMetadataUrl;
copy.customAgents = this.customAgents != null ? new ArrayList<>(this.customAgents) : null;
copy.defaultAgent = this.defaultAgent;
copy.agent = this.agent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,27 @@
assertFalse(cleared.contains("embeddingCacheStorage"));
}

@Test
void jackson_createRequestAuthClientIdMetadataUrlSerializedAndOmitted() throws Exception {
var url = "https://example.com/oauth/client-metadata.json";
var configured = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setAuthClientIdMetadataUrl(url));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
assertTrue(MAPPER.writeValueAsString(configured).contains("\"authClientIdMetadataUrl\":\"" + url + "\""));

var unset = SessionRequestBuilder.buildCreateRequest(new SessionConfig());
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
assertFalse(MAPPER.writeValueAsString(unset).contains("authClientIdMetadataUrl"));
}

@Test
void jackson_resumeRequestAuthClientIdMetadataUrlSerializedAndOmitted() throws Exception {
var url = "https://example.com/oauth/client-metadata.json";
var configured = SessionRequestBuilder.buildResumeRequest("session-id",
new ResumeSessionConfig().setAuthClientIdMetadataUrl(url));
assertTrue(MAPPER.writeValueAsString(configured).contains("\"authClientIdMetadataUrl\":\"" + url + "\""));

var unset = SessionRequestBuilder.buildResumeRequest("session-id", new ResumeSessionConfig());
assertFalse(MAPPER.writeValueAsString(unset).contains("authClientIdMetadataUrl"));
}

@Test
void jackson_infiniteSessionConfigClearedFieldsOmitted() throws Exception {
var cfg = new InfiniteSessionConfig();
Expand Down
Loading
Loading