Skip to content

feat(mcp): MCP client foundation (Phase 4.2) - #6

Merged
flg77 merged 1 commit into
mainfrom
feat/phase-4.2-mcp-foundation
Apr 29, 2026
Merged

flg77 merged 1 commit into
mainfrom
feat/phase-4.2-mcp-foundation

Conversation

@flg77

@flg77 flg77 commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the symbiote layer that complements the in-house Skills package from PR 4.1. Where Skills are organelles the cell synthesises, MCP servers are external symbionts the cell admits through a governed membrane (allowed_mcps whitelist).

This PR wires the client surface only. PR 4.3 grafts it onto RoleDefinitionConfig and CognitiveCore; PR 4.4 surfaces it in the TUI Ecosystem screen.

What's in the box

acc/mcp/ — new package

Module Role
manifest.py MCPManifest pydantic model. Transport enum (http / stdio), per-transport field-set validation (http demands url, stdio demands command), tool allow/deny lists with is_tool_allowed(), risk_level + requires_actions governance hooks, lowercase-snake_case server_id validator.
errors.py MCPError{ManifestError, ServerNotFound, ToolNotFound, Connection, Protocol, Transport}. Shape mirrors the acc.skills error tree so contributors learn one pattern.
client.py MCPClient — JSON-RPC 2.0 envelope (monotonic id allocator), MCP initialize + tools/list + tools/call methods, cached tool advertisement (refreshable), bearer auth via api_key_env, lazy connection on first call, idempotent initialize().
registry.py MCPRegistry — same load_from() / manifest() / list_server_ids() surface as SkillRegistry, plus client(server_id) lazy instantiation with HTTP-connection caching across hot-reloads when the manifest is unchanged. close_all() releases connections during agent shutdown.

mcps/ — directory convention

  • mcps/_base/{mcp.yaml, README.md}: defaults document + minimum-manifest walkthrough + transport status table + tool sandbox rules + risk-level mapping + governance-hook reference.
  • mcps/echo_server/mcp.yaml: stub diagnostic manifest pointing at a hypothetical local echo server (acc-mcp-echo:8080). No server process is shipped here; the manifest exists so the loader has one row to validate against, and so the TUI Ecosystem screen (PR 4.4) is never empty on a fresh checkout.

Transport status

Transport Status (this PR) Notes
http implemented JSON-RPC 2.0 over HTTP POST. Production path.
stdio reserved Manifest enum accepts the value; the client raises NotImplementedError if used. Subprocess-pipe transport will land in a follow-up PR.

Round-trip smoke tests (run locally — all green)

  • Loader discovery + deep-merge: 1 server loaded as expected.
  • Tool allow/deny gating: blocks forbidden names client-side.
  • Manifest validation: http transport without url raises ValidationError; stdio without command raises ValidationError.
  • JSON-RPC envelope: against a mocked httpx, initializetools/listtools/call returns expected payload, manifest-filtered tools are not advertised, manifest-blocked invocations raise MCPToolNotFoundError.
# Reproduce loader test
python -c "
from acc.mcp import MCPRegistry
reg = MCPRegistry(); reg.load_from('mcps')
print(reg.list_server_ids())                 # ['echo_server']
print(reg.manifest('echo_server').url)       # http://acc-mcp-echo:8080/rpc
"

Test plan

  • Pull and confirm `from acc.mcp import MCPRegistry` imports cleanly.
  • Run the loader smoke-test above on lighthouse — should print `['echo_server']`.
  • Verify `mcps/echo_server/mcp.yaml` round-trips through the deep-merge with `mcps/_base/mcp.yaml`.
  • Confirm no behaviour change for existing agents (none of them import `acc.mcp` yet — PR 4.3 wires it in).

Out of scope for this PR

  • RoleDefinitionConfig fields (allowed_mcps) — comes in PR 4.3.
  • Cat-A rule A-018 enforcement — comes in PR 4.3.
  • CognitiveCore invocation surface — comes in PR 4.3.
  • TUI Ecosystem live tables — comes in PR 4.4.
  • Stdio transport implementation — separate follow-up PR.
  • Real echo MCP server image — separate follow-up PR.

…ry (Phase 4.2)

Adds the symbiote layer that complements the in-house Skills package
from PR 4.1.  Where Skills are organelles the cell synthesises, MCP
servers are external symbionts the cell admits through a governed
membrane (allowed_mcps whitelist).  This PR wires the client surface;
PR 4.3 grafts it onto RoleDefinitionConfig and CognitiveCore.

