7 Commits
Author SHA1 Message Date
Teknium c7993457bc fix(mcp): spend tally survives multiplex rekey and never evicts; budget prompts are serialized per chat
- Budget prompts for one chat run one at a time and re-read the total, so a
  parallel call sees what the previous approval booked and identical prompts
  can't coalesce into one answer that releases several charges.
- Totals are never evicted (a forgotten total would renew a spent budget) and
  move with their policy when a process starts multiplexing.
- A native entry rejected for a malformed budget is not revived by a same-name
  package's looser one.
2026-10-21 04:12:11 -07:00
Teknium 4baddd2606 feat(mcp): declared per-tool costs and a per-chat spend limit for MCP servers
Servers (config.yaml tool_costs/spend_limit/cost_unit, portable packages
extensions...servers.<name>.costs/spend_limit/cost_unit) can declare what each
tool spends. The approval prompt names the cost and the chat's running total,
and a costed call that would take a chat past spend_limit asks even on a
trust: full server; unattended contexts refuse it without dispatching.

Tally is per chat (approval session key) per profile-scoped server key, counts
only dispatched calls, and refunds a call that never reached the server.
Without a declaration nothing changes. Idea: Adolanium/hermes-tripo.
2026-10-21 04:12:11 -07:00
Teknium 0319e113c9 fix(mcp): rekeyed launch-profile connections keep their owner and tools
rekey_unscoped_connections moved the bare key but left the owner in
_server_scope_keys as None and the tools in the process-global registry map.
After the switch, status read the server as configured, config pruning and
/reload-mcp (which select by owner == scope) skipped it, and teardown
deregistered from the wrong slot. The owner, the adoption set and the tool
registrations now move to the launch profile's scope with the key.
2026-10-20 08:42:13 -07:00
Teknium c5708b1003 fix(mcp): the default profile's live MCP servers stay connected across the in-place switch
A single-profile process keys MCP connections by bare name. After the switch,
serves_routed_profile() is true and lookups resolve (launch scope, name), so the
default profile's servers read as disconnected until a restart.
tools.mcp_tool_scope.rekey_unscoped_connections moves every bare-name ledger
entry under the launch scope at activation.
2026-10-20 08:42:13 -07:00
Maciej Lisiewski aff64c3eeb ci: UP007, UP022, UP028, UP031, UP041 linter fixes 2026-10-09 17:31:52 +02:00
Maciej Lisiewski d76cce4f11 ci: apply all automatic linter fixes available 2026-10-08 17:22:54 +02:00
Teknium ceaf622c6d fix(mcp): same-named MCP servers with different credentials connect per profile; owner /reload-mcp keeps adopters' tools
Under gateway.multiplex_profiles every connection ledger in tools/mcp_tool.py
(_servers, _server_scope_keys/_server_tool_scopes, connecting/error/cooldown
maps, the circuit breaker, lazy schema-cache configs, trust metadata) was keyed
by the bare server NAME. The common per-tenant layout — each profile names its
server `github`/`notion` with its own token — gave only the first profile a
connection: the second profile's register_mcp_servers saw the name as "already
connected", refused to adopt it (different credentials, 4ddbcbd35e), and left
the profile silently tool-less with a healthy-looking `configured` status
(#106005 Bug 1/2, #91654). Siblings of the same bug: profile A's failing `x`
put profile B's healthy `x` into A's 10-minute connect cooldown and A's open
circuit breaker short-circuited B's calls; toolsets._resolve_toolset_memo was
not scope-keyed, so B resolved A's `mcp-<server>` tool names.

Keys are now the connection key from the new tools/mcp_tool_scope.py: the bare
name outside a multiplexer (single-profile processes are unchanged) and
(owner_scope, name) under one. Call-time lookups (_resolve_server_key) prefer
the calling scope's own connection, then a shared connection it adopted, so
identical-route profiles still share one subprocess. _select_new_servers,
the cooldown/breaker/trust maps, lazy registration and get_mcp_status all read
and write through the composite key; teardown resolves a task's key by
identity (the MCP loop has no profile context). The toolset memo key includes
registry.current_scope_key().

An owner's scoped /reload-mcp tore down its connection and, with it, every
adopting profile's tool overlay; nothing re-ran the adopters' discovery until
they reloaded. shutdown_mcp_servers(scope=) now records the orphaned adopters
and register_mcp_servers re-registers them under their own home + secret scope
once the owner's rediscovery pass completes.

Docs: multi-profile-gateways.md states the per-profile connection rule.

Fixes #106005
Fixes #91654
Co-authored-by: Bergmann89 <info@bergmann89.de>
Co-authored-by: Izzy-Gottz <srulynj@gmail.com>
2026-09-11 15:27:23 -07:00