An MCP (Model Context Protocol) server that exposes Safecast radiation monitoring data to AI assistants like Claude. The server provides 17 tools for querying both real-time sensor readings and historical radiation measurements, browsing sensor tracks, spectroscopy data, analytics, and educational reference data.
- 17 tools for querying Safecast radiation data
- Real-time and historical data access: Query both real-time sensor readings and historical measurements
- Dual transport: SSE and Streamable HTTP (works with Claude.ai)
- PostgreSQL + PostGIS for fast spatial queries (with REST API fallback)
- DuckDB analytics for usage statistics and aggregate queries
- Structured runtime logging for monitoring tool usage and performance
- Read-only access to public Safecast data
| Tool | Data Type | Description |
|---|---|---|
query_radiation |
Historical | Find measurements near a lat/lon coordinate |
search_area |
Historical | Search within a geographic bounding box |
list_tracks |
Historical | Browse bGeigie Import tracks by year/month |
get_track |
Historical | Get measurements from a specific track |
device_history |
Mixed | Historical data from a monitoring device (supports both bGeigie and real-time sensors) |
list_sensors |
Real-time | Discover active fixed sensors (Pointcast, Solarcast, bGeigieZen, etc.) by location or type |
sensor_current |
Real-time | Get the latest reading(s) from a specific sensor or from all sensors in a geographic area |
sensor_history |
Real-time | Pull time-series data from a fixed sensor over a date range |
list_spectra |
Historical | Browse and search gamma spectroscopy records |
get_spectrum |
Historical | Get full spectroscopy channel data for a measurement |
radiation_info |
Reference | Educational reference (units, safety levels, detectors, isotopes) |
radiation_stats |
Aggregate | Aggregate radiation statistics by year/month |
query_extreme_readings |
Aggregate | Find highest/lowest radiation readings with full location details |
top_uploaders |
Aggregate | Statistics on which users/devices uploaded the most data |
query_analytics |
Analytics | Server usage statistics (call counts, durations) |
db_info |
Diagnostic | Database connection and status (diagnostic) |
ping |
Diagnostic | Health check |
search_tracks_by_location |
Historical | Find measurement tracks by country name or bounding box |
The Safecast MCP server provides access to real-time radiation data from fixed sensors (Pointcast, Solarcast, bGeigieZen, etc.) through dedicated tools. These tools query the realtime_measurements table in the PostgreSQL database to retrieve the most current readings from active sensors.
list_sensors: Discover active fixed sensors by location or typesensor_current: Get the latest reading(s) from specific sensors or geographic areassensor_history: Pull time-series data from fixed sensors over date rangesdevice_history: Access both historical bGeigie data and real-time sensor data for a specific device
Note: Real-time data tools require a database connection to access the
realtime_measurementstable. These tools will fall back to the Safecast REST API if no database is configured.
Find radiation measurements near a geographic location. Returns measurements within a specified radius, sorted by most recent.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lat |
number | Yes | Latitude (-90 to 90) | |
lon |
number | Yes | Longitude (-180 to 180) | |
radius_m |
number | No | 1500 | Search radius in meters (25 to 50,000) |
limit |
number | No | 25 | Max results (1 to 10,000) |
Example: Find measurements within 5km of Fukushima Daiichi:
{"name": "query_radiation", "arguments": {"lat": 37.42, "lon": 141.03, "radius_m": 5000}}Each result includes: id, value (dose rate in uSv/h), captured_at, location (lat/lon), device_id, detector, track_id, has_spectrum, and distance_m.
Find radiation measurements within a geographic bounding box.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
min_lat |
number | Yes | Southern boundary latitude | |
max_lat |
number | Yes | Northern boundary latitude | |
min_lon |
number | Yes | Western boundary longitude | |
max_lon |
number | Yes | Eastern boundary longitude | |
limit |
number | No | 100 | Max results (1 to 10,000) |
Example: Search the Tokyo metropolitan area:
{"name": "search_area", "arguments": {"min_lat": 35.5, "max_lat": 35.8, "min_lon": 139.5, "max_lon": 139.9}}Browse bGeigie Import tracks (bulk radiation measurement drives/journeys). Each track represents a set of measurements collected during a single bGeigie session.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
year |
number | No | Filter by year (2000-2100) | |
month |
number | No | Filter by month (1-12, requires year) |
|
limit |
number | No | 50 | Max results (1 to 50,000) |
Example: Browse tracks from January 2024:
{"name": "list_tracks", "arguments": {"year": 2024, "month": 1}}Each result includes: track_id, filename, detector, file_size, recording_date, created_at, username (uploader), map_url (direct link to track view like https://simplemap.safecast.org/trackid/8fCxVw), and optional uploader object with username and email.
Find bGeigie measurement tracks by country name or geographic bounding box. This tool searches for radiation measurement journeys (tracks) that were recorded within a specified geographic area. Use country name for convenient searching, or provide bounding box coordinates for precise control.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
country |
string | No | Country name to search for (e.g., 'South Africa', 'Japan', 'Germany'). Case-insensitive. Uses predefined bounding boxes for 80+ countries. | |
min_lat |
number | No | -90 | Southern boundary latitude (use with country for custom area, or alone for precise control) |
max_lat |
number | No | 90 | Northern boundary latitude |
min_lon |
number | No | -180 | Western boundary longitude |
max_lon |
number | No | 180 | Eastern boundary longitude |
year |
number | No | Filter tracks by year (e.g., 2024) | |
month |
number | No | Filter tracks by month (1-12, requires year parameter) | |
limit |
number | No | 50 | Max results (1 to 50,000) |
Example: Find all tracks from South Africa:
{"name": "search_tracks_by_location", "arguments": {"country": "South Africa"}}Example: Find tracks from Japan in 2024:
{"name": "search_tracks_by_location", "arguments": {"country": "Japan", "year": 2024}}Example: Find tracks in a custom bounding box (Tokyo area):
{"name": "search_tracks_by_location", "arguments": {"min_lat": 35.5, "max_lat": 35.8, "min_lon": 139.5, "max_lon": 139.9, "limit": 100}}Each result includes: track_id, filename, detector, file_size, recording_date, created_at, username (uploader), centroid (approximate center of track), map_url (direct link to track view like https://simplemap.safecast.org/trackid/8fCxVw), and optional uploader object with username and email.
Note: Requires database connection. Country name lookup supports 80+ countries including South Africa, USA, Japan, Germany, France, UK, Australia, and many more.
Retrieve all radiation measurements from a specific track/journey. Use list_tracks to find track IDs first.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
track_id |
string | Yes | Track identifier | |
from |
number | No | Start marker ID for filtering | |
to |
number | No | End marker ID for filtering | |
limit |
number | No | 200 | Max results (1 to 10,000) |
Example: Get measurements from a specific track:
{"name": "get_track", "arguments": {"track_id": "8eh5m1"}}Get historical radiation measurements from a specific monitoring device over a time period. This tool now supports both bGeigie import data and real-time sensor data.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | Yes | Device identifier | |
days |
number | No | 30 | Days of history (1 to 365) |
limit |
number | No | 200 | Max results (1 to 10,000) |
Example: Get 90 days of history from a device:
{"name": "device_history", "arguments": {"device_id": "12345", "days": 90}}Note: This tool queries both the
markerstable (for bGeigie imports) and therealtime_measurementstable (for fixed sensors) to provide a comprehensive history from the specified device.
Discover active fixed sensors (Pointcast, Solarcast, bGeigieZen, etc.) by location or type, returning device IDs, locations, status, and last reading timestamp.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | No | Filter by sensor type (e.g., 'Pointcast', 'Solarcast', 'bGeigieZen') | |
min_lat |
number | No | -90 | Southern boundary for geographic filter |
max_lat |
number | No | 90 | Northern boundary for geographic filter |
min_lon |
number | No | -180 | Western boundary for geographic filter |
max_lon |
number | No | 180 | Eastern boundary for geographic filter |
limit |
number | No | 50 | Max results (1 to 1000) |
Example: Find all Pointcast sensors in Japan:
{"name": "list_sensors", "arguments": {"type": "Pointcast", "min_lat": 30, "max_lat": 46, "min_lon": 129, "max_lon": 146}}Note: Requires database connection to access
realtime_measurementstable.
Get the latest reading(s) from a specific sensor or from all sensors in a geographic area.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | No | Specific device ID to get latest reading from | |
min_lat |
number | No | -90 | Southern boundary for geographic filter |
max_lat |
number | No | 90 | Northern boundary for geographic filter |
min_lon |
number | No | -180 | Western boundary for geographic filter |
max_lon |
number | No | 180 | Eastern boundary for geographic filter |
limit |
number | No | 25 | Max results (1 to 1000) |
Example: Get latest reading from a specific sensor:
{"name": "sensor_current", "arguments": {"device_id": "sensor-123"}}Example: Get latest readings from all sensors in Tokyo:
{"name": "sensor_current", "arguments": {"min_lat": 35.5, "max_lat": 35.8, "min_lon": 139.5, "max_lon": 139.9, "limit": 50}}Note: Requires database connection to access
realtime_measurementstable.
Pull time-series data from a fixed sensor over a date range.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | Yes | Device identifier to get historical data from | |
start_date |
string | Yes | Start date in YYYY-MM-DD format | |
end_date |
string | No | Today | End date in YYYY-MM-DD format |
limit |
number | No | 200 | Max results (1 to 10,000) |
Example: Get 30 days of history from a sensor:
{"name": "sensor_history", "arguments": {"device_id": "sensor-123", "start_date": "2024-01-01", "end_date": "2024-01-31"}}Note: Requires database connection to access
realtime_measurementstable.
Browse and search gamma spectroscopy records. Returns metadata (filename, device, energy range, location) without the full channel data. Use get_spectrum with a marker_id from the results to fetch full channel data.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
min_lat |
number | No | Southern boundary (requires all 4 bbox params) | |
max_lat |
number | No | Northern boundary (requires all 4 bbox params) | |
min_lon |
number | No | Western boundary (requires all 4 bbox params) | |
max_lon |
number | No | Eastern boundary (requires all 4 bbox params) | |
source_format |
string | No | Filter by file format (e.g., "spe", "csv") |
|
device_model |
string | No | Filter by detector name (partial match) | |
track_id |
string | No | Filter by track identifier (e.g., "8eh5m1", "8ZnI7f") |
|
limit |
number | No | 50 | Max results (1 to 500) |
Example: Find all SPE spectrum files:
{"name": "list_spectra", "arguments": {"source_format": "spe"}}Example: Browse all spectra (no filters):
{"name": "list_spectra", "arguments": {}}Example: Get all spectrum files for a specific track:
{"name": "list_spectra", "arguments": {"track_id": "8eh5m1"}}Example: Get all spectrum files regardless of track (no filters):
{"name": "list_spectra", "arguments": {"limit": 100}}Each result includes: spectrum_id, marker_id, filename, source_format, device_model, channel_count, energy_range, live_time_sec, calibration, created_at, and nested marker with location and track_id.
Note: Requires database connection. No REST API fallback.
Get full gamma spectroscopy channel data for a specific measurement point. Returns the complete spectrum including all channel counts.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
marker_id |
number | Yes | Marker/measurement identifier (get from list_spectra) |
Example:
{"name": "get_spectrum", "arguments": {"marker_id": 4902886}}Returns: channels (array of counts), channel_count, energy_min_kev, energy_max_kev, live_time_sec, real_time_sec, device_model, calibration, source_format, filename, plus marker location and dose rate.
Get educational reference information about radiation. Returns static content.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
topic |
string | Yes | One of: units, dose_rates, safety_levels, detectors, background_levels, isotopes |
Topics:
units-- Explanation of uSv/h, CPM, Bq, Svdose_rates-- Typical dose rate ranges and what they meansafety_levels-- International safety standards and thresholdsdetectors-- Types of radiation detectors and how they workbackground_levels-- Natural background radiation by regionisotopes-- Common radioactive isotopes and their properties
Get aggregate radiation statistics from the Safecast database grouped by time interval. Powered by DuckDB + PostgreSQL.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
interval |
string | No | "year" |
Aggregation: "year", "month", or "overall" |
Example: Get yearly statistics:
{"name": "radiation_stats", "arguments": {"interval": "year"}}Find the highest or lowest radiation readings in the database with full location details. Unlike radiation_stats which provides aggregates, this tool returns specific measurements with coordinates, device IDs, and timestamps. Supports filtering out anomalous sources by device or geographic area. Powered by DuckDB + PostgreSQL.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
direction |
string | No | "highest" |
"highest" for maximum readings or "lowest" for minimum readings |
limit |
number | No | 10 | Number of readings to return (1 to 100) |
min_lat |
number | No | -90 | Southern boundary for optional geographic filter |
max_lat |
number | No | 90 | Northern boundary for optional geographic filter |
min_lon |
number | No | -180 | Western boundary for optional geographic filter |
max_lon |
number | No | 180 | Eastern boundary for optional geographic filter |
exclude_devices |
array | No | [] |
Array of device IDs to exclude from results (e.g., ["bGeigie-2113"]) |
exclude_areas |
string | No | "" |
JSON array of bounding boxes to exclude (see example below) |
Example: Find the 20 highest readings globally:
{"name": "query_extreme_readings", "arguments": {"direction": "highest", "limit": 20}}Example: Find the 10 highest readings in Japan (Fukushima region):
{"name": "query_extreme_readings", "arguments": {"direction": "highest", "limit": 10, "min_lat": 36.5, "max_lat": 38.5, "min_lon": 140.0, "max_lon": 141.5}}Example: Find highest readings excluding anomalous device bGeigie-2113:
{"name": "query_extreme_readings", "arguments": {"direction": "highest", "limit": 50, "exclude_devices": ["bGeigie-2113"]}}Example: Find highest readings excluding Cork, Ireland (known anomalous source):
{"name": "query_extreme_readings", "arguments": {"direction": "highest", "limit": 50, "exclude_areas": "[{\"min_lat\":51.8,\"max_lat\":52.0,\"min_lon\":-8.6,\"max_lon\":-8.3}]"}}Example: Exclude both a device and a geographic area:
{"name": "query_extreme_readings", "arguments": {"direction": "highest", "limit": 50, "exclude_devices": ["bGeigie-2113", "bGeigie-456"], "exclude_areas": "[{\"min_lat\":51.8,\"max_lat\":52.0,\"min_lon\":-8.6,\"max_lon\":-8.3}]"}}Each result includes: id, value (µSv/h), location (lat/lon), captured_at, device_id, track_id, and detector.
Get statistics about which users or devices uploaded the most radiation measurement data to Safecast. Supports grouping by user (default) or device. Returns aggregated upload counts, individual marker counts, file sizes, and associated devices/users.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
number | No | 20 | Maximum number of results to return (1 to 100) |
sort_by |
string | No | marker_count | Sort by 'upload_count' (tracks), 'marker_count' (measurements), or 'total_size' (data size) |
group_by |
string | No | user | Group by 'user' (uploader) or 'device' (individual device) |
year |
number | No | Filter by year (e.g., 2024, 2026) |
Example: Get top 10 users by measurement count:
{"name": "top_uploaders", "arguments": {"limit": 10, "group_by": "user", "sort_by": "marker_count"}}Example: Get top devices by measurement count:
{"name": "top_uploaders", "arguments": {"limit": 10, "group_by": "device", "sort_by": "marker_count"}}Example: Get top devices by track count in 2026:
{"name": "top_uploaders", "arguments": {"limit": 20, "group_by": "device", "sort_by": "upload_count", "year": 2026}}When grouped by user, each result includes:
username: Uploader nameupload_count: Number of track files uploadedmarker_count: Total individual measurement pointstotal_size_mb: Total data in megabytesdevices: Array of device names usedprimary_device: Most used device or "Multiple"
When grouped by device, each result includes:
device_name: Device identifier (e.g., "bGeigie-5149")upload_count: Number of track files from this devicemarker_count: Total individual measurement pointstotal_size_mb: Total data in megabytesusers: Array of usernames who used this deviceprimary_user: Main user of this device or "Multiple"
Get usage statistics for all MCP tools including call counts, average duration, and max duration. Powered by DuckDB local logs. No parameters required.
The server includes comprehensive structured logging for monitoring AI tool usage and performance. The logging system is implemented via:
main.go:instrument()wrapper function that wraps each tool handler to capture execution metricsduckdb_client.go:LogQueryAsync()function that asynchronously writes logs to DuckDB'smcp_query_logtable
The logging system:
- Records timestamps, tool names, parameters, result counts, and query durations
- Provides asynchronous logging that doesn't block tool execution
- Stores logs in DuckDB for fast analytics via the
query_analyticstool - Supports optional persistent storage via
DUCKDB_PATHenvironment variable
This enables better observability into how tools are being used and helps identify performance bottlenecks.
Diagnostic tool that returns database connection info, PostgreSQL version, replication status, and upload counts. No parameters required.
Health check. Returns "pong". No parameters required.
The server also exposes a standard REST API on the same port as the MCP endpoints. All endpoints return JSON and are documented interactively via Swagger UI.
| Method | Path | Description |
|---|---|---|
| GET | /api/radiation |
Find measurements near lat/lon |
| GET | /api/area |
Find measurements in a bounding box |
| GET | /api/tracks |
List bGeigie measurement tracks |
| GET | /api/track/{id} |
Get measurements from a track |
| GET | /api/device/{id}/history |
Device history (bGeigie + fixed sensors) |
| GET | /api/sensors |
List active fixed sensors |
| GET | /api/sensor/{id}/current |
Latest reading from a sensor |
| GET | /api/sensor/{id}/history |
Time-series from a sensor |
| GET | /api/spectra |
Browse gamma spectroscopy records |
| GET | /api/spectrum/{marker_id} |
Full spectroscopy channel data |
| GET | /api/stats |
Aggregate radiation statistics |
| GET | /api/extreme |
Find highest/lowest readings with locations |
| GET | /api/info/{topic} |
Reference information (units, safety levels, etc.) |
| GET | /docs/ |
Interactive Swagger UI |
| GET | /docs/doc.json |
Raw OpenAPI spec |
Quick example:
curl "http://localhost:3333/api/radiation?lat=37.42&lon=141.03&radius_m=5000&limit=10"The Swagger docs are generated from // @Summary, // @Param, and // @Router annotations in the rest_*.go files. After changing any annotation, regenerate with:
# Install swag CLI (one-time)
go install github.com/swaggo/swag/cmd/swag@latest
# Regenerate from go/ directory
cd go && swag init -g cmd/mcp-server/rest.go --dir cmd/mcp-server --output cmd/mcp-server/docsThe generated docs/ folder is committed to the repo — the deployed binary does not need the swag CLI.
cd go
go build -o safecast-mcp ./cmd/mcp-server/
./safecast-mcpThe server listens on port 3333 by default. It serves both MCP protocol endpoints and the REST API.
Open http://localhost:3333/docs/ for the interactive Swagger UI.
| Variable | Required | Description |
|---|---|---|
MCP_BASE_URL |
No | Base URL advertised by the SSE transport so clients know where to POST messages back (default: http://localhost:3333). Must not include /mcp — the server appends that automatically. |
DATABASE_URL |
No | PostgreSQL connection string. If not set, uses the Safecast REST API. |
- SSE:
/mcp/sse(GET) and/mcp/message(POST) - Streamable HTTP:
/mcp-http(POST)
The Safecast MCP server includes a web-based AI assistant that provides a friendly, conversational interface to query radiation data. The assistant uses Claude Haiku 4.5 for fast, cost-effective responses while accessing the full MCP toolset.
Try it now: https://simplemap.safecast.org/ (web chat interface)
The assistant can help with a wide variety of radiation-related queries:
Real-time Sensor Data:
- "What's the current radiation level near Tokyo?"
- "Show me all active sensors in Fukushima prefecture"
- "What are the latest readings in Osaka?"
- "Is there a sensor near my coordinates 35.6762, 139.6503?"
Historical Measurements:
- "What are the highest radiation readings ever recorded?"
- "Show me measurements from Fukushima in March 2011"
- "Find radiation data near Chernobyl"
- "What was the average radiation level in Tokyo in 2020?"
Sensor Trends & Time Series:
- "Show me the radiation trend for sensor pointcast:10042 over the past week"
- "How has radiation changed at this location over the past year?"
- "Compare radiation levels between 2012 and 2024"
Spectroscopy Data:
- "Find gamma spectroscopy measurements near Fukushima"
- "Show me spectrum files from track 8eh5m1"
- "What isotopes were detected in this area?"
Aggregate Statistics:
- "How does radiation compare year over year?"
- "What's the global average background radiation?"
- "Show me monthly statistics for 2023"
Educational & Reference:
- "What's the difference between µSv/h and CPM?"
- "What are safe radiation levels?"
- "Explain how radiation detectors work"
- "What's normal background radiation?"
- Conversational Interface: Ask questions in natural language
- Real-time & Historical Data: Access both live sensor readings and archived measurements
- Smart Tool Selection: Automatically uses the right tools based on your query
- Formatted Tables: Sensor data displayed in clean, readable markdown tables
- Clickable Map Links: Device IDs and coordinates link directly to the map
- Download Conversations: Save your chat history as markdown
- AI Disclaimer: All responses include a note about AI-generated content
The web chat server is included in the go/cmd/web-chat/ directory:
cd go/cmd/web-chat
export ANTHROPIC_API_KEY=sk-ant-...
export MCP_URL=http://localhost:3333/mcp-http # optional
export CLAUDE_MODEL=claude-haiku-4-5-20251001 # optional (default: claude-sonnet-4-5)
export PORT=3334 # optional
go run main.goThen open http://localhost:3334 in your browser.
Environment Variables:
ANTHROPIC_API_KEY(required): Your Anthropic API keyMCP_URL(optional): MCP server endpoint (default:http://localhost:3333/mcp-http)CLAUDE_MODEL(optional): Claude model to use (default:claude-sonnet-4-5)PORT(optional): Web server port (default:3334)
Note: The production deployment at simplemap.safecast.org uses Claude Haiku 4.5 for optimal performance and cost efficiency.
The fastest way to add the MCP server using Claude Code CLI:
# Add the production server
claude mcp add --transport http safecast https://simplemap.safecast.org/mcp-http
# Or add with user scope (available in all projects)
claude mcp add --transport http safecast --scope user https://simplemap.safecast.org/mcp-httpManagement commands:
claude mcp list # List all configured servers
claude mcp get safecast # View server details
claude mcp remove safecast # Remove if neededTo connect via the web interface:
- Open claude.ai in your browser
- Go to Settings (bottom-left) > Integrations
- Click Add more > Add custom integration
- Enter a name (e.g. "Safecast") and paste the Streamable HTTP endpoint URL:
https://simplemap.safecast.org/mcp-http - Click Save — the Safecast tools will now be available in your conversations
To connect Claude to a locally running MCP server:
-
Start your local MCP server:
cd go go run ./cmd/mcp-server/ -
The server will start on
http://localhost:3333by default -
Expose your local server to the internet using a tool like ngrok:
ngrok http 3333
-
Take the HTTPS URL provided by ngrok (e.g.,
https://abc123.ngrok.io) and append/mcp-httpto form the endpoint URL (e.g.,https://abc123.ngrok.io/mcp-http) -
Follow steps 2-5 from the "Using the Deployed Server" section above, using your ngrok URL instead
- The MCP server supports both SSE and Streamable HTTP transports
- The Streamable HTTP endpoint (
/mcp-http) is recommended for Claude integration - If using a custom domain or different port, adjust the endpoint URL accordingly
- Make sure your server is accessible from the internet for Claude to connect
Claude / AI client
|
v
MCP Server (Go, mcp-go)
|
+---> Tool Execution with Structured Logging
|
+---> PostgreSQL + PostGIS (primary, if DATABASE_URL set)
| |
| +---> markers table (historical bGeigie data)
| |
| +---> realtime_measurements table (real-time sensor data)
| |
| +---> spectra table (spectroscopy data)
|
+---> DuckDB Analytics Engine
| |
| +---> Local usage statistics
|
+---> Structured Logging System
|
+---> Session tracking
|
+---> Performance metrics
|
+---> Error logging
|
+---> Optional external export
|
+---> simplemap.safecast.org REST API (fallback)
The server uses mcp-go for MCP protocol support. All tools attempt a direct database query first and fall back to the Safecast REST API if no database is configured or the query fails. Real-time data tools specifically query the realtime_measurements table for current sensor readings.
go/cmd/mcp-server/
main.go # Server setup, tool registration, dual transport, instrumentation
api_client.go # Safecast REST API client
db_client.go # PostgreSQL connection pool (pgx)
duckdb_client.go # DuckDB analytics engine, async logging (LogQueryAsync)
reference_data.go # Static radiation reference data
# MCP Tools
tool_query_radiation.go
tool_search_area.go
tool_list_tracks.go
tool_get_track.go
tool_device_history.go
tool_get_spectrum.go
tool_list_spectra.go
tool_radiation_info.go
tool_list_sensors.go
tool_sensor_current.go
tool_sensor_history.go
tool_analytics.go # query_analytics, radiation_stats tools
tool_db_info.go
# REST API
rest.go # REST handler, Swagger UI, theme CSS
rest_radiation.go
rest_area.go
rest_tracks.go
rest_device.go
rest_sensors.go
rest_spectra.go
rest_stats.go
rest_info.go
# Generated Documentation
docs/
docs.go # Generated by swag init
swagger.json
swagger.yaml
# Static Assets
static/
favicon.ico
favicon-16x16.png
favicon-32x32.png
cd go
go run ./cmd/mcp-server/Cross-compile for Linux deployment:
cd go
GOOS=linux GOARCH=amd64 go build -o safecast-mcp ./cmd/mcp-server/Pushing to main automatically builds and deploys to simplemap.safecast.org (the map server) via GitHub Actions (only when Go source files, go.mod/go.sum, or the workflow itself change).
Important: The domain uses CloudFront CDN. SSH deployment must use the server IP (65.108.24.131), not the domain name. See CloudFront Deployment Guide for details.
Note: The MCP server runs on the same server as the database for optimal performance (localhost connections eliminate network latency). See MIGRATION_TO_MAP_SERVER.md for migration details.
- GitHub Action cross-compiles the Go binary
- Uploads it to the map server via rsync
- Creates
.envfile with localhost database connection - Restarts the MCP server via systemd
- Configures nginx proxy for
/mcp-http,/docs/, and/api/endpoints - Runs a health check against the
/mcp-httpendpoint
The GitHub Action requires two repository secrets. Go to Settings > Secrets and variables > Actions and add:
| Secret | Description |
|---|---|
SSH_PRIVATE_KEY |
SSH private key with access to the map server (ed25519 format) |
MAP_SERVER_HOST |
Map server IP address: 65.108.24.131 (use IP, not domain) |
65.108.24.131 for MAP_SERVER_HOST, not simplemap.safecast.org. The domain uses CloudFront CDN which doesn't handle SSH traffic.
To generate a deploy key:
ssh-keygen -t ed25519 -C "github-deploy-mcp@simplemap" -f ~/.ssh/safecast-mcp-deploy -N ""Then add the public key to the map server (use IP address):
ssh-copy-id -i ~/.ssh/safecast-mcp-deploy.pub root@65.108.24.131And paste the contents of ~/.ssh/safecast-mcp-deploy (the private key) as the SSH_PRIVATE_KEY secret in GitHub.
simplemap.safecast.org uses CloudFront CDN, which only handles HTTP/HTTPS traffic. For SSH/SCP deployment, you must use the server IP address (65.108.24.131), not the domain name.
cd go
GOOS=linux GOARCH=amd64 go build -o safecast-mcp ./cmd/mcp-server/
scp safecast-mcp root@65.108.24.131:/root/safecast-mcp-server/
ssh root@65.108.24.131 "systemctl restart safecast-mcp"Why IP address? CloudFront operates on ports 80/443 (HTTP/HTTPS) and doesn't forward SSH traffic (port 22). When you SSH to the domain name, it tries to connect to CloudFront's servers, not the actual server.
Contributions welcome. If changing a tool's interface, please open an issue first. Fork, branch, PR.
MIT
The application handles cross-origin resource sharing through standard HTTP headers managed by the underlying mcp-go library.
Used for mapping radiation values to visual properties like marker opacity and color intensity for better visualization of wide-ranging values.
The application converts Unix timestamps from the API to human-readable formats for display in map popups.
The application adapts to various screen sizes using responsive CSS techniques.
- Endpoints Used:
/mcp/sse(SSE transport)/mcp/message(message transport)/mcp-http(streamable HTTP transport)
- Data Types: JSON responses containing radiation measurements, sensor information, and spectroscopy data
- Request Methods: Both GET (for SSE) and POST (for message passing) are supported
- HTML5/CSS3/ES5+ compatible browsers
- WebSocket support for SSE communication
- Modern JavaScript engine for processing MCP protocol messages
- The application must be served via HTTP(S) server - direct file opening in browsers will not work due to CORS restrictions and API communication requirements.
- Requires a backend server to handle MCP protocol communication with AI clients like Claude.