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.
Website
This website is built using Docusaurus, a modern static website generator.
Installation
yarn
Local Development
yarn start
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
Build
yarn build
This command generates static content into the build directory and can be served using any static contents hosting service.
Deployment
Using SSH:
USE_SSH=true yarn deploy
Not using SSH:
GIT_USER=<Your GitHub username> yarn deploy
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the gh-pages branch.
Diagram Linting
CI runs ascii-guard to lint docs for ASCII box diagrams. Use Mermaid (````mermaid`) or plain lists/tables instead of ASCII boxes to avoid CI failures.