hermes - 💡(How to fix) Fix Feature request: `mcp_pre_request(server_name, request)` plugin hook [1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#28757Fetched 2026-05-20 04:02:14
View on GitHub
Comments
1
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4closed ×1commented ×1

Root Cause

Per-user auth state is dynamic — it has to be re-read on every request because the user-identity is set by the inbound Slack/Telegram message and the token can be refreshed mid-session. Static config can't express that.

Fix Action

Fix / Workaround

Per-user OAuth token injection. We run a Slack-facing agent where each Slack user holds a personal OAuth token for a particular MCP server. When the agent makes a tool call, the outbound MCP request must carry that user's token (not the install-wide service-account token, not the next-in-cache token). Today we accomplish this by patching the httpx.Auth provider in tools/mcp_oauth_manager.py to read a per-user runtime file just before adding the Authorization header. This works but is fragile — the anchor moves on every mcp_oauth_manager.py refactor, and we have no way to express it as a clean plugin.

Dispatch site: tools/mcp_oauth_manager.py's _ResolverTokenAuth.async_auth_flow (or wherever the SDK lets you intercept — there's also httpx.AsyncBaseTransport.handle_async_request if the SDK doesn't naturally expose this). First non-None return wins; subsequent hooks see the chained request. Failures are logged but don't break the request (parallel to how pre_tool_call and pre_gateway_dispatch already work).

Current workaround

Code Example

def my_hook(server_name: str, request: httpx.Request, **kwargs) -> Optional[httpx.Request]:
    # Return None  → use the request unchanged
    # Return a modified httpx.Request → use that one instead
    ...
RAW_BUFFERClick to expand / collapse

Feature request: mcp_pre_request(server_name, request) plugin hook

Problem

Hermes' MCP client (built on the Anthropic MCP SDK's httpx transport) doesn't expose a hook for modifying outbound MCP HTTP requests at the time-of-send. Authentication-bearing data — bearer tokens, custom headers, request signing — has to be wired into the SDK's httpx.Auth object, which lives inside tools/mcp_oauth_manager.py's cache[key].provider. There's no first-class way for a plugin to influence the headers Hermes sends to a given MCP server.

Use case

Per-user OAuth token injection. We run a Slack-facing agent where each Slack user holds a personal OAuth token for a particular MCP server. When the agent makes a tool call, the outbound MCP request must carry that user's token (not the install-wide service-account token, not the next-in-cache token). Today we accomplish this by patching the httpx.Auth provider in tools/mcp_oauth_manager.py to read a per-user runtime file just before adding the Authorization header. This works but is fragile — the anchor moves on every mcp_oauth_manager.py refactor, and we have no way to express it as a clean plugin.

We're not the only operators doing this. Anyone running Hermes in a multi-tenant context with per-user MCP auth (a workplace SaaS-management agent, a customer-facing MCP gateway, a per-user IDE) will end up with the same shape.

Proposal

A pre_mcp_request plugin hook called at MCP-request-send time:

def my_hook(server_name: str, request: httpx.Request, **kwargs) -> Optional[httpx.Request]:
    # Return None  → use the request unchanged
    # Return a modified httpx.Request → use that one instead
    ...

Dispatch site: tools/mcp_oauth_manager.py's _ResolverTokenAuth.async_auth_flow (or wherever the SDK lets you intercept — there's also httpx.AsyncBaseTransport.handle_async_request if the SDK doesn't naturally expose this). First non-None return wins; subsequent hooks see the chained request. Failures are logged but don't break the request (parallel to how pre_tool_call and pre_gateway_dispatch already work).

The hook name is bikesheddable — mcp_pre_request, pre_mcp_call, transform_mcp_request all work.

Why a plugin hook (not a config setting)

Per-user auth state is dynamic — it has to be re-read on every request because the user-identity is set by the inbound Slack/Telegram message and the token can be refreshed mid-session. Static config can't express that.

Current workaround

Source patch on tools/mcp_oauth_manager.py that wraps _ResolverTokenAuth(httpx.Auth) and reads a per-user runtime file (~/.hermes/runtime/active/<email>.json) on every request. Anchor: the entry.provider = ...; return entry.provider line in the cache-population path. The patch re-applies on every Hermes upgrade via a small idempotent .sh script. The fingerprint of the target file is pinned and checked at install time to detect upstream drift.

Offer

If the API surface above is acceptable, happy to open a PR adding mcp_pre_request to the plugin lifecycle and migrating one downstream use case (a per-user-OAuth example) as the test. The implementation is small — a few lines in hermes_cli/plugins.py's invoke_hook registry plus a thin call site.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix Feature request: `mcp_pre_request(server_name, request)` plugin hook [1 comments, 1 participants]