Skip to content

feat(mcp): real echo MCP server image — makes mcps/echo_server operational - #24

Merged
flg77 merged 1 commit into
mainfrom
feat/echo-mcp-server-image
May 2, 2026
Merged

feat(mcp): real echo MCP server image — makes mcps/echo_server operational#24
flg77 merged 1 commit into
mainfrom
feat/echo-mcp-server-image

Conversation

@flg77

@flg77 flg77 commented May 1, 2026

Copy link
Copy Markdown
Owner

Summary

Ships the diagnostic JSON-RPC echo server that backs mcps/echo_server/mcp.yaml. Until now the manifest was a fixture: it round-tripped through the loader, lit up the TUI Ecosystem row, and let `coding_agent` allow-list `echo_server` — but agents dispatching `[MCP: echo_server.echo {...}]` markers got `connection refused`, and the manual smoke recipe in PR #20's prompt-pane help had to inject synthetic events.

After this PR, a single env flag spins up a real server:

```bash
MCP_ECHO=true ./acc-deploy.sh up
```

…and `coding_agent`'s LLM-emitted MCP markers actually round-trip through a real container.

What lands

File Change
`container/production/echo_mcp_server/main.py` (new) Tiny stdlib-only Python server. Implements exactly the three JSON-RPC methods `acc.mcp.client.MCPClient` exercises: `initialize`, `tools/list`, `tools/call`. No Flask / aiohttp / external pip deps.
`container/production/Containerfile.echo-mcp-server` (new) UBI10 python-312-minimal base. ~1 MB layer on top of the base (no pip installs). Non-root UID 1001, EXPOSE 8080, healthcheck via stdlib urllib so we don't ship curl.
`container/production/podman-compose.yml` New `acc-mcp-echo` service behind `--profile mcp-echo`. Container name matches the hostname in the manifest URL → zero manifest edits required.
`acc-deploy.sh` New `MCP_ECHO` env var (default false). Auto-attaches the profile on `build`/`rebuild` so the image is baked even when the operator doesn't opt in at `up` time.
`mcps/echo_server/mcp.yaml` Header rewritten — no longer "stub example". Includes the manual-smoke recipe.
`tests/test_echo_mcp_server.py` (new) 8 cases.

Tests (8 new, all green)

Group Cases
Pure dispatch initialize / tools/list / tools/call echo / unknown tool / wrong arg type / unknown method / id always echoed (int / str / "" / None)
End-to-end Real `HTTPServer` on `127.0.0.1:` in a background thread, driven via `acc.mcp.client.MCPClient` with a real `MCPManifest` — confirms wire-format compatibility on both sides

Combined regression: 265 passed locally on Windows
(test_echo_mcp_server + test_mcp_stdio_transport + the four coding_agent tier files + test_task_progress_* + prompt-pane suite + redis-compat + telemetry + ecosystem + file-picker + config + role-store + guardrails + compliance, with `TestEd25519Validation` deselected for the lighthouse OpenSSL platform limit).

Test plan

  • `pytest tests/test_echo_mcp_server.py -v --no-cov` → 8 passed
  • Combined regression → 265 passed
  • `bash -n acc-deploy.sh` → syntax OK
  • Manual on acc1 post-merge (when SSH is back):
    ```bash
    MCP_ECHO=true ./acc-deploy.sh rebuild
    MCP_ECHO=true ./acc-deploy.sh up
    podman ps # acc-mcp-echo healthy
    curl http://acc-mcp-echo:8080/ # banner reachable from agent net
    ./acc-deploy.sh cli nats sub 'acc.sol-01.>' &

    Send a prompt via screen 7 that yields [MCP: echo_server.echo {"text":"X"}]

    → response carries the canonical {content:[{type,text,X}]} envelope

    ```

acc1 verification

Still deferred — SSH timeout to `10.199.12.91:22` since PR #21 (3 PRs ago). Tests are pure-Python + stdlib HTTP, no platform-specific code; the local Windows result transfers cleanly when the host is back.