acc/mcp/ — new package
  * manifest.py: MCPManifest pydantic model.  Transport enum
    (http | stdio), per-transport field-set validation (http demands
    url, stdio demands command), tool allow/deny lists with manifest-
    side gating via is_tool_allowed(), risk_level + requires_actions
    governance hooks, lowercase-snake_case server_id validator.
  * errors.py: MCPError → {ManifestError, ServerNotFound, ToolNotFound,
    Connection, Protocol, Transport}.  Shape mirrors acc.skills error
    tree so contributors learn one pattern.
  * client.py: MCPClient — JSON-RPC 2.0 envelope with monotonic id
    allocator, MCP `initialize` + `tools/list` + `tools/call` methods,
    cached tool advertisement (refreshable), bearer auth via
    api_key_env, lazy connection on first call, idempotent
    initialize().  Stdio transport is wired for future PR — the
    manifest enum accepts the value, the client raises
    NotImplementedError if used.
  * registry.py: MCPRegistry — same load_from() / manifest() / list
    surface as SkillRegistry, plus client(server_id) lazy
    instantiation with HTTP-connection caching across hot-reloads
    when the manifest is unchanged.  close_all() releases
    connections during agent shutdown.

mcps/ — directory convention
  * mcps/_base/{mcp.yaml,README.md}: defaults document + minimum-
    manifest walkthrough + transport status table + tool sandbox
    rules + risk-level mapping + governance-hook reference.
  * mcps/echo_server/mcp.yaml: stub diagnostic manifest pointing at a
    hypothetical local echo server (acc-mcp-echo:8080).  No server
    process is shipped here; the manifest exists so the loader has
    one row to validate against, and so the TUI Ecosystem screen
    (Phase 4.4) is never empty on a fresh checkout.

Round-trip smoke tests (run locally, all green):
  * Loader discovery + deep-merge: 1 server loaded as expected.
  * Tool allow/deny gating: blocks forbidden names client-side.
  * Manifest validation: http transport without url raises
    ValidationError; stdio without command raises ValidationError.
  * JSON-RPC envelope: against a mocked httpx, initialize →
    tools/list → tools/call returns expected payload, manifest-
    filtered tools are not advertised, manifest-blocked invocations
    raise MCPToolNotFoundError.

Unblocks PR 4.3 (RoleDefinitionConfig.allowed_skills /
allowed_mcps + Cat-A A-017/A-018 + CognitiveCore wiring) and PR 4.4
(TUI Ecosystem live tables + how-to docs).
@flg77
flg77 merged commit 4b11dae into main Apr 29, 2026
flg77 added a commit that referenced this pull request May 9, 2026
Closes the deferral from the extension repo's v0.1 PR #6 (the
documentation closer).  Both INDEX docs now point operators
living in Podman Desktop at the sibling extension that surfaces
the same wire format as a webview.

* `INDEX_subagent_clustering.md` — see-also entry for the
  cluster topology panel + extension docs.
* `AUTORESEARCHER_index.md` — companion section enumerating the
  PD panels (Stack, Examples, Cluster, Compliance, Performance)
  that exercise the autoresearcher demo end-to-end inside PD.

No code changes.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
flg77 added a commit that referenced this pull request May 31, 2026
…ible state (v0.3.40) (#6)

Closes follow-up #51.

PR-MEM2 shipped in v0.3.1 but `acc/agent.py::_reflection_loop` is
gated on `ACC_REFLECTION_INTERVAL_S > 0` and the baseline production
compose never set it. Reflection loop returned immediately; the entire
memory consolidation chain (episodes → reflection → memory_notes →
Redis hot-cache → PR-I retrieval) has been silently disabled on every
default deploy since v0.3.1. It only surfaced now because v0.3.37
unblocked the episode-persist producer side — episodes finally flow
(verified `episodes` rows = 5 on lighthouse) but memory_notes stays 0.

Three coupled changes:

1. `container/production/podman-compose.yml` — adds
   `ACC_REFLECTION_INTERVAL_S: ${ACC_REFLECTION_INTERVAL_S:-600}` to
   every agent service's `environment:` block (baseline + coding-split
   profile). 10-min default with operator override.

2. `acc/agent.py::_reflection_loop` — boot-time INFO log lines so the
   on/off state is operator-visible. Pre-v0.3.40 the loop was silent
   when disabled, hiding the fact that `memory_notes` was empty by
   configuration rather than failure. Now logs either:
     memory_reflection: disabled (ACC_REFLECTION_INTERVAL_S=0)
   or:
     memory_reflection: enabled interval=600s role=assistant agent_id=assistant-1

3. `tests/test_reflection_loop.py` — 4 new env-gating regression tests:
   - skipped when env unset (with disabled log line)
   - skipped when env="0" (with disabled log line)
   - enabled logs interval + role + agent_id
   - silent skip when cognitive_core is None (dormant worker contract
     preserved)

Downstream consumers unblocked:
- PR-I memory retrieval (queries memory_notes)
- ACC-6a/11 centroid drift (uses memory_notes as priors)
- SIP-P3 contextual prior (last_eval_reward + memory-informed)
- Dreamer Phase 2 (M1 dedup, M2 prune) — operates on memory_notes

Tests: 11 passing in tests/test_reflection_loop.py; 278 passing across
the broader memory/agent/reflection sweep.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant