|
| 1 | +# mcpd Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +mcpd is a single-header-friendly MCP server SDK designed for resource-constrained microcontrollers. The architecture prioritizes low memory usage, zero dynamic allocation where possible, and a clean separation between protocol handling and hardware abstraction. |
| 6 | + |
| 7 | +``` |
| 8 | +┌─────────────────────────────────────────────┐ |
| 9 | +│ MCP Clients │ |
| 10 | +│ (Claude Desktop, Cursor, custom clients) │ |
| 11 | +└──────────┬──────────┬──────────┬────────────┘ |
| 12 | + │ HTTP/SSE │ WS │ BLE |
| 13 | + ▼ ▼ ▼ |
| 14 | +┌──────────────────────────────────────────────┐ |
| 15 | +│ Transport Layer │ |
| 16 | +│ MCPTransport │ MCPTransportWS │ BLETransport│ |
| 17 | +└──────────────────┬───────────────────────────┘ |
| 18 | + │ JSON-RPC 2.0 |
| 19 | + ▼ |
| 20 | +┌──────────────────────────────────────────────┐ |
| 21 | +│ mcpd::Server │ |
| 22 | +│ ┌─────────┐ ┌──────────┐ ┌───────────────┐ │ |
| 23 | +│ │ Dispatch │ │ Sessions │ │ Capabilities │ │ |
| 24 | +│ │ Router │ │ Manager │ │ Negotiation │ │ |
| 25 | +│ └────┬─────┘ └──────────┘ └───────────────┘ │ |
| 26 | +│ │ │ |
| 27 | +│ ┌────┴──────────────────────────────────┐ │ |
| 28 | +│ │ Feature Modules │ │ |
| 29 | +│ │ Tools │ Resources │ Prompts │ Tasks │ │ |
| 30 | +│ │ Logging │ Sampling │ Elicitation │ │ |
| 31 | +│ │ Progress │ Roots │ Completion │ │ |
| 32 | +│ └───────────────────────────────────────┘ │ |
| 33 | +│ │ │ |
| 34 | +│ ┌────┴──────────────────────────────────┐ │ |
| 35 | +│ │ Cross-cutting Concerns │ │ |
| 36 | +│ │ Auth │ RateLimit │ Metrics │ Hooks │ │ |
| 37 | +│ └───────────────────────────────────────┘ │ |
| 38 | +└──────────────────┬───────────────────────────┘ |
| 39 | + │ |
| 40 | +┌──────────────────┴───────────────────────────┐ |
| 41 | +│ Platform HAL + Built-in Tools │ |
| 42 | +│ GPIO │ I2C │ SPI │ ADC │ PWM │ UART │ ... │ |
| 43 | +│ (106 hardware tools across 20+ categories) │ |
| 44 | +└──────────────────────────────────────────────┘ |
| 45 | +``` |
| 46 | + |
| 47 | +## Core Components |
| 48 | + |
| 49 | +### `mcpd::Server` (`mcpd.h` / `mcpd.cpp`) |
| 50 | + |
| 51 | +The central orchestrator (~1500 lines). Responsibilities: |
| 52 | + |
| 53 | +- **JSON-RPC dispatch**: Routes incoming requests to the correct handler based on `method` field. Supports batch requests (JSON arrays). |
| 54 | +- **Capability negotiation**: `initialize` handshake advertises supported features (tools, resources, prompts, logging, sampling, etc.). |
| 55 | +- **Session management**: Tracks client sessions with unique IDs, handles `initialized` lifecycle. |
| 56 | +- **Notification fan-out**: Pushes `notifications/tools/list_changed`, `notifications/resources/list_changed`, etc. to all active sessions. |
| 57 | + |
| 58 | +### Transport Layer |
| 59 | + |
| 60 | +Three transport implementations, all feeding JSON-RPC strings into the same dispatch: |
| 61 | + |
| 62 | +| Transport | File | Use Case | |
| 63 | +|-----------|------|----------| |
| 64 | +| **Streamable HTTP + SSE** | `MCPTransport.h` / `MCPTransportSSE.h` | Primary. Claude Desktop via mcpd-bridge. | |
| 65 | +| **WebSocket** | `MCPTransportWS.h` | Browser clients, persistent connections. | |
| 66 | +| **BLE GATT** | `MCPTransportBLE.h` | Phone apps, proximity-based. Automatic chunking for MTU limits. | |
| 67 | + |
| 68 | +### Feature Modules |
| 69 | + |
| 70 | +Each MCP capability is encapsulated in its own header: |
| 71 | + |
| 72 | +| Module | Header | MCP Method(s) | |
| 73 | +|--------|--------|---------------| |
| 74 | +| Tools | `MCPTool.h` | `tools/list`, `tools/call` | |
| 75 | +| Resources | `MCPResource.h` | `resources/list`, `resources/read`, `resources/subscribe` | |
| 76 | +| Resource Templates | `MCPResourceTemplate.h` | `resources/templates/list` | |
| 77 | +| Prompts | `MCPPrompt.h` | `prompts/list`, `prompts/get` | |
| 78 | +| Tasks | `MCPTask.h` | `tasks/get`, `tasks/list`, `tasks/result`, `tasks/cancel` | |
| 79 | +| Logging | `MCPLogging.h` | `logging/setLevel` | |
| 80 | +| Sampling | `MCPSampling.h` | `sampling/createMessage` (server→client) | |
| 81 | +| Elicitation | `MCPElicitation.h` | `elicitation/create` (server→client) | |
| 82 | +| Progress | `MCPProgress.h` | `notifications/progress` | |
| 83 | +| Roots | `MCPRoots.h` | `roots/list` | |
| 84 | +| Completion | `MCPCompletion.h` | `completion/complete` | |
| 85 | +| Content | `MCPContent.h` | Text, Image, Audio, ResourceLink content types | |
| 86 | +| Icons | `MCPIcon.h` | Server/tool/resource/prompt icons | |
| 87 | + |
| 88 | +### Cross-cutting Concerns |
| 89 | + |
| 90 | +| Module | Header | Purpose | |
| 91 | +|--------|--------|---------| |
| 92 | +| Auth | `MCPAuth.h` | API key authentication, multi-key support | |
| 93 | +| Rate Limiting | `MCPRateLimit.h` | Token-bucket rate limiter per endpoint | |
| 94 | +| Metrics | `MCPMetrics.h` | Prometheus-compatible metrics endpoint | |
| 95 | +| Diagnostics | `MCPDiagnostics.h` | Heap monitoring, uptime, request counters | |
| 96 | +| Session | `MCPSession.h` | Client session state, subscription tracking | |
| 97 | +| Heap | `MCPHeap.h` | Memory-safe allocation tracking for MCU | |
| 98 | + |
| 99 | +### Platform HAL (`src/platform/`) |
| 100 | + |
| 101 | +Hardware abstraction layer isolating platform-specific calls: |
| 102 | + |
| 103 | +- `MCPPlatformESP32.h` — ESP-IDF / Arduino ESP32 |
| 104 | +- `MCPPlatformRP2040.h` — Raspberry Pi Pico |
| 105 | +- `MCPPlatformSTM32.h` — STM32 HAL |
| 106 | +- `MCPPlatformGeneric.h` — Fallback for testing |
| 107 | + |
| 108 | +### Built-in Tools (`src/tools/`) |
| 109 | + |
| 110 | +106 pre-built hardware tools organized by category (GPIO, PWM, Servo, I2C, SPI, ADC, UART, CAN, Modbus, LCD, IR, RTC, Camera, ESP-NOW, OTA, etc.). Each tool auto-registers with correct JSON Schema and annotations. |
| 111 | + |
| 112 | +## Design Decisions |
| 113 | + |
| 114 | +### Single-header-friendly |
| 115 | + |
| 116 | +All modules are header-only except `mcpd.cpp`. This means a project can `#include <mcpd.h>` and get everything. The trade-off is compile time, but for MCU projects this is acceptable. |
| 117 | + |
| 118 | +### ArduinoJson for JSON |
| 119 | + |
| 120 | +ArduinoJson v7 is used throughout for JSON parsing and serialization. It provides: |
| 121 | +- Static allocation (`StaticJsonDocument`) for predictable memory use |
| 122 | +- Streaming serialization to avoid large intermediate buffers |
| 123 | + |
| 124 | +### String over std::string |
| 125 | + |
| 126 | +Arduino `String` is used instead of `std::string` for compatibility across Arduino cores. This is a pragmatic choice — some Arduino cores don't ship a full C++ stdlib. |
| 127 | + |
| 128 | +### No Exceptions |
| 129 | + |
| 130 | +The entire codebase is exception-free. Error handling uses return values (bool, nullptr, error JSON). This is critical for MCU targets where exceptions may not be available or are prohibitively expensive. |
| 131 | + |
| 132 | +### Test Architecture |
| 133 | + |
| 134 | +Tests compile as native C++ (no MCU needed) using mock headers in `test/mock_includes/` that stub out Arduino, WiFi, BLE, and peripheral APIs. The test framework (`test_framework.h`) is custom and minimal — no external dependencies. |
| 135 | + |
| 136 | +``` |
| 137 | +test/ |
| 138 | +├── mock_includes/ # Arduino/WiFi/BLE stubs for native compilation |
| 139 | +├── native/ |
| 140 | +│ ├── Makefile # Build and run all test suites |
| 141 | +│ └── test_mcp_http.cpp |
| 142 | +├── test_framework.h # Custom test macros (TEST, ASSERT_*, RUN_TEST) |
| 143 | +├── test_jsonrpc.cpp # JSON-RPC protocol tests |
| 144 | +├── test_tools.cpp # Tool/resource/prompt tests |
| 145 | +├── test_modules.cpp # Feature module tests |
| 146 | +├── test_tasks.cpp # Async tasks tests |
| 147 | +└── ... # 14 test files, 1146+ tests total |
| 148 | +``` |
| 149 | + |
| 150 | +## Message Flow |
| 151 | + |
| 152 | +``` |
| 153 | +Client mcpd::Server |
| 154 | + │ │ |
| 155 | + │──── POST /mcp ────────────────────│ |
| 156 | + │ {"jsonrpc":"2.0", │ |
| 157 | + │ "method":"tools/call", │ |
| 158 | + │ "params":{...}, │ |
| 159 | + │ "id":1} │ |
| 160 | + │ │ |
| 161 | + │ ┌────────────┤ |
| 162 | + │ │ Auth check │ |
| 163 | + │ │ Rate limit │ |
| 164 | + │ │ Session │ |
| 165 | + │ │ lookup │ |
| 166 | + │ │ Before-hook│ |
| 167 | + │ │ Dispatch │ |
| 168 | + │ │ After-hook │ |
| 169 | + │ └────────────┤ |
| 170 | + │ │ |
| 171 | + │◄─── 200 OK ──────────────────────│ |
| 172 | + │ {"jsonrpc":"2.0", │ |
| 173 | + │ "result":{...}, │ |
| 174 | + │ "id":1} │ |
| 175 | + │ │ |
| 176 | +``` |
| 177 | + |
| 178 | +## Memory Budget |
| 179 | + |
| 180 | +Target memory footprint for a typical configuration: |
| 181 | + |
| 182 | +| Component | RAM (approx.) | |
| 183 | +|-----------|---------------| |
| 184 | +| Server core | ~2 KB | |
| 185 | +| Per session | ~200 bytes | |
| 186 | +| Per tool registration | ~100 bytes | |
| 187 | +| Per resource registration | ~120 bytes | |
| 188 | +| JSON parse buffer | 4-16 KB (configurable) | |
| 189 | +| HTTP/SSE transport | ~1 KB | |
| 190 | +| WebSocket transport | ~2 KB | |
| 191 | +| BLE transport | ~3 KB | |
| 192 | +| **Typical total** | **~10-20 KB** | |
| 193 | + |
| 194 | +This leaves plenty of room on ESP32 (520 KB SRAM) and RP2040 (264 KB SRAM). |
0 commit comments