Skip to content

feat(mcp): OAB MCP Facade MVP — shared openab-mcp crate + broker loopback HTTP server#1448

Open
chaodu-agent wants to merge 2 commits into
mainfrom
feat/oab-mcp-facade-mvp
Open

feat(mcp): OAB MCP Facade MVP — shared openab-mcp crate + broker loopback HTTP server#1448
chaodu-agent wants to merge 2 commits into
mainfrom
feat/oab-mcp-facade-mvp

Conversation

@chaodu-agent

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

Copy link
Copy Markdown
Collaborator

0. Discord Discussion URL

Design discussed in the OpenAB Discord; the governing design is ADR PR #1446 (OAB MCP Adapter, §6.2 as amended for the broker-hosted loopback HTTP transport). No public Discord URL is included in the repository context.

1. What problem does this solve?

Implements the MVP of the OAB MCP Adapter ADR (#1446): the OAB MCP Facade — a loopback-only Streamable HTTP MCP server exposing exactly two agent-facing tools, search_capabilities and execute_capability, so any MCP-capable coding CLI on the host (Kiro, Claude Code, Codex, …) connects to http://127.0.0.1:<port>/mcp. The broker serves it in-process, enabled by a [mcp] section in config.toml.

It also closes a real gap the ADR's least-privilege profiles depend on: the accepted MCP ADR (§5.6) documents tool_filter glob include/exclude lists and the config schema parses them, but nothing enforced them until now.

2. How it works

config.toml                       coding CLI (Kiro / Claude Code / …)
  [mcp]                              │  MCP over http://127.0.0.1:8848/mcp
  listen = "127.0.0.1:8848"          ▼
        │                     OAB MCP Facade  (in openab broker process)
        └── presence spawns ──►  search_capabilities / execute_capability
                                     │
                              crates/openab-mcp  (extracted shared runtime)
                                     │  lazy connect · tool_filter · OAuth
                                     ▼  jsonschema · breaker · redaction
                              downstream MCP servers (mcp.json)
  • New workspace crate crates/openab-mcp: the entire MCP runtime extracted from openab-agent — outbound client (Streamable HTTP + stdio), OAuth/PKCE + auth.json credential store (moved wholesale: atomic writes, corruption quarantine, refresh locks), layered mcp.json config, tool cache, circuit breaker, jsonschema argument validation, secret redaction — plus the new facade. The broker links it; openab-agent re-exports it (crate::{mcp,auth} shims, shared LLM type layer, HostBridge). One runtime, two hosts, zero duplication.
  • One dispatcher, two frontends (ADR §6.4): the facade delegates to the same meta_tool call path the native agent uses.
  • tool_filter enforcement at both boundaries: discovery drops filtered tools before caching; execution rejects filtered names before any connect.
  • Loopback-only, no auth: non-loopback binds are refused at startup; the endpoint carries no authentication layer, so the host/pod boundary is the trust boundary (documented in the ADR, the config example, and the startup log line).
  • Backward compatible: no [mcp] section → no listener, no behavior change. mcp.json remains the only provider source of truth ([mcp] carries listener settings only — ADR Alternative E).
  • openab-agent mcp-facade --listen 127.0.0.1:8848 serves the same facade standalone.
  • from_config no longer implicitly binds an LLM provider (the crate has no concrete providers); the agent injects it via set_provider alongside the host bridge — sampling behavior unchanged for the agent, correctly absent for the broker.

3. Scope

Root workspace (new crate + broker wiring + Dockerfile.unified dummy-stage + CI paths) and openab-agent (re-export shims). ACP session/new mcpServers advertisement is a documented follow-up — the loopback URL is already reachable by any CLI the operator points at it.

4. Validation

On macOS arm64:

  • openab-mcp: 198 tests pass (moved runtime/meta-tool/auth tests + facade tests + loopback-bind validation)
  • openab-agent: fmt clean, clippy -D warnings = 0 findings, 62 tests pass
  • Root workspace: clippy --workspace --all-features -D warnings = 0 errors; openab-core tests pass incl. 3 new [mcp] parse tests (only failure is the documented pre-existing secrets::resolve_exec_nonzero_exit macOS-env issue, identical on main)
  • Live smoke (standalone): initialize handshake returns oab-mcp-facade; tools/list returns exactly the two tools; --listen 0.0.0.0:… refused
  • Live smoke (broker): binary with [mcp] in config.toml binds the facade at startup and answers initialize with oab-mcp-facade on 127.0.0.1:18850/mcp
  • Test-race fix verified: 8 acp.rs env-mutating tests moved from a private ENV_LOCK to temp_env (single global lock domain); full agent suite green in parallel runs

Review Contract

Goal

Ship the OAB MCP Facade MVP per ADR #1446 §6.2 (as amended): a loopback-only Streamable HTTP MCP server exposing exactly search_capabilities/execute_capability, hosted in-process by the broker when [mcp] is present in config.toml, backed by the MCP runtime extracted into the shared crates/openab-mcp crate — with the ADR's tool_filter least-privilege contract actually enforced.

Non-goals

  • Authentication on the facade endpoint (loopback + host boundary is the MVP trust model; token/socket-permission scheme is a follow-up).
  • ACP session/new mcpServers advertisement of the facade URL (follow-up; not required for CLIs configured directly).
  • Notion/Gmail live OAuth validation (ADR rollout steps 2–3; requires interactive login).
  • Renaming/moving auth.json or mcp.json locations; provider config stays in mcp.json (no second source of truth).
  • Gateway crate changes (it does not depend on openab-mcp).

Accepted Residual Risks

  • Unauthenticated loopback endpoint: any process on the host can call authorized capabilities when [mcp] is enabled. Mitigations: loopback-only bind enforced at startup, explicit warnings in ADR/config example/startup log, opt-in activation; deployments colocating untrusted processes must not enable it.
  • Broker gains the runtime dependency set (rmcp, jsonschema, oauth2, axum) — accepted for one-runtime/zero-duplication; binary size impact is bounded and the gateway is unaffected.
  • The extraction moves auth.json code wholesale; behavior is regression-tested (198 tests) but subtle path/permission differences on exotic platforms would surface only in the follow-up provider validation.
  • search_capabilities with no query lists the full filtered catalog (token-heavy on very large downstream catalogs); mitigated by enforced include filters and the query parameter.
  • Facade sampling requests are declined (broker has no LLM provider) — correct for a non-LLM host, noted for provider docs.

Acceptance Criteria

  • Both workspaces: fmt/clippy/test green per §4 with zero introduced clippy findings vs origin/main on the same toolchain.
  • [mcp] absent → no listener (config parse test); present → listener on the configured loopback address (live smoke), default 127.0.0.1:8848.
  • Non-loopback listen refused at startup (unit test + live smoke).
  • Facade tools/list returns exactly the two documented tools; initialize identifies oab-mcp-facade.
  • A tool_filter-excluded tool is invisible to discovery and rejected pre-connect at execution (regression tests, incl. the Gmail default-profile send/delete block).
  • One failing provider does not fail discovery; it is reported in unavailable with a redacted error (regression test).
  • openab-agent behavior unchanged through the re-export shims (62 agent tests green, incl. ACP session, model-switch, sampling paths).

Follow-ups

  • ACP session/new mcpServers advertisement of the facade URL (currently hardcoded [] in openab-core, unchanged here).
  • Facade endpoint authentication (bearer token or unix-socket permissions).
  • Notion, then Gmail (preview) provider validation and checked-in profile examples (ADR rollout steps 2–3).
  • tools/list_changed-driven cache invalidation surfaced through the facade; optional result cap/pagination for large catalogs.
  • Consider trimming openab-agent's now-redundant direct deps further (kept minimal already).

…er enforcement

Implements the OAB MCP Adapter ADR (#1446) MVP slice in openab-agent:

- New 'openab-agent mcp-facade' subcommand: inbound stdio MCP server
  exposing exactly search_capabilities / execute_capability (ADR §6.1,
  §6.4), backed by the existing McpRuntimeManager + meta_tool dispatcher
  (one capability dispatcher, two frontends).
- search_capabilities: lazy discovery across all configured servers with
  per-provider failure isolation, name-collision qualification
  (server:tool), risk labels from MCP tool annotations, and redacted
  provider errors in an 'unavailable' list.
- execute_capability: exact-name resolution against current discovery,
  then delegation to the shared call path (jsonschema argument
  validation, timeouts, circuit breaker, redaction).
- tool_filter enforcement (accepted MCP ADR §5.6 contract, previously
  parsed but unenforced): glob include/exclude applied at discovery
  (fetch_tools, pre-cache) and execution (call_tool, pre-connect) for
  both the meta-tool and the facade.
- rmcp features: + server, transport-io.

Broker-side ACP session/new mcpServers advertisement is PR-2.
@chaodu-obk

chaodu-obk Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

LGTM ✅ - The OAB MCP Facade MVP is correctly scoped and enforces the documented least-privilege tool surface.

Note

No Critical or Important findings were identified for the reviewed commit.

What This PR Does

This PR adds the agent-facing OAB MCP Facade MVP as an openab-agent mcp-facade stdio server. It exposes exactly two tools, search_capabilities and execute_capability, and makes the existing tool_filter configuration enforceable for discovery and execution.

How It Works

The facade reuses the existing meta_tool dispatcher and McpRuntimeManager, so schema validation, timeouts, circuit breaking, redaction, and policy enforcement remain single-sourced. Discovery connects lazily to configured providers, isolates provider failures in an unavailable list, filters tools before caching, and qualifies duplicate names as server:tool. Execution resolves an exact current discovery result and applies the pre-connect filter gate before dispatch.

Findings

# Severity Finding Location
1 🟢 Shared dispatcher and runtime preserve one enforcement path for the native meta-tool and the facade. openab-agent/src/mcp/facade.rs:167-190
2 🟢 tool_filter is applied before caching and is independently enforced before a guessed tool can connect. openab-agent/src/mcp/meta_tool.rs:156-166, openab-agent/src/mcp/meta_tool.rs:449-460
3 🟢 Provider failure isolation and secret-redacted unavailable responses are implemented and covered by tests. openab-agent/src/mcp/facade.rs:114-131, openab-agent/src/mcp/facade.rs:423-447
4 🟢 The public facade surface is constrained to the two documented tools with schemas and initialization metadata. openab-agent/src/mcp/facade.rs:208-247, openab-agent/src/mcp/facade.rs:267-283
5 🟢 Duplicate qualification and include/exclude filter semantics have focused regression tests. openab-agent/src/mcp/facade.rs:372-388, openab-agent/src/mcp/config.rs:559-615
Finding Details

🟢 F1: Shared dispatcher and runtime

The facade delegates execution through meta_tool::dispatch(Action::Call) instead of duplicating provider lifecycle or security behavior. This keeps argument validation, timeout handling, breaker behavior, and redaction aligned with the existing native path.

🟢 F2: Defense-in-depth filter enforcement

Filtered tools are removed before the shared tools cache is populated, and guessed filtered names are rejected before connect. This satisfies the least-privilege contract at both discovery and execution boundaries.

🟢 F3: Failure isolation and redaction

Discovery continues across configured providers when one fails and returns a concise redacted error under unavailable, preserving access to healthy providers without exposing provider secrets.

🟢 F4: Narrow protocol surface

The stdio server advertises exactly search_capabilities and execute_capability, identifies itself as oab-mcp-facade, keeps logs on stderr, and serializes provider results as MCP text content.

🟢 F5: Focused regression coverage

The patch covers empty catalogs, unavailable providers, unknown execution names, duplicate-name qualification, glob semantics, include/exclude precedence, and the Gmail least-privilege profile shape.

Addressing External Reviewer Feedback

No external GitHub reviewer comments or submitted reviews were present for this round, so there is no unresolved external feedback to address.

Baseline Check
  • PR opened: 2026-07-24
  • Base branch: main
  • Merge-base: 967270087ab74b32bcba9f6bc89a402e7abc3aca
  • Main already has: the existing downstream MCP runtime and native mcp meta-tool, but not the inbound facade, its two-tool protocol surface, or enforced tool_filter behavior.
  • Net-new value: a per-session stdio facade with progressive disclosure and least-privilege filtering, without changing broker advertisement or the native meta-tool contract.

5. Three Reasons We Might Not Need This PR

  1. The broker integration is deferred - Until the follow-up broker advertisement lands, the new facade command is not yet exposed through normal ACP session setup.
  2. Provider discovery remains potentially large - An unqualified search can return a large filtered catalog, so production profiles may still need result caps or pagination.
  3. The facade adds another protocol boundary - Maintaining a second MCP server frontend increases integration surface compared with using the native meta-tool alone.

These are documented scope and rollout trade-offs, not blockers for this MVP.

Validation

  • git diff --check: passed.
  • Local source review completed against origin/main...pr-1448 at the exact requested SHA.
  • Rust validation could not run in this environment because cargo, rustc, and rustup are not installed.

chaodu-obk[bot]
chaodu-obk Bot previously approved these changes Jul 24, 2026

@chaodu-obk chaodu-obk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ✅ - No Critical or Important findings were identified.

Consolidated review: #1448 (comment)

GitHub event: APPROVE

…back HTTP server

Implements the OAB MCP Adapter ADR (#1446) MVP:

- New workspace crate crates/openab-mcp: the MCP runtime extracted from
  openab-agent (client runtime, OAuth/PKCE + auth.json store, layered
  mcp.json config, tool cache, circuit breaker, jsonschema validation,
  redaction) plus the new OAB MCP Facade — an inbound MCP server
  exposing exactly search_capabilities / execute_capability.
- Facade transport: loopback-only Streamable HTTP (http://127.0.0.1:
  <port>/mcp). Non-loopback binds are refused. Any MCP-capable coding
  CLI on the host can connect.
- Broker activation: presence of [mcp] in config.toml starts the
  listener in-process (absent = no listener, backward compatible).
  openab-agent re-exports the crate (crate::{mcp,auth} shims + llm type
  layer + HostBridge) — one runtime, two hosts, zero duplication.
- tool_filter enforcement (accepted MCP ADR §5.6, previously parsed but
  unenforced): glob include/exclude applied at discovery (pre-cache)
  and execution (pre-connect), for the meta-tool and facade alike.
- openab-agent mcp-facade --listen: standalone facade server.
- acp.rs env-mutating tests moved to temp_env (single global lock
  domain; a private ENV_LOCK raced with temp_env-based tests once the
  moved tests changed binary scheduling); session_new_missing_key now
  sandboxes HOME instead of deleting the developer's real auth.json.
- Dockerfile.unified + ci-openab-agent.yml updated for the new member.
@chaodu-agent
chaodu-agent force-pushed the feat/oab-mcp-facade-mvp branch from 4c12c65 to 26a8395 Compare July 24, 2026 11:40
@chaodu-agent chaodu-agent changed the title feat(agent): OAB MCP Facade MVP — mcp-facade stdio server + tool_filter enforcement feat(mcp): OAB MCP Facade MVP — shared openab-mcp crate + broker loopback HTTP server Jul 24, 2026
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.

1 participant