Files
alkhttp/tasks/adapters/review-001-consumer-adapter-hygiene.md
T
glm-5.3-flash 8700ed0fea fix(adapters): consumer adapter hygiene (CON-01, CON-03..CON-13)
- CON-01: from_mcp discovery follows tools/list pagination
  (rmcp list_all_tools); three-page paginating-server test
- CON-03: from_wss refuses ws:// with a Bearer token unless
  FromWss::allow_plaintext() is called explicitly (tests: refusal,
  opt-in, token-less passthrough)
- CON-04: audio variant of content_block_union_schema requires
  ["type","data","mimeType"]; jsonschema-validated audio block
- CON-05/07: import-time credential documented on both adapters;
  dead per-call capability read removed
- CON-06: 401 classification typed-first (downcast to rmcp
  StreamableHttpError<reqwest::Error>; AuthRequired/InsufficientScope/
  Client with status 401); a :40101 URL no longer misclassifies (tested)
- CON-11: transport tools/call failures declare MCP_TRANSPORT_ERROR;
  rmcp JSON-RPC errors preserve code (MCP_JRPC_<code>) and data
- CON-12: tool names validated at import (/, whitespace, empty →
  SchemaParse); unit + integration tests
- CON-13: tokens held as alkcall Secret<String> (zeroize, redacted Debug)
- CON-08/09: no close handles; explicit-limitation notes in from_mcp
  module docs, from_wss module docs, and ADR-070
- CON-10: full_surface [[test]] required-features = ["mcp","test-support"];
  cargo test --features mcp now compiles and passes

Verified: cargo test; cargo test --features mcp; cargo test --all-features;
cargo clippy (--all-features) --all-targets -- -D warnings; cargo fmt --check
2026-08-29 10:54:33 +00:00

8.5 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
review-001-consumer-adapter-hygiene from_wss/from_mcp consumer fixes — plaintext ws://, 401 classification, token hygiene (CON-03, CON-05..CON-13) completed
moderate low component implementation
adapters
review-001
mcp
from-wss

Description

