Overview
Our MCP client (tools/mcp_tool.py) currently supports HTTP transport authentication only via static headers (e.g., Authorization: Bearer sk-...). A growing number of MCP servers — including ml.ink (#495), and potentially future services — use OAuth 2.1 with PKCE for authentication instead of (or in addition to) static API keys.
This enhancement would add OAuth 2.1 PKCE flow support to our MCP HTTP transport, allowing Hermes Agent to authenticate with MCP servers that require interactive OAuth without manual token management.
Discovered during: Research into ml.ink integration (#495). Confirmed the OAuth discovery endpoint works at https://mcp.ml.ink/.well-known/oauth-authorization-server and dynamic client registration succeeds at /oauth/register.
Research Findings
How MCP OAuth 2.1 PKCE Works
The MCP specification supports OAuth 2.1 with PKCE as a standard authentication mechanism for HTTP transport. The flow:
- Discovery — Client checks
<server>/.well-known/oauth-authorization-server for endpoints
- Dynamic Client Registration — Client registers at the
registration_endpoint (one-time)
- Authorization Code Flow —
- Client generates a code verifier + S256 challenge
- Opens browser to
authorization_endpoint with PKCE challenge
- User authenticates and grants permissions
- Server redirects to callback URL with authorization code
- Token Exchange — Client exchanges code + verifier at
token_endpoint for access + refresh tokens
- Token Usage — Client adds
Authorization: Bearer <access_token> to MCP requests
- Token Refresh — Client uses
refresh_token to get new tokens when expired
ml.ink's OAuth Implementation (Confirmed)
{
"authorization_endpoint": "https://mcp.ml.ink/oauth/authorize",
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"issuer": "https://mcp.ml.ink",
"registration_endpoint": "https://mcp.ml.ink/oauth/register",
"response_types_supported": ["code"],
"token_endpoint": "https://mcp.ml.ink/oauth/token",
"token_endpoint_auth_methods_supported": ["none"]
}
Dynamic client registration was tested and works:
curl -X POST https://mcp.ml.ink/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name": "hermes-agent", "redirect_uris": ["http://localhost:3000/callback"]}'
# Returns: {"client_id": "hermes-agent", ...}
Current State in Hermes Agent
What we have:
- MCP HTTP transport in
tools/mcp_tool.py supports static headers config:
mcp_servers:
remote_api:
url: "https://my-mcp-server.example.com/mcp"
headers:
Authorization: "Bearer sk-..."
- This works for API key auth but not OAuth flows
What we lack:
- No OAuth discovery (
/.well-known/oauth-authorization-server)
- No PKCE code challenge generation
- No browser-based authorization redirect
- No token storage/refresh logic
- No dynamic client registration
Implementation Plan
This is a Tool Enhancement (not a Skill)
This modifies tools/mcp_tool.py — the existing MCP client tool — to support an additional authentication method. Per CONTRIBUTING.md: this is core tool code that handles auth flows, token management, and credential storage, which all require deterministic processing logic.
What We'd Need
- OAuth discovery — Check for
/.well-known/oauth-authorization-server on first connection to URL-based MCP servers
- Dynamic client registration — Register
hermes-agent as an OAuth client if needed
- PKCE flow — Generate code verifier/challenge, open browser for auth, handle callback
- Token storage — Store access/refresh tokens securely (e.g.,
~/.hermes/mcp-tokens/<server>.json)
- Token refresh — Automatically refresh expired tokens before MCP requests
- Config syntax — New auth option in config.yaml:
mcp_servers:
ink:
url: "https://mcp.ml.ink/mcp"
auth: oauth # triggers OAuth PKCE flow on first use
Phased Rollout
Phase 1: Basic OAuth PKCE
- OAuth discovery endpoint detection
- PKCE code generation (S256)
- Browser-based authorization (open URL, listen on localhost callback)
- Token exchange and storage at
~/.hermes/mcp-tokens/
- Automatic Bearer header injection on MCP requests
auth: oauth config option
Phase 2: Token lifecycle
- Automatic token refresh using refresh_token grant
- Token expiry detection and proactive refresh
- Graceful re-auth if refresh token is revoked
- CLI command:
hermes mcp auth <server> for manual re-authentication
Phase 3: Headless/gateway support
- Device code flow for headless environments (Telegram/Discord gateway)
- Or: prompt user to visit URL and paste back auth code
- Token sharing across CLI and gateway sessions
Pros & Cons
Pros
- Unlocks OAuth MCP servers — ml.ink, and any future MCP server using OAuth, becomes seamlessly accessible
- Better security — OAuth tokens can be scoped and revoked; static API keys are all-or-nothing
- Standards-compliant — OAuth 2.1 PKCE is the MCP specification's recommended auth flow
- User convenience — No need to manually generate and paste API keys
Cons / Risks
- Complexity — OAuth PKCE adds ~200-300 lines of code to mcp_tool.py, plus a localhost HTTP server for the callback
- Browser dependency — CLI environments need
webbrowser.open() which may not work in all contexts (SSH, Docker, headless)
- Gateway challenge — Telegram/Discord users can't easily do browser-based OAuth. Fallback to API key auth is necessary.
- Token storage security — Tokens stored on disk need appropriate permissions (chmod 600)
Open Questions
- Priority — API key auth works for ml.ink and most MCP servers. Is OAuth support urgent, or can it wait?
- Headless flow — For gateway/SSH users, should we support device code flow, or just fall back to API key auth?
- Callback server — Should the localhost callback be a simple
http.server handler, or use a more robust approach?
- Token storage location —
~/.hermes/mcp-tokens/ vs ~/.hermes/auth.json (where we already store Nous Portal OAuth tokens)?
References
Overview
Our MCP client (
tools/mcp_tool.py) currently supports HTTP transport authentication only via static headers (e.g.,Authorization: Bearer sk-...). A growing number of MCP servers — including ml.ink (#495), and potentially future services — use OAuth 2.1 with PKCE for authentication instead of (or in addition to) static API keys.This enhancement would add OAuth 2.1 PKCE flow support to our MCP HTTP transport, allowing Hermes Agent to authenticate with MCP servers that require interactive OAuth without manual token management.
Discovered during: Research into ml.ink integration (#495). Confirmed the OAuth discovery endpoint works at
https://mcp.ml.ink/.well-known/oauth-authorization-serverand dynamic client registration succeeds at/oauth/register.Research Findings
How MCP OAuth 2.1 PKCE Works
The MCP specification supports OAuth 2.1 with PKCE as a standard authentication mechanism for HTTP transport. The flow:
<server>/.well-known/oauth-authorization-serverfor endpointsregistration_endpoint(one-time)authorization_endpointwith PKCE challengetoken_endpointfor access + refresh tokensAuthorization: Bearer <access_token>to MCP requestsrefresh_tokento get new tokens when expiredml.ink's OAuth Implementation (Confirmed)
{ "authorization_endpoint": "https://mcp.ml.ink/oauth/authorize", "code_challenge_methods_supported": ["S256"], "grant_types_supported": ["authorization_code", "refresh_token"], "issuer": "https://mcp.ml.ink", "registration_endpoint": "https://mcp.ml.ink/oauth/register", "response_types_supported": ["code"], "token_endpoint": "https://mcp.ml.ink/oauth/token", "token_endpoint_auth_methods_supported": ["none"] }Dynamic client registration was tested and works:
Current State in Hermes Agent
What we have:
tools/mcp_tool.pysupports staticheadersconfig:What we lack:
/.well-known/oauth-authorization-server)Implementation Plan
This is a Tool Enhancement (not a Skill)
This modifies
tools/mcp_tool.py— the existing MCP client tool — to support an additional authentication method. Per CONTRIBUTING.md: this is core tool code that handles auth flows, token management, and credential storage, which all require deterministic processing logic.What We'd Need
/.well-known/oauth-authorization-serveron first connection to URL-based MCP servershermes-agentas an OAuth client if needed~/.hermes/mcp-tokens/<server>.json)Phased Rollout
Phase 1: Basic OAuth PKCE
~/.hermes/mcp-tokens/auth: oauthconfig optionPhase 2: Token lifecycle
hermes mcp auth <server>for manual re-authenticationPhase 3: Headless/gateway support
Pros & Cons
Pros
Cons / Risks
webbrowser.open()which may not work in all contexts (SSH, Docker, headless)Open Questions
http.serverhandler, or use a more robust approach?~/.hermes/mcp-tokens/vs~/.hermes/auth.json(where we already store Nous Portal OAuth tokens)?References
https://mcp.ml.ink/.well-known/oauth-authorization-servertools/mcp_tool.py