Test Spec: MCP Server¶
Document Control¶
| Field | Value |
|---|---|
| Status | Implemented |
| Design doc | docs/design/mcp-server.md |
| Test file | tests/test_mcp.py (plus tests/test_cli.py for the CLI-side stdin-pipeline regressions, TEST-MCP-57/58) |
Requirement Traceability¶
| REQ ID | Description | TEST IDs |
|---|---|---|
| REQ-MCP-01 | mcp serve subcommand starts MCP server |
TEST-MCP-01 |
| REQ-MCP-02 | Each selected CLI command surfaces as an MCP tool | TEST-MCP-02, TEST-MCP-03 |
| REQ-MCP-03 | Input schemas derived from argparse definitions | TEST-MCP-04 |
| REQ-MCP-04 | Tool responses use canonical event envelope | TEST-MCP-05, TEST-MCP-06, TEST-MCP-19, TEST-MCP-20, TEST-MCP-21 |
| REQ-MCP-05 | Invalid inputs return same error codes as CLI | TEST-MCP-07, TEST-MCP-08 |
| REQ-MCP-06 | Tool discovery returns all registered MCP tools with metadata | TEST-MCP-02, TEST-MCP-03 |
| REQ-MCP-07 | stdio transport only | TEST-MCP-01 |
| REQ-MCP-08 | Unknown tool name raises error | TEST-MCP-09 |
| REQ-MCP-09 | mcp declared in pyproject.toml |
TEST-MCP-10 |
| REQ-MCP-10 | shell and tui not exposed as MCP tools |
TEST-MCP-11 |
| REQ-MCP-11 | call_tool handler runs execute_command in a thread pool |
TEST-MCP-15 |
| REQ-MCP-12 | File-backed J1939 tools expose max_frames and seconds parameters |
TEST-MCP-16, TEST-MCP-17 |
| REQ-MCP-13 | Dataset MCP tools expose provider workflows, safe replay planning, conversion, and replay file listing | TEST-MCP-22, TEST-MCP-23, TEST-MCP-24, TEST-MCP-25, TEST-MCP-37, TEST-MCP-38 |
| REQ-MCP-14 | Dataset fetch returns index_instructions for curated indexes | TEST-MCP-26 |
| REQ-MCP-15 | Dataset fetch returns download_instructions for normal datasets | TEST-MCP-27 |
| REQ-MCP-16 | Skills provider workflows shall be exposed as MCP tools | TEST-MCP-28, TEST-MCP-29, TEST-MCP-30, TEST-MCP-31, TEST-MCP-32 |
| REQ-MCP-17 | J1939 tools j1939_compare, j1939_faults, j1939_tp_compare shall be exposed as MCP tools |
TEST-MCP-33, TEST-MCP-34, TEST-MCP-35 |
| REQ-MCP-18 | re signals shall be exposed as MCP tool re_signals |
TEST-MCP-36 |
| REQ-MCP-19 | datasets convert and datasets replay --list-files shall be exposed as MCP tools |
TEST-MCP-37, TEST-MCP-38 |
| REQ-MCP-20 | Tool responses are bounded by the output cap with explicit truncation markers and totals | TEST-MCP-39, TEST-MCP-40, TEST-MCP-41, TEST-MCP-42, TEST-MCP-61 |
| REQ-MCP-21 | An in-tool exception returns a TOOL_EXECUTION_ERROR envelope and leaves the session usable |
TEST-MCP-43 |
| REQ-MCP-22 | Every flag an MCP tool forwards maps to a real CLI flag; stats exposes the same top knob on both surfaces |
TEST-MCP-44, TEST-MCP-45, TEST-MCP-46 |
| REQ-MCP-23 | A parse-level CLI failure relayed over MCP carries the invoked tool name, not the generic cli |
TEST-MCP-47 |
| REQ-MCP-24 | The - stdin sentinel is refused before argv construction on every stdin-capable parameter |
TEST-MCP-48, TEST-MCP-49, TEST-MCP-50, TEST-MCP-51, TEST-MCP-52, TEST-MCP-53, TEST-MCP-56 |
| REQ-MCP-25 | The refusal carries the canonical envelope and sets the MCP isError flag; the session survives it |
TEST-MCP-48, TEST-MCP-55 |
| REQ-MCP-26 | Guarded parameters document the restriction; CLI stdin pipelines are unchanged | TEST-MCP-54, TEST-MCP-57, TEST-MCP-58 |
| REQ-MCP-27 | The restriction note claims nothing about the value's shape, so it cannot contradict a parameter that takes a ref, URL, id or session name | TEST-MCP-59 |
| REQ-MCP-28 | Canonical tool failures match protocol failure status; subsequent calls remain usable | TEST-MCP-60 |
| REQ-MCP-29 | SDK validation is retained and distinguished from domain envelopes | TEST-MCP-60 |
Representative Test Cases¶
TEST-MCP-01 — Server entry point is callable¶
Given the `canarchy.mcp_server` module is importable
When `run_server` is retrieved from the module
Then it shall be callable
And `canarchy mcp serve --help` shall exit with code `0`
Fixture: none.
TEST-MCP-02 — Tool discovery returns expected tool names¶
Given the MCP server is initialised
When `handle_list_tools()` is called
Then the returned set shall contain at minimum: `capture`, `send`, `filter`, `stats`, `capture_info`, `decode`, `encode`, `dbc_inspect`, `dbc_provider_list`, `dbc_search`, `dbc_fetch`, `dbc_cache_list`, `dbc_cache_prune`, `dbc_cache_refresh`, `datasets_provider_list`, `datasets_search`, `datasets_inspect`, `datasets_fetch`, `datasets_cache_list`, `datasets_cache_refresh`, `datasets_replay_plan`, `j1939_monitor`, `j1939_decode`, `j1939_pgn`, `j1939_spn`, `j1939_tp`, `j1939_dm1`, `j1939_summary`, `j1939_inventory`, `uds_scan`, `uds_trace`, `uds_services`, `config_show`, `replay`, `gateway`, `generate`, `export`, `session_save`, `session_load`, `session_show`, `re_correlate`, `re_counters`, `re_entropy`, `re_match_dbc`, `re_shortlist_dbc`
Fixture: none.
TEST-MCP-03 — Tool count matches the registered MCP surface¶
Given the MCP server is initialised
When `handle_list_tools()` is called
Then at least 45 tools shall be returned — one per registered MCP tool in the current curated surface
Fixture: none.
TEST-MCP-04 — Each tool has a non-empty description and a valid inputSchema¶
Given the MCP server is initialised
When `handle_list_tools()` is called
Then every tool shall have a non-empty string `description`
And every tool `inputSchema` shall be a dict containing a `"type"` key
Fixture: none.
TEST-MCP-05 — config_show returns structured result¶
Given the MCP server is initialised
When `handle_call_tool("config_show", {})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"config show"`
And `data` shall contain a `"backend"` key
Fixture: none (config show requires no files).
TEST-MCP-06 — uds_services returns service catalogue¶
Given the MCP server is initialised
When `handle_call_tool("uds_services", {})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `data.service_count` shall be greater than `0`
Fixture: none.
TEST-MCP-19 — capture_info returns capture metadata¶
Given the MCP server is initialised
When `handle_call_tool("capture_info", {"file": "tests/fixtures/sample.candump"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"capture-info"`
And `data.frame_count` and `data.unique_ids` shall both be greater than `0`
Fixture: tests/fixtures/sample.candump.
TEST-MCP-20 — stats maps file input to current CLI grammar¶
Given the MCP server is initialised
When `handle_call_tool("stats", {"file": "tests/fixtures/sample.candump"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"stats"`
And the result shall report the expected frame and arbitration-ID counts
Fixture: tests/fixtures/sample.candump.
TEST-MCP-21 — filter maps expression and file input to current CLI grammar¶
Given the MCP server is initialised
When `handle_call_tool("filter", {"file": "tests/fixtures/sample.candump", "expression": "id==0x18FEEE31"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"filter"`
And the result shall contain one matching frame
Fixture: tests/fixtures/sample.candump.
TEST-MCP-22 — Dataset search returns machine fields¶
Given the MCP server is initialised
When `handle_call_tool("datasets_search", {"query": "candid"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"datasets search"`
And the result shall include stable machine fields such as `ref`, `is_replayable`, and `default_replay_file`
Fixture: embedded dataset catalog.
TEST-MCP-23 — Dataset inspect returns index metadata¶
Given the MCP server is initialised
When `handle_call_tool("datasets_inspect", {"ref": "catalog:pivot-auto-datasets"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And the result shall report `is_index=true` and `is_replayable=false`
Fixture: embedded dataset catalog.
TEST-MCP-24 — Dataset replay plan does not stream¶
Given the MCP server is initialised
When `handle_call_tool("datasets_replay_plan", {"source": "catalog:candid", "max_seconds": 2.5})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"datasets replay"`
And the result shall report `dry_run=true` and `streamed=false`
And the result shall preserve the requested `max_seconds` value
Fixture: embedded dataset catalog.
TEST-MCP-25 — Dataset replay plan preserves index errors¶
Given the MCP server is initialised
When `handle_call_tool("datasets_replay_plan", {"source": "catalog:pivot-auto-datasets"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `false`
And `errors[0].code` shall equal `"DATASET_INDEX_NOT_REPLAYABLE"`
Fixture: embedded dataset catalog.
TEST-MCP-26 — Dataset fetch returns index_instructions for curated indexes¶
Given the MCP server is initialised
When `handle_call_tool("datasets_fetch", {"ref": "catalog:pivot-auto-datasets"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `data.is_index` shall equal `true`
And `data.index_instructions` shall be a non-empty string
And `data.index_instructions` shall contain `"Visit the index page"`
Fixture: embedded dataset catalog.
TEST-MCP-27 — Dataset fetch returns download_instructions for normal datasets¶
Given the MCP server is initialised
When `handle_call_tool("datasets_fetch", {"ref": "catalog:road"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `data.is_index` shall equal `false`
And `data.index_instructions` shall be `null`
And `data.download_instructions` shall be a non-empty string
And `data.download_instructions` shall contain `"Download the data manually"`
Fixture: embedded dataset catalog.
TEST-MCP-13 — _build_argv produces correct argv for representative tools¶
Given the `_build_argv` helper is available
When called with `("capture", {"interface": "can0"})`
Then the result shall equal `["capture", "can0", "--json"]`
When called with `("capture_info", {"file": "trace.candump"})`
Then the result shall equal `["capture-info", "--file", "trace.candump", "--json"]`
When called with `("j1939_monitor", {"interface": "can0", "pgn": 60160})`
Then the result shall contain `["j1939", "monitor", "can0", "--pgn", "60160"]` in order
When called with `("encode", {"dbc": "t.dbc", "message": "Msg", "signals": ["RPM=1000"]})`
Then the result shall contain `"--dbc"`, `"t.dbc"`, `"Msg"`, and `"RPM=1000"`
When called with `("dbc_inspect", {"dbc": "t.dbc", "message": "Msg", "signals_only": true, "layout": true})`
Then the result shall contain `"dbc"`, `"inspect"`, `"t.dbc"`, `"--message"`, `"Msg"`, `"--signals-only"`, and `"--layout"`
When called with `("j1939_pgn", {"pgn": 61444, "file": "trace.candump"})`
Then the result shall contain `["j1939", "pgn", "61444", "--file", "trace.candump"]` in order
When called with `("j1939_inventory", {"file": "trace.candump", "max_frames": 5000})`
Then the result shall contain `["j1939", "inventory", "--file", "trace.candump", "--max-frames", "5000"]` in order
And `"--json"` shall be the final element in each case
Fixture: none.
TEST-MCP-14 — _build_argv raises for unknown tool¶
Given the `_build_argv` helper is available
When called with `("bad_tool", {})`
Then a `ValueError` shall be raised
Fixture: none.
TEST-MCP-15 — handle_call_tool does not block the event loop¶
Given the MCP server is initialised
When `handle_call_tool` is awaited for any tool that delegates to `execute_command`
Then the asyncio event loop shall remain live during execution
And a concurrently scheduled coroutine shall be able to run while the tool executes
Fixture: none (uses config_show which requires no files).
TEST-MCP-16 — _build_argv threads max_frames and seconds through for J1939 file tools¶
Given the `_build_argv` helper is available
When called with a J1939 file tool and `max_frames` set to N
Then the returned argv shall contain `["--max-frames", str(N)]`
When called with a J1939 file tool and `seconds` set to T
Then the returned argv shall contain `["--seconds", str(T)]`
When called with a J1939 file tool and neither limit is supplied
Then neither `"--max-frames"` nor `"--seconds"` shall appear in the returned argv
Applies to: j1939_decode, j1939_pgn, j1939_spn, j1939_tp, j1939_dm1, j1939_summary, j1939_inventory.
Fixture: none.
TEST-MCP-17 — File-backed J1939 tool schemas expose max_frames and seconds¶
Given the MCP server is initialised
When `handle_list_tools()` is called
Then for each of `j1939_decode`, `j1939_pgn`, `j1939_spn`, `j1939_tp`, `j1939_dm1`, `j1939_summary`, `j1939_inventory`
the tool's `inputSchema.properties` shall contain `"max_frames"` of type `"integer"`
and `"seconds"` of type `"number"`
TEST-MCP-28 — Skills provider list exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("skills_provider_list", {})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"skills provider list"`
And `data.providers` shall be a list
Fixture: embedded skills catalog.
TEST-MCP-29 — Skills search exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("skills_search", {"query": "j1939"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"skills search"`
And `data.results` shall be a list
Fixture: embedded skills catalog.
TEST-MCP-30 — Skills fetch exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("skills_fetch", {"ref": "github:j1939_compare_triage"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"skills fetch"`
And the result shall include `local_manifest_path` and `local_entry_path`
Fixture: embedded skills catalog.
TEST-MCP-31 — Skills cache list exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("skills_cache_list", {})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"skills cache list"`
Fixture: none.
TEST-MCP-32 — Skills cache refresh exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("skills_cache_refresh", {"provider": "github"})` is called
Then the response `text` shall parse as JSON with `ok` equal to `true`
And `command` shall equal `"skills cache refresh"`
Fixture: none.
TEST-MCP-33 — j1939_compare exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("j1939_compare", {"files": ["a.candump", "b.candump"]})` is called
Then `command` shall equal `"j1939 compare"`
And the argv shall contain both file names and `--json`
Fixture: none.
TEST-MCP-34 — j1939_faults exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("j1939_faults", {"file": "trace.candump"})` is called
Then `command` shall equal `"j1939 faults"`
And the argv shall contain `"--file", "trace.candump", "--json"`
Fixture: none.
TEST-MCP-35 — j1939_tp_compare exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("j1939_tp_compare", {"file": "trace.candump", "sa": "0x80,0x81"})` is called
Then `command` shall equal `"j1939 tp compare"`
And the argv shall contain `"--sa", "0x80,0x81", "--json"`
Fixture: none.
TEST-MCP-36 — re_signals exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("re_signals", {"file": "trace.candump"})` is called
Then `command` shall equal `"re signals"`
And the argv shall equal `["re", "signals", "trace.candump", "--json"]`
Fixture: none.
TEST-MCP-37 — datasets_convert exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("datasets_convert", {"file": "sample.csv", "source_format": "hcrl-csv", "format": "jsonl"})` is called
Then `command` shall equal `"datasets convert"`
And the argv shall contain `"--source-format", "hcrl-csv", "--format", "jsonl"`
Fixture: none.
TEST-MCP-38 — datasets_replay_files exposed as MCP tool¶
Given the MCP server is initialised
When `handle_call_tool("datasets_replay_files", {"source": "catalog:candid"})` is called
Then `command` shall equal `"datasets replay"`
And the argv shall contain `"--list-files", "--json"`
And the result shall include `data.count` and `data.files`
Fixture: embedded dataset catalog.
Fixture: none.
TEST-MCP-39 — Small payloads pass through the output cap unchanged¶
Given a payload that serializes below the output cap
When `bound_payload(payload, cap)` is called
Then the identical payload object shall be returned
And no `truncated` marker shall be added
Fixture: none.
TEST-MCP-40 — Oversized list data is truncated with marker and totals¶
Given a payload whose `data.events` list serializes far beyond the cap
When `bound_payload(payload, cap)` is called
Then the serialized result shall fit within the cap
And `data.truncated` shall be `true`
And `data.truncation.lists` shall record `total_items` and `returned_items` for the trimmed list
And a truncation warning shall be appended to `warnings`
And the original payload shall be left unmodified
Fixture: synthetic 2000-element event list.
TEST-MCP-41 — Non-list oversized data falls back to a stub envelope¶
Given a payload whose `data` holds one giant scalar (no trimmable list)
When `bound_payload(payload, cap)` is called
Then the serialized result shall fit within the cap
And the `ok` / `command` envelope fields shall be preserved
And `data.truncated` shall be `true`
Fixture: 100 kB string in data.content.
TEST-MCP-42 — High-rate j1939_pgn returns a bounded, well-formed envelope¶
Given a synthetic high-rate EEC1 (PGN 61444) capture of 4000 frames
And `CANARCHY_MCP_MAX_RESPONSE_BYTES` is set to 65536
When `handle_call_tool("j1939_pgn", {"pgn": 61444, "file": <capture>})` is called
Then the response text shall not exceed 65536 bytes
And it shall parse as JSON with `ok: true`
And `data.truncated` shall be `true` with at least one trimmed list recording `total_items > returned_items`
Fixture: generated per-test high-rate candump file.
TEST-MCP-43 — In-tool exception is isolated; session stays usable¶
Given `execute_command` is patched to raise `RuntimeError`
When `handle_call_tool("uds_services", {})` is called
Then the response shall be a canonical envelope with `ok: false` and error code `TOOL_EXECUTION_ERROR`
When the patch is removed and the same tool is called again
Then the call shall succeed with `ok: true`
Fixture: none.
TEST-MCP-44 — Every MCP tool flag maps to a real CLI flag¶
Given the registered MCP tools and the CLI argument parser
When `_build_argv` is invoked for each tool with a fully populated argument set
Then every `--flag` it emits shall be an accepted option of the resolved CLI command
And no tool shall forward a flag the CLI rejects with `unrecognized arguments`
Test: test_mcp_tool_flags_map_to_real_cli_flags. Fixture: none.
TEST-MCP-45 — stats schema exposes the top parameter¶
Given the registered MCP tool schemas
When the `stats` tool input schema is inspected
Then its properties shall include `top`, matching the CLI `--top` flag
Test: test_stats_schema_exposes_top_param. Fixture: none.
TEST-MCP-46 — stats top limits the detailed-id list end-to-end¶
Given a candump capture fixture
When `handle_call_tool("stats", {"file": ..., "top": 1})` is called
Then the envelope shall be `ok: true`
And `data.top_ids_returned` shall equal `1`
Test: test_call_tool_stats_top_limits_detailed_ids. Fixture: sample.candump.
TEST-MCP-47 — Parse-level error envelope carries the invoked tool name¶
Given a `stats` call whose `pgn` value fails the CLI argparse type converter
When `handle_call_tool("stats", {"file": "x", "pgn": "notanint"})` is called
Then the envelope shall be `ok: false` with error code `INVALID_ARGUMENTS`
And `command` shall be `stats`, not the generic `cli`
Test: test_parse_level_error_envelope_carries_tool_name. Fixture: none.
TEST-MCP-48 — Stdin sentinel is refused with the canonical envelope and isError¶
Given an initialised MCP server
When `handle_call_tool("capture_info", {"file": "-"})` is called
Then the result shall be a `CallToolResult` with `isError` set to true
And its single text item shall be the canonical envelope with `ok: false`
And `command` shall be `capture_info` with empty `data` and `warnings`
And the error code shall be `STDIN_MCP_EXCLUDED`
And the message shall name the offending parameter `file`
And the hint shall point at a real file path
Test: test_capture_info_rejects_stdin_sentinel. Fixture: none.
TEST-MCP-49 — Refusal happens before any CLI reader or argv build¶
Given `_build_argv` and `execute_command` are patched to raise if called
When `handle_call_tool("stats", {"file": "-"})` is called
Then neither patched function shall be invoked
And the response shall carry error code `STDIN_MCP_EXCLUDED`
Test: test_stdin_rejection_never_reaches_a_cli_reader. Fixture: none.
TEST-MCP-50 — Refusal starts no worker thread, so none can leak¶
Given `asyncio.to_thread` is patched to raise if called
When `handle_call_tool("capture_info", {"file": "-"})` is called
Then no worker thread shall be dispatched
And the live thread count shall not increase
Test: test_stdin_rejection_starts_no_worker_thread. Fixture: none.
TEST-MCP-51 — Every stdin-capable parameter shape is refused¶
Given the registered MCP tool surface
When a tool is called with `-` in a scalar path parameter (`file`, `dbc`,
`baseline`, `bundle`, `source`), in a path array (`files`,
`artifacts`), in a dry-run replay input, or on an acknowledged
active-transmit tool
Then each call shall be refused with `STDIN_MCP_EXCLUDED`
And the message shall name the offending parameter
Test: test_stdin_sentinel_rejected_across_the_tool_surface. Fixture: none.
TEST-MCP-52 — No path parameter drifts out of the stdin guard¶
Given the registered MCP tool schemas and `_STDIN_CAPABLE_PARAMS`
When every schema property whose name denotes a readable input path is inspected
Then each shall be registered in the guard or listed as a documented exemption
Test: test_every_path_parameter_is_stdin_guarded_or_documented. Fixture: none.
TEST-MCP-53 — The stdin guard registry matches the tool schemas¶
Given `_STDIN_CAPABLE_PARAMS`
When each registered tool and parameter is looked up in the tool schemas
Then every tool name shall exist
And every registered parameter shall be a declared property of that tool
Test: test_stdin_guard_registry_matches_the_tool_schemas. Fixture: none.
TEST-MCP-54 — Guarded parameters document the restriction¶
Given the tool list returned by `handle_list_tools()`
When the description of each guarded parameter is inspected
Then it shall mention the `-` sentinel and stdin
Test: test_stdin_restricted_parameters_document_the_restriction. Fixture: none.
TEST-MCP-55 — A rejected stdin call leaves the stdio session usable¶
Given a real `canarchy mcp serve` process driven over stdio by `stdio_client`
When `capture_info` is called with `file: "-"`
Then the call shall return within the timeout with `isError` true and
error code `STDIN_MCP_EXCLUDED`
When `plugins_list` is called on the SAME session
Then it shall return promptly with `ok: true`
And the transport shall shut down cleanly afterwards
Test: test_stdin_rejection_survives_a_real_stdio_session. Fixture: none
(spawns python -m canarchy.cli mcp serve with an isolated HOME).
TEST-MCP-59 — The restriction note claims nothing about value shape¶
Given the stdin restriction note appended to every covered parameter
When the published tool schemas are inspected
Then neither the note nor any covered parameter's description shall claim
that the value must be a filesystem path
And `datasets_replay_plan.source` shall still document `catalog:candid`
And `datasets_replay_plan.file` shall still document a manifest file id
And `export.source` shall still document a session name
And each shall still state the `-` restriction
Fixture: none; asserted against the live tool schemas.
TEST-MCP-56 — Ordinary file paths are unaffected by the guard¶
Given a candump capture fixture
When `handle_call_tool("capture_info", {"file": <path>})` is called
Then the result shall be the normal `TextContent` list, not a refusal
And the envelope shall be `ok: true`
Test: test_ordinary_file_paths_are_unaffected. Fixture: sample.candump.
TEST-MCP-57 — CLI capture-info --file - pipeline is unchanged¶
Given candump text piped on stdin
When `canarchy capture-info --file - --json` is run
Then it shall exit `0`
And `data.implementation` shall be `stdin-metadata` with the piped frame count
Test: test_capture_info_stdin_pipeline_still_supported (tests/test_cli.py).
Fixture: none (inline candump text).
TEST-MCP-58 — CLI filter --file - pipeline is unchanged¶
Given candump text piped on stdin
When `canarchy filter id==0x123 --file - --json` is run
Then it shall exit `0`
And `data.input` shall be `stdin-candump` with only the matching frame
Test: test_filter_stdin_candump_pipeline_still_supported (tests/test_cli.py).
Fixture: none (inline candump text).
TEST-MCP-60 — Protocol error status and recovery over actual stdio¶
Given a real SDK stdio client connected to a CANarchy server with an injected deterministic command crash
When calls fail for a missing capture, negative frame limit, DoIP exclusion, or unexpected exception
Then each result shall set isError true and retain the canonical JSON error code, message, and applicable hint
And a successful service-catalog call after each failure shall set isError false
When stats is called without its required file field or send without its required acknowledgement
Then SDK validation shall return plain error text with isError true
And a later valid stats call shall succeed on the same session
And canonical responses at both the 1024-byte and default caps shall remain bounded, including oversized exception diagnostics
Fixture: sample.candump, nonexistent temporary capture path, child-only crash injection; scaffold backend, no live hardware or network egress.
TEST-MCP-61 — Final byte cap and surviving metadata¶
Given nested lists containing Unicode, oversized scalars, or large envelope diagnostics
When responses are bounded at 1024, 2048, or the default 512000 bytes
Then the final serialized UTF-8 JSON shall fit the configured cap
And metadata paths and returned counts shall describe surviving output only
And a compact fallback shall retain an actionable CLI hint
Given a positive configured cap below 1024 bytes
When the server starts
Then it shall reject the setting before opening the transport
Given the complex DBC layout result with a 1024-byte cap
When dbc_inspect is followed by plugins_list
Then both JSON text responses shall fit and the session shall remain usable
Fixture: tests/fixtures/complex.dbc; generated nested Unicode lists and scalar/envelope data. Covered by test_bound_payload_final_size_and_surviving_nested_paths, test_bound_payload_small_cap_handles_giant_scalars_and_envelopes, test_response_cap_rejects_unsupported_positive_value_before_transport, test_call_tool_small_cap_dbc_layout_and_session_usability, and test_domain_failures_and_exception_survive_real_stdio (real SDK stdio at 1024 and 512000 bytes, including early refusals and oversized exceptions). Traces to REQ-MCP-20.