cMCP 0.4.1
Model Context Protocol library in pure C11
Loading...
Searching...
No Matches
v0.6.0 Acceptance Criterion

Written 2026-05-30, immediately after D1 dogfooding.

Why this doc exists

The Tier 6 cut at v0.5.0 hit an internal-quality bar: 22 test binaries green, 50k+ calls/s, Ajv-parity schema validator, threat model, packaging, retro-tags. None of those numbers measured the library against the seat of a host author actually trying to ship something.

The 2026-05-30 council session was convened to pick the next path. Five advisors converged on the same uncomfortable observation: cMCP has 2922 assertions and zero users. The peer reviewers added a council-wide blind-spot: no proposed path had a stated exit condition. Without one, Tier 7 (regression gates) would polish forever; Path B (deferred protocol work) would chase spec forever; any "spike" would manufacture urgency.

D1 produced evidence (see `dogfood-cragmcp.md`). This document is D2: the written exit condition for v0.6.0. It is the artefact every council reviewer flagged as missing.

What v0.6.0 ships

v0.6.0 is the first release shaped by host-side evidence rather than spec-reading or internal-test green. It closes the API-ergonomics gaps F1–F4 from D1, nothing else.

A1 — Single-client typed helpers (closes F1)

cmcp_client.h gains the same typed shortcuts that cmcp_session.h already exposes, so a host talking to one server doesn't have to drop to raw JSON walking or wrap one server in a session just to get them.

cmcp_session_tool_t **out, size_t *n);
cmcp_session_resource_t **out, size_t *n);
cmcp_session_prompt_t **out, size_t *n);
int cmcp_client_resource_read (cmcp_client_t *c, const char *uri,
char **out_text, size_t *out_n);
int cmcp_client_prompt_get (cmcp_client_t *c, const char *name,
cmcp_json_t *args,
cmcp_json_t **out_messages);
int cmcp_client_resources_list(cmcp_client_t *c, cmcp_session_resource_t **out_resources, size_t *out_n)
int cmcp_client_tools_list(cmcp_client_t *c, cmcp_session_tool_t **out_tools, size_t *out_n)
int cmcp_client_prompts_list(cmcp_client_t *c, cmcp_session_prompt_t **out_prompts, size_t *out_n)
int cmcp_client_resource_read(cmcp_client_t *c, const char *uri, char **out_text, size_t *out_n)
int cmcp_client_prompt_get(cmcp_client_t *c, const char *name, cmcp_json_t *args, cmcp_json_t **out_messages)
struct cmcp_client cmcp_client_t
Definition cmcp_client.h:31

Re-use the cmcp_session_tool_t / _resource_t / _prompt_t shapes that already exist. Pair each list helper with the existing cmcp_session_*_free to avoid a doubled-API surface. No client-side struct duplication.

A2 — Error-model flattener (closes F2)

tools/call carries two failure channels. A host should write one branch per outcome, not a dance through response.error then result.isError. v0.6.0 ships:

typedef enum {
CMCP_TOOL_OK, /* success, *out_result owned by caller */
CMCP_TOOL_ERR_TOOL_LEVEL, /* handler/schema error, *out_text owned */
CMCP_TOOL_ERR_PROTOCOL, /* JSON-RPC error, *out_err owned */
const char *name,
cmcp_json_t *args /* consumed */,
cmcp_json_t **out_result,
char **out_text,
cmcp_rpc_error_t **out_rpc_err);
cmcp_tool_result_t cmcp_client_tool_call(cmcp_client_t *c, const char *name, cmcp_json_t *args)
cmcp_tool_outcome_t
Definition cmcp_client.h:50
@ CMCP_TOOL_ERR_TOOL_LEVEL
Definition cmcp_client.h:52
@ CMCP_TOOL_ERR_PROTOCOL
Definition cmcp_client.h:53
@ CMCP_TOOL_OK
Definition cmcp_client.h:51

Exactly one of out_result / out_text / out_rpc_err is populated on return. Caller owns the populated one (free via the matching cmcp_json_free / free / cmcp_rpc_error_free).

A3 — Doc tightenings (closes F3 + F4)

Nothing is removed. No struct layout change. No protocol change. SemVer-minor.

Observable conditions for shipping v0.6.0

The release ships when all four conditions are simultaneously true. This is the council's missing exit condition.

O1 — Dogfood harness rewrites to public helpers only

tools/dogfood-crag-host/main.c is rewritten to use ONLY the new typed helpers from A1 + A2. After the rewrite:

  • Zero direct cmcp_json_object_get(...) calls in the harness.
  • Zero direct cmcp_rpc_message_t declarations or cmcp_rpc_message_clear calls in the harness.
  • Step 6 (error paths) becomes one switch (cmcp_client_tool_call(...)) per call site — no inspection of response.error and no inspection of result.isError from the host's seat.
  • LOC reduction is informational, not gated: the expectation is ~40% drop. If it doesn't drop, A1/A2 are not earning their keep and should be revisited.

