Reported issues for MCP Hangar
Pod holds 24 of 335 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.
Back to MCP Hangar.
Most discussed
T4 feat(core): gate mid-flight task consent (input_required / tasks/update)
Parent
Follow-up to the closed WS-4 epic #302.
Status
Serving path shipped in 2.0.0rc1; blocked on python-sdk#3005 for a native SEP-2663 backend.
This issue's previous body said it was "blocked by the relay-only task decision captured in ADR-008 ... no supported input_required / tasks/update serving path exists to gate." That is out of date in both halves:
- ADR-008's relay-only absolutism was superseded in pa
Read the thread · 2026-06-30 · closed · 6 comments
T8 feat(core): best-effort tools/list_changed per session (INKR 4)
Status
Blocked pending an upstream MCP library capability. Reconfirmed against MCP 1.26.0 during the 2026-07-08 review.
Why It Is Blocked
Hangar has no supported access to active ServerSession instances, so it cannot send a per-session tools/list_changed notification safely:
StreamableHTTPSessionManagerexposes no public session lookup or session enumeration API.ServerSessionis created and destroyed inside the library run loop; the manager transport map does not retain it
Read the thread · 2026-06-23 · closed · 6 comments
[BUG] Mcp-Param-* validation skips are unobserved, and some skip the check while the call still proceeds
Short summary
SEP-2243's header–body agreement is what stops a caller from routing on one value
while executing another. The SDK enforces it for Mcp-Param-*, but the
enforcement is fail-open by design: if the pre-dispatch tools/list cannot
produce a schema, validation is skipped and the call proceeds.
Rewritten after 2.14.0. The original text said "Hangar does not currently
route or authorize on Mcp-Param-*", so the worst case was a deployment that put
an intermediary in front of
Read the thread · 2026-08-24 · closed · 5 comments
Streamable HTTP sessions are per-replica, so a coordinated deployment is not one server to a client
A Streamable HTTP session lives in one replica's memory. Three replicas of one coordinated gateway — one database, one management lease, one identity by ADR-020 — are therefore three different servers to any MCP client, and the Service in front of them hands each request to whichever one it feels like.
Measured on core 2.5.2, replicaCount: 3, through the chart's own ClusterIP
Service. Each attempt is one client doing initialize → tools/list →
session teardown:
15 attempts through t
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/877) · 2026-08-11 · closed · 5 comments
### feat(core): migrate to mcp SDK v2 beta (2.0.0b2) on mcp2
## Verdict (audit, WS-1)
**The stateless alignment was hand-rolled on SDK v1 (1.28.x), not on v2 beta.** So migrating to the mcp SDK v2 beta is a *separate* workstream — and the largest one in this preparation. Confirmed in code:
- Pin is `mcp>=1.28.1`; `uv.lock` resolves **1.28.1**; venv runs 1.28.1.
- `src/mcp_hangar/protocol.py` is a leaf module that **manually** builds the reverse-DNS `_meta` keys (`io.modelcontextprotocol/protocolVersion` / `/clientInfo` / `/capabilities`) and merges dict
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/547) · 2026-07-20 · closed · 5 comments
### Proxy an upstream's prompts and resources
Split from the prompts/resources parent.
Carry an upstream's prompts and resources through the gateway, as we already do
for tools.
The reference server is a good target: 4 prompts (one with argument
completions), 7 resources, 2 resource templates, plus subscriptions.
Design questions that make this more than plumbing, and that mirror decisions
already made for tools:
- **Naming.** Tools are flat and per-tenant in `front_door`, prefixed in the
registry view. Prompt names and resource URIs
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/889) · 2026-08-11 · closed · 4 comments
### front_door: two replicas serve different tools/list to the same tenant
Split from the `front_door` cold-projection parent.
Discovery is per-instance, and the flat projection is built from it, so two
replicas of one gateway answer the same tenant differently. Warm one of two,
then ask the same question with the same token:
replica A 18 tools replica B 0 tools
and through the Service, alternating:
18 tools 0 tools 18 tools
This is worse than a slow start. A client that lists tools, caches the answer
and calls one later can be told t
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/886) · 2026-08-11 · closed · 4 comments
### Approval resolution was unauthorized and misattributed (fixed in 2.0.0rc4; 1.6.x not patched)
Approval resolution was not authorized, and the identity recorded against a decision came from the caller. Both are fixed in **2.0.0rc4**, published today.
This is disclosed after the fix was available rather than before, which is why it is only being written now — the correction has been on `mcp2` for a while, but nobody could install it until this release.
## What was wrong
**1. `approval:resolve` was defined, granted, and never checked.**
The permission existed in full — declared in `auth
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/665) · 2026-07-30 · closed · 4 comments
## Most recent
### [TASK] make the per-tenant resource_link cap configurable
Part of #1139, depends on the per-tenant capacity task. **This is the `feat:` part of the parent** -- it adds a config key, so it is what makes the release a minor rather than a patch. Land it separately for that reason.
## Goal
`_MAX_LINKS` is a module constant referenced by nothing else (verified: no other module names it), so an operator whose tenants legitimately hand out more links than the cap has no lever.
## Change
* Make the per-tenant cap configurable, defaulting to today's value s
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1146) · 2026-08-29 · closed · 0 comments
### [TASK] count resource_link evictions
Part of #1139, depends on the per-tenant capacity task.
## Goal
Nothing counts link evictions, so the defect in the parent issue is invisible to an operator: the victim's `resources/list` simply gets shorter. This is the shape #1128 argued against on the egress path -- an outcome with no record.
## Change
`mcp_hangar_resource_links_evicted_total`, incremented wherever an eviction happens (per-tenant cap and tenant-map cap alike).
**It is appended in `_register_all_metrics` or it does not ex
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1145) · 2026-08-29 · closed · 0 comments
### [TASK] bound the handed-out resource_link map per tenant, not globally
Part of #1139. **Do this one first**; the metric and the config key in the same parent build on it.
## Goal
`_links` (`src/mcp_hangar/fastmcp_server/resource_link_read_through.py:81`) is one process-wide `OrderedDict` capped at `_MAX_LINKS = 4096` (`:79`) and evicted oldest-first across all tenants (`_remember`, `:152-158`). One tenant's traffic therefore expires another's links.
## Change
An outer map `tenant_id -> OrderedDict[uri, (server, block)]`:
* each inner map capped at `_MAX_LINKS_
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1144) · 2026-08-29 · closed · 0 comments
### [TASK] remove_group_policy / remove_member_policy remove every kind
Part of #1138. Independent of the other task in this parent -- **no production caller reaches either function today**, which is why nothing has broken and why the first caller would inherit the bug.
## Goal
`remove_group_policy` (`tool_access_resolver.py:304-312`) pops `(group_id, DEFAULT_KIND)` only. `remove_member_policy` (`:314-324`) pops `(group_id, member_id, DEFAULT_KIND)` and the `DEFAULT_KIND` cache entry only. Both are kind-incomplete in exactly the way #1034 fixed for servers.
## Ch
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1143) · 2026-08-29 · closed · 0 comments
### [TASK] unloading an mcp_server also retires its per-tenant policies
Part of #1138.
## Goal
`remove_mcp_server_policy` (`src/mcp_hangar/domain/services/tool_access_resolver.py:283-298`) clears `_mcp_server_policies` and never touches `_standalone_member_policies` (declared `:94`, written from `tool_access.member.<tenant>`, read on the resolve path `:425`). An id reloaded after a hot-unload inherits its predecessor's per-tenant rules.
Verified on 2.16.0, through the public API:
```python
r.set_standalone_member_policy("billing", "tenant:a", ToolAccessPolicy(de
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1142) · 2026-08-29 · closed · 0 comments
### [TASK] the withdraw/restore endpoints accept a kind, and a resource URI can reach them
Part of #1137, depends on the events task in the same parent.
## Goal
`server/api/admin_tools.py` never accepts or passes a `kind`, so both endpoints are pinned to the registry's default. A prompt or resource cannot be withdrawn at runtime, the response says it was, and -- verified -- the write withdraws the **same-named tool** instead.
## Two traps, both verified on 2.16.0
**1. A resource URI cannot ride in the current route.** The path is `/{server:str}/{tool:str}/withdraw`, and Starlette'
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1141) · 2026-08-29 · closed · 0 comments
### [TASK] ToolWithdrawn / ToolRestored carry the kind that was withdrawn
Part of #1137. **Do this one first** -- #1137's endpoint work depends on the field existing.
## Goal
`ToolWithdrawn` and `ToolRestored` (`src/mcp_hangar/domain/events/approvals.py:71`, `:90`) carry `tenant_id`, `mcp_server`, `tool`, `schema_version` -- and no kind. A consumer rebuilding a `LOCAL_VIEW` from the log therefore replays a prompt withdrawal as a tool one, and will keep doing so even after the endpoint is fixed.
## Change
1. Both events grow `kind: str = "tool"`, documented in the
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1140) · 2026-08-29 · closed · 0 comments
### [BUG] the handed-out resource_link map is keyed per tenant and capped globally, so one busy tenant evicts another's links
## Short summary
`_links` (`fastmcp_server/resource_link_read_through.py:81`) remembers every
`resource_link` the front door hands out, keyed `(tenant_id, projected_uri)`. The keys
are properly tenant-scoped -- there is no cross-tenant read here. The *capacity* is not:
one `OrderedDict` per process, `_MAX_LINKS = 4096` (`:79`), evicting oldest-first across
all tenants at once (`_remember`, `:152-158`).
So the lifetime of a link handed to tenant A is set by tenant B's traffic. A tenant that
han
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1139) · 2026-08-29 · closed · 1 comment
### [BUG] hot-unload retires an mcp_server's own policies and leaves its per-tenant ones, so the next server under that id inherits them
## Short summary
#1034 made policy removal kind-complete, on an argument written into the docstring
that is still there today (`domain/services/tool_access_resolver.py:283-291`):
removing a single kind would leave the others behind for an id that is free to be
loaded again, "so a later server inheriting that id would be governed by its
predecessor's rules".
The same argument applies one axis over, and there the loop does not run.
`remove_mcp_server_policy` (`:283-298`) clears `_mcp_server_poli
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1138) · 2026-08-29 · closed · 2 comments
### [BUG] runtime withdrawal is tool-only over REST: a prompt or resource can be withdrawn from a file and from nowhere else
## Short summary
2.13.0 made the withdrawal overlay kind-aware. `ToolProjectionRegistry.withdraw()`
(`application/read_models/tool_projection.py:328`) and `.restore()` (`:360`) take
`kind: str = "tool"`, the overlay is keyed `(mcp_server, kind, name)` (`:345`, `:381`),
and `is_withdrawn()` (`:415`) reads all three kinds. The admin API never followed:
`server/api/admin_tools.py` neither accepts a kind from the request nor passes one,
so both endpoints are pinned to the default.
`withdrawn_promp
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1137) · 2026-08-29 · closed · 1 comment
### [BUG] approval argument redaction is root-only: a secret one level down is persisted and served verbatim
## Short summary
`_sanitize_arguments` (`approvals/service.py:60`) is what stands between a tool call's
arguments and (a) the persisted approval record and (b) the REST DTO served to every
`approval:read` holder. It has two passes: redact by key name, then redact by value
shape.
**The key-name pass runs only at the top level.** A secret one level down -- under the
same key names the function lists -- is stored and served verbatim. The value-shape
pass does not cover it, because a plain passwor
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1130) · 2026-08-29 · closed · 1 comment
### [BUG] no verdict names the policy that produced it; `policy_id` is documented for audit and never set
## Short summary
`PolicyEvaluationResult` carries a `policy_id` field whose docstring says
"Identifier of the policy that made the decision (**for audit**)". Nothing ever sets
it, and nothing ever reads it. No verdict Hangar emits -- authorization, egress,
approval, digest -- records which policy, or which version of it, produced the answer.
This is the same class as the four metrics that were defined and never registered
(#1059): a field that reads as an implemented audit capability and is an
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1129) · 2026-08-29 · closed · 1 comment
### [BUG] an Enforce-mode egress deny leaves no record; Audit mode leaves three
## Short summary
An L7 egress policy in `Audit` mode records its would-be verdict as a domain event and
a metric. The same policy in `Enforce` mode, actually refusing the call, records
**nothing**: no event, no metric, and a `logger.debug` line that a default deployment
does not emit. The reason the policy computed is dropped on the way out.
The enforcing verdict is the least auditable one in the product, and it is the one an
auditor, a SIEM export, or a second team reading a refusal will ask
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1128) · 2026-08-29 · closed · 0 comments
### [BUG] the fuzz targets run blind: the code under test is never instrumented
The fuzz targets execute, pass CI, and search **blind**. From the
ClusterFuzzLite run on #1111
([log](https://github.com/mcp-hangar/mcp-hangar/actions/runs/33177126278)):
INFO: Instrumenting invariants #2 INITED cov: 4 ft: 4 corp: 1/1b #1048576 pulse cov: 4 ft: 4 corp: 1/1b #1645204 DONE cov: 4 ft: 4 corp: 1/1b Done 1645204 runs in 61 second(s)
**1.6 million inputs and coverage never moved off 4 features.** The corpus
never grew past the one empty seed. That is not fuzzing,
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1112) · 2026-08-28 · closed · 1 comment
### [BUG] merge() drops a deny_list, so a denied tool comes back allowed
Found while writing the precedence fuzz target for #1103, on the first handful
of inputs. This is the class the epic predicted that target could find: a
**policy bypass**, not a crash.
## Reproduction
```python
from mcp_hangar.domain.value_objects.tool_access_policy import ToolAccessPolicy
broader = ToolAccessPolicy(allow_list=["*"], deny_list=["drop_*"]) # server scope
narrower = ToolAccessPolicy(allow_list=["*"]) # group scope
broader.is_tool_allowed("drop_db")
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1106) · 2026-08-28 · closed · 0 comments
### test: an atheris harness over the policy evaluator
Part of #1101. Follows #1102.
## Goal
Fuzzing looks for an input where the decision function **does not produce a
decision**. Not "the answer should be X" assertions -- invariants whose breach
is by definition a security bug.
## Targets
| Module | Input | Invariant |
|---|---|---|
| `domain/policies/egress_l7.py::evaluate` | `tool_name`, `arguments: Any`, policy, headers | Always returns a `Decision`. Never propagates. |
| `domain/policies/egress_l7.py::L7Policy.from_dict` | `dict[str, Any]`
[Read the thread](https://github.com/mcp-hangar/mcp-hangar/issues/1103) · 2026-08-28 · closed · 0 comments
The remaining reports are on [the project's issue tracker](https://github.com/mcp-hangar/mcp-hangar/issues).