Out of scope (future enhancements)

  • Resources / prompts methods (the MCP spec covers more than tools; ACC only consumes tools today).
  • HTTPS / mTLS — diagnostic server, host-network only.
  • SSE / streamable HTTP transport — current ACC HTTPTransport posts one JSON-RPC envelope per request; SSE would require wiring on the client side too.

…ional end-to-end

The ``mcps/echo_server`` manifest has shipped since Phase 4.2 as a
loader / TUI / role-allow-list fixture, but no actual server backed
the URL.  ``coding_agent``'s ``allowed_mcps=["echo_server"]``
declaration was a smoke target that 404'd at run time, and the
manual-smoke recipe in PR #20's prompt-pane help doc had to inject
synthetic events because the [MCP: echo_server.echo {...}] markers
the agent emits couldn't reach a real server.

This PR ships:

container/production/echo_mcp_server/main.py (NEW)
  Tiny diagnostic JSON-RPC 2.0 server.  stdlib http.server only —
  no Flask, aiohttp, mcp, or json-rpc deps.  Implements exactly the
  three methods :class:`acc.mcp.client.MCPClient` exercises:
    * initialize     → returns canonical protocolVersion + serverInfo
    * tools/list     → advertises one ``echo`` tool with valid
                       JSON Schema for its input
    * tools/call     → name=echo round-trips arguments.text in MCP's
                       canonical { content: [{type, text}] } envelope
  Listens on ACC_MCP_ECHO_HOST / _PORT (default 0.0.0.0:8080).
  Logs go through the standard logging pipeline at ACC_LOG_LEVEL
  (default INFO).  GET / returns a banner so curl /health-style
  probes don't 404.

container/production/Containerfile.echo-mcp-server (NEW)
  UBI10 python-312-minimal base, ~1 MB on top of the base layer
  (no pip deps).  Non-root UID 1001, EXPOSE 8080, USER 1001,
  CMD python3 /app/main.py.

container/production/podman-compose.yml
  + acc-mcp-echo service behind ``--profile mcp-echo``.  Container
    name matches the hostname referenced in the manifest's
    ``url: http://acc-mcp-echo:8080/rpc`` so agents resolve it on
    the acc-net bridge with zero manifest edits.  Healthcheck via
    GET / using stdlib urllib so we don't add curl to the image.
    Auto-built alongside the others by ``./acc-deploy.sh build``.

acc-deploy.sh
  + MCP_ECHO env var (default false) wires through to the
    --profile mcp-echo activation.  Header echoes the flag when set.
  + Profile auto-attaches on build/rebuild so a single
    ``./acc-deploy.sh rebuild`` produces the image without
    needing MCP_ECHO=true.
  + Header docstring documents the new env var.

mcps/echo_server/mcp.yaml
  Header rewritten — the manifest is no longer a "stub example", it
  describes a live diagnostic server.  Manual-smoke recipe added
  (MCP_ECHO=true ./acc-deploy.sh up + nats sub).

