fix(mcp): scope MCP status, inventory and /reload-mcp to the request profile
Two profiles configuring a same-named MCP server shared one connection and
the MCP settings panel showed another profile's runtime ("Active, N tools"
next to an empty inventory); /reload-mcp stopped every profile's servers and
rediscovered from the process-default config.
- api.profiles._set_hermes_home() is the single writer of the process-profile
home: it records get_process_profile_home() and hands it to Hermes Agent's
hermes_constants.pin_process_hermes_home when available, at startup and on
switch_profile(process_wide=True), so the per-turn HERMES_HOME mirror no
longer makes every turn look like the process profile (bare-name,
cross-profile MCP keys) and the anchor cannot drift from HERMES_HOME.
- Run get_mcp_status/registry reads and /reload-mcp inside
api.mcp_runtime.mcp_runtime_scope(): request profile's Hermes-home override
and secret scope (root included), no os.environ mutation, restored on exit.
- One ledger predicate, ledger_key_serves_view() (owned via
_server_scope_keys/key scope, or adopted via _server_tool_scopes), is shared
by status, inventory and the reload summary. The Agent's launch-profile view
is process-wide and name-indexed, so filter_runtime_status_to_view()
rebuilds connected/tools/status/error from the entries serving the profile:
the root profile never shows a routed profile's connection (nor lets its own
parked failed-spawn task borrow a routed profile's live same-named one).
- Inventory lists only tools in the profile's own registry slot for servers it
configures; responses carry runtime_scope.
- Fail closed when the agent's routing does not match the resolved profile:
hide runtime data (runtime_scope "unavailable", shown as a notice in the MCP
panel, 14 locales) and refuse /reload-mcp.
- /reload-mcp shuts down only the profile's own ledger names (parked
included), drops its own connect cooldown/error entries so a failed spawn is
retried (the wildcard cleared every cooldown; a scoped shutdown only clears
the live keys it tears down), rediscovers under the profile's home and
counts live connections only. The bare wildcard remains for agents without
profile-scoped MCP.
- Signature introspection is centralized (accepts_keywords) and falls back to
the bare call instead of blanking every status; ledger key helpers resolve
to the Agent's tools.mcp_tool_scope when present; tools.mcp_tool is
resolved by full name so stubbed agents still work.
- ARCHITECTURE.md §4.10 and docs/troubleshooting.md document the boundary.
- Tests: agent-free contract (models adoption, parked tasks, cooldowns and the
process-wide root view) and real-Agent integration (fake stdio MCP server,
skips without hermes-agent + mcp SDK).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d1e6f812a
commit
90b814f673
12 files changed
+1628
-58
No files matched your search
@@ -475,6 +475,52 @@ pointer-removed cases to `tests/test_agent_compat.py`. The resolver is
|
||||
compatibility-only: delete it, and import directly from the new homes, once the WebUI
|
||||
stops supporting Agents that predate the split.
|
||||
|
||||
### 4.10 MCP Runtime Profile Boundary
|
||||
|
||||
Hermes Agent owns the in-process MCP ledger. It keys a connection by
|
||||
`(profile_home_key, name)` and registers its tools in that profile's registry overlay
|
||||
only when the calling task serves a *routed* profile: the context-local Hermes-home
|
||||
override differs from the process home. Otherwise the connection uses the bare server
|
||||
name and the global registry slot, which belong to the process profile.
|
||||
|
||||
- `api.profiles._set_hermes_home()` is the single writer of the process-profile home:
|
||||
startup (`init_profile_state()`) and `switch_profile(process_wide=True)` both go
|
||||
through it, so `get_process_profile_home()` and the Agent pin
|
||||
(`hermes_constants.pin_process_hermes_home()`, when the Agent provides it) are updated
|
||||
in the same step as `HERMES_HOME`. Streaming turns still mirror their profile into
|
||||
`os.environ['HERMES_HOME']` for legacy readers; without the pin, that mirror makes a
|
||||
turn's own profile look like the process profile, so same-named servers of different
|
||||
profiles share one bare-name connection.
|
||||
- `/api/mcp/servers`, `/api/mcp/tools`, `/api/notes/sources` and `/reload-mcp` run
|
||||
their Agent calls inside `api.mcp_runtime.mcp_runtime_scope()`, which binds the
|
||||
request profile's home and secret scope (root profile included) without touching
|
||||
`os.environ`. Status, tool count and inventory are read from one scope; the inventory
|
||||
lists only tools in that profile's own registry slot for servers it configures.
|
||||
- A connection *serves* a profile when that profile owns it (`_server_scope_keys`, else
|
||||
the scope in the key) or adopted it (`_server_tool_scopes`, an identical shared
|
||||
connection). `api.mcp_runtime.ledger_key_serves_view()` is the one predicate for
|
||||
status, inventory and the reload summary. The Agent's launch-profile view (scope
|
||||
`None`) is process-wide, so `filter_runtime_status_to_view()` downgrades
|
||||
`get_mcp_status()` rows of servers the root profile does not serve to `configured`
|
||||
rather than showing a routed profile's connection, tool count or connect error.
|
||||
- The scope is trusted only when the Agent's routing decision matches the profile WebUI
|
||||
resolved. When it cannot be confirmed (for example a same-profile turn's mirror on an
|
||||
Agent without the pin), status and inventory withhold runtime data
|
||||
(`runtime_scope: "unavailable"`, shown as a notice in the MCP panel) and
|
||||
`/reload-mcp` refuses instead of resetting another owner's connection.
|
||||
- `/reload-mcp` calls `shutdown_mcp_servers(scope=..., names=...)` with the profile's
|
||||
own scope and the names of its live connections; `scope=None` without `names` is the
|
||||
process-wide wildcard and is used only with Agents that predate profile-scoped MCP
|
||||
(`runtime_scope: "legacy_process"`). A scoped shutdown only clears connect backoff for
|
||||
the live keys it tears down, so WebUI also drops the profile's own cooldown/error
|
||||
entries (`clear_profile_connect_cooldowns()`) before rediscovery: a server that failed
|
||||
to spawn is retried by the reload, as the wildcard did, without touching another
|
||||
owner's backoff.
|
||||
|
||||
Status and inventory stay passive: they never start or probe an MCP server. Ledger key
|
||||
helpers resolve to Hermes Agent's `tools.mcp_tool_scope` when present so the key shape
|
||||
has one owner; the local fallbacks only cover Agents that predate that module.
|
||||
|
||||
---
|
||||
|
||||
## 5. Frontend Architecture: Current State
|
||||
|
||||
Reference in new issue
Block a user