Commit Graph

27 Commits

Author SHA1 Message Date
teknium1 e609efb06a fix(mcp): an adopting profile keeps its own trust policy for a shared MCP connection
Under gateway.multiplex_profiles a profile whose mcp_servers entry has the
same route and credentials as another profile's live connection adopts that
connection instead of opening its own. _same_server_route() compares only
the connection identity, so a `trust: untrusted` profile adopted a
`trust: full` profile's connection; _trust_gate_check() then resolved the
OWNER's connection key, read `full`, and let the untrusted profile run
write-capable tools without the approval prompt its config demands
(review of #108352, finding A; regression from ceaf622c6d, where the
name-keyed ledger let the last registrant's trust win instead).

`trust` is the consuming profile's policy, not a property of the
connection: _server_trust_levels is now keyed by the calling profile's own
key (recorded at its own registration and at adoption, dropped when its
overlay is removed), while readOnlyHint stays under the connection key
because it describes the server's tools. Sharing the connection is still
allowed — only the gate is per profile.
2026-09-13 15:41:01 -07:00
teknium1 84bc93a0ff fix(mcp): read the OAuth auth type by name, not by tuple position
The salvaged refactor tested `ident[-1] == "oauth"`; once the mTLS fields
were appended to `_connection_identity()` the last element became the
frozen `client_key` and the cross-profile OAuth refusal silently stopped
firing (the live probe showed B adopting A's OAuth session again). Name
the auth-type accessor so the tuple can grow without moving the check.
2026-09-13 14:38:20 -07:00
kshitijk4poor b72055ece0 refactor(mcp): read the key's scope and the identity's auth type instead of re-deriving them
_key_scope(key) already answers 'owned by another scope'; rebuilding the
tuple via _server_key said the same thing less directly. The OAuth check
re-normalised both configs' auth strings although the normalised value is
the last element of _connection_identity, which the route test compares
anyway — one side suffices once identities match.
2026-09-13 14:38:20 -07:00
KoNit-K bbe4089405 fix(mcp): isolate mTLS connection identities 2026-09-13 14:38:20 -07:00
Amandeep Khurana 399238f2c2 fix(mcp): isolate OAuth connections by profile 2026-09-13 14:38:20 -07: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
kshitijk4poor 4ddbcbd35e fix(mcp): a profile only adopts a shared connection whose credentials match its own config
_same_server_route compared config_fingerprint alone, which by design excludes
env/headers/auth (so the schema cache survives a token rotation). Profile B with the
same URL but different headers/env therefore adopted profile A's live connection and
called tools as A. _connection_identity = route fingerprint + env + headers + auth mode,
used by both the adopt and the stale-removal checks.

Also collapses the three writers of _server_tool_scopes to two: the adoption loop
re-implemented in mcp_tool_discovery._select_new_servers is dropped —
register_connected_into_current_scope (which runs first in register_mcp_servers) is
the single adopter, and _register_candidates records scope for freshly registered tools.
2026-09-09 12:20:21 +05:30
joaomarcos d02edc2cbc fix(gateway): register shared MCP tools per profile 2026-09-09 12:20:21 +05:30
Teknium f4d4831e70 simplify(compat): tools/mcp_tool facade — drop 13 re-export blocks (127 names) + shutil re-import; siblings read sibling-defined names directly (_core kept for facade state) 2026-09-03 13:28:47 -07:00
Teknium e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium 0c62b297c6 review-fix(mcp): preserve MCP SDK 2 input schemas in the schema-cache write-through (salvaged from #91451 / #102129)
`_write_schema_cache` in the extracted owner tools/mcp_tool_registration.py
read `t.inputSchema` with a bare camelCase getattr. mcp 2.0 renamed the
model field to `input_schema` and kept `inputSchema` only as a serialization
alias, which pydantic does not apply to attribute access, so on SDK 2.x the
cache persisted `"inputSchema": {}` for every tool and a `lazy: true` server
registered from that cache was advertised to the model with all parameters
stripped. Pre-existing on BASE (tools/mcp_tool.py); fixed with the canonical
`mcp_field(t, "input_schema", "inputSchema")` helper, as both PRs do.

Adds the real-SDK regression test from #102129 (genuine `mcp.types.Tool`
driven through `_register_server_tools` -> cache write ->
`_register_from_cache_sync`), ported to the new module layout and isolated
from the module-global registration state.

Co-authored-by: mzkarami <mehrzad.karami@gmail.com>
Co-authored-by: lijinxiao1982 <120761624+lijinxiao1982@users.noreply.github.com>
2026-09-03 09:32:31 -07:00
Teknium bccfd1de26 refactor(tools): MCP handlers inline single-use render helpers, drop banners; registration foreign-owner log inlined; body blank squeeze 2026-09-03 01:31:18 -07:00
Teknium 58a993a54d refactor(tools): MCP group L docstring/comment compaction, >118-col fixes 2026-09-03 01:30:13 -07:00
Teknium a5bee5eaab refactor(tools): MCP registration cache stand-ins -> SimpleNamespace factory; string-fragment joins 2026-09-03 01:25:48 -07:00
Teknium 9458f4c27a refactor(tools): MCP group L docstring rewrap 2026-09-03 01:20:43 -07:00
Teknium 760ac0b609 refactor(tools): MCP handlers/errors/transport/config/health/registration/oauth-manager compaction — shared _dispatch, folded utility factory, auth-type cache tuple 2026-09-03 01:18:04 -07:00
Teknium c74c187f42 refactor(tools): MCP registration/common/handlers comprehension and predicate collapses 2026-09-02 23:56:55 -07:00
Teknium da18bcd338 refactor(tools): MCP run/discovery/registration log-call folding, _publish_error helper 2026-09-02 23:40:12 -07:00
Teknium 49c38ef10f refactor(tools): MCP docstring/comment compaction, status predicate, renderer tidy 2026-09-02 23:36:18 -07:00
Teknium 7f7fd4a533 refactor(tools): MCP discovery/run/handlers defensive collapse, shared helpers, blank squeeze 2026-09-02 23:07:18 -07:00
Teknium 47be9a3629 refactor(tools): MCP SDK type-family table, drop identity capability map, layout compaction 2026-09-02 22:39:46 -07:00
Teknium 5e4f30bc9d refactor(tools): compact MCP facade/discovery/handlers/run/registration/schema/cache modules 2026-09-02 22:28:12 -07:00
Teknium d9f6d9eda0 refactor(mcp): wrap overlong lines in reg/sampling/schema/agent 2026-09-02 16:01:24 -07:00
Teknium 97fbd7e232 refactor(mcp): table-drive elicitation answers, unify sampling response log, tighten collision diagnostics 2026-09-02 15:57:42 -07:00
Teknium f59fb2e292 refactor(mcp): tighten registration candidates and sampling handler; drop dead _extract_tool_result_text 2026-09-02 15:51:24 -07:00
Teknium d6ec8c3018 refactor(mcp): unify live/cache registration into one candidate loop; phase-split sampling and elicitation handlers 2026-09-02 15:38:42 -07:00
Teknium c1f8af1e86 refactor(tools/mcp): split mcp_tool.py into transport/lifecycle/schema/handlers/... sibling modules; compact watchdog and schema cache 2026-09-02 14:44:15 -07:00