SEP-2624: Interceptors for the Model Context Protocol#2624
Open
Degiorgio wants to merge 3 commits into
Open
Conversation
Degiorgio
force-pushed
the
SEP-1763
branch
2 times, most recently
from
April 22, 2026 10:21
7da4402 to
0fca7d1
Compare
Degiorgio
marked this pull request as ready for review
April 22, 2026 11:23
Degiorgio
force-pushed
the
SEP-1763
branch
4 times, most recently
from
April 22, 2026 11:45
adaefca to
52679f2
Compare
This was referenced May 10, 2026
|
Adding a concrete persistent-memory-server use case from Memoria, a local MCP memory server for coding agents. The interceptor/use-case shape that seems important for memory tools is that one MCP response can contain two very different output classes:
For this kind of server, tool-level annotations are not enough by themselves. The risky sequence is often cross-call, for example: |
sambhav
pushed a commit
to modelcontextprotocol/experimental-ext-interceptors
that referenced
this pull request
Jun 16, 2026
Interceptor SEP has been moved from modelcontextprotocol/modelcontextprotocol#1763 to modelcontextprotocol/modelcontextprotocol#2624 . This PR updates the SEP references over the codebase and import revised SEP Signed-off-by: Ukjae Jeong <[email protected]>
mapyr
added a commit
to mcp-hangar/mcp-hangar
that referenced
this pull request
Jul 16, 2026
…gelog link, stdio flag) (#492) - README: note the 1.5 fail-closed non-loopback bind (`--unsafe-no-auth` / auth block) so the `--http` quickstart doesn't fail as written (#485). - release.yml: point the release-notes Documentation link at the canonical mcp-hangar.io, not the stale github.io domain (#486). - CHANGELOG: render the upstream MCP `modelcontextprotocol/modelcontextprotocol#2624` reference as code so it stops auto-linking to this repo's issues (#487). - Dockerfile.test-provider: add `-i` to the stdio run example (container exits on EOF without it) (#482). Co-authored-by: Claude Opus 4.8 <[email protected]>
Signed-off-by: Kurt Degiorgio <[email protected]>
Separate interceptor capability (server-declared type/hooks/configSchema) from execution policy (invoker-declared). Chain entries gain an optional InterceptorOverrides (failOpen, priorityHint, mode, timeoutMs, and narrowing-only hooks) that takes precedence over server-declared defaults, enabling per-deployment control (e.g. audit->active promotion, SLO-based failOpen/timeouts, hook narrowing) without modifying interceptor servers. Also switch capability negotiation to the io.modelcontextprotocol/interceptors extensions namespace and bump the example protocolVersion to 2025-06-18. Signed-off-by: Kurt Degiorgio <[email protected]>
mapyr
added a commit
to mcp-hangar/mcp-hangar
that referenced
this pull request
Jul 20, 2026
* feat(cli): add `auth bootstrap-admin` command (durable initial admin) (#463) Exposes `mcp-hangar auth bootstrap-admin --config PATH --principal PRINCIPAL` (#451): the escape hatch for a deployment with API-key auth enabled and anonymous access off, where no admin yet exists to create the first one through the protected API. The command reuses the server's own `bootstrap_auth()` storage composition (never an in-memory store) and performs the single atomic claim from #456. Fails closed: auth must be enabled, non-anonymous, on a durable backend (sqlite/postgresql -- memory/event_sourcing refused); a second run exits nonzero without mutating storage. No credential is printed -- the grant is a global admin *role* for an existing external principal that authenticates via its own identity. While testing this end-to-end (#452), found and fixed a durability bug in the SQLite role store: `initialize()` seeded built-in roles with `INSERT OR REPLACE`, which DELETEs the conflicting row, and `role_assignments.role_name` has `ON DELETE CASCADE`. So every store re-initialization (every process start / `bootstrap_auth`) cascade-wiped all assignments to built-in roles -- the bootstrapped admin lost its admin role on the next restart. The seed and `add_role` now upsert in place via `ON CONFLICT(name) DO UPDATE`, matching the PostgreSQL store. Tests (#452): CLI success/refusal/no-credential/no-mutation coverage, plus a store-level regression that the admin assignment survives re-init. Full suite 4452 passed; mypy + ruff clean. Closes #451 Closes #452 Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * docs(core): fix CONTRIBUTING dev setup to build from the repo root (#465) The Quick Start told contributors to `cd packages/core` and run `pip install -e ".[dev]"` + `pytest`, but that path no longer works after the monorepo -> multi-repo split. `packages/core/` has no `mcp_hangar` source -- its pyproject declares `[tool.hatch.build.targets.wheel] packages = ["mcp_hangar"]`, but that package directory does not exist there -- so the editable install fails and pytest finds no suite. The Python core lives at the repo root: `src/mcp_hangar/` (root pyproject, `packages = ["src/mcp_hangar"]`, v1.4.0), `tests/` (`testpaths = ["tests"]`, 236 test files), and working `make setup` / `make test` targets. This matches the authoritative guide in mcp-hangar/docs, which uses the root flow. Drop the `cd packages/core` / `cd ../..` dance so the documented commands run from the repo root, and correct the structure table (Core is at `src/mcp_hangar/`, not `packages/core/`). How tested: confirmed the root wheel target `packages = ["src/mcp_hangar"]` resolves to an existing `src/mcp_hangar/__init__.py`, while the old `packages/core` target `["mcp_hangar"]` points at a directory that does not exist (editable install cannot find the package). `make setup`/`make test` and root `pytest` (testpaths=["tests"]) are the working targets. Closes #464 Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * docs(core): use mcp-hangar namespace in the kubernetes discovery example (#468) Rename mcp-system -> mcp-hangar in examples/kubernetes/discovery-source.yaml to match the product namespace and the operator default (mcp-hangar/mcp-hangar-operator#28). Example manifest only. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * ci(ci): upgrade ambient setuptools/wheel before pip-audit (#469) The Dependency Audit job failed on setuptools 79.0.1 (PYSEC-2026-3447, fixed in 83.0.0) -- the setuptools that ships with the runner's Python. The project builds with hatchling and does not depend on setuptools, so the finding is purely the ambient build tool. Upgrade pip/setuptools/wheel before running pip-audit so the audited environment is current. No project dependency or runtime change. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * test(tests): implement the T2 live auth harness (Keycloak/OIDC) (#470) Fill in the tier-2 live-verification stubs. Add conftest fixtures that bring up the example Keycloak (examples/auth-keycloak/) and front it with `mcp-hangar serve --http` in OIDC front-door mode, reusing the exported realm (issuer, mcp-hangar client, group->role mapping). All fixtures skip cleanly when Docker/Keycloak/the binary are absent, so this stays opt-in and CI-safe. Implements the four T2 claims black-box over HTTP: unauthenticated front-door call is denied (401), a valid OIDC token authenticates, a well-formed token from an untrusted issuer is rejected, and the PRM endpoint advertises the trusted issuer (RFC 9728). Runs via the live-verify workflow (dispatch + nightly). Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * fix(security): read the Authorization header case-insensitively in JWTAuthenticator (#472) * fix(security): read the Authorization header case-insensitively in JWTAuthenticator OIDC/JWT bearer auth was fully broken over the HTTP surface. The auth middleware normalizes header names to lowercase (ASGI headers already are), but JWTAuthenticator.supports()/authenticate() looked up "Authorization" (capitalized), so supports() returned False, the middleware routed no authenticator, and every valid token was rejected as auth_method:none. ApiKeyAuthenticator already handled both cases; JWT now does too via a shared case-insensitive helper. Add a unit regression. Caught by the T2 live auth harness (its valid-token case now passes). Fixes #471. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * docs(core): changelog entry for the JWT header-casing fix (#471) --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * chore(release): release 1.5.0 (#288) Co-authored-by: mcp-hangar-release-bot[bot] <283430731+mcp-hangar-release-bot[bot]@users.noreply.github.com> * docs(repo): note auth/RBAC and the admin bootstrap in the README capabilities (#473) 1.5.0 makes auth first-class (OIDC front-door now works over serve --http; mcp-hangar auth bootstrap-admin grants the initial admin). Add an Auth & RBAC bullet to What you get so the overview reflects it. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * fix(repo): add basic client scope to keycloak example realm so tokens carry sub (#476) Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> * chore(infra): remove the divergent duplicate install script (#478) * docs(docs): point ADR governance at the real ADR location (#479) ADR_AGENTS.md still claimed to govern a docs/adr/ directory in this repo, but the ADRs moved to the mcp-hangar/docs repo's top-level adr/ as part of the ADR-009 multi-repo split. Every location reference and relative ADR link in the file was stale or dead. Repoint them at the real repo and paths without changing any governance rule. Co-authored-by: Claude Opus 4.8 <[email protected]> * ci(ci): use the shared reusable PR validators (#480) Replace the full local copies of pr-title, pr-body, and branch-name with thin callers into mcp-hangar/.github (reusable), matching the helm-charts pilot. Removes scripts/check_pr_body.sh since its logic is now inlined in the reusable pr-body workflow. Refs mcp-hangar/.github#1 * chore(core): retire the Hangar Cloud connector, /agent/policy endpoint, and agent RBAC role (#490) * chore(server): retire the Hangar Cloud connector, /agent/policy endpoint, and agent RBAC role Removes the dead Hangar Cloud connector coupling from the core Python package: - Delete src/mcp_hangar/cloud/ (CloudConfig, CloudClient, CloudConnector, buffer) and its tests. - Delete the /agent/policy REST endpoint (server/api/agent_policy.py) and its route mount, plus its tests. - Remove the --cloud-key/--cloud-url CLI flags and CLIConfig fields. - Remove the cloud connector wiring from lifecycle.run_server() (_resolve_cloud_config, start/stop/event-bus subscription). - Remove the "agent" RBAC role (AGENT_ROLE / ROLE_AGENT) from domain/value_objects/security.py, auth/roles.py, and the domain/security re-export stubs. PERMISSION_POLICY_WRITE is kept (shared with the admin role). - Drop the now-obsolete K1 security regression test that targeted /agent/policy, and the agent-role RBAC test. - Reword stale hangar-agent/hangar-cloud references in tools.py's docstring, AGENTS.md's directory tree, and UPGRADE.md. PolicyPushRejected is intentionally KEPT in domain/events.py, infrastructure/persistence/event_serializer.py, and the event serialization fuzz test: it's a durable, versioned domain event that must remain decodable for event-store replay, even though its only producer (the removed agent_policy.py) is gone. ToolAccessResolver, ToolAccessPolicy, and all tenant/front-door/OIDC functionality are untouched. Co-Authored-By: Claude Opus 4.8 <[email protected]> * chore(core): drop stale /agent/policy comment in the batch executor Follow-up to the connector removal: the approval-gate comment still pointed at the removed POST /api/agent/policy endpoint. The policy is config-driven. Co-Authored-By: Claude Opus 4.8 <[email protected]> * docs(core): add CHANGELOG entry for the agent/cloud retirement Satisfies the changelog gate (scripts/check_changelog.sh) with a real [Unreleased] Removed entry rather than skip-changelog, since this removes user-facing surface (CLI flags, an endpoint, an RBAC role). Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Co-authored-by: Claude Opus 4.8 <[email protected]> * docs(repo): fix four review findings (auth note, release domain, changelog link, stdio flag) (#492) - README: note the 1.5 fail-closed non-loopback bind (`--unsafe-no-auth` / auth block) so the `--http` quickstart doesn't fail as written (#485). - release.yml: point the release-notes Documentation link at the canonical mcp-hangar.io, not the stale github.io domain (#486). - CHANGELOG: render the upstream MCP `modelcontextprotocol/modelcontextprotocol#2624` reference as code so it stops auto-linking to this repo's issues (#487). - Dockerfile.test-provider: add `-i` to the stdio run example (container exits on EOF without it) (#482). Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): resolve discovery/config review findings (#481, #483, #484) (#493) - http/sse container discovery now prefers the published host-port binding (NetworkSettings.Ports) over the internal bridge-network IP, falling back to the bridge IP only when no host binding exists. The documented deployment runs Hangar on the host, which cannot reach a container's internal bridge IP but can reach its published port. - Container/stdio-mode discovery is unaffected (still gateway-launched via its own image, no network reachability needed). - A discovery-only config.yaml (discovery.enabled: true, no top-level mcp_servers) now loads by defaulting mcp_servers to {} instead of raising; configs with neither a static mcp_servers section nor a configured discovery source still fail loudly. - The expected/transient "container has no IP" discovery skip now logs at debug instead of warning. Co-authored-by: Claude Opus 4.8 <[email protected]> * ci(core): run the core test suite on dependency changes (uv.lock, packages) (#494) ci-core.yml was path-filtered to src/**, tests/**, and the root pyproject.toml, so dependency-only PRs never ran the test suite: dependabot's `mcp` SDK bumps touch packages/core/pyproject.toml + uv.lock, neither of which matched, so #491 (bump the MCP protocol SDK 1.25.0 -> 1.27.2) went all-green without a single test running. A protocol-SDK bump is exactly the change that must run tests. Add `packages/**` and `uv.lock` to the push/pull_request path filters. Co-authored-by: Claude Opus 4.8 <[email protected]> * chore(deps): bump mcp from 1.25.0 to 1.27.2 in /packages/core (#491) --- updated-dependencies: - dependency-name: mcp dependency-version: 1.27.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * ci(core): remove the vestigial no-op enterprise-boundary check (#495) The `enterprise-boundary` job stopped checking anything when the enterprise tier was merged into `src/mcp_hangar/` and license gating was removed (#196) — it now only echoes "Enterprise boundary check removed". It remained a required status check on branch protection, so every PR waited on a job that validates nothing. Drop the job and its `required-check` dependency; `required-check` still runs (needs detect-changes) as the branch-protection aggregate. NOTE: `enterprise-boundary` must also be dropped from the branch-protection required-status-checks contexts, or PRs will hang waiting for a check that no longer runs. Co-authored-by: Claude Opus 4.8 <[email protected]> * chore(deps): bump mcp from 1.27.2 to 1.28.1 in /packages/core (#496) Bumps [mcp](https://github.com/modelcontextprotocol/python-sdk) from 1.27.2 to 1.28.1. - [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases) - [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md) - [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.27.2...v1.28.1) --- updated-dependencies: - dependency-name: mcp dependency-version: 1.28.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * fix(security): require mcp>=1.28.1 (CVE-2026-59950) (#497) The dependabot bumps (#491, #496) moved the uv.lock to mcp 1.28.1, but the published dependency constraint stayed `mcp>=1.0.0` in both pyproject.toml files. The lock only pins the dev/CI environment — a user installing mcp-hangar from PyPI resolves `mcp` themselves and could still get a vulnerable `mcp < 1.28.1`. Raising the floor to `>=1.28.1` makes the published package actually require the patched SDK, so cutting a release ships a real fix rather than a hollow one. CVE-2026-59950 (HIGH, CVSS 7.6): MCP Python SDK WebSocket server transport accepts handshakes without Host/Origin validation (CWE-346/1385), enabling cross-origin / DNS-rebinding connections. Fixed in mcp 1.28.1. Co-authored-by: Claude Opus 4.8 <[email protected]> * chore(release): release 1.5.1 (#477) Co-authored-by: mcp-hangar-release-bot[bot] <283430731+mcp-hangar-release-bot[bot]@users.noreply.github.com> * chore(core): sync the vestigial packages/core version to 1.5.1 (#499) packages/core/pyproject.toml still declared version 1.0.2 while the actual package (built from the root pyproject) is 1.5.1 — release-please only tracks the root manifest, so this file drifted far behind. Sync it so the number isn't misleading. (The real fix is removing this vestigial manifest, tracked in #466.) Co-authored-by: Claude Opus 4.8 <[email protected]> * chore: remove the vestigial packages/core tree (monorepo split leftover) (#500) packages/core/ is a leftover from the pre-split monorepo layout. It cannot build (its pyproject targets `packages = ["mcp_hangar"]` but there is no mcp_hangar/ source dir under it — the code lives in src/mcp_hangar/, which the root pyproject builds and publishes). Nothing imports it, no CI/Makefile runs it, and its duplicate pyproject/uv.lock only confused Dependabot (the mcp CVE bump landed in this non-building manifest). Root Dependabot security scanning is unaffected — it auto-discovers the real root manifest. Also removes the now-dead scripts/verify-migration.sh (verified a long-finished migration; would fail today) and the stale packages/* CODEOWNERS entries (packages/core, and packages/operator + packages/helm-charts which moved to their own repos) plus the packages/core/dist gitignore line. The `* @mapyr` default already owns everything, so those specific entries were redundant too. Closes #466. Co-authored-by: Claude Opus 4.8 <[email protected]> * ci(repo): add dependabot version-update config (#502) Core had no dependabot.yml — only Dependabot security updates ran. Add weekly version-updates for the uv (Python) and github-actions ecosystems, with minor+patch grouped into one PR (majors stay individual for scrutiny), conventional-commit titles (chore(deps): / ci(deps):) so the reusable pr-title validator passes, and a bounded PR count. Track 1 of the change-management epic (#501). ci-core already runs the suite on these (uv.lock/pyproject in its path filter, #494). Co-authored-by: Claude Opus 4.8 <[email protected]> * ci(ci): run project-add on pull_request_target so Dependabot PRs get the project token (#517) GitHub withholds Actions secrets from workflows triggered by Dependabot on the pull_request event, so `secrets.PROJECT_AUTOMATION_TOKEN` was empty and add-to-project failed ("github-token not supplied") on every Dependabot PR. pull_request_target runs in the base context with secrets available; it is safe here because the workflow only adds the PR to the board by node id and never checks out or runs PR code. Co-authored-by: Claude Opus 4.8 <[email protected]> * ci(deps): bump docker/build-push-action from 6 to 7 (#504) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump DavidAnson/markdownlint-cli2-action from 19 to 24 (#506) Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 19 to 24. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/v19...v24) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: '24' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump docker/login-action from 3 to 4 (#505) Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump aquasecurity/trivy-action (#503) Bumps the github-actions group with 1 update in the / directory: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.35.0 to 0.36.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/v0.35.0...v0.36.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-version: 0.36.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump dependabot/fetch-metadata from 2 to 3 (#507) Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2 to 3. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v2...v3) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(ci): stop cancel-in-progress orphaning required validator checks (#518) * ci(ci): keep required validator checks from being cancelled * ci(ci): keep required validator checks from being cancelled * ci(ci): keep required validator checks from being cancelled * docs(repo): reposition README as MCP policy enforcement plane (#519) Tagline and Why paragraph move from 'control plane' to 'policy enforcement plane'; tagline now names what the code actually enforces (deterministic admission/egress policy, attributable audit, SIEM export) plus MIT/self-hosted/no-SaaS. Co-authored-by: Claude Opus 4.8 <[email protected]> * chore(deps): bump the python-minor-patch group with 14 updates (#508) Bumps the python-minor-patch group with 14 updates: | Package | From | To | | --- | --- | --- | | [pydantic](https://github.com/pydantic/pydantic) | `2.12.5` | `2.13.4` | | [prometheus-client](https://github.com/prometheus/client_python) | `0.24.1` | `0.25.0` | | [docker](https://github.com/docker/docker-py) | `7.1.0` | `7.2.0` | | [typer](https://github.com/fastapi/typer) | `0.24.1` | `0.27.0` | | [python-multipart](https://github.com/Kludex/python-multipart) | `0.0.31` | `0.0.32` | | [pytest](https://github.com/pytest-dev/pytest) | `9.0.3` | `9.1.1` | | [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) | `1.3.0` | `1.4.0` | | [ruff](https://github.com/astral-sh/ruff) | `0.14.10` | `0.15.22` | | [hypothesis](https://github.com/HypothesisWorks/hypothesis) | `6.151.9` | `6.156.6` | | [langfuse](https://github.com/langfuse/langfuse) | `4.0.1` | `4.14.0` | | [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python) | `1.40.0` | `1.44.0` | | [opentelemetry-sdk](https://github.com/open-telemetry/opentelemetry-python) | `1.40.0` | `1.44.0` | | [opentelemetry-exporter-otlp](https://github.com/open-telemetry/opentelemetry-python) | `1.40.0` | `1.44.0` | | [vulture](https://github.com/jendrikseipp/vulture) | `2.15` | `2.16` | Updates `pydantic` from 2.12.5 to 2.13.4 - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.12.5...v2.13.4) Updates `prometheus-client` from 0.24.1 to 0.25.0 - [Release notes](https://github.com/prometheus/client_python/releases) - [Commits](https://github.com/prometheus/client_python/compare/v0.24.1...v0.25.0) Updates `docker` from 7.1.0 to 7.2.0 - [Release notes](https://github.com/docker/docker-py/releases) - [Commits](https://github.com/docker/docker-py/compare/7.1.0...7.2.0) Updates `typer` from 0.24.1 to 0.27.0 - [Release notes](https://github.com/fastapi/typer/releases) - [Changelog](https://github.com/fastapi/typer/blob/master/docs/release-notes.md) - [Commits](https://github.com/fastapi/typer/compare/0.24.1...0.27.0) Updates `python-multipart` from 0.0.31 to 0.0.32 - [Release notes](https://github.com/Kludex/python-multipart/releases) - [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kludex/python-multipart/compare/0.0.31...0.0.32) Updates `pytest` from 9.0.3 to 9.1.1 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.3...9.1.1) Updates `pytest-asyncio` from 1.3.0 to 1.4.0 - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v1.3.0...v1.4.0) Updates `ruff` from 0.14.10 to 0.15.22 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.10...0.15.22) Updates `hypothesis` from 6.151.9 to 6.156.6 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.151.9...v6.156.6) Updates `langfuse` from 4.0.1 to 4.14.0 - [Release notes](https://github.com/langfuse/langfuse/releases) - [Commits](https://github.com/langfuse/langfuse/commits) Updates `opentelemetry-api` from 1.40.0 to 1.44.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.44.0) Updates `opentelemetry-sdk` from 1.40.0 to 1.44.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.44.0) Updates `opentelemetry-exporter-otlp` from 1.40.0 to 1.44.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.44.0) Updates `vulture` from 2.15 to 2.16 - [Release notes](https://github.com/jendrikseipp/vulture/releases) - [Changelog](https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md) - [Commits](https://github.com/jendrikseipp/vulture/compare/v2.15...v2.16) --- updated-dependencies: - dependency-name: pydantic dependency-version: 2.13.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: prometheus-client dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: docker dependency-version: 7.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: typer dependency-version: 0.27.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: python-multipart dependency-version: 0.0.32 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-minor-patch - dependency-name: pytest dependency-version: 9.1.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: pytest-asyncio dependency-version: 1.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: ruff dependency-version: 0.15.22 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: hypothesis dependency-version: 6.156.6 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: langfuse dependency-version: 4.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: opentelemetry-api dependency-version: 1.44.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: opentelemetry-sdk dependency-version: 1.44.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: opentelemetry-exporter-otlp dependency-version: 1.44.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: vulture dependency-version: '2.16' dependency-type: direct:development update-type: version-update:semver-minor dependency-group: python-minor-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * chore(deps): bump structlog from 25.5.0 to 26.1.0 (#512) Bumps [structlog](https://github.com/hynek/structlog) from 25.5.0 to 26.1.0. - [Release notes](https://github.com/hynek/structlog/releases) - [Changelog](https://github.com/hynek/structlog/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/structlog/compare/25.5.0...26.1.0) --- updated-dependencies: - dependency-name: structlog dependency-version: 26.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * chore(deps): bump protobuf from 6.33.6 to 7.35.1 (#513) Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 6.33.6 to 7.35.1. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Commits](https://github.com/protocolbuffers/protobuf/commits) --- updated-dependencies: - dependency-name: protobuf dependency-version: 7.35.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump cryptography from 48.0.1 to 49.0.0 (#509) Bumps [cryptography](https://github.com/pyca/cryptography) from 48.0.1 to 49.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/48.0.1...49.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-version: 49.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * chore(deps): update mkdocs-material requirement from >=9.5 to >=9.7.6 (#510) Updates the requirements on [mkdocs-material](https://github.com/squidfunk/mkdocs-material) to permit the latest version. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.0...9.7.6) --- updated-dependencies: - dependency-name: mkdocs-material dependency-version: 9.7.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * chore(deps): update mkdocs-git-revision-date-localized-plugin requirement (#511) Updates the requirements on [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) to permit the latest version. - [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases) - [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.2.0...v1.5.3) --- updated-dependencies: - dependency-name: mkdocs-git-revision-date-localized-plugin dependency-version: 1.5.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * ci(ci): allow deps-dev scope for Dependabot dev-dependency PRs (#520) * chore(deps-dev): bump mypy from 1.19.1 to 2.3.0 (#515) Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 2.3.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.19.1...v2.3.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 2.3.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * chore(deps): bump rich from 14.3.3 to 15.0.0 (#516) Bumps [rich](https://github.com/Textualize/rich) from 14.3.3 to 15.0.0. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/main/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v14.3.3...v15.0.0) --- updated-dependencies: - dependency-name: rich dependency-version: 15.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mapyr <[email protected]> * chore(deps): update mkdocstrings[python] requirement (#514) Updates the requirements on [mkdocstrings[python]](https://github.com/mkdocstrings/mkdocstrings) to permit the latest version. - [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases) - [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.0...1.0.6) --- updated-dependencies: - dependency-name: mkdocstrings[python] dependency-version: 1.0.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(ci): add domain-lint caller (ADR-011) (#522) * docs(repo): link the release compatibility matrix from the README (ADR-011) (#521) The compatibility matrix (docs RELEASE_COMPATIBILITY.md) reconciles core, operator, and chart versions but was unreachable from the flagship repo. Link it from the README Documentation section per ADR-011 (matrix = the versions/compat SSOT, reachable from core). Closes the reachability gap in #489; the --http fail-closed auth behavior is already documented here (the core README owns server security behavior per ADR-011). Co-authored-by: Claude Opus 4.8 <[email protected]> * ci(ci): add interceptor-pin drift detection (ADR-012) (#523) * security(core): validate WebSocket handshake Origin/Host at the edge (#524) * security(core): validate WebSocket handshake Origin/Host at the edge Non-/api/ WebSocket connections were forwarded to mcp_app with no Hangar-layer Origin/Host check (asgi.py) -- the CVE-2026-59950 class at our own trust boundary. Add handshake validation before forwarding: - loopback connections are trusted (no cross-origin / rebinding threat); - non-loopback is fail-closed: a *present* Origin must be allow-listed (MCP_CORS_ORIGINS), a *missing* Origin is a non-browser client and is allowed (auth still applies); the Host must be in the trusted-hosts allowlist (MCP_TRUSTED_HOSTS -- same list the REST API uses; * disables). Rejected handshakes close with 1008. Reuses the existing API-protection allowlists so one config governs both surfaces. Independent of #497 (SDK floor). Co-Authored-By: Claude Opus 4.8 <[email protected]> * style: ruff-format asgi.py + add CHANGELOG entry for #498 Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Co-authored-by: Claude Opus 4.8 <[email protected]> * feat(core): add L7 egress policy engine (#526) The core-side half of MCPEgressPolicy (operator epic #53, ADR-013). The operator enforces L3/L4 (which upstream hosts a server may reach); this adds the deterministic L7 enforcement on the connections Hangar already proxies: - tool-call matching: glob on the MCP tool name -> allow / deny / require-approval, with a policy default action; precedence deny > require-approval > allow > default. - argument scanning: named secret-pattern groups (reusing the output redactor's value-regexes, plus PEM private-key blocks) and a payload-size limit. A secret or oversized payload denies the call even for an allowed tool -- deny wins. Pure and deterministic (no I/O, no ML) -- full DLP/ML detection are non-goals (ADR-013). New module domain/policies/egress_l7.py with 16 tests. Wiring the evaluator into the tool-invocation path (fed by the operator's compiled policy document) is a follow-up. Co-authored-by: Claude Opus 4.8 <[email protected]> * feat(core): enforce L7 egress policy at the tool-invocation chokepoint (#527) Wires the L7 engine (#526) into McpServer.invoke_tool -- the single method every tool call funnels through. McpServer gains an optional l7_policy (constructor arg + property + set_l7_policy setter); invoke_tool evaluates each call against it before ensure_ready, so a denied or approval-gated call never wakes the server or reaches the upstream: - DENY (tool deny-rule/default, or a secret/oversized argument) -> EgressPolicyDeniedError - REQUIRE_APPROVAL -> EgressPolicyApprovalRequiredError (fails closed; interactive approval-queue routing is a follow-up) No policy attached => the branch is skipped and behavior is unchanged. Per ADR-011 (single source of truth), the policy is meant to come from the operator's compiled MCPEgressPolicy, not a parallel config schema; this PR builds the enforcement seam + setter, and the operator->core transport that populates it is the remaining integration step. New exceptions EgressPolicyDeniedError / EgressPolicyApprovalRequiredError (ToolError subclasses; caller-facing message generic, reason in details for audit). 6 tests; 222 existing invoke/tool tests still pass. Co-authored-by: Claude Opus 4.8 <[email protected]> * feat(core): receive compiled L7 egress policy over the REST API (#528) The operator->core transport sink for MCPEgressPolicy L7 rules. Lets the operator push a compiled L7 policy per mcp_server so the CRD drives the L7 enforcement that #527 wired into invoke_tool. - L7Policy.from_dict: parse the operator's camelCase wire form (tools.allow/deny/requireApproval, arguments.secretPatterns/maxPayloadBytes, defaultAction), with strict validation. - SetL7PolicyCommand + SetL7PolicyHandler: resolve the McpServer from the repository and call set_l7_policy (None clears). 404 if unknown. - Routes POST/PUT /api/mcp_servers/{id}/l7_policy (set/replace) and DELETE (clear), guarded by the mcp_servers:write permission -- same guard as the other provider mutations. Additive and dormant until the operator pushes (next PR). 36 L7 tests (from_dict parse/validation, handler set/clear/not-found, and a set->invoke end-to-end that proves the transport meets the enforcement). ruff/mypy clean; existing crud/provider-api tests unchanged. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): L7 argument scan fails closed on unserializable arguments (#529) Adversarial testing found that scan_arguments serialized the tool-call arguments unconditionally via json.dumps, so a pathological payload (e.g. a circular reference) raised ValueError from inside the policy evaluation -- crashing the call instead of denying it. Tool arguments arrive as decoded JSON in practice, but a policy control must not fail open (or crash) on odd input. - _serialize_arguments returns None when the payload can't be serialized; scan_arguments then reports a violation (fail closed) rather than raising. - Short-circuit: when no secret patterns and no size limit are configured, return early without serializing at all (cheaper, and cannot crash). Two regression tests (unserializable -> violation; no-rules -> clean, no crash). Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): redact secret values in logs and the log buffer (#530) Observability audit finding P1/L1 (critical): the value-level OutputRedactor was never installed in the logging pipeline, so a secret embedded in a log message string or under a non-secret key reached the JSON logs; and raw MCP server stderr was appended verbatim to the log buffer and served by GET /mcp_servers/{id}/logs -- exposing any token/key an upstream printed to stderr to anyone with mcp_servers:read. - domain/security/redactor: add get_default_redactor() -- a shared, process-wide OutputRedactor with long-string redaction OFF (only builtin token-shape patterns, so real secrets are caught without mangling arbitrary long values). - logging_config: new _redact_secret_values structlog processor (runs after the key-name sanitizer, also in foreign_pre_chain so stdlib-routed logs are covered) scrubbing every string in the record. - mcp_server: redact each stderr line at the source in the reader thread before it enters the buffer -- one choke that protects the /logs API and any future log stream. 5 tests; existing redactor/logging/mcp_server suites unchanged (185 pass). Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): scrub Langfuse tool inputs/outputs by default (#531) Observability audit finding T3/P1 (security): the Langfuse exporter shipped full tool-call inputs and outputs to Langfuse unless scrub_inputs/scrub_outputs were explicitly enabled. Flip both defaults to true across LangfuseConfig, the bootstrap LangfuseBootstrapConfig, the env-var fallbacks, and the doc example -- secure by default. When scrubbing is on the exporter sends only the argument/ result key names, not their values. Operators who want full content for debugging can set them false. Guarded by two tests asserting the on-by-default posture. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(metrics): stop poisoning the latency histogram; drop stream_id label (#532) Observability audit Phase 2 (findings M4, M1/stream_id) -- two unambiguous correctness fixes: - tool_call_duration_seconds observed only on success. observe_tool_call previously recorded duration=0.0 for every failed call (the failure path has no real duration), injecting artificial 0-second samples that dragged the latency percentiles down. Failures are still counted via tool_call_errors_total. - events_compacted_total no longer carries the unbounded stream_id label (per-stream identifiers = cardinality bomb). record_events_compacted keeps the arg for callers/logging but does not use it as a label. No alert/dashboard referenced it. 3 tests parse the /metrics output; existing metrics suites pass (47). Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(observability): correlate logs with traces (trace_id/span_id) (#533) Observability audit finding P7/L2: OTel tracing existed but no trace_id/span_id was ever injected into log records -- get_current_trace_id/span_id were dead code and the structlog chain had no trace processor, so you could not pivot from a log line to its trace. Add a _add_trace_context structlog processor that stamps trace_id/span_id when inside an active OTel span. Lazily imports the tracing helpers to avoid the circular import (tracing imports this module for its logger), no-ops when tracing is off / no active span, and is wrapped so a tracing error never breaks a log call. 3 tests. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): consolidate discovery metrics onto the scraped registry (#534) Observability audit finding M1: the discovery orchestrator recorded some metrics to a second prometheus_client registry (application/discovery/discovery_metrics.py) that the /metrics endpoint never serialized -- so discovery registrations, errors, validation failures, and validation durations were silently dropped, while cycle/quarantine/deregistration were double-recorded (once to the dead registry, once to the scraped one). - Delete the dead secondary system (discovery_metrics.py + its DiscoveryMetrics class and unused @observe_* decorators) and its export. - Add the two metrics it uniquely carried to the primary registry: mcp_hangar_discovery_validation_failures_total and mcp_hangar_discovery_validation_duration_seconds (registered + scraped). - Rewire the orchestrator through the primary record_discovery_* fns; drop the now-redundant double-records. Label naming is unified (source_type, not source). A regression test asserts registrations/errors/validation now appear in the /metrics output. 230 discovery/metrics tests pass. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): emit the cost-attribution metrics (#535) Observability audit Phase 2 (dead-metric double-check -> wire): the cost handler computed per-invocation cost via the ICostAttributor port and published a CostReportGenerated event, but never fed the mcp_hangar_cost_cents_total / mcp_hangar_cost_attributions_total metrics its own docstring promised -- so the governance dashboard cost panels were always empty even with a real attributor. Add record_cost(mcp_server, tool, cost_cents, cost_model) and call it in the handler after a non-zero cost is computed. No-op under the default NullCostAttributor (early return on zero cost). Cost is a real subsystem (ICostAttributor / DefaultCostAttributor / pricing rules), so this wires rather than deletes. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(core): wire connections_active; delete the redundant connection metrics (#536) Observability audit Phase 2 (dead-metric double-check): - WIRE connections_active: it has a provider-details "Active Connections" panel and represents a real state, but was never emitted. Set 1 when a server's client is created, 0 on close/shutdown (both teardown paths). The panel now has data. - DELETE connections_total and connection_duration_seconds: never emitted, no dashboard/alert referenced them, and they duplicated the server-lifecycle signals (starts_total, cold_start_seconds, server lifetime). Removed the defs and their registration entries. Tests: the gauge toggles 1/0; the two deleted metrics are absent from the exposition and the module. 161 metrics/mcp_server tests pass. Co-authored-by: Claude Opus 4.8 <[email protected]> * feat(observability): trace the upstream call boundary (client spans, stdio propagation, sampler) (#537) - CLIENT spans (SpanKind.CLIENT) around outgoing MCP RPCs in both stdio and HTTP transports, named per OTel GenAI/MCP semconv (execute_tool {tool} with gen_ai.tool.name / gen_ai.operation.name / mcp.method.name). Opened before context injection so the upstream's server span parents to this CLIENT span. - stdio transport propagates W3C trace context into the MCP _meta field, mirroring the HTTP header injection (no caller-dict mutation; no-op when tracing is off). Closes the stdio distributed-tracing gap. - init_tracing honors OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG (the hand-built TracerProvider previously ignored them despite the documented contract). Co-authored-by: Claude Opus 4.8 <[email protected]> * refactor(core): collapse the vestigial enterprise plugin boundary (#538) The enterprise/SaaS product tier was retired (agent + Hangar Cloud archived); its plugin-discovery boundary in bootstrap/enterprise.py was left behind as dead indirection. Nothing ever registered a provider (register_enterprise_provider was never called; the mcp_hangar.extensions entry-point group was empty save a commented example), so every call already fell through to the built-in path that loads the in-core auth / approvals modules. Remove the EnterpriseProvider registry, entry-point discovery, and the per-function provider loops; the public functions (load_enterprise_modules, register_auth_cqrs, get_enterprise_api_routes, create_enterprise_event_store, create_enterprise_observability_adapter) now invoke the built-in logic directly. Behavior is unchanged — auth/approvals still load, empty config still yields empty components. Also drops the dead pyproject entry-point group and the two tests that exercised the removed plugin-discovery path. Co-authored-by: Claude Opus 4.8 <[email protected]> * refactor(observability): align tool-invocation spans to OTel GenAI/MCP semconv (#539) Follows the transport CLIENT-span work: bring the rest of the tool-invocation telemetry onto OTel GenAI/MCP semantic conventions so GenAI-aware backends and the OTel Collector recognize it without Hangar-specific mapping. - Span name: application-layer `tool.invoke.{tool}` -> `execute_tool {tool}` (matches the transport CLIENT span). - Attributes: `mcp.tool.name` -> `gen_ai.tool.name`; `mcp.cost.input_tokens` / `mcp.cost.output_tokens` -> `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens`; `gen_ai.operation.name` and `mcp.method.name` now set. New `GenAI` conventions class; the `mcp.*` governance namespaces (enforcement/risk/audit/behavioral/caller, cost cents/model/currency, mcp.session.id) are unchanged -- no semconv equivalent. Scope is spans only: metrics are a hand-rolled Prometheus registry (not OTel), so their `mcp_hangar_*` names stay -- dotted semconv names would break the scraped dashboards/alerts and don't fit Prometheus. Also fixes a pre-existing botched `Provider`->`McpServer` rename that pinned `OTEL_LOGS_AVAILABLE` to false (the `LoggerMcpServer` import always ImportError'd), silently disabling OTLP audit-log export -- so the semconv-aligned audit attributes now actually ship. Breaking for consumers querying the old span/OTLP-audit attribute names. Co-authored-by: Claude Opus 4.8 <[email protected]> * feat(observability): wire transport message metrics; drop never-emitted pool/SSE gauges (#540) Wire the JSON-RPC message metrics that were defined but never emitted, at the stdio and HTTP transport boundaries: - mcp_hangar_messages_sent (by method) + mcp_hangar_messages_received (by type: response/notification/error) + mcp_hangar_message_size_bytes (by direction). - Labeled per upstream server. StdioClient gains an mcp_server_id (the domain tags stdio clients after launch; HTTP clients are labeled at construction). - Sent is recorded in call() with the serialized request; received in the stdio reader loop / from the HTTP response body, so there's no re-serialization on the hot path. Added classify_jsonrpc_message + record_message_sent/received helpers. Remove three never-emitted metrics with nothing to populate them: - mcp_hangar_http_connection_pool_size (httpx pool internals aren't exposed), - mcp_hangar_http_sse_streams_active / mcp_hangar_http_sse_events (the streaming-SSE reader path is unused; SSE responses are batch-parsed). Repurpose the dead "Active SSE Streams" dashboard panel to a messages-sent rate. Tested: unit (helpers, classify, both transports, removed-metric absence) + live on the OSS stack — Prometheus scrapes messages_sent {initialize:1, tools/list:1, tools/call:20} and messages_received{type=response}:22 labeled mcp_server=math_subprocess; removed metrics absent from /metrics. Co-authored-by: Claude Opus 4.8 <[email protected]> * feat(observability): add telemetry-health alerts (OTLP export + discovery validation) (#541) Two emitted signals had no alert coverage. Add: - MCPHangarTelemetryExportFailing: rate(mcp_hangar_otlp_export_failures_total[5m]) > 0 for 10m. A silent OTLP export failure drops spans with no app-level error -- this guards exactly the "we've gone blind on tracing" class (cf. the OTLP audit-log export that a botched rename had silently disabled). - MCPHangarDiscoveryValidationFailing: rate(mcp_hangar_discovery_validation_failures_total[15m]) > 0 for 15m. Discovered servers failing validation are silently not registered. Both warning severity (no runbook_url, matching the other warning alerts). Validated with `promtool check rules` (30 rules, all valid). The packaged helm-charts alert bundle should sync these separately. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(observability): stop logging expected stdio shutdowns as errors (#542) Reviewing live logs surfaced a spurious ERROR on every idle-TTL shutdown: close() sets self.closed=True before terminating the subprocess, so when the reader thread hits EOF it called _capture_process_stderr(), which logged `stdio_client_process_exited` (and any drained stderr) at ERROR unconditionally -- even though we initiated the exit. That inflates the error stream and any log-based alerting. Pick the log level from self.closed: an exit we initiated is expected -> info (with expected=true); an unsolicited process death is still an error. Same for the EOF-on-stdout line. Unit tests cover both the expected and unexpected paths. Co-authored-by: Claude Opus 4.8 <[email protected]> * chore(repo): apply ruff format to restore green lint (#543) `ruff format --check` (CI's ruff 0.14.13) was red on main for 12 files, so the non-required `lint` check has been failing on every PR and masking real lint regressions. Apply `ruff format` to the drifted files (pure formatting -- line wrapping / spacing, no logic change). stdio_client.py is intentionally left to PR #542, which already formats it alongside its fix. Co-authored-by: Claude Opus 4.8 <[email protected]> * fix(observability): mark a failed tool call's span as ERROR (#544) Reviewing the error path on the live stack: a failing tool call (tool-level error or ToolInvocationError) produced a trace where every span was UNSET -- in Jaeger the failing call looked completely successful and couldn't be filtered as an error, even though tool_call_errors_total was incremented. Cause: the batch executor handles failures as data (returns CallResult(success=False)) inside the `batch.call.{tool}` span's with-block, so the span never sees an exception and its status stays UNSET. Set ERROR status on the span (with the failure message) when result.success is false. Add a NoOp-safe mark_span_error() helper for the failure-as-data pattern. Co-authored-by: Claude Opus 4.8 <[email protected]> * chore(release): release 1.6.0 (#525) Co-authored-by: mcp-hangar-release-bot[bot] <283430731+mcp-hangar-release-bot[bot]@users.noreply.github.com> * test(core): update audit test for gen_ai.tool.name semconv rename Reconcile fallout: main 1.6.0 (#539) renamed the span/audit attribute mcp.tool.name -> gen_ai.tool.name, but this mcp2-side test still asserted the old name. Git didn't flag it (code in main, test in mcp2), so it only surfaced at runtime. Assert the new attribute. Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: mcp-hangar-release-bot[bot] <283430731+mcp-hangar-release-bot[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
PederHP
added a commit
to modelcontextprotocol/experimental-ext-interceptors
that referenced
this pull request
Jul 21, 2026
Addresses the first two items of #15 (the chain-orchestrator multi-server item follows in a separate PR). ## `mode`: keep `"active"`, add `"audit"` round-trip coverage > **Update:** an earlier revision of this PR renamed `mode` from `"active"` to `"enforce"`, following a SEP **draft**. The now-current revised SEP-1763 ([PR #2624](modelcontextprotocol/modelcontextprotocol#2624)) defines `mode?: "active" | "audit"` with default `"active"` — so `"active"` was correct all along. @jeongukjae flagged this; the rename has been reverted (commit 58edf2c). The C# SDK's wire value `"active"` matches the SEP and the TypeScript SDK. `InterceptorMode` keeps members `Active` (wire `"active"`, default) and `Audit` (wire `"audit"`). Added a deserialization test alongside the existing serialization test so both wire forms are covered. ## `priorityHint`: support `number | { request?, response? }` The SDK only supported a plain `int`. This adds a `PriorityHint` protocol type whose converter accepts both wire forms and preserves them on round-trip (a number stays a number, an object stays an object). Chain ordering now resolves the effective priority per phase exactly as the SEP's `resolvePriority` algorithm (unset → 0, alphabetical tie-break unchanged). - `Interceptor.PriorityHint` is now `PriorityHint?`; an implicit conversion from `int` keeps existing code compiling. - `[McpServerInterceptor]` gains `RequestPriorityHint` / `ResponsePriorityHint` (mutually exclusive with the scalar `PriorityHint`), following the official SDK's backing-field pattern for optional attribute values. - Behavior note: attribute-discovered interceptors with no priority set now omit `priorityHint` from the wire instead of emitting `0` — semantically identical per the SEP default, and consistent with the existing default-skipping for `Mode`/`FailOpen`. ## Testing `dotnet test` from `csharp/sdk/`: all passing, including new coverage for both `priorityHint` wire forms, partial objects, unknown-key tolerance, phase-dependent mutation ordering (the SEP's pii-redactor/compressor example), the attribute conflict error, and both `mode` wire values. Part of #15. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces a SEP for an interceptor framework for the Model Context Protocol, defining a new MCP primitive that allows context operations to be intercepted, validated, and transformed at key points in the agentic lifecycle.
Motivation
The MCP ecosystem is rapidly developing a sprawling landscape of sidecars, proxies, client-wrappers and gateways to address cross-cutting context governance concerns (like PII Checking/reduction, Toxicity filtering, etc) . However, these implementations are largely:
Overall this is leading to an M × N problem: Each of M clients must integrate with each of N middleware solutions, resulting in M × N integration points and configurations.
What This SEP Proposes
This SEP proposes an Interceptor framework that effectively elevates middleware to an MCP primitive in-order to provide control over context at key points in the agentic lifecycle.
Towards this end, the SEP:
tools/call) to ensure governance over context is reliably applied before it crosses trust boundaries for both the server and the client.interceptors/listandinterceptor/invoke) that allow clients and servers to dynamically discover and trigger these primitives.Important: The SEP is fully backward compatible, interceptors are an optional server capability, no existing protocol methods are modified, and clients that do not support interceptors continue to work unchanged.
Other Relevant material
Relevant MCP Dev Summit (2016) Talks:
Reference Implementations (WIP):