Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,55 @@ public enum ResourceWriteMode
Insert,
}

/// <summary>Identifies the consumer of an authentication token.</summary>
[JsonConverter(typeof(AuthenticateTargetKindConverter))]
public readonly struct AuthenticateTargetKind : IEquatable<AuthenticateTargetKind>
{
private readonly string? _value;

/// <summary>Wraps a raw wire value — including one this build does not recognize.</summary>
/// <param name="value">The raw wire string.</param>
public AuthenticateTargetKind(string value)
{
_value = value;
}

/// <summary>The raw wire value.</summary>
public string Value => _value ?? string.Empty;

public static readonly AuthenticateTargetKind McpServer = new AuthenticateTargetKind("mcpServer");

/// <inheritdoc />
public bool Equals(AuthenticateTargetKind other) => string.Equals(Value, other.Value, StringComparison.Ordinal);

/// <inheritdoc />
public override bool Equals(object? obj) => obj is AuthenticateTargetKind other && Equals(other);

/// <inheritdoc />
public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value);

/// <inheritdoc />
public override string ToString() => Value;

/// <summary>Ordinal equality over the raw wire value.</summary>
public static bool operator ==(AuthenticateTargetKind left, AuthenticateTargetKind right) => left.Equals(right);

/// <summary>Ordinal inequality over the raw wire value.</summary>
public static bool operator !=(AuthenticateTargetKind left, AuthenticateTargetKind right) => !left.Equals(right);
}

/// <summary>Reads and writes <see cref="AuthenticateTargetKind"/> as its raw wire string, preserving unrecognized values.</summary>
internal sealed class AuthenticateTargetKindConverter : JsonConverter<AuthenticateTargetKind>
{
/// <inheritdoc />
public override AuthenticateTargetKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new AuthenticateTargetKind(reader.GetString() ?? throw new JsonException("AuthenticateTargetKind expects a JSON string."));

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, AuthenticateTargetKind value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.Value);
}

// ─── Command Payloads ─────────────────────────────────────────────────

/// <summary>Opens a TCP connection from the host to a destination on the host's network.
Expand Down Expand Up @@ -1639,6 +1688,20 @@ public sealed record DispatchActionParams
public required StateAction Action { get; init; }
}

/// <summary>An MCP server identified by host-published session and customization state.</summary>
public sealed record McpServerAuthenticateTarget
{
public AuthenticateTargetKind Kind { get; init; } = AuthenticateTargetKind.McpServer;

/// <summary>Session URI whose `SessionState.customizations` contains the MCP server.</summary>
public required string Session { get; init; }

/// <summary>The server's session-unique `McpServerCustomization.id`, echoed from the
/// customization carrying `McpServerAuthRequiredState` or from
/// `ToolCallAuthRequiredState.contributor.customizationId`.</summary>
public required string CustomizationId { get; init; }
}

/// <summary>Pushes a Bearer token for a protected resource. The `resource` field MUST
/// match a protected-resource identifier the client has discovered from the
/// server — whether declared statically in `AgentInfo.protectedResources`,
Expand Down Expand Up @@ -1693,6 +1756,23 @@ public sealed record AuthenticateParams
/// token.</summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? Scopes { get; init; }

/// <summary>Optional consumer of the token. Clients SHOULD include this when answering
/// a specific MCP server's `McpServerAuthRequiredState` or
/// `ToolCallAuthRequiredState` challenge, and omit it for provider-level
/// resources (`AgentInfo.protectedResources` or `AuthRequired` -32007 data).
///
/// When present, the host MUST apply the token only to that MCP server in
/// that session, never to sibling servers, other sessions, or agent-level
/// resources, even if they share the resource and scopes. `resource` MUST
/// still equal that server's advertised `McpAuthRequirement.resource.resource`.
/// The host MUST reject an unknown session or customization, or a
/// customization that is not an MCP server, with `InvalidParams` (-32602).
///
/// Without a target, resource/scope matching is unchanged; the token may
/// satisfy every matching pending challenge. Older hosts ignore this field.</summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AuthenticateTarget? Target { get; init; }
}

/// <summary>Result of the `authenticate` command.
Expand Down Expand Up @@ -2091,6 +2171,34 @@ public sealed record FetchAutomationRunsResult
{
}

// ─── AuthenticateTarget Union ─────────────────────────────────────────

/// <summary>AuthenticateTarget identifies the consumer of an authentication token.</summary>
[JsonConverter(typeof(AuthenticateTargetConverter))]
public sealed class AuthenticateTarget : AhpUnion
{
/// <summary>Creates an empty AuthenticateTarget (no active variant).</summary>
public AuthenticateTarget() { }

/// <summary>Creates a AuthenticateTarget wrapping the given variant value.</summary>
public AuthenticateTarget(object? value) : base(value) { }
}

/// <summary>System.Text.Json converter for the AuthenticateTarget discriminated union.</summary>
internal sealed class AuthenticateTargetConverter : UnionConverter<AuthenticateTarget>
{
public AuthenticateTargetConverter()
: base(
discriminator: "kind",
variants: new Dictionary<string, Type>
{
["mcpServer"] = typeof(McpServerAuthenticateTarget),
},
allowUnknown: true)
{
}
}

// ─── ReconnectResult Union ────────────────────────────────────────────

