|
| 1 | +# Architecture |
| 2 | + |
| 3 | +**How qso-graph servers work together — one foundation, ten packages, zero cloud dependencies.** |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## System Overview |
| 8 | + |
| 9 | +``` |
| 10 | +┌─────────────────────────────────────────────────────────┐ |
| 11 | +│ AI Assistant │ |
| 12 | +│ Claude · ChatGPT · Cursor · Gemini │ |
| 13 | +└────────────────────┬────────────────────────────────────┘ |
| 14 | + │ MCP Protocol (stdio) |
| 15 | + ▼ |
| 16 | +┌─────────────────────────────────────────────────────────┐ |
| 17 | +│ MCP Servers (local) │ |
| 18 | +│ │ |
| 19 | +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ |
| 20 | +│ │ eqsl-mcp │ │ qrz-mcp │ │ lotw-mcp │ │ pota-mcp │ │ |
| 21 | +│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ |
| 22 | +│ │ │ │ │ │ |
| 23 | +│ ┌────┴─────────────┴────────────┴─────────────┘ │ |
| 24 | +│ │ │ |
| 25 | +│ │ adif-mcp (foundation) │ |
| 26 | +│ │ ├── PersonaManager → OS Keyring │ |
| 27 | +│ │ ├── ADIF 3.1.6 Spec (186 fields, 25 enums) │ |
| 28 | +│ │ ├── Validation Engine │ |
| 29 | +│ │ └── Geospatial (distance, heading) │ |
| 30 | +│ │ │ |
| 31 | +│ └──────────────────────────────────────────────────────┘ |
| 32 | +│ │ |
| 33 | +└─────────────────────────────────────────────────────────┘ |
| 34 | + │ HTTPS only |
| 35 | + ▼ |
| 36 | +┌─────────────────────────────────────────────────────────┐ |
| 37 | +│ External Services │ |
| 38 | +│ │ |
| 39 | +│ eQSL.cc · QRZ.com · LoTW · Club Log · HamQTH │ |
| 40 | +│ POTA · SOTA · NOAA SWPC · WSPR Network │ |
| 41 | +└─────────────────────────────────────────────────────────┘ |
| 42 | +``` |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## Foundation: adif-mcp |
| 47 | + |
| 48 | +adif-mcp provides three capabilities that all other servers depend on: |
| 49 | + |
| 50 | +### 1. Persona Management |
| 51 | + |
| 52 | +Named identities with credentials stored in the OS keyring. One persona serves all services: |
| 53 | + |
| 54 | +``` |
| 55 | +Persona: "ki7mt" |
| 56 | + ├── eqsl → password in OS keyring |
| 57 | + ├── lotw → password in OS keyring |
| 58 | + ├── qrz → password in OS keyring |
| 59 | + └── hamqth → password in OS keyring |
| 60 | +``` |
| 61 | + |
| 62 | +When a server needs credentials, it calls `adif-mcp` which reads them from the keyring at runtime. Credentials never exist in config files, environment variables, or MCP protocol messages. |
| 63 | + |
| 64 | +### 2. ADIF 3.1.6 Specification |
| 65 | + |
| 66 | +The complete ADIF 3.1.6 spec bundled as JSON: |
| 67 | + |
| 68 | +- **186 fields** with data types, valid ranges, and descriptions |
| 69 | +- **25 enumerations** with 4,427 records (Mode, Band, DXCC, Contest_ID, etc.) |
| 70 | +- **28 data types** (Number, String, Date, GridSquare, etc.) |
| 71 | + |
| 72 | +All servers share this spec for consistent parsing and validation. |
| 73 | + |
| 74 | +### 3. Validation Engine |
| 75 | + |
| 76 | +Record validation against the full spec: |
| 77 | + |
| 78 | +- Field name recognition (186 fields) |
| 79 | +- Data type checking (Number, Date, etc.) |
| 80 | +- Enum membership checking (43 enum-typed fields across 25 enumerations) |
| 81 | +- Compound format parsing (CreditList, multi-medium) |
| 82 | +- Conditional validation (Submode depends on Mode) |
| 83 | +- Import-only detection (warn, don't reject historical data) |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +## Server Architecture |
| 88 | + |
| 89 | +Each MCP server follows the same pattern: |
| 90 | + |
| 91 | +``` |
| 92 | +MCP Client Request |
| 93 | + │ |
| 94 | + ▼ |
| 95 | +Input Validation ──── Regex on all user strings |
| 96 | + │ |
| 97 | + ▼ |
| 98 | +Rate Limiter ──────── Per-service throttle (prevents account bans) |
| 99 | + │ |
| 100 | + ▼ |
| 101 | +Credential Lookup ─── OS keyring via adif-mcp (authenticated servers only) |
| 102 | + │ |
| 103 | + ▼ |
| 104 | +API Call ──────────── HTTPS only, response parsed |
| 105 | + │ |
| 106 | + ▼ |
| 107 | +Response Cache ────── In-memory TTL cache |
| 108 | + │ |
| 109 | + ▼ |
| 110 | +Safe Return ───────── No credentials in results, errors, or logs |
| 111 | +``` |
| 112 | + |
| 113 | +### Common Properties |
| 114 | + |
| 115 | +| Property | Value | |
| 116 | +|----------|-------| |
| 117 | +| Transport | stdio (default) or `--transport streamable-http` for MCP Inspector | |
| 118 | +| Framework | FastMCP 3.x | |
| 119 | +| Python | 3.10+ | |
| 120 | +| License | GPL-3.0-or-later | |
| 121 | +| Mock mode | `<NAME>_MCP_MOCK=1` for testing without credentials | |
| 122 | + |
| 123 | +### Rate Limiting |
| 124 | + |
| 125 | +Each server implements rate limiting appropriate for its service: |
| 126 | + |
| 127 | +| Server | Min Delay | Max Rate | Notes | |
| 128 | +|--------|-----------|----------|-------| |
| 129 | +| eqsl-mcp | 500ms | — | Respectful crawl | |
| 130 | +| qrz-mcp | 500ms | 35/min | IP ban risk above 35/min | |
| 131 | +| clublog-mcp | 500ms | 30/min | API key rate limit | |
| 132 | +| lotw-mcp | 500ms | — | Respectful crawl | |
| 133 | +| hamqth-mcp | 500ms | — | XML session rate limit | |
| 134 | +| pota-mcp | 100ms | — | Public API | |
| 135 | +| sota-mcp | 200ms | — | Public API | |
| 136 | +| solar-mcp | 200ms | — | NOAA public data | |
| 137 | +| wspr-mcp | 200ms | — | Public API | |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## Credential Flow |
| 142 | + |
| 143 | +Credentials take one path and never deviate: |
| 144 | + |
| 145 | +``` |
| 146 | +User ──── adif-mcp creds set ──── OS Keyring (encrypted) |
| 147 | + │ |
| 148 | +MCP Server ──── adif-mcp read ─────────┘ |
| 149 | + │ (in-process, never serialized) |
| 150 | + ▼ |
| 151 | +HTTPS Request ──── credential in Authorization header |
| 152 | + │ |
| 153 | + ▼ |
| 154 | +Response ──── parsed, credential stripped |
| 155 | + │ |
| 156 | + ▼ |
| 157 | +MCP Tool Result ──── data only, no credentials |
| 158 | +``` |
| 159 | + |
| 160 | +**What gets persisted:** Nothing. Credentials exist in memory only during the API call. The OS keyring handles encryption at rest. |
| 161 | + |
| 162 | +**What the AI sees:** Tool parameters (`persona`, `callsign`, `band`) and tool results (lookup data, QSO records). Never passwords, API keys, or session tokens. |
| 163 | + |
| 164 | +--- |
| 165 | + |
| 166 | +## Package Independence |
| 167 | + |
| 168 | +Each server is a standalone `pip install`: |
| 169 | + |
| 170 | +```bash |
| 171 | +pip install eqsl-mcp # just eqsl-mcp + its dependencies |
| 172 | +pip install pota-mcp # just pota-mcp, no auth needed |
| 173 | +``` |
| 174 | + |
| 175 | +Servers don't depend on each other. You can install one or all ten. |
| 176 | + |
| 177 | +Authenticated servers depend on `adif-mcp` for credential management. Public servers (POTA, SOTA, Solar, WSPR) have no dependency on adif-mcp. |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## MCP Client Configuration |
| 182 | + |
| 183 | +All servers work with any MCP client. Example for Claude Desktop: |
| 184 | + |
| 185 | +```json |
| 186 | +{ |
| 187 | + "mcpServers": { |
| 188 | + "adif": { |
| 189 | + "command": "adif-mcp" |
| 190 | + }, |
| 191 | + "eqsl": { |
| 192 | + "command": "eqsl-mcp" |
| 193 | + }, |
| 194 | + "pota": { |
| 195 | + "command": "pota-mcp" |
| 196 | + } |
| 197 | + } |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +For Claude Code, add to `~/.claude/settings.json`: |
| 202 | + |
| 203 | +```json |
| 204 | +{ |
| 205 | + "mcpServers": { |
| 206 | + "adif": { "command": "adif-mcp", "args": [] }, |
| 207 | + "eqsl": { "command": "eqsl-mcp", "args": [] } |
| 208 | + } |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +See [Getting Started](getting-started.md) for configuration for all 6 supported MCP clients. |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +## Design Principles |
| 217 | + |
| 218 | +### Good Neighbor Policy |
| 219 | + |
| 220 | +qso-graph servers **wrap** external APIs — they don't replicate them. Rate limiting is built in to prevent account bans. If a service goes down, the server fails gracefully. |
| 221 | + |
| 222 | +### Read-Only Security Model |
| 223 | + |
| 224 | +No qso-graph server writes to external services. All operations are read-only: lookups, downloads, queries. No log uploads, no QSO submissions, no account modifications. |
| 225 | + |
| 226 | +### Validate Before Upload |
| 227 | + |
| 228 | +adif-mcp's validation engine catches data defects at the source. A busted QSO is not a confirmation — and a rare DXpedition contact may be irreplaceable. Validate before uploading to LoTW, eQSL, or Club Log. |
| 229 | + |
| 230 | +### Pip Install and Go |
| 231 | + |
| 232 | +Every server is one `pip install` away. No Docker, no containers, no config files (except MCP client config). Credentials go in the OS keyring, not YAML files. |
0 commit comments