Port from openai/codex#39615: the authorization server discovered for an
MCP server can change (protected-resource metadata edit, server
migration, DNS takeover). Without binding, Hermes would send the stored
refresh token to whatever issuer the server now advertises — handing a
long-lived credential to a different authorization server.
- HermesTokenStorage records hermes_issuer alongside cached tokens
(stripped before OAuthToken.model_validate; never sent on the wire).
- Both provider classes (tools/mcp_oauth.py legacy path and
tools/mcp_oauth_manager.py managed path) stamp the discovered issuer
on every token save and enforce the binding on _initialize.
- On mismatch: refresh token is stripped from memory and disk; the
unexpired access token keeps working; full re-auth happens at expiry.
- Legacy token files without an issuer adopt the current one once
(no forced re-login for existing installs — deliberate divergence
from Codex, which requires reauth).
Validated: 12 new tests + 163 existing MCP OAuth tests green; sabotage
run confirms the new tests fail without the enforcement; E2E against
the real manager provider class with a temp HERMES_HOME confirms
mismatch strips and match preserves.