You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
One Vaara process now serves a fleet of upstream MCP servers over
Streamable HTTP, with multi-tenant policy, audit chain, and OVERT
attestation on the same substrate. v0.39 ran one Vaara process per
upstream; v0.40 collapses that into a single multi-tenant deployment.
Streamable HTTP transport on the proxy. `vaara-mcp-proxy --transport
http --http-host H --http-port P` runs POST /mcp via FastAPI / uvicorn.
The endpoint reads `X-Vaara-Tenant` and `X-Vaara-Upstream` per request,
pushes them into ContextVars, and dispatches into the existing
`_handle_request` path so policy, perimeter, OVERT, and
progress-notification handling all light up unchanged. Notifications
return 202. Bodies above 1 MiB return 413. Unknown upstream returns 404.
Fan-out via repeatable `--upstream NAME=CMD`. One Vaara process holds N
UpstreamMCPClient instances in a name -> client map. Bare `--upstream
CMD` keeps the v0.39 single-upstream contract (lands in the "default"
slot). When more than one upstream is configured, a request with no
`X-Vaara-Upstream` header returns 400 with the list of valid slots in
the error envelope. Single-upstream deployments keep the silent-default
contract.
tenant_id end-to-end. ScoreRequest, AuditEventRequest, PolicyReloadRequest
accept a `tenant_id` body field, with `X-Vaara-Tenant` as the HTTP-header
alternative (body wins over header). AuditRecord gains a `tenant_id`
field, excluded from `compute_hash()` so pre-v0.40 chains still
re-verify. AuditTrail keeps an `action_id -> tenant_id` map seeded by
`record_action_requested`, soft-capped at 50k entries.
SQLiteAuditBackend.write_record prefers per-record tenant. OVERT
envelopes carry `tenant_id` as a `non_content_metadata` claim.
Per-tenant policy plane. `vaara.policy.registry.PolicyRegistry` holds
one PolicyController per tenant with the empty string slot reserved as
the default fallback. `vaara serve --policy-dir DIR` loads one YAML/JSON
policy per file (filename stem = tenant_id). `POST /v1/policy/reload`
routes per tenant via body field or header.
Installs `vaara-mcp-proxy` as a top-level console script so the proxy
CLI matches what every v0.39+ docs surface advertises. Earlier releases
only shipped the proxy as `python -m vaara.integrations.mcp_proxy`;
v0.40 closes that gap. v0.41 will fold the proxy into the main `vaara`
verb tree (`vaara mcp-proxy ...`) and keep `vaara-mcp-proxy` as a thin
alias for one release cycle.
Per-tenant threshold dispatch at evaluate-time. `AdaptiveScorer.evaluate`
consults the registry on every call. A new `policy_lookup` constructor
arg (and `set_policy_lookup` for late binding from ServerState) lets
the scorer ask which tenant policy applies right now and use its
allow/deny thresholds for THIS evaluation. Unknown tenant or no lookup
configured falls back to the scorer-bound defaults that the default-slot
listener keeps fresh on reload. The backend decision dict surfaces the
applied threshold_allow and threshold_deny so operators can confirm
which tenant's policy ran. MWU expert state, the conformal calibrator,
agent profiles, and sequence patterns stay shared across tenants; only
threshold application is per-tenant in v0.40.
Scope notes. HTTP transport is POST-only (GET-SSE is v0.41). Per-tenant
policy reload is hot; classifier hot-reload still restart-only.
Cancellation routing across fan-out is v0.41 hardening. Fan-out latency
bench is v0.40.1 measurement.
862 passed, 12 skipped. 45 new tests across tests/test_v040_tenant.py,
tests/test_v040_policy_registry.py, tests/test_v040_mcp_http_transport.py,
tests/test_v040_per_tenant_threshold.py.
References modelcontextprotocol/modelcontextprotocol#2787 for the
SEP-2787 envelope shape v0.40 builds on top of.
Co-authored-by: vaaraio <[email protected]>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+96-5Lines changed: 96 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,97 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
6
6
7
7
## [Unreleased]
8
8
9
+
## [0.40.0] - 2026-05-28
10
+
11
+
**Theme: deployment shape. One Vaara process now serves a fleet of
12
+
upstream MCP servers, with multi-tenant policy, audit, and attestation
13
+
on the same substrate.**
14
+
15
+
The v0.39 sidecar shape ran one Vaara process per upstream. v0.40
16
+
turns that into a single process that speaks Streamable HTTP, holds
17
+
N upstream MCP-server connections, picks the upstream per request
18
+
from a header, scopes every score, audit record, and OVERT envelope
19
+
to a tenant, and reloads per-tenant policy in place.
20
+
21
+
### Added
22
+
-`vaara-mcp-proxy --transport http --http-host H --http-port P`:
23
+
Streamable HTTP transport at `POST /mcp`, backed by FastAPI /
24
+
uvicorn (the `vaara[server]` extra already shipped in v0.39 for
25
+
`vaara serve`). The endpoint reads `X-Vaara-Tenant` and
26
+
`X-Vaara-Upstream` per request, pushes them into ContextVars, and
27
+
dispatches into the existing `_handle_request` path so the policy,
28
+
perimeter, OVERT, and progress-notification handling all light up
29
+
unchanged. Notifications (no JSON-RPC `id`) return 202 Accepted.
30
+
Bodies above 1 MiB return 413.
31
+
-`vaara-mcp-proxy --upstream NAME=CMD` (repeatable) for fan-out.
32
+
One Vaara process holds N `UpstreamMCPClient` instances in a name
33
+
-> client map. Bare `--upstream CMD` keeps the v0.39 single-
34
+
upstream contract; it lands in the "default" slot. Commands that
Copy file name to clipboardExpand all lines: README.md
+21-2Lines changed: 21 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -168,13 +168,32 @@ if (r.decision === "deny") throw new Error("blocked");
168
168
`vaara.integrations.mcp_proxy.VaaraMCPProxy` sits between an MCP client (Claude Code, Cursor, any MCP-capable host) and an upstream MCP server. Every `tools/call` from the client routes through Vaara's interception pipeline before reaching the upstream. Allowed calls forward transparently and report the upstream outcome back to the scorer. Blocked calls return an MCP `isError: true` response with the block reason. The initialization handshake and `notifications/*` forward unchanged. `tools/list`, `resources/list`, `resources/read`, `prompts/list`, and `prompts/get` route through the operator perimeter before reaching the client or upstream.
Point your MCP client at the proxy instead of the upstream. The audit chain captures every tool call without changing client or upstream behavior. Distinct from `mcp_server`, which exposes Vaara itself as an MCP server for agents that consult Vaara as a tool.
177
177
178
+
<details>
179
+
<summary>Fleet shape (v0.40): one proxy, many upstreams, multi-tenant policy</summary>
180
+
181
+
`vaara-mcp-proxy` also runs over Streamable HTTP with fan-out, so one process can serve a fleet of upstream MCP servers:
182
+
183
+
```bash
184
+
vaara-mcp-proxy \
185
+
--transport http \
186
+
--http-host 127.0.0.1 \
187
+
--http-port 8765 \
188
+
--upstream 'github=npx -y @github/mcp-server' \
189
+
--upstream 'sap=npx -y @sap/mdk-mcp-server'
190
+
```
191
+
192
+
Each `POST /mcp` reads two headers. `X-Vaara-Upstream` picks the upstream slot. `X-Vaara-Tenant` scopes the policy, audit chain, and OVERT envelope for that call. Single-upstream deployments keep the v0.39 silent-default contract. Multi-upstream deployments require `X-Vaara-Upstream` per call and return 400 with the available slot list when the header is missing.
193
+
194
+
The reference HTTP API server (`vaara serve --policy-dir DIR`) loads one YAML or JSON policy per file in the directory (filename stem becomes the `tenant_id`, `default.yaml` lands in the fallback slot) and hot-reloads per tenant via `POST /v1/policy/reload` with a `tenant_id` body field or `X-Vaara-Tenant` header. The scorer dispatches allow and deny thresholds per call against the calling tenant's policy at `evaluate()` time.
0 commit comments