Skip to content

feat(facade): session-aware in-process capability sources#1454

Merged
thepagent merged 9 commits into
mainfrom
feat/facade-session-sources
Jul 25, 2026
Merged

feat(facade): session-aware in-process capability sources#1454
thepagent merged 9 commits into
mainfrom
feat/facade-session-sources

Conversation

@chaodu-agent

@chaodu-agent chaodu-agent commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Groundwork for routing #1447's per-session browser capability through the facade (one listener, one discovery surface), per tonight's design discussion. Self-contained: no behavior change for existing deployments.

What's added

  • sources module (openab-mcp):
    • CapabilitySource trait — in-process providers registered at facade construction (no mcp.json entry, no extra listener, no subprocess). Every call receives an optional SessionCtx; requires_session() marks session-bound sources.
    • SessionTokens — the broker↔facade identity contract: broker mints one opaque token per agent session (delivered to that agent's MCP config as Authorization: Bearer), revokes on evict; facade resolves it per request from the HTTP parts rmcp injects into request extensions (documented rmcp 1.7 mechanism — stateless, no session-binding map). Constant-time token comparison.
  • Facade integration: with_sources/serve_http_with; discovery merges source tools into the catalog (downstream names win; shadowed source tools published as provider:tool, mirroring the existing duplicate rule); execution dispatches sources first with the same JSON-Schema pre-flight the meta-tool applies; audit lines carry the channel id.
  • Invisible ≠ forbidden: session-bound sources are absent from anonymous discovery AND resolve as "unknown capability" on anonymous execution — no dangling entries, nothing to probe.

Self-review round 2

Two more findings, fixed:

  1. Source-vs-source collisions: the discovery collision set only contained downstream names and didn't grow — two sources sharing a tool name produced duplicate bare-name catalog entries, with the later source's tools unreachable. The set now grows as sources publish: first registrant keeps the bare name, later ones are provider:tool (matching execution's registration-order resolution; regression test added).
  2. Audit parity: source calls now log args_sha256 like the meta_tool dispatcher — one audit shape across both dispatch paths.

Noted-not-changed (deliberate): SessionTokens uses std::sync::RwLock with expect (no user code in the critical sections, poisoning is unreachable short of an allocator panic); execution resolving the downstream catalog before source dispatch adds a mostly-cache-hit lookup to source calls — consistency with the publish rule is worth that.

Validation

  • openab-mcp: 214 tests pass (7 new: full-HTTP session-gating e2e; token mint/replace/revoke lifecycle, constant-time eq, ctx-from-http-parts resolution incl. no-parts/wrong-token; facade: session-bound invisibility+unreachability, ctx-routed execution, anonymous host-level source + schema pre-flight rejection)
  • clippy --workspace --all-features -D warnings clean; fmt clean
  • Zero-source facade behavior byte-identical (all 208 pre-existing tests untouched and green)

Review Contract

Goal

Give the facade a session-aware in-process capability-source interface and a broker-shared session-token registry, so session-bound capabilities (browser control, #1447) can later be served through the single facade listener and discovery surface.

Non-goals

  • No browser code, no feat(acp): browser control via MCP-over-ACP + stdio bridge (Phase 2) #1447 changes (that rewire is the follow-up on its branch, depends on this landing).
  • No broker/SessionPool wiring yet (mint/revoke call sites land with the first real source).
  • No per-source tool_filter — source registration is code-wired by the broker and is itself the operator grant.
  • No change to anonymous/config-only facade behavior.

Accepted Residual Risks

  • The session token travels as a header value in agent-local MCP config files (0600) — same exposure class as feat(acp): browser control via MCP-over-ACP + stdio bridge (Phase 2) #1447's per-session bearer and the existing octobroker key delivery.
  • Token resolve is a linear scan (constant-time compare per entry) — session counts are small by construction (pool max_sessions).

Acceptance Criteria

  • All listed tests green; zero-source behavior identical (pre-existing tests unmodified).
  • Anonymous clients can neither list nor execute a requires_session source (tests).
  • Source execution applies schema validation before dispatch (test).
  • rmcp extension mechanism verified against rmcp 1.7 docs (tower.rs documented Parts-in-extensions pattern).

Follow-ups

- sources module: CapabilitySource trait (optional SessionCtx per call,
  requires_session gating), SessionTokens registry (broker mints one
  opaque token per agent session, constant-time resolve, revoke on
  evict), per-request identity resolution from the Authorization header
  via the HTTP parts rmcp injects into request extensions.
- facade: with_sources/serve_http_with; discovery merges source tools
  (downstream names win, shadowed source tools published as
  provider:tool); execution dispatches sources first with the same
  schema pre-flight the meta-tool applies, audit lines carry channel.
- Session-bound sources are invisible AND unreachable to anonymous
  clients (no dangling catalog entries, nothing to probe).
- Anonymous behavior with zero sources is byte-identical to before.

Groundwork for routing #1447's per-session browser capability through
the facade (one listener, one discovery surface).
@chaodu-agent
chaodu-agent requested a review from thepagent as a code owner July 25, 2026 02:43
…r session tokens

Self-review findings:
- BUG: execution dispatched sources before the downstream catalog while
  discovery publishes downstream-wins-bare-name — a source tool sharing a
  downstream tool's name would hijack calls addressed to the downstream
  tool. Execution now resolves the catalog first (mirroring the publish
  rule); shadowed source tools stay reachable via provider:tool.
- GAP: the rmcp Parts-into-extensions mechanism (which token resolution
  rides on) had no end-to-end coverage. Added build_router + a tower
  oneshot test driving real HTTP initialize→tools/call with/without/
  wrong Authorization, asserting source visibility flips accordingly.
- constant_time_eq: black_box the accumulator so the claim is enforced,
  not asserted in a comment.
…ce calls

Self-review round 2:
- Discovery's collision set now grows as sources publish, so two sources
  sharing a tool name no longer produce duplicate catalog entries — the
  first registrant keeps the bare name, later ones publish as
  provider:tool (matching execution's registration-order resolution).
- Source-call audit lines now carry args_sha256 like the meta_tool
  dispatcher's — one audit shape across both dispatch paths.
@thepagent
thepagent merged commit 28a8717 into main Jul 25, 2026
60 of 64 checks passed
pahud pushed a commit to brettchien/openab that referenced this pull request Jul 25, 2026
…ade mode, default)

Routes browser tools through the OAB MCP Facade as a session-aware
in-process capability source (openab-mcp openabdev#1454), replacing per-session
proxy servers as the default transport. Proxy and Option C bridge modes
are unchanged and remain explicit opt-outs (OPENAB_BROWSER_MODE).

- src/browser_source.rs: CapabilitySource over the existing AcpMcpTunnel
  (requires_session; D4 static-advertise; tunnel errors surface as MCP
  error results); FacadeRegistrar adapts the facade's SessionTokens to
  core's new SessionTokenRegistrar hook (core stays openab-mcp-free).
- core mcp_proxy: BrowserMode::Facade (new default; runtime fallback to
  Proxy when no facade is serving), write_facade_mcp_config — a static,
  write-once 'openab' entry whose Authorization references
  ${OPENAB_SESSION_TOKEN}; the per-session secret rides the agent
  process env instead of config files, eliminating the shared-workdir
  clobber class entirely (incl. kiro --agent files + @OpenAB allowlist).
- pool: with_facade_sessions wiring; mints/injects the token per spawn,
  revokes via the same DropGuard plumbing proxy mode uses.
- main: facade constructed with the BrowserSource; one listener, one
  discovery surface (search_capabilities/execute_capability).
- rmcp re-exported from openab-mcp for source implementors.
- docs: facade-mode section in the browser setup guide.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants