Skip to content

feat(mcp): associate OAuth tokens with requesting user and surface consent in chat - #79449

Open
astraltrekkin wants to merge 5 commits into
NousResearch:mainfrom
astraltrekkin:feat/mcp-oauth-per-user
Open

astraltrekkin wants to merge 5 commits into
NousResearch:mainfrom
astraltrekkin:feat/mcp-oauth-per-user

Conversation

@astraltrekkin

@astraltrekkin astraltrekkin commented Aug 5, 2026 •

Copy link
Copy Markdown

What does this PR do?

Hermes historically stored one MCP OAuth token set per profile/server, so on a shared gateway User B could silently reuse User A's authorization. This PR adds an opt-in mcp.oauth.identity_mode: per_user boundary that scopes tokens and connections to the authenticated session user, fails closed when identity is missing, and surfaces headless OAuth consent URLs in the originating chat (with paste-back) instead of only the host terminal.

Default remains shared for single-operator CLI/profile compatibility.

Related Issue

Fixes #78174
Fixes #78169

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)

Changes Made

  • tools/mcp_oauth_identity.py — shared / per_user mode, session-derived user key, fail-closed missing identity, credential-selector arg stripping
  • tools/mcp_oauth.py / tools/mcp_oauth_manager.py — per-user token paths (mcp-tokens/by-user/<sanitized-key>/), provider cache keyed by user, wipe-all vs single-identity remove
  • tools/mcp_gateway_oauth.py — gateway consent flow, chat URL delivery, paste correlation, consent failure notify (mcp_oauth_consent_result)
  • tools/mcp_tool.py — per-user registry keys, lazy connect + gateway reauth, needs_reauth includes authorization_url when published
  • gateway/run.py / gateway/platforms/base.py — deliver consent / consent-result messages; OAuth paste bypass of active-session queue
  • hermes_cli/mcp_config.py / subcommands/mcp.py — hermes mcp login --user, hermes mcp logout [--user]
  • hermes_cli/web_server.py / login path — dashboard/CLI re-auth uses all_identities=False so by-user trees are not wiped
  • hermes_cli/config_defaults.py — mcp.oauth.identity_mode: shared
  • website/docs/user-guide/features/mcp.md — per-user mode, redirect_uri recommendation, logout/remove, notify-path desktop note
  • Tests: tests/tools/test_mcp_oauth_per_user_identity.py, test_mcp_gateway_oauth.py, test_mcp_tool_401_handling.py, tests/hermes_cli/test_mcp_config.py

How to Test

  1. Enable per-user mode in config.yaml:
    mcp:
      oauth:
        identity_mode: per_user
  2. From the worktree, run:
    export HERMES_PYTHON=/path/to/venv/bin/python   # if needed
    scripts/run_tests.sh \
      tests/tools/test_mcp_oauth_per_user_identity.py \
      tests/tools/test_mcp_gateway_oauth.py \
      tests/tools/test_mcp_oauth_manager.py \
      tests/tools/test_mcp_tool_401_handling.py \
      tests/hermes_cli/test_mcp_config.py \
      -q
  3. Manual / gateway (optional): two messaging users hit the same OAuth MCP — confirm separate mcp-tokens/by-user/ files, consent URL lands in the originating chat, and pasting User A's redirect on User B's session is rejected.
  4. CLI: hermes mcp logout <server> clears shared + all by-user tokens; --user <key> clears one identity. Dashboard/hermes mcp login must not wipe other users' tokens.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run scripts/run_tests.sh on the MCP OAuth-focused files above and they pass (full suite not re-run in this session)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS (darwin)

Documentation & Housekeeping

  • I've updated relevant documentation (website/docs/user-guide/features/mcp.md)
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A (key lives in config.yaml / config_defaults.py)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A (token paths use pathlib / Hermes home)
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A (identity is session ContextVar, not tool schema)

Screenshots / Logs

N/A — hermetic unit/integration coverage; no live Telegram/Discord E2E in CI for this PR.

astraltrekkin and others added 4 commits August 5, 2026 17:16
…78174)

Add mcp.oauth.identity_mode=per_user so gateway callers use isolated
token storage and MCP connections instead of silently sharing the
profile-scoped OAuth bearer. Shared mode remains the default.

Co-authored-by: Cursor <cursoragent@cursor.com>
Bare-name _servers lookups missed server@@user_key entries after
identity_mode=per_user, breaking OAuth recovery and availability checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
Deliver authorize URLs via the session notify path when a messaging user
hits an OAuth MCP without tokens, and accept pasted redirect callbacks
so headless consent no longer depends on the Hermes host terminal (NousResearch#78169).

Co-authored-by: Cursor <cursoragent@cursor.com>
Add wipe-all/logout revocation, gateway reauth with authorization_url on
needs_reauth, actionable consent failure chat replies, and harden tool
args so models cannot select another user's credentials. Keep dashboard
and login re-auth from clearing every by-user token tree.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard tool/mcp MCP client and OAuth area/auth Authentication, OAuth, credential pools needs-decision Awaiting maintainer decision before any implementation sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 5, 2026
Mocks for handle_401 / invalidate_if_disk_changed must accept the
per-user kwarg so recovery paths still run under identity_mode wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>

This branch has not been deployed

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

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/mcp MCP client and OAuth type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Associate MCP OAuth authorization with the requesting user [Feature]: Surface headless MCP OAuth consent URLs in the originating platform

2 participants