fast-agent declares roots.listChanged and never sends the notification

Kind: gap · Status: current · Last verified: 2026-08-25 · Clients: fast-agent, opencode, goose · Revisions: 2025-11-25, 2026-07-28

_make_list_roots_callback returns None unless the server entry lists roots. When it does return a callback, the roots are built from server_config.roots, which is read once from config and never changes for the life of the process. Nothing in the repository sends notifications/roots/list_changed; the string does not appear.

The declaration is not fast-agent's. The SDK builds it:

roots = (
    # TODO: Should this be based on whether we
    # _will_ send notifications, or only whether
    # they're supported?
    types.RootsCapability(list_changed=True)
    if self._list_roots_callback is not _default_list_roots_callback
    else None
)

The TODO is upstream's own. There is no parameter a caller can pass to declare roots without the notification.

The same shape in the TypeScript SDK

opencode is in the same position for the same reason: it declares roots, answers roots/list, holds one root fixed at client creation, and never notifies. goose declares no roots capability at all and sends the notification anyway, which is the mirror image.

So across two reference SDKs and three clients, the listChanged flag on roots carries no information. A server that reads it and skips its own re-polling on the strength of it will hold a stale root list against every one of them.

What this means for the rmcp question

The open question in the plan was whether the rmcp roots gap is worth reporting upstream, and the answer had been blocked on finding a caller that both opts into roots and changes them. fast-agent is not that caller either. Three clients now declare roots.listChanged and none of them can send it, which makes the flag itself the more useful thing to report: two SDKs derive it from "a callback exists" rather than from "changes will be announced".

Sources