Review 001 consumer-adapter findings, minus the hang fix (its own task — review-001-ws-robustness carries WS-02/CON-02), grouped as the small CON cleanup list:

  • CON-01 (one-liner): from_mcp/mod.rs:86 imports only the first tools/list page; next_cursor is never followed. rmcp 1.8 provides list_all_tools() for exactly this. Servers that paginate silently truncate the import — no error, no log. Fix and add a paginating-server test (COV gap 11).
  • CON-03 (security): from_wss.rs:114-128 accepts plaintext ws:// and attaches the Bearer token unconditionally. A config typo silently ships a long-lived bearer credential over plaintext. Refuse ws:// when a token is present — or unconditionally unless explicitly allowed — with a clear error.
  • CON-04 (from_mcp/mod.rs:240-248): the audio variant of content_block_union_schema requires non-existent "audio" (property is data) — valid audio blocks never satisfy the published output_schema. Fix and validate a real audio block.
  • CON-05/CON-07: docs claim per-call credential reads; reality is import-time credentials (from_mcp transport-pinned token, from_wss bundles carry no capabilities at all). Correct docs, remove the dead capability read (mod.rs:139-143).
  • CON-06 (mod.rs:103-116): 401 classified via format!("{error:?}").contains("401") — a :4010/ port in a URL misclassifies as Unauthorized and real auth failures can be missed. Match typed variants; substring only as last resort.
  • CON-11: transport-level tools/call failures → undeclared CallError::internal; declare the failure mode (and preserve JSON-RPC error fidelity where cheap).
  • CON-12: remote tool names interpolated into op names unsanitized — a / in a tool name breaks the two-segment ns/op convention. Sanitize/validate at import.
  • CON-13 (from_wss.rs:48, from_mcp/mod.rs:37): held tokens are plain Option<String>; use Secret<String> to match the crate's posture (no Debug derives exists — keep it that way).
  • CON-10: [[test]] full_surface is missing test-support in required-featurescargo test --features mcp fails to compile (empirically verified; masked by CI's --all-features).
  • CON-08/CON-09: no teardown path on FromMCP (each import() leaks an open server-side session + GET SSE stream) and from_wss import stacks duplicate sessions on reconnect by design (ADR-070 v1). Either add explicit close/teardown handles or write the ADR-070 note + module docs that make the limitation explicit — do not silently expand scope into a reconnect layer.

Acceptance Criteria

  • Paginated tools/list fully imported (paginating-server test)
  • ws:// + token refused (or explicit opt-out) with a clear error (test)
  • Audio variant of content_block_union_schema satisfied by a valid block (test)
  • No substring-based 401 classification on typed paths; a URL containing :401x no longer misclassifies (test)
  • CON-05/07 docs corrected; dead capability read removed
  • CON-11/12: transport-failure error declared; remote tool names sanitized (test)
  • Tokens held as Secret<String>
  • cargo test --features mcp compiles and passes (CON-10), as does cargo test --all-features
  • CON-08/09: close handles exist or docs/ADR-070 note state the limitation

References

  • docs/reviews/001-initial-implementation-review.md (Part G, CON-03..CON-13)
  • docs/architecture/decisions/070-from-wss-consumer-adapter.md

Notes

Independent of the WS tasks. The from_wss notify/pending fixes are tracked separately in review-001-ws-robustness (WS-02/CON-02 share one mechanism).

Implementation notes:

  • CON-03 chose the safer default: ws:// is refused whenever a token is present unless FromWss::allow_plaintext() is called explicitly (unconditional refusal would break every existing test dial of a token-less local producer; without a token there is no credential to leak). WssSession::connect grew an allow_plaintext parameter; the refusal lives in the config/validation path, before the dial.
  • CON-06: typed matching on rmcp's StreamableHttpError<reqwest::Error> via DynamicTransportError downcast (AuthRequired, InsufficientScope, Client(e) with e.status() == 401), with a word-boundary string fallback (AuthRequired / InsufficientScope / www-authenticate / the word "unauthorized") — a URL containing :40101 misclassifies no more (tested with a live 401 server and a dead :40101 endpoint).
  • CON-13: alkcall already exports Secret<T> (zeroizing, [REDACTED] Debug) from alkcall::core::types — reused it for both adapters' builder-held tokens; no new type. Debug-suppression test added.
  • CON-11: transport-level tools/call failures declare MCP_TRANSPORT_ERROR in each op's error_schemas (retryable); rmcp ServiceError::McpError is preserved as MCP_JRPC_<rrrr0> with the message and data carried into the CallError (JSON-RPC fidelity).
  • CON-12: sanitization is validate-and-refuse (not rewrite): a tool name that is empty, contains / or whitespace fails the whole import with AdapterError::SchemaParse — silently renames would break the remote's own tools/call round-trip.
  • CON-08/09: explicit-limitation notes, no close handles and no reconnect layer — module docs on both adapters plus an "Explicit session-lifetime limitation" section in ADR-070.

Summary

Filled on completion.

Status: complete. All eleven consumer-adapter findings from review 001 Part G are resolved; see the checkbox list plus the implementation notes above.

  • CON-01: from_mcp discovery now uses rmcp's list_all_tools() (follows next_cursor); integration test import_follows_tools_list_pagination exercises a three-page paginating server.
  • CON-03: WssSession::connect refuses ws:// when a token is present unless FromWss::allow_plaintext() was called; clear AdapterError::Transport naming CON-03. Tests: refusal, explicit opt-in, and token-less passthrough.
  • CON-04: audio variant of content_block_union_schema now requires ["type", "data", "mimeType"]; jsonschema-validated audio block test.
  • CON-05: dead _token_present capability read removed; module docs rewritten to state the import-time (transport-pinned) credential semantics.
  • CON-06: 401 classification is typed-first (downcast to rmcp's StreamableHttpError<reqwest::Error>, matching AuthRequired, InsufficientScope, and Client errors with status() == 401); the string fallback checks auth wording, not bare "401". Tests cover a live 401, a dead :40101 endpoint whose URL would substring-match, and the wording cases.
  • CON-07: from_wss docs now state the reality (dial-time token, imported handlers carry no capabilities at all).
  • CON-08: rmcp session teardown remains absent (no close handle); documented as an explicit limitation in the from_mcp module docs (import-once guidance, fire-and-forget leak named).
  • CON-09: same-disposition ADR-070 note: no teardown handle in v1, reconnect stacks duplicate sessions by design; no reconnect layer built.
  • CON-10: [[test]] full_surface required-features now ["mcp", "test-support"]; cargo test --features mcp compiles and passes (verified).
  • CON-11: transport-level tools/call failures map to the newly declared MCP_TRANSPORT_ERROR error schema (retryable); rmcp JSON-RPC errors preserve code (MCP_JRPC_<code>) and data (as error details). Integration test kills the server mid-call.
  • CON-12: sanitize_tool_name validates at import — / (which would break the two-segment ns/op convention), whitespace, and empty names fail the import with SchemaParse; unit + integration tests.
  • CON-13: both adapters hold tokens as alkcall's Secret<String> (zeroizing on drop, [REDACTED] Debug); redaction is tested.

Verification: cargo test (265 lib), cargo test --features mcp (313 lib + 9 from_mcp_integration — the CON-10 gate), cargo test --all-features (329 lib + all integration suites), cargo clippy --all-targets -- -D warnings, cargo clippy --all-features --all-targets -- -D warnings, cargo fmt --check — all pass.