Tests (tests/test_echo_mcp_server.py — NEW, 8 cases all green):
  Pure JSON-RPC dispatch (7):
    * initialize returns protocolVersion + serverInfo,
    * tools/list advertises echo with valid JSON Schema,
    * tools/call name=echo round-trips text,
    * unknown tool name → JSON-RPC error with name in message,
    * tools/call with non-string text → param error -32602,
    * unknown method → method-not-found -32601,
    * response id always matches request id (across int / str / "" / None
      so ACC client's id-mismatch check never fires).
  End-to-end round-trip (1):
    * Spin up the real HTTPServer on 127.0.0.1:<ephemeral_port> in a
      background thread, drive it via :class:`acc.mcp.client.MCPClient`
      with a real ``MCPManifest``.  Confirms wire-format compatibility
      on both sides — server response shape passes the client's
      JSON-RPC envelope validation, MCPClient.list_tools + call_tool
      return the expected dicts.

Combined regression: 265 unit tests pass on Windows
(test_echo_mcp_server + test_mcp_stdio_transport + the four
coding_agent tier files + test_task_progress_* + the prompt-pane
suite + the redis-compat suite + telemetry + ecosystem +
file-picker + config + role-store + guardrails + compliance,
with TestEd25519Validation deselected for the lighthouse OpenSSL
platform limit).

acc1 verification deferred — host SSH still unreachable
(timeout to 10.199.12.91:22 since PR #21).  Tests are pure-Python
+ stdlib HTTP — local Windows result transfers cleanly when the
host is back.  Manual smoke recipe for acc1 is in mcps/echo_server/mcp.yaml.

Out of scope:
* Resources / prompts methods (the MCP spec covers more than tools;
  ACC only consumes tools today, so this server only implements the
  three methods the client exercises).
* HTTPS / mTLS — diagnostic server, host-network only.
* SSE / streamable HTTP transport — current ACC HTTPTransport posts
  one JSON-RPC envelope per request; SSE would require wiring on the
  client side too (separate PR if needed).
@flg77
flg77 merged commit 16ca584 into main May 2, 2026
@flg77
flg77 deleted the feat/echo-mcp-server-image branch May 2, 2026 08:53
flg77 added a commit that referenced this pull request Jun 5, 2026
…surface)

Eighth and final sub-slice of Stage 1
(openspec/changes/20260605-acc-pkg-trust-and-assistant/).  Lands the
declarative DC install API surface — the GitOps seam for Stage
1.5.3's pkg-install code path.

What ships:

  * operator/api/v1alpha1/acccatalog_types.go (NEW):
    - AccCatalog CRD mirroring acc.pkg.catalog.Catalog Pydantic
      model so the operator's rendered YAML validates cleanly
      against the Python loader.
    - Spec fields: catalogId, tier (trusted|tp|community|self),
      mode (https|file), url|path, requiredSigner (issuer +
      subjectPattern + optional keyPath), priority.
    - Status fields: observedGeneration, conditions[],
      lastRenderedAt.
    - Printcolumns + shortNames for kubectl-friendly UX.

  * operator/api/v1alpha1/accpackageinstall_types.go (NEW):
    - AccPackageInstall CRD — one @scope/name@constraint install.
    - Spec fields: name (regex-validated), constraint, catalogRef
      (optional pin), targetCorpus (optional scope), allowUnsigned
      (operator-explicit bypass).
    - Status fields: phase (Pending|Installing|Installed|Failed),
      installedVersion, installPath, contentSha256, lastInstalledAt,
      conditions[].

  * operator/api/v1alpha1/zz_generated_stage1_6_deepcopy.go (NEW):
    - Hand-written DeepCopy / DeepCopyObject methods following
      controller-gen's emission style.  Replace on next
      `make generate`.

  * gitops/argocd/applications/accpackage-sample.yaml (NEW):
    - End-to-end ArgoCD Application driving two AccCatalog entries
      (canonical https + corp-internal file-mode) + two
      AccPackageInstall objects.  Operators copy + adjust the
      catalog URL + signer pattern for their environment.

  * gitops/argocd/applications/README.md (NEW):
    - Documents what Stage 1.6 ships (API + sample) vs what's
      deferred to 1.6b (reconcilers + RBAC + OLM bundle +
      envtest).  The deferred reconciler consumes the same
      fetch_and_install Python entry point that 1.5.3 and 1.4
      already use — single seam, no parallel logic.

Design choices:

  * API surface lands now so downstream GitOps tooling can import
    the types; reconciler logic (exec-into-pod, leader election,
    status patching) is multi-day Go work and ships as 1.6b.
  * AccCatalog Spec mirrors acc.pkg.catalog.Catalog 1:1 — operator
    renders directly to /etc/acc/catalogs.yaml ConfigMap, no
    translation layer.
  * AccPackageInstall.spec.constraint accepts the same range syntax
    acc.pkg._semver implements; operator does shape validation, the
    installer is the resolution authority.
  * AllowUnsigned at the CR level so dev-environment opt-out is
    declarative + audit-logged via the controller's events.

Tests: no new Python tests (Go-only changes; Python pkg suite
unchanged at 415/1 green).  Go envtest integration tests land in
1.6b alongside the reconciler.  Manifest sample YAML parses (5
documents: Application + 2 AccCatalog + 2 AccPackageInstall).

This completes Stage 1's eight sub-slices:
  1.5.1 dual-source role loader (#21)
  1.5.2 required_packages (#22)
  1.5.3 acc-deploy.sh boot-time fetch (#23)
  1.4   PROPOSE_INFUSE marker (#24)
  1.1   eval format (#25)
  1.2   EC policy depth (#26)
  1.3   OIDC keyless publish (#27)
  1.6   operator CRDs (this)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
flg77 added a commit that referenced this pull request Jun 5, 2026
…I variant

End-to-end runner that walks the operator through the five-phase
acc1 K8s hub smoke after PRs #20-#28 land.  Hermetic CI variant
exercises the same chain in-process against a file-mode catalog
so PR-time tests prove the wiring without a running cluster.

What ships:

  * tools/smoke-acc1-hub.sh (NEW):
    - Phase 0 preflight — checks cosign / kubectl / python / jq /
      curl / acc-pkg on PATH.
    - Phase 1 — applies gitops/acc-hub/ if not present; waits for
      rollout; curls /index.json.
    - Phase 2 — generates pilot cosign keypair via
      tools/cosign-pilot-keygen.sh if not on disk.
    - Phase 3 — builds pilot pkg, signs with cosign sign-blob,
      publishes via gitops/acc-hub/publish-to-hub.sh; verifies the
      hub now advertises the package via jq on the live index.
    - Phase 4 — downloads tarball + sig from live hub, runs
      acc-pkg install into a tmp sandbox, exercises cosign verify.
    - Phase 5 — RoleLoader resolves coding_agent from the
      installed-package path (proves the dual-source loader chain
      from PRs #21-#23).
    - Coloured logging + idempotent steps + smoke-specific exit
      codes (7 = hub deploy fail, 8 = roundtrip verification fail).

  * tests/pkg/test_live_smoke_hermetic.py (NEW):
    - Mirrors the bash script's Phase 3-5 in-process against a
      file-mode catalog with mocked cosign so CI exercises the
      chain without acc1 reachability.
    - 7 tests: build determinism, end-to-end install + load,
      idempotent re-install, signing-floor refusal, --allow-unsigned
      bypass, PROPOSE_INFUSE shares the same fetch_and_install seam,
      and smoke script wiring sanity (script references the right
      helpers).

  * tools/SMOKE.md (NEW):
    - Operator runbook: prerequisites, run command, what each
      phase does, exit codes, troubleshooting matrix.

Test growth: 2979/37 (PR #27 baseline) -> 422/1 pkg suite (this PR
adds +7 hermetic tests on top of the operator-only script).  Full
sweep impact is +7 (since #28 was Go-only, no Python tests).

Stage 1 close-out — every code path the eight sub-slices ship is
now exercised by a single hermetic test that proves they compose
correctly:

  Build (#20) -> Sign (#27) -> Publish (#27) ->
  Catalog resolve (#20) -> Verify (#20 + #26) ->
  Install (#20) -> Registry (#20) -> RoleLoader (#21) ->
  PROPOSE_INFUSE dispatch (#24) all hit the same code path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
flg77 added a commit that referenced this pull request Jun 5, 2026
…rface)

Closes the visual gap deferred from PR #24 (Stage 1.4 PROPOSE_INFUSE).
The dispatcher was wired but operators had no panel to act in;
this adds a projection over the existing oversight queue with
infuse-specific columns.

Design — projection, not parallel queue:

  * The new Collapsible reads from the SAME oversight_pending_items
    list the main oversight queue uses.
  * Approve / reject post the SAME _OversightAction message →
    existing dispatch path → dispatch_approved_proposal →
    _dispatch_infuse (#24) → fetch_and_install (#23).
  * One dispatch path invariant pinned by
    test_decide_pkg_proposal_uses_same_oversight_action_envelope.

What ships in acc/tui/screens/compliance.py:

  * New Collapsible "Package Proposals (PROPOSE_INFUSE)" added
    next to "Rule Proposals" inside the gov-left column.
  * #pkg-proposals-table DataTable with columns:
    ID | Package | Constraint | Tier | Signer | Status
  * #pkg-proposals-actions Horizontal with Approve / Reject buttons.
  * #pkg-proposals-status Static for action feedback.

  * _is_pkg_proposal(item) classifier:
    - Prefers explicit kind="infuse" field.
    - Falls back to summary prefix "Install @" for compat with
      mixed-version arbiter fleets (older HEARTBEATs lack `kind`).
    - Case-insensitive on kind.

  * _pkg_proposal_columns(item) extractor:
    - Reads params.{name,constraint} when present (canonical case).
    - Falls back to summary parsing for name + constraint.
    - Accepts tier OR catalog_tier; signer_identity OR signer.
    - Returns "—" placeholders on missing fields (never raises).

  * _render_pkg_proposals(snap) — filtered render hooked into the
    main snapshot rendering after _render_oversight_queue.

  * _decide_pkg_proposal(approve=True/False) — extracts highlighted
    row's oversight_id, posts _OversightAction.

  * on_button_pressed wired to btn-pkg-proposal-approve|reject.

Tests: 14 new (tests/pkg/test_compliance_pkg_proposals.py).
Full sweep 3000/37 (vs 2986/37 baseline — exact +14 delta, no
regressions).  Existing 139 compliance-related tests unaffected.

What's NOT in this PR:

  * WebGUI parity — the dispatch is via _OversightAction so the
    existing WebGUI oversight queue already serves these
    proposals as plain rows; a dedicated React surface is a
    follow-up once Stage 2 Marketplace work scopes UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
flg77 added a commit that referenced this pull request Jun 29, 2026
…cryptography (#141)

Resolves the Python (pip) Dependabot alerts on flg77/acc-spearhead. Most were a
stale uv.lock (last generated 2026-06-14) whose pins lagged the already-permissive
pyproject constraints; one needed a constraint widening.

Lockfile refresh (constraints already allowed the fix — `uv lock --upgrade-package`):
  aiohttp   3.14.0 → 3.14.1   (#15-22, 8 alerts)
  pyjwt     2.12.1 → 2.13.0   (#9-13, 5 alerts)
  starlette 1.0.1  → 1.3.1    (#24-27, 4 alerts; transitive via fastapi)
  msgpack   1.1.2  → 1.2.1    (#37, high)
  joserfc   1.6.5  → 1.7.2    (#39; transitive via authlib)

Constraint widening (fix was outside the pin):
  cryptography 46.0.7 → 48.0.1 (#14/#23, two HIGH) — pyproject `<47`→`>=48.0.1,<49`.
  The Ed25519 arbiter sign/verify API is stable across 46→48; operator "pin and
  bump" one major at a time.

The lock also catches up on the speech/turbovec optional-deps added since 06-14
(faster-whisper/piper-tts/ctranslate2/onnxruntime/av/turbovec) — no security
content, just lock/pyproject reconciliation. No downgrades.

Not fixed here (separate handling):
  transformers (#1) — vulnerable `Trainer` not in our execution path; ST<5.0 blocks
    the 5.0.0rc3 fix → dismissed on GitHub with that reason (intent already in
    pyproject).
  torch (#7, low) — no patched release exists → dismissed "no fix available".
  npm console-plugin (7 alerts) — need npm/node (absent here) → fleet hand-off.

Verified with the bumped libs installed: signatures/spiffe 153 ✓, a2a/messenger/
slack/webgui/redis 168 ✓ (321 total, 0 failures).

Co-authored-by: flg <flg@acc1.ic3net.internal>
Co-authored-by: Claude Opus 4.8 <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