/// <summary>ReconnectResult is the result of the `reconnect` command.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ namespace Microsoft.AgentHostProtocol;
[JsonSerializable(typeof(AnnotationsUpdatedAction))]
[JsonSerializable(typeof(AuthenticateParams))]
[JsonSerializable(typeof(AuthenticateResult))]
[JsonSerializable(typeof(AuthenticateTarget))]
[JsonSerializable(typeof(AuthenticateTargetKind))]
[JsonSerializable(typeof(AuthRequiredErrorData))]
[JsonSerializable(typeof(AuthRequiredParams))]
[JsonSerializable(typeof(AuthRequiredReason))]
Expand Down Expand Up @@ -262,6 +264,7 @@ namespace Microsoft.AgentHostProtocol;
[JsonSerializable(typeof(McpAuthRequiredReason))]
[JsonSerializable(typeof(McpAuthRequirement))]
[JsonSerializable(typeof(McpOAuthClient))]
[JsonSerializable(typeof(McpServerAuthenticateTarget))]
[JsonSerializable(typeof(McpServerAuthRequiredState))]
[JsonSerializable(typeof(McpServerCustomization))]
[JsonSerializable(typeof(McpServerCustomizationApps))]
Expand Down
103 changes: 103 additions & 0 deletions clients/go/ahptypes/commands.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ const (
ResourceWriteModeInsert ResourceWriteMode = "insert"
)

// Identifies the consumer of an authentication token.
type AuthenticateTargetKind string

const (
AuthenticateTargetKindMcpServer AuthenticateTargetKind = "mcpServer"
)

// ─── Command Payloads ─────────────────────────────────────────────────

// Opens a TCP connection from the host to a destination on the host's network.
Expand Down Expand Up @@ -1182,6 +1189,17 @@ type DispatchActionParams struct {
Action StateAction `json:"action"`
}

// An MCP server identified by host-published session and customization state.
type McpServerAuthenticateTarget struct {
Kind AuthenticateTargetKind `json:"kind"`
// Session URI whose `SessionState.customizations` contains the MCP server.
Session URI `json:"session"`
// The server's session-unique `McpServerCustomization.id`, echoed from the
// customization carrying `McpServerAuthRequiredState` or from
// `ToolCallAuthRequiredState.contributor.customizationId`.
CustomizationId string `json:"customizationId"`
}

// Pushes a Bearer token for a protected resource. The `resource` field MUST
// match a protected-resource identifier the client has discovered from the
// server — whether declared statically in `AgentInfo.protectedResources`,
Expand Down Expand Up @@ -1227,6 +1245,21 @@ type AuthenticateParams struct {
// Omit when the client doesn't track granted scopes separately from the
// token.
Scopes []string `json:"scopes,omitempty"`
// Optional consumer of the token. Clients SHOULD include this when answering
// a specific MCP server's `McpServerAuthRequiredState` or
// `ToolCallAuthRequiredState` challenge, and omit it for provider-level
// resources (`AgentInfo.protectedResources` or `AuthRequired` -32007 data).
//
// When present, the host MUST apply the token only to that MCP server in
// that session, never to sibling servers, other sessions, or agent-level
// resources, even if they share the resource and scopes. `resource` MUST
// still equal that server's advertised `McpAuthRequirement.resource.resource`.
// The host MUST reject an unknown session or customization, or a
// customization that is not an MCP server, with `InvalidParams` (-32602).
//
// Without a target, resource/scope matching is unchanged; the token may
// satisfy every matching pending challenge. Older hosts ignore this field.
Target *AuthenticateTarget `json:"target,omitempty"`
}

// Result of the `authenticate` command.
Expand Down Expand Up @@ -1761,6 +1794,76 @@ func (u ChatMoveDestination) MarshalJSON() ([]byte, error) {
return json.Marshal(u.Value)
}

// ─── AuthenticateTarget Union ─────────────────────────────────────────

// AuthenticateTarget identifies the consumer of an authentication token.
type AuthenticateTarget struct {
Value isAuthenticateTarget
}

// isAuthenticateTarget is the marker interface implemented by every
// concrete variant of AuthenticateTarget.
type isAuthenticateTarget interface{ isAuthenticateTarget() }

func (*McpServerAuthenticateTarget) isAuthenticateTarget() {}

// AuthenticateTargetUnknown carries an unrecognized AuthenticateTarget variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully.
type AuthenticateTargetUnknown struct {
Raw json.RawMessage
}

func (*AuthenticateTargetUnknown) isAuthenticateTarget() {}

// UnmarshalJSON decodes the variant indicated by the "kind" discriminator.
func (u *AuthenticateTarget) UnmarshalJSON(data []byte) error {
disc, _, err := readDiscriminator(data, "kind")
if err != nil {
return err
}
switch disc {
case "mcpServer":
var value McpServerAuthenticateTarget
if err := json.Unmarshal(data, &value); err != nil {
return err
}
u.Value = &value
default:
raw := make(json.RawMessage, len(data))
copy(raw, data)
u.Value = &AuthenticateTargetUnknown{Raw: raw}
}
return nil
}

// MarshalJSON encodes the active variant back to JSON.
func (u AuthenticateTarget) MarshalJSON() ([]byte, error) {
if unk, ok := u.Value.(*AuthenticateTargetUnknown); ok {
if len(unk.Raw) == 0 {
return []byte("null"), nil
}
return unk.Raw, nil
}
if u.Value == nil {
return []byte("null"), nil
}
data, err := json.Marshal(u.Value)
if err != nil {
return nil, err
}
var object map[string]json.RawMessage
if err := json.Unmarshal(data, &object); err != nil {
return nil, err
}
if object == nil {
return data, nil
}
switch u.Value.(type) {
case *McpServerAuthenticateTarget:
object["kind"] = json.RawMessage("\"mcpServer\"")
}
return json.Marshal(object)
}

// ─── ReconnectResult Union ────────────────────────────────────────────

// ReconnectResult is the result of the `reconnect` command.
Expand Down
Loading
Loading