O1 is the only criterion that proves A1/A2 are well-shaped. Everything else (tests, conformance, fuzz) is hygiene.

O2 — Full quality matrix stays green

Gate Command Required state
Unit tests make test 22+ binaries pass, ≥2922 assertions
Valgrind make valgrind leak-clean
Sanitisers make test-asan / make test-tsan clean
Conformance make conformance green vs TS reference SDK
Schema conformance make schema-conformance ≥83/83 cMCP vs Ajv agreement
Replay make replay every tracked fixture passes
Fuzz smoke make fuzz-smoke no crashes (60s/harness × 4)

New helpers ship with positive + negative tests in tests/test_client_* that mirror the cMCP test convention (one test_<name> function per behaviour). Aim for ≥30 new assertions.

O3 — One new replay fixture lands

conformance/fixtures/crag-mcp/dogfood/session-2026-06-XX.jsonl is re-captured by re-running make dogfood-crag-host after A1/A2 land. Registered in conformance/replay/fixtures.json. After that, the dogfood pass becomes a permanent regression gate via make replay.

O4 — Release-cut paperwork is complete

  • CHANGELOG.md gains a v0.6.0 section above v0.5.0, naming A1/A2/A3 and citing the council session + docs/dogfood-cragmcp.md as the origin.
  • README.md Status section flips: "v0.6 — first host-driven cut."
  • docs/SEMVER.md retro-tag chain gains v0.6.0 (with the closing commit SHA pinned, per the post-Tier-6 fix).
  • include/cmcp.h CMCP_VERSION advances to "0.6.0".
  • Annotated tag v0.6.0 pushed to origin.

Out of scope for v0.6.0 (deliberate)

The council-D1 findings F5 and F6, and the entire Tier 7 axis, are out of scope for this release. Each has a reason.

  • F5 (JSON helper naming consistency). cmcp_json_new_string (verb-first) vs cmcp_json_object_set (subject-first) is a paper cut, not a barrier. Renaming is an ABI break — folds into v0.7.0.
  • F6 (server-concurrency capability flag). Real observability gap, but it is a spec-level conversation. cMCP introducing a vendor-prefixed extension without spec engagement would be exactly the "spec-reading without consumer pain" trap the council named in Path B. Park as a discussion item.
  • Tier 7 (perf regression CI gate, nightly fuzz, nightly soak, coverage delta, schema corpus growth). All five axes remain filed in TODO.md. They become v0.7's tier. The principled reason: Tier 7 gates baselines, but until A1/A2 land, the baseline is the wrong shape — the regression fixtures Tier 7 would protect are the ones the rewritten dogfood harness produces. Sequencing is forced.
  • butlerbot integration. Standing user constraint. Unchanged.

Effort budget

Axis Estimate
A1 typed helpers (impl + tests) ½ day
A2 flattener + outcome enum ½ day
A3 doc updates ½ day
O1 harness rewrite ½ day
O3 fixture re-capture ¼ day
O4 release-cut paperwork ¼ day

Total: ~2.5 days. Aim: v0.6.0 cuts ≤ 7 calendar days from v0.5.0 (2026-05-29) — so on or before 2026-06-05.

Decision rules during implementation

These let v0.6.0 ship without scope creep:

  1. If A1/A2's signatures surface a hidden API design question (e.g. how resource_read handles a binary blob content item): document the decision in include/cmcp_client.h near the declaration, choose the simplest semantics, ship. Do not add a second helper for the alternate case.
  2. If a finding-class hole becomes visible mid-flight: append to docs/dogfood-cragmcp.md. Do not retroactively expand the v0.6.0 scope. A v0.6.x patch series is fine.
  3. If O2 ever goes red after A1/A2 land: treat as a P0. Do not nolint, --no-verify, or sanitiser-disable.

Stop conditions

v0.6.0 work stops and reverts to the design table if:

  • A1's helper signatures require any cmcp_session.h change. Indicates the typed shapes aren't reusable, which is a deeper question (probably v0.7.0 architecture-level).
  • A2's outcome enum needs more than three cases. Indicates the error model has more structure than F2 found; warrants more dogfooding before locking the surface.
  • The harness rewrite cannot drop direct cmcp_json_object_get to zero. Indicates a missing helper. The right move is to identify which one and ship it inside A1's batch.

How to know v0.6.0 is "done"

The release is done when these are simultaneously true:

  • A1 + A2 + A3 land as PRs / commits, each with the matching tests.
  • O1 holds (harness rewrite uses only public helpers).
  • O2 holds (full quality matrix green).
  • O3 holds (new dogfood fixture in make replay).
  • O4 holds (paperwork + tag).

If you can answer "yes" to all five, cut v0.6.0. If you cannot, do not cut. There is no third option.