- 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
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 |
|
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:86imports only the firsttools/listpage;next_cursoris never followed. rmcp 1.8 provideslist_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-128accepts plaintextws://and attaches the Bearer token unconditionally. A config typo silently ships a long-lived bearer credential over plaintext. Refusews://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 ofcontent_block_union_schemarequires non-existent"audio"(property isdata) — valid audio blocks never satisfy the publishedoutput_schema. Fix and validate a real audio block. - CON-05/CON-07: docs claim per-call credential reads; reality is
import-time credentials (
from_mcptransport-pinned token,from_wssbundles carry no capabilities at all). Correct docs, remove the dead capability read (mod.rs:139-143). - CON-06 (
mod.rs:103-116): 401 classified viaformat!("{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/callfailures → undeclaredCallError::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-segmentns/opconvention. Sanitize/validate at import. - CON-13 (
from_wss.rs:48,from_mcp/mod.rs:37): held tokens are plainOption<String>; useSecret<String>to match the crate's posture (noDebugderives exists — keep it that way). - CON-10:
[[test]] full_surfaceis missingtest-supportinrequired-features→cargo test --features mcpfails to compile (empirically verified; masked by CI's--all-features). - CON-08/CON-09: no teardown path on
FromMCP(eachimport()leaks an open server-side session + GET SSE stream) andfrom_wssimport 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/listfully imported (paginating-server test) ws://+ token refused (or explicit opt-out) with a clear error (test)- Audio variant of
content_block_union_schemasatisfied by a valid block (test) - No substring-based 401 classification on typed paths; a URL containing
:401xno 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 mcpcompiles and passes (CON-10), as doescargo 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 unlessFromWss::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::connectgrew anallow_plaintextparameter; the refusal lives in the config/validation path, before the dial.- CON-06: typed matching on rmcp's
StreamableHttpError<reqwest::Error>viaDynamicTransportErrordowncast (AuthRequired,InsufficientScope,Client(e)withe.status() == 401), with a word-boundary string fallback (AuthRequired/InsufficientScope/www-authenticate/ the word "unauthorized") — a URL containing:40101misclassifies no more (tested with a live 401 server and a dead:40101endpoint).- CON-13: alkcall already exports
Secret<T>(zeroizing,[REDACTED]Debug) fromalkcall::core::types— reused it for both adapters' builder-held tokens; no new type. Debug-suppression test added.- CON-11: transport-level
tools/callfailures declareMCP_TRANSPORT_ERRORin each op'serror_schemas(retryable); rmcpServiceError::McpErroris preserved asMCP_JRPC_<rrrr0>with the message anddatacarried 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 withAdapterError::SchemaParse— silently renames would break the remote's owntools/callround-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_mcpdiscovery now uses rmcp'slist_all_tools()(followsnext_cursor); integration testimport_follows_tools_list_paginationexercises a three-page paginating server. - CON-03:
WssSession::connectrefusesws://when a token is present unlessFromWss::allow_plaintext()was called; clearAdapterError::Transportnaming CON-03. Tests: refusal, explicit opt-in, and token-less passthrough. - CON-04: audio variant of
content_block_union_schemanow requires["type", "data", "mimeType"]; jsonschema-validated audio block test. - CON-05: dead
_token_presentcapability 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>, matchingAuthRequired,InsufficientScope, andClienterrors withstatus() == 401); the string fallback checks auth wording, not bare "401". Tests cover a live 401, a dead:40101endpoint whose URL would substring-match, and the wording cases. - CON-07:
from_wssdocs 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_mcpmodule 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_surfacerequired-featuresnow["mcp", "test-support"];cargo test --features mcpcompiles and passes (verified). - CON-11: transport-level
tools/callfailures map to the newly declaredMCP_TRANSPORT_ERRORerror schema (retryable); rmcp JSON-RPC errors preserve code (MCP_JRPC_<code>) anddata(as error details). Integration test kills the server mid-call. - CON-12:
sanitize_tool_namevalidates at import —/(which would break the two-segmentns/opconvention), whitespace, and empty names fail the import withSchemaParse; 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.