Skip to content

proposal: ClientOptions.ResourceSubscriptionEndedHandler for observing resource-subscription termination #1284

Description

@jeremy

Motivation

#1283 fixes a bug where a resource subscription whose subscriptions/listen stream ends (server teardown, revoked resource, dropped connection) leaves a stale internal entry, so a bare re-Subscribe no-ops. That PR clears the subscription silently so re-Subscribe re-opens it, with no new exported API.

Silent cleanup, though, gives the application no way to observe that a subscription ended or to react to it — reconnect with its own backoff, surface a UI state, stop retrying. Today a client gets no signal at all when a live subscription dies underneath it.

Proposed API

type ClientOptions struct {
    // ...
    // ResourceSubscriptionEndedHandler is called (under SEP-2575) when the
    // subscriptions/listen stream backing a Subscribe ends for a reason other
    // than a client-initiated Unsubscribe or Close.
    ResourceSubscriptionEndedHandler func(context.Context, *ResourceSubscriptionEndedRequest)
}

// ResourceSubscriptionEndedRequest describes a resource subscription whose
// SEP-2575 subscriptions/listen stream has ended without a client-initiated
// Unsubscribe or Close.
type ResourceSubscriptionEndedRequest struct {
    URI      string // the resource whose subscription ended
    Err      error  // the terminating error, or nil on a graceful listen result
    Graceful bool   // whether it ended with a normal result rather than an error
}

Semantics

  • Opt-in. A nil handler keeps the mcp: clear a resource subscription when its listen stream ends #1283 behavior exactly: the subscription is cleared silently and a bare re-Subscribe re-opens it.
  • The SDK does not auto-resubscribe — a permanently revoked URI would hot-loop. The application owns retry/backoff and may call Subscribe again for the URI from within the handler. This is safe: the entry is cleared before the handler fires, and a per-subscription generation guard makes re-subscribe-from-callback (and an Unsubscribe→Subscribe race) well-defined.
  • Fires only for non-client-initiated ends; Unsubscribe and Close do not invoke it.

Cross-SDK context

The python and typescript SDKs already surface subscription-termination signals to callers (python raises on subscription loss / ends the async iteration; typescript reports a close reason distinguishing local vs graceful vs remote). This proposal gives Go clients an equivalent, idiomatic callback.

Process

This adds exported API (ResourceSubscriptionEndedHandler, ResourceSubscriptionEndedRequest), so per CONTRIBUTING it goes through the proposal process — filing ahead of a PR. A working implementation already exists (it was the original form of #1283, before the behavior fix was split out to land without new API); happy to open the PR once the shape is approved.

Related: #1283 (behavior-only fix, no new exported API).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions