v2.4: Agents & Teams — SDK extraction (pmcp-agent, pmcp-team-servers, pmcp-package + cargo-pmcp verbs)#302
Merged
Merged
Conversation
… audience The "Publish to MCP Registry" job has failed on every release since v2.10.0 with: invalid audience: expected https://registry.modelcontextprotocol.io, got [mcp-registry] mcp-publisher v1.5.0 mints its github-oidc token with the legacy "mcp-registry" audience; the registry now requires the URL audience, which newer publisher releases send. Non-blocking historically (crates.io publish is a separate job), but the registry listing has been stuck since 2.9.x. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit fb99bca)
… module - Move Root + ListRootsResult to target-agnostic src/types/roots.rs (server::roots keeps a back-compat re-export; now compiles on wasm32) - Add client::host module: HostSamplingHandler, HostElicitationHandler traits - Add RootsProvider (Result-returning), ClientHostRegistry, and the preflight/result-review approval types (ApprovalDecision/PreflightApproval/ SamplingResultReview) — invocation lands in the next task - Add pure classify_host_request + HostRequestKind for dispatch routing - Re-export the host surface from client::mod for pmcp::client::host::* (cherry picked from commit d8985918ad636189ba9cf47738756e64d82a0b35)
- Add host_registry field to Client (all 3 constructors + Clone + Debug) - Add ClientBuilder host_registry field + generic builder methods: on_sampling / on_elicitation / on_roots / on_sampling_approval / on_sampling_result_review (closures, no Arc-alias in signatures) - Replace the ":2234" Unexpected-message error arm with dispatch_host_request: routes inbound sampling (both parse variants) / elicitation / roots to the host handlers, answers or returns -32601, then continues the receive loop - Handler/provider errors log full context locally (tracing::error!) and return a sanitized -32603 (no internal text forwarded to the remote server) - create_message rustdoc names the LLM-server pattern and cross-links the real pmcp::SamplingHandler + pmcp::client::host::HostSamplingHandler paths - Unit tests: sampling client-alias routing, known-unhandled -> -32601, sanitized -32603, roots provider answer (cherry picked from commit 6d0d0bd)
…ample - tests/client_host_roundtrip.rs: raw-duplex-pump round-trips proving the client answers inbound sampling (HOST-01), roots (HOST-03), and elicitation (HOST-02) from its registered host handlers while a tools/list is in flight; plus prop_sampling_passthrough asserting params (tools/tool_choice/tool_use/ tool_result) reach the handler unchanged - examples/s49_sampling_host.rs: runnable sampling-host example (mock LLM host answers an inbound sampling/createMessage over an in-process duplex); registered in Cargo.toml + examples/README.md - Uses a raw pump (not Server::run + PeerHandle): the high-level Server::run serializes request handling on one loop, so a tool blocking on extra.peer().sample() deadlocks (pre-existing server-side limitation, flagged for Phase 108); PeerHandle is also intentionally not extended with elicit (cherry picked from commit 86d4d29)
RootsProvider/ApprovalDecision are re-exported into client scope, so the bare intra-doc link form resolves — silences 3 rustdoc redundant-link warnings. (cherry picked from commit 9025699da3c98142b4c8a8ca4a37f4bc499bc3bf)
- Contrast spec host sampling (server->client, HostSamplingHandler) vs the LLM-server pattern (client->server, pmcp::SamplingHandler) - Document the preflight on_sampling_approval gate + result-review hook - Note the nested-flow/idle-host + Server::run (D-106-A) limitations - Reference the s49_sampling_host runnable example (cherry picked from commit 8e066f0e342a911c8f0b602f61c872c2db2d5d79)
- Add ch17-04 sub-entry under Chapter 17, after Tool with Sampling - mdbook build passes with no broken-link warning (cherry picked from commit 85ba059)
… test - Two-stage sampling approval in dispatch_host_sampling: preflight gate runs BEFORE the handler (Deny prevents the LLM call, no tokens billed), optional result-review runs AFTER (default pass-through) - Sanitized -32603 'request denied by host policy'; raw deny reason logged locally via tracing::warn!, never forwarded - Unit tests (a-e): default-allow, preflight-deny-skips-handler, allow, result-review-deny-after-handler, review-absent passthrough - tests/client_host_approval.rs: duplex preflight-deny + connection survives; plus known-unhandled elicitation -> -32601 + connection survives (addendum) (cherry picked from commit fc9873a34f0ce8c2fe6232385166e03dac69e845)
- initialize now applies the registry-authoritative rule per host field: handler absent => force None (anti-capability-lie); handler present + caller None => default(); handler present + caller detail => preserve - tasks/experimental and all non-host fields left untouched; no independent public setter for the three host fields - Unit tests: registered=>present, unregistered=>absent, anti-lie discards caller value, preservation of caller sub-field detail, elicitation+roots parallel coverage, tasks/experimental untouched (cherry picked from commit 69ee638129ce18468d5be1154ace0167a6089bed)
…uest) - New fuzz/fuzz_targets/client_host_routing.rs drives the REAL routing path: bytes -> JSONRPCRequest<Value> -> parse_request -> classify_host_request (plus the CreateMessageParams/ElicitRequestParams serde boundary) - Expose classify_host_request + HostRequestKind as #[doc(hidden)] pub so the fuzz crate can reach them (not part of the stable public API) - Register [[bin]] client_host_routing in fuzz/Cargo.toml - Add empty [workspace] to fuzz/Cargo.toml so the crate resolves as its own workspace root under git-worktree checkouts (Rule 3 blocking fix) - Smoke: cargo fuzz run client_host_routing -- -runs=2000 => Done, exit 0 (cherry picked from commit 62b0ffb)
- Root pmcp crate 2.15.0 -> 2.16.0 (additive host surface, minor bump) - cargo-pmcp workbook_server.rs PMCP_VERSION pin 2.15.0 -> 2.16.0 in lockstep (the drift-guard tripwire emitted_pmcp_version_matches_workspace_pin that broke the 2.15.0 release) - cargo-pmcp own version 0.17.3 -> 0.17.4 so the scaffold-pin change ships - No unrelated downstream pin edits (cherry picked from commit ffd5099)
- Capability tests: use struct-init with ..Default::default() instead of field-reassign-after-default (clippy::field_reassign_with_default) - Reflow assert! macros to rustfmt width - tests/client_host_roundtrip.rs (plan-01): backtick tool_choice/tool_use/ tool_result in arb_params doc (clippy::doc_markdown, surfaced by the current toolchain; blocks the shared quality gate) [Rule 3 - blocking, pre-existing] (cherry picked from commit ed5fdaa01de920a8e315cc4317146362ff75457c)
…ge works Client::create_message unconditionally errored because assert_capability had no "sampling" match arm — the check fell through to _ => false and always returned a capability error, so the documented LLM-server pattern was dead. Add the arm mirroring ServerCapabilities.sampling (which ServerBuilder::sampling sets), plus positive/negative lib unit tests and an end-to-end duplex regression proving create_message round-trips against a Server::builder().sampling(..) server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 96987bb)
The host dispatch path classified an inbound server->client ping
(ClientRequest::Ping) as Unhandled and replied -32601, so keepalive pings
failed and connections that treat ping failure as death would drop. Add a
HostRequestKind::Ping arm that answers with an empty-object success result
independent of the registry, plus a classify unit test and a duplex test
proving the ping returns {} and the connection survives.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit ee3415d503db9c1d27f63ae22318d61a856ce726)
…fails The receive-loop Request arm propagated a transport send error via `?` without removing request_id from active_requests, leaking the map entry and its oneshot cancel sender (a regression vs the replaced code path and inconsistent with the Response arm). Remove the pending entry before returning the error, and add a unit test driving a transport whose host- response send fails. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 5c95fec)
… optional) The sampling approval docs claimed the hooks' invocation was deferred to a follow-on plan and labelled PreflightApproval "Mandatory". Both are wrong: dispatch_host_sampling invokes the preflight and result-review hooks in this phase, and the posture is optional with default-allow. Rewrite the type docs (sampling.rs) and both builder-method docs to state the hooks are live as of this phase and describe the default-allow / pass-through posture accurately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 606bbcf37e6a0fb50a18209330a17d039ea0e322)
… unchanged) The HOST-05 anti-capability-lie rule overrides caller-set sampling/ elicitation/roots capability fields on initialize when no host handler is registered — including for Client::new users who cannot register handlers. This is the locked phase decision and is intentionally left unchanged; document it instead. Add a rustdoc note on `initialize`, rewrite the contradicting `send_roots_list_changed` doc example to register a roots provider via `ClientBuilder::on_roots` (and preserve `list_changed`), and add a CHANGELOG 2.16.0 entry flagging the silent-change risk for existing Client::new callers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 9cea2f7)
…y gate) (cherry picked from commit 12f682a)
Behavior-preserving cleanups from a 4-angle review of the Phase 106
client host surface (fix 8 additionally repairs pre-existing
active_requests leaks on error paths, approved):
1. Collapse host_method_not_found / host_policy_denied /
host_internal_error into one host_error(id, code, message) built on
JSONRPCResponse::error + JSONRPCError::new + ErrorCode constants
(no more magic numbers / struct literals).
2. Extract sync_cap<C: Default> helper for the HOST-05 rule in
derive_host_capabilities, replacing the three duplicated stanzas.
3. Borrow host_registry entries (&self.host_registry.X) instead of
Arc-cloning in the four dispatch paths.
4. Make handler param clone conditional: clone for result-review only
when a review callback is registered (zero clones otherwise).
5. Inline the single-use extract_elicitation_params via let-else;
keep extract_sampling_params (two-variant ambiguity).
6. Extract duplicated pump helpers (recv_request_id, recv_response,
send_success, init_result_value + shared inject_and_capture prefix)
into tests/common/host_pump.rs, included per-crate.
7. Match response.payload directly (ResponsePayload::Result) instead of
serialize-and-lookup in result_payload and the s49 example.
8. Install a single WR-04 exit-cleanup invariant in send_request: the
post-registration body runs in an inner future; active_requests entry
is removed at one point on every error exit (fixes the three prior
leaks). Adds test_receive_failure_cleans_active_requests.
9. Make assert_capability's fall-through loud: tracing::error! +
debug_assert! on an unknown capability string (still returns false in
release).
10. Remove the redundant clone chain in the client_host_routing fuzz
target; probe both param types from &Value via Deserialize.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit a255be8)
…oc wording
Code-review findings on the Phase 106 host surface:
- assert_capability unknown-arm diagnostic logged {method} while labeling it
'unknown capability string'; now logs the actual unmatched {capability} token
(plus method for context) so the 'add an arm' hint names the right string.
- dispatch_host_sampling doc called preflight 'a mandatory gate', contradicting
the default-allow posture two lines down and the corrected sampling.rs wording
(WR-02 missed this occurrence); reworded to 'optional gate (default-allow)'.
(cherry picked from commit 402db4e158e51d33901d446a1ea8e16e32ba00c2)
- 4 equations: fs/mem/approval/team_dispatch tool surfaces - namespaced provisional extension; resolve_approval/get_approval unnamespaced legacy - storage-agnostic team-mcp dispatch invariants (stable member ID, ToolOutput::Result) - no lean_theorem, no binding.yaml (deferred to Phase 109) (cherry picked from commit e8496d836539bc0e547f211565a707062bf7247a)
- Copy the full crate (Cargo.toml, src/ 23 files, tests/) verbatim from pmcp-run/crates/pmcp-package, excluding target/ - Keep the empty [workspace] table (standalone-isolation mechanism) - Relax the repo-coupled deploy.toml fixture-coverage discovery floor (was >=15, a pmcp-run-specific count) to the environment-agnostic invariant: every tracked descriptor parses [Rule 1/3 - port fix] Standalone: cd crates/pmcp-package && cargo test -> 118 passed. (cherry picked from commit b7fa50b)
- Cargo.toml: repository -> paiml/rust-mcp-sdk, add authors/readme/ documentation/keywords/categories, public description, and a [package.metadata.docs.rs] table (PKG-01); scrub internal refs from dependency comments; keep license and pinned deps unchanged - LICENSE-MIT: copy repo-root MIT body verbatim - LICENSE-APACHE: byte-identical canonical Apache-2.0 template (no injected copyright holder) - NOTICE: ownership/attribution kept out of the license body - README.md: public overview + explicit Wire-Freeze Policy (0.1.x digest-stable, shape change bumps 0.2.0) - CHANGELOG.md: 0.1.0 entry recording adoption + wire-freeze policy cargo publish --dry-run --allow-dirty exits 0; README/CHANGELOG/both licenses present in cargo package --list. (cherry picked from commit 8c2fe36)
- 13 fixtures across team-fs/mem-mcp/approval-mcp/team-mcp (versioned schema v1) - positive per family + both dynamic families + related_task _meta surface - 5 negative/security cases (invalid args, unknown member, malformed/excessive depth, self-call) - tests/team_contracts_conformance.rs: schema-aware gate, contract cross-reference via CARGO_MANIFEST_DIR - dependency-free (serde_json + std); no serde_yaml added (cherry picked from commit 1f2bfc3)
- Remove internal ticket IDs (I-N, D-N, T-168, Phase N, Wave 0) and 'Plan NN' refs from every shipped file's comments and test messages, preserving all behavioral/security rationale (path-traversal guard, trust-boundary, canonicalize-then-hash explanations retained) - Rewrite the crate-level lib.rs rustdoc to a clean public description - Disambiguate intra-doc links to crate::digest::verify (module vs fn) so docs build under -D rustdoc::broken_intra_doc_links - Reword 'OCI-1.1' -> 'OCI 1.1' (the substring 'I-1' tripped the scrub gate as a false positive) Zero files match the scrub regex; RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links" cargo doc --no-deps exits 0; 118 tests still pass. (cherry picked from commit a16cff97d3771325e9d488503b1b5d5bdd17ab67)
…kinds - Add agent + team golden fixtures (canonical-JSON input, round-trips) - Add tests/golden_fixtures/canonical/<kind>.canonical.json snapshots - Pin EXPECTED_<KIND>_DIGEST wire-freeze constants (server/workflow/agent/team) - Assert canonicalize() byte-equals each snapshot (silent field add/remove gate) - Retain per-kind >=100-recomputation determinism assertions (cherry picked from commit 3aa09737ff981e7136128cd6f6ea9a241d8ce0ce)
…ygiene) - Makefile: add pmcp-package-gate (fmt/clippy/test via --manifest-path), chain into quality-gate - ci.yml: add standalone pmcp-package fmt/clippy/test step (closes workspace-exclusion blind spot) - release.yml: add early-leaf publish step via --manifest-path with precise failure classification - Cargo.toml: exclude tests/team_contracts_conformance.rs from published pmcp crate - CLAUDE.md: document pmcp-package as workspace-excluded leaf, publish before cargo-pmcp - normalize pmcp-package sources now that standalone fmt is gated (cherry picked from commit ee21734)
pmcp-package is a standalone workspace-excluded leaf with no in-repo consumers yet. Its release.yml publish step sat before core `pmcp` with an `exit 1`-on-failure guard and a rationale claiming cargo-pmcp pins it — but nothing in the repo depends on it. Moving it to the end of the crates.io job so a failure in this experimental 0.x crate cannot gate the core SDK release; corrected the ordering comments in release.yml and the CLAUDE.md publish-order list. Re-pin earlier once a shipped crate actually depends on it (planned: cargo-pmcp, Phase 110). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 0b37daabfab27f2a14b8db605b358fe977cb08c0)
The untracking of contracts/.pv/cache/... landed in the WR-01 commit; this adds the .pv/ ignore rule so PMAT/pv lint-cache scratch is never re-committed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 12081f66d6eda1baba83c51c2819bbe9a42d435f)
pmcp-package (workspace-excluded) was fmt/clippy/test'd twice in CI: a standalone step in the `test` job AND via the `quality-gate` job's `make quality-gate` -> `pmcp-package-gate`. Both are full from-scratch compiles of the crate tree on separate runners. Removed the test-job copy; the Makefile gate is the single source of truth (reused locally and by the quality-gate CI job, which feeds the merge-blocking `gate`). No coverage lost. (/simplify: efficiency + altitude, deduped.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit d827dfa)
LocalDirPackageResolver joined the raw ComponentRef name into a filesystem
path (root.join(format!("{}.json", r.name()))) with no containment check,
so a name containing '/', '\\', '..' or NUL (e.g. ../../../../etc/hosts)
escaped root and read a file outside it.
Add is_safe_component() and reject any name that is not a single safe path
component in resolve_agent BEFORE the join, returning the existing
ResolveError::NotFound. Adds a unit test proving a traversal name is rejected
(not read) plus a direct test of the guard predicate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 544891924ce2d135bc893840ed01aa51cb148dcf)
- pmcp-agent with openai-compat feature (agent dev source) - pmcp-team-servers with runtime+http (http transitively enables member-llm) - pmcp-package caret "0.1" pin (CLI-04 tripwire) - version 0.17.4 -> 0.18.0 (cherry picked from commit b2dff8de31912c73a6ef21b21cfdb86c5f0bd959)
…lers
- agent group: AgentCommand{New,Dev}, async execute
- team group: TeamCommand{Dev}, async execute
- package group: PackageCommand{Show,Capture}, async execute
- all handler bodies actionable anyhow::bail! stubs (no panic)
- unused params underscore-prefixed for zero-warning gate
- capture carries capture-local --target (Codex MEDIUM, for 110-05)
(cherry picked from commit 7e732c8caf04fe0269f3e6f83783c678fa691d54)
- three enum Commands variants mirroring the Workbook arm - three dispatch arms via execute_agent/team/package async wrappers - each wrapper owns a tokio runtime + block_on (mirrors execute_landing) - Package NOT added to is_target_consuming (capture resolves own target) - verb_help.rs asserts agent/team/package --help list subcommands (cherry picked from commit a238142)
- tests/scaffold_agent.rs invokes the real cargo-pmcp binary, asserts the four emitted files, round-trips agent.package.json through AgentPackage, then spawns cargo check + cargo test --test pin on the emitted crate - covers the --force destination-overwrite policy - extend shared append_crates_io_patch to also patch the unpublished pmcp-agent + pmcp-package 0.1.0 crates (transitive closure for the runner) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 2052b7b)
…evel pin tripwire - templates/agent.rs: generate() emits Cargo.toml (full dep set), src/main.rs (a runner that LOADS agent.package.json and drives AgentEngine), the AgentPackage manifest (built from the real struct → guaranteed round-trip), and an in-scaffold tests/pin.rs tripwire - PMCP_AGENT_VERSION drift-guard test asserts the pin == crates/pmcp-agent [package] version (D-05); manifest round-trip + full-dep + manifest-driven unit tests - register templates::agent in the bin tree and a templates_agent lib seam (mirrors templates_workbook_server) so the drift guard runs under --lib - add semver dep for constructing the starter AgentPackage version Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit d981e2f)
…mote validate_crate_name (GREEN) - promote validate_crate_name to pub(crate) (reuse, not re-implement — D-01a) - agent/new.rs: validate the crate name before any fs write, resolve the target dir (default ./<name>), reject a symlinked destination and refuse a non-empty dir unless --force, create src/, then delegate to templates::agent::generate; gated colored next-steps - ensure_destination_writable helper keeps the handler under cog 25 - the scaffold_agent integration test now passes GREEN (compile + pin + force) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit c22c4a2d2b949a28fbad46a67ae39c4f3beb20be)
…ss format!)
The pin.rs template interpolates nothing itself — its {version}/{EXPECTED_LINE}
are the scaffold's own assert! args. Return a plain literal instead of format!
with doubled braces (byte-identical output, clears clippy useless-format).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7609631817e38254df6ee3bdef71eccf88d43810)
…hosted agent dev tests - fixed_source_runs_offline: drives the lib-safe run_fixed_source seam to RunOutcome::Completed + asserts the real binary agent dev --source fixed exits 0 - sampling_hosted_run_in_process: AgentServer + SamplingSourceFactory over an in-process DuplexTransport, scripted end-turn host, driven to a terminal task - RED: cargo_pmcp::agent_run seam and the wired handler do not exist yet Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 800f0300678dc7a5215dd2233cd3e2f7bf917a26)
…source seam - commands/agent/run.rs: lib-safe run_fixed_source(config) -> RunOutcome (leaf; no clap/GlobalFlags) + shared NoopInvoker; mounted in lib.rs as agent_run via a #[path] seam so the offline test + 110-06 example reach the same production path - commands/agent/dev.rs: --source clap ValueEnum (OpenaiCompat|Sampling|Fixed); loads an AgentPackage (--package / ./agent.package.json / built-in demo) and resolves it via resolve_agent; openai-compat builds OpenAiCompatSource via with_options, maps a construction Decode error to a --allow-insecure-http bail and a non-Completed RunOutcome to an actionable --endpoint/--source fixed bail; sampling serves AgentServer over pmcp::StdioTransport; fixed delegates to the seam - GREEN: fixed + sampling agent_dev tests pass; no PMAT cog>25 on new helpers Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit cbff3b5)
- transcript_drives_seven_step_doc_review_offline: TeamRuntimeBuilder + fixed_override drives the 7-step doc-review flow, asserts step ordering and a clean rt.shutdown() count (fully offline) - serve_exposes_team_mcp_over_http: the public team-mcp binary recipe (build_team_mcp_server + StreamableHttpServer) on an ephemeral loopback port; asserts a tools/list over HTTP lists the team_mcp__<member> tools - llm_drives_against_mock_endpoint: OpenAiCompatSource wrapped in the exported FixedSourceFactory against a mockito endpoint; asserts the mock was hit and a terminal related-task pointer surfaced - Tests characterize the composable primitives the CLI handler assembles (commands::* is bin-only, so the stub cannot be reached from an integration test) (cherry picked from commit bf50b8f)
- default: compose the doc-review team via TeamRuntimeBuilder over in-memory transports on an offline FixedSource and print a labeled 7-step transcript; TeamPackage from --package (+ --data-dir) or the built-in doc-review fixture (D-02 locked default). Composition is delegated entirely to TeamRuntime — no hand-rolled server spin-up. - --serve: expose team-mcp over HTTP via the shipped team-mcp binary's PUBLIC serve recipe (member-wiring loop -> build_team_mcp_server -> serve_streamable_http) on 127.0.0.1:<--port>, running until Ctrl-C. Not TeamRuntime, no upstream API change. - --llm <endpoint>: parse with url::Url, build+validate an OpenAiCompatSource once (Decode -> actionable --allow-insecure-http bail), wrap it in the exported FixedSourceFactory (correct sync/infallible shape), key from --llm-api-key-env as a SecretString. - Replaces the 110-01 bail! stub; each branch is a small helper (cog <=25). (cherry picked from commit 05b26983f414a7bcc06d28c6334f886bd9e5f446)
…pin tripwire - pmcp_package_pin.rs asserts cargo-pmcp pins pmcp-package = "0.1" (CLI-04/D-04b), green immediately - package_show.rs builds an agent fixture via pack_agent and drives the real binary offline - edge cases: non-OCI-layout path + zero-manifest layout must error clearly (RED pending show handler) (cherry picked from commit 361abb8d786832d6dda752f5154c1ef022cd8e24)
…line dispatch - kind.rs: PURE detect_kind + artifact_type_from_manifest_json leaves (never panic on adversarial input) + table-driven units + proptest 'Some iff known constant' - lib #[path] seam exposes kind.rs as cargo_pmcp::package_kind so --lib reaches it and 110-06 can fuzz the untrusted manifest-parse boundary - show.rs: OciLayout::open + reject zero/multiple manifests + detect_kind over BOTH artifactType and config/layer media types + unpack_* dispatch + colored offline render; digest verification surfaces via unpack_* (cherry picked from commit 3084f56)
…iry check, timeout + non-2xx upload - capture.rs: resolve_target(--target) + TokenCacheV1.entries.get + .value (correct APIs); is_near_expiry bails before uploading an expired token; actionable configure/auth guidance when unconfigured; packs the OCI layout to an in-memory zip; never prints the token - capture_upload.rs: lib-safe HTTP seam (Bearer header + request timeout + bounded non-2xx error), mounted into lib as cargo_pmcp::package_capture so --lib capture_upload + 110-06 reach it - mockito unit tests prove Bearer/path/bytes + 2xx->Ok / 500->Err; child-process HOME-isolated unconfigured integration test (cherry picked from commit 76d15a2)
…as a live candidate - gives the pure untrusted-parse leaf a production caller over the RAW manifest bytes (the 110-06 fuzz boundary), removing the bin-target dead-code path (cherry picked from commit d5f2228)
…w tests Cross-plan fmt integration fix — 110-01's multi-line execute_agent signature and 110-05's test formatting are collapsed by rustfmt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit fafb188801ece62841662206ab462377413e8e0d)
…seams - Mark templates_agent / agent_run / package_kind seams #[doc(hidden)] (Codex 110-06 MEDIUM — internal support seams, not stable API) - Add examples/agent_scaffold_and_run.rs: scaffold via templates_agent::generate then drive the PRODUCTION run_fixed_source runner offline (Codex 110-06 HIGH — no re-implemented AgentEngine loop) - Satisfies the CLAUDE.md ALWAYS EXAMPLE requirement for CLI-01/CLI-02 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit ed0118c)
… boundary - New libfuzzer target: raw bytes → artifact_type_from_manifest_json → detect_kind - Exercises the real package show untrusted seam (Codex 110-06 MEDIUM), never panics on adversarial manifest bytes (threat T-110-05-03 / T-110-06-01) - Reaches production via the #[doc(hidden)] package_kind lib seam (no new dep) - Satisfies the CLAUDE.md ALWAYS FUZZ requirement for the package parse path Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 95edacdf8ffa6954778370d2784b2a1dbf41692e)
- New examples/team_dev_transcript.rs: composes the built-in doc-review team via TeamRuntimeBuilder over in-memory transports with a FixedSource override - Drives the 7-step doc-review flow and rt.shutdown() fully offline (D-02 — no hand-rolled server spin-up), mirroring team dev's default path - Satisfies the CLAUDE.md ALWAYS EXAMPLE requirement for CLI-03 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 169b299)
Cleanup pass over the Phase 110 diff (no behavior change): - Extract shared resolve_api_key + build_openai_compat_source into commands/agent/sources.rs; agent dev and team dev --llm both call it instead of maintaining byte-identical copies. - builtin_demo_package delegates to templates::agent::starter_package (the two were identical AgentPackage literals). - Collapse agent_ref/server_ref into one component_ref(name, kind). - Drop dead write-only EndTurnSource.calls field (now a unit struct). - Drop redundant client-builder timeout in package capture (the capture_upload seam already applies the per-request timeout). - Reuse pmcp_team_servers::transport::DuplexTransport and cargo_pmcp::agent_run::NoopInvoker in tests instead of re-implementing. - Add missing #[doc(hidden)] to the package_capture lib seam for consistency with agent_run/package_kind. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 71fba19)
… guards Code-review findings on the Phase 110 diff: - finish_engine_outcome: map RunOutcome::LimitReached and RetryRequired to cause-specific messages instead of the misleading 'endpoint may be unreachable' — a hit iteration/token limit or requested retry is not a connectivity failure. - agent new: reject a destination that already exists as a regular file with an actionable message, instead of failing opaquely in create_dir_all under --force. - package capture: the expiry gate fires within the refresh window (before actual expiry), so say 'expired or about to expire', not 'is expired'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit f17e426)
Pre-existing rustfmt drift (rustfmt 1.9.0) in files outside Phase 110, surfaced by make quality-gate's workspace-wide fmt --all --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit c5a956170b0efeba9a39e6e31a6291783e092599)
Repo + CLI documentation for the Phase 110 verbs (book/course deferred):
- Clean plan-internal leakage from user-facing CLI help ('delivered by Plan
110-0X', internal 'resolve_target' in --target help).
- New per-command reference pages: docs/commands/{agent,team,package}.md.
- cargo-pmcp/README.md: new 'Agents & Teams' section, Commands-table rows,
and a Features bullet, all grounded in the real CLI help.
- Root README.md: new 'Agents & Teams' ecosystem bullet.
- Add README.md for pmcp-agent and pmcp-team-servers (they had none and are
first-publish 0.1.0 crates — crates.io needs a landing page) + wire the
readme field in both manifests, matching pmcp-package.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit f3e6732)
…ct, drop capture pmcp.run platform review: our local package show|capture squatted the verb names their remote capture service defines with opposite semantics, and capture POSTed to a nonexistent endpoint. Per the review + owner decision: - Rename package show → package inspect (honest local offline OCI-layout inspector). Frees show for the platform's remote manifest-fetch thin client. - Drop package capture entirely (handler, capture_upload seam, tests, placeholder endpoint) — its endpoint 404s everywhere and the name belongs to the platform's remote dependency-graph capture. Frees capture too. - Keep the pmcp-package caret 0.1 pin tripwire (platform conceded =0.1.0). Two review DX papercuts fixed: - --api-key-env with an unset/empty var now errors clearly instead of a silent empty key (which surfaced as a confusing 401). - team dev's built-in fixture used max_team_total_tokens:1 + 1s wall clock, which would kill any real --llm run; raised to dev-harness limits. Also gitignore the auto-discovered fuzz_package_kind corpus (was untracked). Docs updated for inspect-only + the 'agent/team produce OCI layouts' overclaim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit a2af479)
pmcp.run platform review found the shipped loop demo-grade: it never told the model its tools and never read provider token usage back, so it couldn't drive real tool use and its budget could never trip. Per the review + owner decision, wire both before the 0.1.0 tag (Gap 3 / opaque provider blocks deferred to 0.2 with a README note): Gap 1 — tools via tools/list discovery: - Add a defaulted list_tools() to the ConnectorClient seam (UrlConnectorClient delegates to pmcp::Client::list_tools) and to the ToolInvoker seam (Arc<dyn> forwards; ClientToolInvoker lists its connector). Defaults keep existing impls compiling — backward-compatible. - Engine resolves tools once per run, filters by the package tool-selection allow-list (empty = advertise all), and passes them via CreateMessageParams .with_tools() so the model can actually request tool calls. Gap 2 — provider usage → live budget: - openai_compat + anthropic now parse provider usage (total_tokens; input+output) into the result _meta the loop already reads, so the token budget can trip. - Split the overloaded max_tokens: it stays the per-turn output cap; a new optional token_budget carries the cumulative run budget (None = iterations bound the run). Prevents the per-turn cap from killing runs after one turn once usage is live. Tests: +2 usage-mapping (both sources), +2 discovery (advertise-all + allow-list filter). Full suite green (lib 74→78) on default and openai/anthropic/url features. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit f7982cb)
CI's PMAT complexity gate (cog <=25) flagged 4 functions in the new Phase 107-109 crates. Behavior-preserving extract-method refactors; all existing unit/property/conformance tests pass unchanged: - pmcp-package validate (47): split into ordered check_target/ check_tier/check_iam/check_statement helpers (first-violation-wins ordering preserved). - pmcp-team-servers match_value (58): delegate the object/array/scalar arms to match_object/match_array/match_scalar. - pmcp-team-servers select (24): extract per-segment descend(). - pmcp-team-servers copy_recursive (27): factor out an io_err() wrapper plus copy_dir/copy_file branch helpers. `pmat quality-gate --checks complexity` now passes (0 violations). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te single-version
umya-spreadsheet 3.0.1 bumped its transitive quick-xml 0.37 -> 0.41. Because
Cargo.lock is gitignored (CI always cold-resolves), a caret `umya = "3.0"`
drifts onto 3.0.1, which:
- forks Cargo.lock into two quick-xml versions (0.37.5 + 0.41.0) against the
compiler's direct `quick-xml = "0.37"` pin, so `cargo tree -i quick-xml`
errors "ambiguous" and the Phase-93 single-version purity gate fails closed;
- regresses a data-validation-list ingest test under 3.0.1.
Exact-pin to =3.0.0 (the version every test + purity run has been validated
against): cold resolve keeps a single quick-xml 0.37.5, `make purity-check`
passes, all 375 compiler tests pass. No code changes. Revisit the 3.0.1 bump
deliberately (realign the quick-xml pin + port the 0.41 reader API + triage the
ingest delta) rather than via silent caret drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Makefile purity-check Layer 2 invokes cargo-deny with 0.18.3's CLI ordering (`check --config deny.toml bans`). CI installed cargo-deny *latest* via taiki-e/install-action, and newer cargo-deny relocated `--config` to a global flag (before `check`), rejecting the old form with a usage error (exit 2) — silently breaking the Purity Gate once the single-version guard passed. Pin both install sites to 0.18.3 (same approach as the pinned PMAT_VERSION). Verified: `make purity-check` passes locally with cargo-deny 0.18.3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Delivers the v2.4 "Agents & Teams" milestone — three new experimental (0.x) crates,
cargo-pmcpverbs to drive them, and additive core-SDK plumbing to support agent/team host surfaces. All changes are additive; no breaking changes to the corepmcpAPI.103 commits, 235 files. This branch is filtered to contain only code/doc changes — GSD
.planning/planning artifacts are excluded so the diff is pure code review.New crates
pmcp-agentWithToolsreal-loop,ClientToolInvoker,ConnectorClientFactory.openai-compat/anthropic/url-connectorfeatures are non-default, so the default publish build is reqwest-free and wasm-clean.pmcp-team-serverswebhook/httpfeatures non-default.pmcp-packagepmcp-agent.Highlights by phase
client::hostmodule; Sampling & Hosting disambiguation docs.contracts/team-servers-v1.yaml.WithToolssampling (client host + peer, additive); Transport Actor pump inServer::run(never-block) + stdio cancel-safety; bump core topmcp2.17.0._metaforwarding — extendRequestMetawith a namespaced_metamap propagated to handlers; scaffold the reference-team-server crate + conformance harness.agent/team/packagecommand groups (cargo-pmcp→ 0.18.0) with version-pin tripwires and--helpcoverage tests.Testing
tests/,fuzz/).contracts/updated/added for the new surfaces.src/changes are additive and covered by existing + new tests.Notes for reviewers
pmcp-packageis workspace-excluded (own[workspace]table); publish viacargo publish --manifest-path crates/pmcp-package/Cargo.toml.🤖 Generated with Claude Code