# mcp-servers Locally-hosted MCP (Model Context Protocol) servers exposed to Claude Code. Each subdirectory is one MCP server. Registered with Claude via: ```bash claude mcp add-json -s user '{"type":"stdio","command":"","args":[...]}' ``` ## Servers | Name | Path | Purpose | |---|---|---| | `forecastlab_db` | `forecastlab_db/server.py` | Read-only SQL access to `forecastlab`, `weewx_db`, `homeassistant` databases | ## forecastlab_db Read-only MariaDB facade. Tools: `list_databases`, `list_tables`, `describe_table`, `query`. **Safety:** - Only the 3 allowlisted databases reachable - SELECT / SHOW / DESCRIBE / EXPLAIN / WITH only — write statements rejected - Multi-statement injection rejected - Hard cap 1000 rows per query (default 200) - Datetimes serialised as ISO strings **Optional read-only DB user** (recommended for production): ```sql CREATE USER 'mcp_ro'@'localhost' IDENTIFIED BY ''; GRANT SELECT ON forecastlab.* TO 'mcp_ro'@'localhost'; GRANT SELECT ON weewx_db.* TO 'mcp_ro'@'localhost'; GRANT SELECT ON homeassistant.* TO 'mcp_ro'@'localhost'; FLUSH PRIVILEGES; ``` Then add to `~/.env`: ``` MCP_DB_RO_USER=mcp_ro MCP_DB_RO_PASSWORD= ``` The server prefers these if set; otherwise falls back to `DB_USER`/`DB_PASSWORD`. ## Adding a new MCP server 1. Create a subdirectory: `mcp_servers//server.py` 2. Use the `mcp` Python SDK and the `FastMCP` helper (see `forecastlab_db/server.py` as a template) 3. Register with Claude: ```bash claude mcp add-json -s user \ '{"type":"stdio","command":"/home/help4bis/miniconda3/envs/forecastlab/bin/python","args":["/home/help4bis/lib/mcp_servers//server.py"]}' ``` 4. Verify: `claude mcp list` should show it Connected 5. Commit + push to Gitea ## Testing Each server's stdio-style protocol is best tested by Claude itself. For sanity-check the read-only guard: ```bash /home/help4bis/miniconda3/envs/forecastlab/bin/python -c " import sys; sys.path.insert(0, '/home/help4bis/lib/mcp_servers/forecastlab_db') from server import _validate_read_only _validate_read_only('SELECT 1') # passes _validate_read_only('DROP TABLE foo') # raises ValueError " ```