feat(v0.1): protocol layer, transports, session state machine, CLI — TDD#29
Merged
Conversation
TDD implementation of Phase v0.1: - MCP JSON-RPC 2.0 type definitions with serde roundtrip tests - stdio transport (newline-delimited JSON, response correlation by id) - HTTP+SSE transport (POST + SSE stream, response correlation) - Session state machine (Unconnected → Initializing → Ready → Closed) - Test harness wrapping session for fuzzer module use - CLI scaffolding: audit, scan, corpus subcommands via clap - GitHub Actions CI: fmt, clippy (-D warnings), test, release build Closes #1, #2, #3, #4, #5, #23 https://claude.ai/code/session_014T1x8ZiDbJcVvkZBfP91nk
…rop cleanup Bug fixes: - Session initialized notification was routed through send() which registers an id and blocks waiting for a response that never arrives. Now uses the dedicated notify() path (fire-and-forget write, no pending map entry). New test `initialize_sends_notification` verifies the path is correct. - Global REQUEST_COUNTER shared across all sessions caused id collisions under concurrent use. Replaced with per-session AtomicI64; two_sessions_have_ independent_counters test confirms isolation. Performance: - HttpTransport pre-computes /mcp and /sse endpoint URLs once at construction instead of formatting strings on every send() call. - HttpTransport uses Arc<Client> shared between SSE listener task and send path, eliminating a Client clone per connection. - StdioTransport Drop impl calls start_kill() to prevent zombie child processes. - Harness::tools() returns &[ToolDefinition] (zero-copy slice) for read access; enumerate_tools() still returns Vec for callers that need ownership. Code quality: - Extracted shared MockTransport into src/testutil.rs — no more duplication between session.rs and harness.rs test modules. - Removed local IntoResult trait; replaced with module-level outcome_result() function (two-line match, no trait machinery needed). - Removed parse_cmd() test helper from production transport code. - Transport trait gains notify() as a first-class method, making the request/notification distinction explicit at every call site. 41 tests passing (up from 37). https://claude.ai/code/session_014T1x8ZiDbJcVvkZBfP91nk
- Extract id_key() and PendingMap to transport/mod.rs — were copy-pasted identically in both stdio.rs and http.rs - Add src/utils.rs with drain_sse_events() and sse_data() — SSE parsing logic extracted from http.rs into tested, reusable helpers; HTTP transport now acquires the pending map lock once per chunk instead of per response - Add Session::ready() test constructor — replaces direct session.state mutation across all test bodies - Add ok_response(), init_response(), tools_response() to testutil.rs — shared response builders used by both session and harness test modules - Delete local init_response/tools_response from session.rs tests - Simplify harness test module: local ready() helper eliminates repeated transport + state setup; test bodies now 2-3 lines each - Add src/utils.rs to module tree in main.rs 43 tests passing (was 41 before simplify pass, was 37 originally). https://claude.ai/code/session_014T1x8ZiDbJcVvkZBfP91nk
…n close - StdioTransport: store reader_task JoinHandle, abort in Drop, clear pending map in close() so waiting callers get RecvError immediately - HttpTransport: store sse_task JoinHandle, abort in Drop and close(), clear pending map in close(); replace from_utf8_lossy with str::from_utf8 to skip malformed chunks rather than silently corrupting the SSE buffer - session.rs list_tools(): move tools into self.tools directly, return a clone of the cached copy (single unambiguous ownership path) https://claude.ai/code/session_014T1x8ZiDbJcVvkZBfP91nk
Captures non-negotiable rules: no unsafe, no repeated code, mandatory JoinHandle/PendingMap resource management, UTF-8 handling policy, and performance guidelines. Includes full annotated source tree and key design invariants so the structure is always recoverable from context. https://claude.ai/code/session_014T1x8ZiDbJcVvkZBfP91nk
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
Phase v0.1 — Protocol Foundation. Closes #1, #2, #3, #4, #5, #23.
src/protocol/mcp.rs) — full serde-annotated type set: request/response envelope, all MCP lifecycle types (InitializeParams,ToolDefinition,CallToolResult,ToolContent, etc.), JSON-RPC error constructorssrc/protocol/transport/stdio.rs) — spawns child process, newline-delimited JSON over stdin/stdout, async response correlation by request idsrc/protocol/transport/http.rs) — POST to/mcp, SSE subscription on/sse, event parsing and response correlationsrc/protocol/session.rs) —Unconnected → Initializing → Ready → Closed, drives initialize handshake,list_tools,call_tool, enforces valid state transitionssrc/runner/harness.rs) — high-level wrapper used by fuzzer modules; caches tool listsrc/cli/mod.rs) —fuzzd audit / scan / corpussubcommands via clap derive; all flags wired.github/workflows/ci.yml) —cargo fmt --check,cargo clippy -D warnings,cargo test,cargo build --releaseon every push/PRTests
37 tests, all passing. Every public type and state machine path has a unit test. Mock transport pattern used throughout — no network or real MCP server required for unit tests.
Test plan
cargo testpasses locallycargo clippy -- -D warningscleancargo fmt --checkcleanfuzzd --help,fuzzd audit --help,fuzzd scan --help,fuzzd corpus --helpall display correctlyhttps://claude.ai/code/session_014T1x8ZiDbJcVvkZBfP91nk
Generated by Claude Code