diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-11 20:12:54 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-11 20:12:54 +0200 |
| commit | 0a218306f8b3381610d219deca10a21406aa08cf (patch) | |
| tree | 5ccbd55c7fc19234b0bd60668ee679d3cac40ea5 | |
| parent | ae38b11a09964e2c291a144c72814559d12d3b96 (diff) | |
Fix MCP transport to use JSONL instead of Content-Length framing
The MCP stdio protocol uses newline-delimited JSON (JSONL), not LSP-style
Content-Length headers. This was discovered during integration testing with
Claude Code CLI which could not connect to the server.
Also updates docs/mcp-setup.md with correct Claude Code CLI configuration
(use `claude mcp add` instead of editing JSON files) and adds integration
test runbook for testing against real Claude Code CLI instances.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | docs/mcp-setup.md | 114 | ||||
| -rw-r--r-- | internal/mcp/server_test.go | 58 | ||||
| -rw-r--r-- | internal/mcp/transport.go | 62 | ||||
| -rw-r--r-- | prompts/mcp-server-integration-tests.md | 243 | ||||
| -rw-r--r-- | prompts/run-mcp-integration-test.md | 53 |
5 files changed, 406 insertions, 124 deletions
diff --git a/docs/mcp-setup.md b/docs/mcp-setup.md index e151d77..4ea3a0b 100644 --- a/docs/mcp-setup.md +++ b/docs/mcp-setup.md @@ -32,60 +32,57 @@ The binary will be installed to `~/go/bin/hexai-mcp-server` (or wherever your `G ## Configuring Claude Code CLI -Claude Code CLI discovers MCP servers via `~/.config/claude/mcp.json`. Create or edit this file: +Claude Code CLI uses the `claude mcp add` command to register MCP servers. The configuration is stored in `~/.claude.json` (user scope) or `.mcp.json` (project scope). -### Option 1: Full Path (Recommended) +### Option 1: User Scope (Recommended) -```json -{ - "mcpServers": { - "hexai-prompts": { - "command": "/home/paul/go/bin/hexai-mcp-server", - "args": [], - "env": {} - } - } -} -``` +Register the MCP server for all projects: -Replace `/home/paul` with your actual home directory path. +```bash +claude mcp add --transport stdio --scope user hexai-prompts -- \ + ~/go/bin/hexai-mcp-server +``` -### Option 2: Using $PATH +### Option 2: Project Scope -If `~/go/bin` is in your PATH: +Register only for the current project (creates `.mcp.json` in project root): -```json -{ - "mcpServers": { - "hexai-prompts": { - "command": "hexai-mcp-server", - "args": [], - "env": {} - } - } -} +```bash +claude mcp add --transport stdio --scope project hexai-prompts -- \ + ~/go/bin/hexai-mcp-server ``` ### Option 3: Custom Configuration -```json -{ - "mcpServers": { - "hexai-prompts": { - "command": "/home/paul/go/bin/hexai-mcp-server", - "args": [ - "--config", "/home/paul/.config/hexai/config.toml", - "--log", "/tmp/hexai-mcp-server.log" - ], - "env": { - "HEXAI_MCP_PROMPTS_DIR": "/home/paul/Dropbox/hexai-prompts" - } - } - } -} +Pass additional flags to the server: + +```bash +claude mcp add --transport stdio --scope user hexai-prompts -- \ + ~/go/bin/hexai-mcp-server \ + --prompts-dir ~/Dropbox/hexai-prompts \ + --log /tmp/hexai-mcp-server.log +``` + +### Verify Connection + +After adding, verify the server is connected: + +```bash +claude mcp list +# Expected: hexai-prompts: ✓ Connected +``` + +### Managing MCP Servers + +```bash +# List all configured servers +claude mcp list + +# Remove a server +claude mcp remove hexai-prompts -s user ``` -**Configuration Options:** +**Server Options:** - `--config`: Path to hexai config file (optional) - `--log`: Path to log file (default: `~/.local/state/hexai/hexai-mcp-server.log`) - `--prompts-dir`: Directory for prompt storage (optional) @@ -116,9 +113,9 @@ After configuring, restart Cursor to load the MCP server. Any MCP-compatible client can use hexai-mcp-server. The general pattern is: -1. Find the client's MCP configuration file (usually in `~/.config/<client>/mcp.json`) +1. Find the client's MCP configuration method (CLI command or JSON config file) 2. Add an entry with the command path to `hexai-mcp-server` -3. Restart the client +3. Restart the client if required ## Prompts Directory @@ -228,8 +225,9 @@ Common issues: ### Client Configuration Issues **Claude Code CLI**: -- Configuration file: `~/.config/claude/mcp.json` -- Restart required after config changes +- Configuration managed via `claude mcp add/remove/list` commands +- User config stored in `~/.claude.json`, project config in `.mcp.json` +- Restart Claude Code after config changes **Cursor**: - Configuration file: `~/.cursor/mcp.json` @@ -240,23 +238,15 @@ Common issues: ### Multiple Prompt Collections -You can run multiple instances of hexai-mcp-server with different prompt directories: +You can register multiple instances of hexai-mcp-server with different prompt directories: -```json -{ - "mcpServers": { - "hexai-general": { - "command": "/home/paul/go/bin/hexai-mcp-server", - "args": ["--prompts-dir", "/home/paul/prompts/general"], - "env": {} - }, - "hexai-go": { - "command": "/home/paul/go/bin/hexai-mcp-server", - "args": ["--prompts-dir", "/home/paul/prompts/go"], - "env": {} - } - } -} +```bash +claude mcp add --transport stdio --scope user hexai-general -- \ + ~/go/bin/hexai-mcp-server --prompts-dir ~/prompts/general + +claude mcp add --transport stdio --scope user hexai-go -- \ + ~/go/bin/hexai-mcp-server --prompts-dir ~/prompts/go +``` ``` ### Shared Team Prompts diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 4a14ffc..0c767f2 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -57,43 +57,34 @@ func createTestServer(t *testing.T, store promptstore.PromptStore) (*Server, *by return NewServer(inBuf, outBuf, logger, store), inBuf, outBuf } +// sendRequest writes a JSON-RPC request as newline-delimited JSON (MCP stdio protocol). func sendRequest(w io.Writer, req Request) error { data, err := json.Marshal(req) if err != nil { return err } - header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(data)) - if _, err := io.WriteString(w, header); err != nil { + if _, err := w.Write(data); err != nil { return err } - if _, err := w.Write(data); err != nil { + if _, err := io.WriteString(w, "\n"); err != nil { return err } return nil } +// readResponse reads a newline-delimited JSON-RPC response (MCP stdio protocol). func readResponse(r io.Reader) (*Response, error) { - // Simple read for testing (assumes one message in buffer) data, err := io.ReadAll(r) if err != nil { return nil, err } - // Find Content-Length - lines := strings.Split(string(data), "\r\n") - var contentLength int - bodyStart := 0 - for i, line := range lines { - if strings.HasPrefix(line, "Content-Length:") { - fmt.Sscanf(line, "Content-Length: %d", &contentLength) - } - if line == "" { - bodyStart = i + 1 - break - } + // Parse newline-delimited JSON; take the last non-empty line as the response + lines := strings.Split(strings.TrimSpace(string(data)), "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("no response data") } - - body := strings.Join(lines[bodyStart:], "\r\n") + body := lines[len(lines)-1] var resp Response if err := json.Unmarshal([]byte(body), &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w, body: %s", err, body) @@ -459,11 +450,9 @@ func TestServer_ReadMessage(t *testing.T) { logger := log.New(io.Discard, "", 0) server := NewServer(inBuf, outBuf, logger, store) - // Write a message with proper framing - msg := []byte(`{"jsonrpc":"2.0","id":1,"method":"test"}`) - header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(msg)) - inBuf.WriteString(header) - inBuf.Write(msg) + // Write a newline-delimited JSON message (MCP stdio protocol) + msg := `{"jsonrpc":"2.0","id":1,"method":"test"}` + inBuf.WriteString(msg + "\n") // Read it back body, err := server.readMessage() @@ -471,7 +460,28 @@ func TestServer_ReadMessage(t *testing.T) { t.Fatalf("readMessage() error = %v", err) } - if string(body) != string(msg) { + if string(body) != msg { + t.Errorf("readMessage() = %s, want %s", body, msg) + } + }) + + t.Run("skips empty lines", func(t *testing.T) { + store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)} + inBuf := &bytes.Buffer{} + outBuf := &bytes.Buffer{} + logger := log.New(io.Discard, "", 0) + server := NewServer(inBuf, outBuf, logger, store) + + // Write empty lines followed by a valid message + msg := `{"jsonrpc":"2.0","id":1,"method":"test"}` + inBuf.WriteString("\n\n" + msg + "\n") + + body, err := server.readMessage() + if err != nil { + t.Fatalf("readMessage() error = %v", err) + } + + if string(body) != msg { t.Errorf("readMessage() = %s, want %s", body, msg) } }) diff --git a/internal/mcp/transport.go b/internal/mcp/transport.go index aba416a..4708e6d 100644 --- a/internal/mcp/transport.go +++ b/internal/mcp/transport.go @@ -1,54 +1,40 @@ -// Summary: MCP transport utilities for reading and writing JSON-RPC messages with Content-Length framing. +// Summary: MCP transport utilities for reading and writing JSON-RPC messages +// using newline-delimited JSON (JSONL) as required by the MCP stdio protocol. package mcp import ( "encoding/json" "fmt" "io" - "net/textproto" - "strconv" "strings" ) -// readMessage reads a Content-Length framed JSON-RPC message from the input stream. -// Returns the raw JSON bytes. Follows LSP/JSON-RPC framing convention. +// readMessage reads a newline-delimited JSON-RPC message from the input stream. +// Returns the raw JSON bytes. Follows the MCP stdio transport specification +// which uses newline-delimited JSON (JSONL), not LSP-style Content-Length framing. +// Uses the server's bufio.Reader (s.in) to avoid losing buffered data between calls. func (s *Server) readMessage() ([]byte, error) { - tp := textproto.NewReader(s.in) - var contentLength int for { - line, err := tp.ReadLine() - if err != nil { - return nil, err - } - if line == "" { // end of headers - break - } - parts := strings.SplitN(line, ":", 2) - if len(parts) != 2 { - continue + line, err := s.in.ReadString('\n') + if err != nil && len(line) == 0 { + if err == io.EOF { + return nil, io.EOF + } + return nil, fmt.Errorf("read error: %w", err) } - key := strings.TrimSpace(strings.ToLower(parts[0])) - val := strings.TrimSpace(parts[1]) - switch key { - case "content-length": - n, err := strconv.Atoi(val) - if err != nil { - return nil, fmt.Errorf("invalid Content-Length: %v", err) + line = strings.TrimSpace(line) + if line == "" { + // If we hit EOF on an empty line, propagate it + if err == io.EOF { + return nil, io.EOF } - contentLength = n + continue // skip empty lines } + return []byte(line), nil } - if contentLength <= 0 { - return nil, fmt.Errorf("missing or invalid Content-Length") - } - buf := make([]byte, contentLength) - if _, err := io.ReadFull(s.in, buf); err != nil { - return nil, err - } - return buf, nil } -// writeMessage writes a JSON-RPC response with Content-Length framing. +// writeMessage writes a JSON-RPC response as newline-delimited JSON. // Thread-safe via mutex lock. func (s *Server) writeMessage(v any) error { s.outMu.Lock() @@ -58,12 +44,12 @@ func (s *Server) writeMessage(v any) error { if err != nil { return fmt.Errorf("marshal error: %w", err) } - header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(data)) - if _, err := io.WriteString(s.out, header); err != nil { - return fmt.Errorf("write header error: %w", err) - } + // Write JSON followed by newline (JSONL format) if _, err := s.out.Write(data); err != nil { return fmt.Errorf("write body error: %w", err) } + if _, err := io.WriteString(s.out, "\n"); err != nil { + return fmt.Errorf("write newline error: %w", err) + } return nil } diff --git a/prompts/mcp-server-integration-tests.md b/prompts/mcp-server-integration-tests.md new file mode 100644 index 0000000..cb24a8f --- /dev/null +++ b/prompts/mcp-server-integration-tests.md @@ -0,0 +1,243 @@ +# hexai-mcp-server Integration Test Runbook + +Real-life integration tests against Claude Code CLI using the hexai MCP server. +These tests verify the full MCP protocol integration: server discovery, prompt +listing/retrieval via slash commands, and file persistence across sessions. + +**Protocol**: The MCP stdio transport uses newline-delimited JSON (JSONL), not +LSP-style Content-Length framing. Each message is a single JSON object followed +by a newline character. + +**Test Status**: +- ✓ **Claude Code CLI**: Tested and working (2026-02-11) +- ⏳ **Cursor**: Needs testing (future) + +## Prerequisites + +- Must be running inside a tmux session +- Claude Code CLI must be installed and authenticated +- `hexai-mcp-server` binary must be built: `go build -o ~/go/bin/hexai-mcp-server ./cmd/hexai-mcp-server/` +- `jq` command-line JSON processor installed +- All unit tests must pass first: `go test ./internal/mcp/` and `go test ./internal/hexaimcp/` + +## Setup: Create Test Environment + +Before running tests, set up an isolated test environment: + +```sh +# 1. Create temporary prompts directory for testing +TEST_PROMPTS_DIR="/tmp/hexai-mcp-test-$(date +%s)" +mkdir -p "$TEST_PROMPTS_DIR" +echo "Test prompts directory: $TEST_PROMPTS_DIR" + +# 2. Create minimal test prompts in JSONL format +cat > "$TEST_PROMPTS_DIR/default.jsonl" << 'EOF' +{"name":"test_review","title":"Test Code Review","description":"A test prompt for code review","arguments":[{"name":"code","description":"Code to review","required":true}],"messages":[{"role":"user","content":{"type":"text","text":"Review this code:\n\n{{code}}\n\nProvide:\n1. Issues found\n2. Suggestions for improvement"}}],"tags":["testing","code-quality"],"created":"2026-02-10T00:00:00Z","updated":"2026-02-10T00:00:00Z"} +{"name":"test_explain","title":"Test Explain Code","description":"A test prompt to explain code","arguments":[{"name":"code","description":"Code to explain","required":true}],"messages":[{"role":"user","content":{"type":"text","text":"Explain what this code does:\n\n{{code}}"}}],"tags":["testing","documentation"],"created":"2026-02-10T00:00:00Z","updated":"2026-02-10T00:00:00Z"} +EOF + +# 3. Create empty user.jsonl (for testing creation) +touch "$TEST_PROMPTS_DIR/user.jsonl" + +# 4. Register test MCP server with Claude Code CLI (user scope) +claude mcp add --transport stdio --scope user hexai-test -- \ + "$HOME/go/bin/hexai-mcp-server" \ + --prompts-dir "$TEST_PROMPTS_DIR" \ + --log /tmp/hexai-mcp-test.log +echo "✓ Registered test MCP server" + +# 5. Verify server is connected +claude mcp list | grep hexai-test +# Expected: hexai-test: ✓ Connected + +# 6. Start Claude Code CLI in a new tmux pane +CLAUDE_PANE=$(tmux split-window -v -d -P -F '#{pane_id}' 'claude') +sleep 3 +echo "Claude running in pane: $CLAUDE_PANE" + +# 7. Verify MCP server started (check logs) +if grep -q "hexai-mcp-server starting" /tmp/hexai-mcp-test.log 2>/dev/null; then + echo "✓ MCP server started successfully" +else + echo "✗ MCP server failed to start (check /tmp/hexai-mcp-test.log)" +fi +``` + +Expected state after setup: +- Test prompts directory created with 2 default prompts +- MCP server registered via `claude mcp add` +- Claude Code CLI running in tmux pane +- MCP server initialized and connected + +## Test 1: Server Connection and Initialization + +**Goal**: Verify that Claude Code successfully connects to the MCP server +and completes the initialization handshake. + +```sh +# 1. Check MCP server log for initialization sequence +grep "initialize" /tmp/hexai-mcp-test.log +# Expected: Log entries showing initialization from client + +# 2. Check for any connection errors +grep -i "error\|fail" /tmp/hexai-mcp-test.log || echo "✓ No errors in log" + +# 3. Verify Claude is responsive in the pane +tmux send-keys -t "$CLAUDE_PANE" "test connection" +sleep 1 +tmux capture-pane -p -t "$CLAUDE_PANE" | tail -5 +``` + +## Test 2: Prompt Discovery via Slash Commands + +**Goal**: Verify that prompts from the MCP server appear as slash commands +in Claude Code CLI. MCP prompts appear as `/hexai-test:<prompt_name>`. + +```sh +# 1. Type the slash command prefix to trigger autocomplete +tmux send-keys -t "$CLAUDE_PANE" "/hexai-test:" +sleep 2 + +# 2. Capture the autocomplete suggestions +tmux capture-pane -p -t "$CLAUDE_PANE" -S -20 +# Expected: Should show test_review and test_explain as available commands + +# 3. Press Escape to clear +tmux send-keys -t "$CLAUDE_PANE" Escape +sleep 0.5 + +# 4. Check MCP server log for prompts/list request +grep "prompts/list" /tmp/hexai-mcp-test.log +``` + +## Test 3: Prompt Retrieval via Slash Command + +**Goal**: Verify that a specific prompt can be retrieved and used via +the slash command mechanism with argument substitution. + +```sh +# 1. Use the test_review prompt via slash command +tmux send-keys -t "$CLAUDE_PANE" "/hexai-test:test_review" +sleep 1 +# Select the prompt from autocomplete if needed +tmux send-keys -t "$CLAUDE_PANE" Enter +sleep 1 + +# 2. Fill in the code argument when prompted +tmux send-keys -t "$CLAUDE_PANE" "func Add(a, b int) int { return a + b }" +tmux send-keys -t "$CLAUDE_PANE" Enter +sleep 4 + +# 3. Check MCP server log for prompts/get request +grep "prompts/get" /tmp/hexai-mcp-test.log | tail -1 + +# 4. Capture Claude's response +tmux capture-pane -p -t "$CLAUDE_PANE" -S -30 | tail -15 + +# 5. Clear for next test +tmux send-keys -t "$CLAUDE_PANE" Escape +sleep 0.5 +``` + +## Test 4: Persistence Across Sessions + +**Goal**: Verify that prompts persist when starting a new Claude Code CLI session. + +```sh +# 1. Kill the current Claude pane +tmux kill-pane -t "$CLAUDE_PANE" +sleep 1 + +# 2. Start a fresh Claude session +CLAUDE_PANE=$(tmux split-window -v -d -P -F '#{pane_id}' 'claude') +sleep 3 + +# 3. Verify the MCP server reconnects (check new log entries) +grep "initialize" /tmp/hexai-mcp-test.log | wc -l +# Expected: Should show 2 or more initialization entries + +# 4. Try using the prompt in the new session +tmux send-keys -t "$CLAUDE_PANE" "/hexai-test:test_review" +sleep 2 +tmux capture-pane -p -t "$CLAUDE_PANE" -S -10 +# Expected: Prompt should appear in autocomplete + +# 5. Clear +tmux send-keys -t "$CLAUDE_PANE" Escape +sleep 0.5 +``` + +## Test 5: Error Handling + +**Goal**: Verify proper error messages for invalid operations. + +```sh +# Test 5a: Get non-existent prompt +tmux send-keys -t "$CLAUDE_PANE" "Get the prompt called 'nonexistent_prompt' from the hexai-test MCP server" +tmux send-keys -t "$CLAUDE_PANE" Enter +sleep 3 + +# Check for error in logs +grep -i "not found" /tmp/hexai-mcp-test.log | tail -1 + +tmux send-keys -t "$CLAUDE_PANE" Escape +sleep 0.5 +``` + +## Cleanup + +After all tests, remove the test server and clean up: + +```sh +# 1. Kill Claude pane +if [ -n "$CLAUDE_PANE" ]; then + tmux kill-pane -t "$CLAUDE_PANE" + echo "✓ Closed Claude pane: $CLAUDE_PANE" +fi + +# 2. Remove test MCP server registration +claude mcp remove hexai-test -s user +echo "✓ Removed test MCP server" + +# 3. Keep test directory and logs for inspection +echo "Test artifacts preserved:" +echo " Prompts: $TEST_PROMPTS_DIR" +echo " Log: /tmp/hexai-mcp-test.log" +echo "" +echo "To clean up manually:" +echo " rm -rf $TEST_PROMPTS_DIR" +echo " rm /tmp/hexai-mcp-test.log" + +# 4. Check for hung MCP server processes +HUNG_PROCS=$(pgrep -f hexai-mcp-server | wc -l) +if [ "$HUNG_PROCS" -gt 0 ]; then + echo "⚠ Warning: $HUNG_PROCS hexai-mcp-server processes still running" + echo "Run: pkill -f hexai-mcp-server" +else + echo "✓ No hung MCP server processes" +fi +``` + +## Troubleshooting + +**Claude doesn't see MCP server**: +- Verify registration: `claude mcp list` should show `hexai-test: ✓ Connected` +- If not connected, re-register: `claude mcp add --transport stdio --scope user hexai-test -- ...` +- Restart Claude Code CLI (kill pane and start new one) +- Check `/tmp/hexai-mcp-test.log` for startup errors + +**MCP server not starting**: +- Verify binary is executable: `ls -la ~/go/bin/hexai-mcp-server` +- Check prompts directory exists: `ls -la $TEST_PROMPTS_DIR` +- Try running manually: `echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | hexai-mcp-server --prompts-dir $TEST_PROMPTS_DIR --log /tmp/manual-test.log` + +**Prompts not appearing as slash commands**: +- MCP prompts appear as `/hexai-test:<name>` in Claude Code CLI +- Type `/hexai-test:` and wait for autocomplete +- Check prompts directory has valid JSONL files +- Verify JSONL: `jq -s '.' $TEST_PROMPTS_DIR/default.jsonl` + +**Protocol errors**: +- The MCP stdio transport uses JSONL (newline-delimited JSON), not Content-Length framing +- Check JSON-RPC format in logs +- Test with: `echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | hexai-mcp-server` diff --git a/prompts/run-mcp-integration-test.md b/prompts/run-mcp-integration-test.md new file mode 100644 index 0000000..23aeb00 --- /dev/null +++ b/prompts/run-mcp-integration-test.md @@ -0,0 +1,53 @@ +# Prompt: Run MCP Server Integration Tests + +**Context**: You are running integration tests for the hexai-mcp-server against a real Claude Code CLI instance in tmux. These tests verify the full Model Context Protocol integration including prompt discovery via slash commands and persistence across sessions. + +**Your Role**: Execute each test systematically, report results clearly, and identify any failures. + +## Instructions + +Read the test runbook at `prompts/mcp-server-integration-tests.md` and execute each test in order. For each test: + +1. **Announce** what you're testing +2. **Execute** the test commands +3. **Verify** the expected outcomes +4. **Report** results clearly (PASS or FAIL) +5. **Investigate** any failures before moving to next test + +## Test Execution Flow + +### Phase 1: Setup +- Create test environment (temp directory, test prompts) +- Register MCP server via `claude mcp add --transport stdio --scope user` +- Start Claude Code CLI in tmux pane +- Verify MCP server connection via `claude mcp list` + +### Phase 2: Core Tests +Execute tests 1-5 in sequence: +1. Server Connection and Initialization +2. Prompt Discovery via Slash Commands (`/hexai-test:<name>`) +3. Prompt Retrieval via Slash Command +4. Persistence Across Sessions (kill and restart Claude) +5. Error Handling + +### Phase 3: Cleanup +- Kill Claude pane +- Remove test MCP server: `claude mcp remove hexai-test -s user` +- Report final summary + +## Key Details + +- MCP prompts appear as slash commands: `/hexai-test:<prompt_name>` +- The MCP stdio transport uses JSONL (newline-delimited JSON), not Content-Length +- Register servers with `claude mcp add`, not by editing JSON config files +- Always clean up: kill panes and remove test server registration + +## After Completion + +Report: +1. **Overall result**: X/5 tests passed +2. **Failures**: List any failed tests with details +3. **Artifacts location**: Where logs and test data are stored +4. **Recommendations**: Any bugs found or improvements needed + +Start with Phase 1 (Setup) and proceed systematically through all phases. |
