SGU Brunnar MCP Server
Provides geocoding of free-text addresses via the Google Maps Geocoding API, enabling address-based searches for wells.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SGU Brunnar MCP Serversearch for wells near Centralstationen, Stockholm"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
SGU Brunnar MCP Server
A read-only Model Context Protocol (MCP) server that exposes the Swedish Geological Survey (SGU) Brunnar (wells) open dataset over Streamable HTTP transport.
The server is designed for deployment on Google Cloud Run and can be configured as a remote MCP server in the OpenAI Platform.
Table of Contents
Related MCP server: kartverket-mcp
Architecture
Client (OpenAI / any MCP client)
│ POST /mcp
▼
BearerTokenMiddleware ── 401 if token missing/invalid
│
▼
FastMCP (Streamable HTTP, stateless)
│
▼
MCP Tools ──→ SGUClient ──→ api.sgu.se (OGC API Features)
──→ GeocodingClient ──→ maps.googleapis.com
──→ ExportStore (in-memory, TTL)
──→ InMemoryCache (TTL, bounded LRU)Key modules:
Module | Responsibility |
| Starlette ASGI app, routes, health endpoints |
| Constant-time bearer token middleware |
| Pydantic Settings from environment |
| SGU OGC API client (retry, pagination, dedup) |
| Google Geocoding API client |
| HMAC-signed continuation tokens |
| SGU field definitions, code lists |
| Haversine distance, bbox, SWEREF99TM ↔ WGS84 |
| In-memory CSV / GeoJSON export store |
Available Tools
Tool | Description |
| Geocode a free-text address (Swedish or international) via Google Maps |
| Search wells by location, radius, bbox, municipality, attributes, and depth |
| Retrieve a single well by |
| Get geological layer records for a well |
| Aggregate statistics (depth, capacity, quality) for a bounded area |
| Explain SGU field names, units, and code values in Swedish and English |
| Dataset and API metadata (collections, CRS, extent, license) |
| Create a filtered CSV or GeoJSON export (download URL returned) |
Sample Tool Calls
Search wells near an address (English)
{
"tool": "search_wells",
"arguments": {
"address": "Drottninggatan 1, Stockholm",
"radius_m": 2000,
"page_size": 10
}
}Sök brunnar nära en adress (Svenska)
{
"tool": "search_wells",
"arguments": {
"address": "Drottninggatan 1, Stockholm",
"radius_m": 2000,
"page_size": 10
}
}Retrieve a well by ID
{
"tool": "get_well",
"arguments": {
"brunnsid": 12345,
"include_layers": true
}
}Export to GeoJSON
{
"tool": "create_export",
"arguments": {
"municipality_code": "0180",
"format": "geojson",
"max_records": 500
}
}Local Setup
Prerequisites: Python 3.12+, pip
# Clone the repository
git clone https://github.com/deanmcgowan/mcp-sgu-open-data.git
cd mcp-sgu-open-data
# Create a virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install -e ".[dev]"
# Copy the example config
cp .env.example .env
# Edit .env and fill in MCP_BEARER_TOKEN and GOOGLE_MAPS_API_KEYRunning Locally
# Set environment variables (or use .env with a tool like python-dotenv)
export MCP_BEARER_TOKEN=my-dev-token
export GOOGLE_MAPS_API_KEY=my-google-key
export APP_ENV=development
# Start the server
python -m mcp_sgu.app
# Or with uvicorn directly
uvicorn mcp_sgu.app:create_app --factory --host 0.0.0.0 --port 8080 --reloadVerify the server is running:
curl http://localhost:8080/healthz
curl http://localhost:8080/api/statusRunning Tests
# Run all tests (no external secrets required)
python -m pytest tests/
# With coverage
python -m pytest tests/ --cov=mcp_sgu --cov-report=term-missing
# Linting
ruff check .
ruff format --check .Tests use mocked HTTP responses and do not require real SGU or Google credentials.
Environment Variables
Variable | Required | Default | Description |
| Yes | — | Auth token that MCP clients must present |
| For geocoding | — | Google Maps / Geocoding API key |
| No |
| SGU OGC API base URL |
| No |
|
|
| No |
| Python log level |
| No |
| Max records per MCP tool response |
| No |
| Max records per export |
| No |
| Max concurrent SGU requests |
| No |
| Cache TTL in seconds |
| No |
| Export file TTL before deletion |
| No |
| Server host |
| No |
| Server port (Cloud Run sets this automatically) |
MCP Endpoint
POST https://<SERVICE_URL>/mcp
Authorization: ******
Content-Type: application/jsonThe endpoint uses the Streamable HTTP MCP transport. All requests without a valid `Authorization: ****** header are rejected with HTTP 401.
OpenAI Platform Configuration
Add the server as a remote MCP server in your OpenAI assistant configuration:
{
"type": "mcp",
"server_label": "sgu-brunnar",
"server_url": "https://<SERVICE_URL>/mcp",
"headers": {
"Authorization": "******"
}
}Security note: Never commit real tokens to source control. Use environment variables or secrets management.
Google Geocoding Setup
Go to the Google Cloud Console
Enable the Geocoding API for your project
Create an API key under APIs & Services → Credentials
Restrict the key to the Geocoding API and your server's IP/referrer if possible
Set
GOOGLE_MAPS_API_KEY=<your-key>in your environment or.envfile
The resolve_address tool and address-based search_wells searches require this key.
Without it, address-based searches will return an error.
Docker Build
# Build the image locally
docker build -t sgu-brunnar-mcp:local .
# Run locally with environment variables
docker run --rm \
-e MCP_BEARER_TOKEN=dev-token \
-e GOOGLE_MAPS_API_KEY=your-key \
-e APP_ENV=development \
-p 8080:8080 \
sgu-brunnar-mcp:local
# Verify
curl http://localhost:8080/healthzGoogle Cloud Run Deployment
Prerequisites
Google Cloud SDK (
gcloud) installed and authenticatedProject with billing enabled
Required APIs enabled (see below)
1. Enable Required APIs
gcloud services enable \
cloudbuild.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
secretmanager.googleapis.com2. Create Artifact Registry Repository
gcloud artifacts repositories create sgu-mcp \
--repository-format=docker \
--location=europe-north1 \
--description="SGU MCP server images"3. Build with Cloud Build
gcloud builds submit --config cloudbuild.yaml \
--substitutions _REGION=europe-north1,_REPO=sgu-mcp,_SERVICE=sgu-brunnar-mcp .4. Create Secrets
# Store the bearer token in Secret Manager
echo -n "$(python -c "import secrets; print(secrets.token_urlsafe(32))")" | \
gcloud secrets create MCP_BEARER_TOKEN --data-file=-
# Store the Google Maps key
echo -n "YOUR_GOOGLE_MAPS_API_KEY" | \
gcloud secrets create GOOGLE_MAPS_API_KEY --data-file=-5. Deploy to Cloud Run
PROJECT=$(gcloud config get-value project)
REGION=europe-north1
IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/sgu-mcp/sgu-brunnar-mcp:latest"
gcloud run deploy sgu-brunnar-mcp \
--image="${IMAGE}" \
--region="${REGION}" \
--platform=managed \
--no-allow-unauthenticated \
--port=8080 \
--min-instances=0 \
--max-instances=3 \
--memory=512Mi \
--cpu=1 \
--set-env-vars="APP_ENV=production,LOG_LEVEL=INFO" \
--set-secrets="MCP_BEARER_TOKEN=MCP_BEARER_TOKEN:latest,GOOGLE_MAPS_API_KEY=GOOGLE_MAPS_API_KEY:latest"6. Get the Service URL
gcloud run services describe sgu-brunnar-mcp \
--region=europe-north1 \
--format="value(status.url)"7. Test Health Endpoints
SERVICE_URL=$(gcloud run services describe sgu-brunnar-mcp \
--region=europe-north1 --format="value(status.url)")
curl "${SERVICE_URL}/healthz"
curl "${SERVICE_URL}/readyz"
curl "${SERVICE_URL}/api/status"8. Allow Invocations (if using Cloud Run IAM)
If you use --no-allow-unauthenticated and want to call from outside GCP:
# Add allUsers invoker (use with caution; the bearer token provides app-level auth)
gcloud run services add-iam-policy-binding sgu-brunnar-mcp \
--region=europe-north1 \
--member=allUsers \
--role=roles/run.invokerPaging Behaviour
The server follows OGC API Features next link pagination:
Each tool call returns up to
MAX_INLINE_RESULTSrecords (default 100)If more records exist, a
continuation_tokenis returnedPass the
continuation_tokenin the next call to get the next pageContinuation tokens are HMAC-signed and expire after
EXPORT_TTL_SECONDSThe server enforces
MAX_INLINE_RESULTSglobally; it will not return more than this even if the upstream page is largerPartial-result warnings are included when a limit is reached
Export Behaviour
The create_export tool:
Streams matching records from SGU (up to
MAX_EXPORT_RECORDS)Serialises to CSV (UTF-8 with BOM for Excel compatibility) or GeoJSON
Stores the result in memory with a TTL of
EXPORT_TTL_SECONDSReturns a download URL:
GET /api/exports/{export_id}(requires same bearer token)Exports are automatically deleted after the TTL expires
Cloud Run restart note: In-memory exports are lost when the Cloud Run instance restarts. For production, replace
ExportStorewith a Google Cloud Storage backend.
Security Considerations
The
/mcpendpoint and/api/exportsrequireAuthorization: ******Token comparison uses
hmac.compare_digestto prevent timing attacksTokens are never logged
The server only makes outbound requests to the configured SGU URL and Google Maps API
No arbitrary upstream URLs can be injected by callers
Stack traces are not exposed in production error responses
No secrets are stored in the source code; all secrets come from environment variables
Data-Quality Limitations
Position quality: Well coordinates vary in accuracy (GPS, map digitising, address-level). Always check
positionskvalitetskodandpositionskvalitet.Capacity:
kapacitetis the reported capacity at the time of drilling, not necessarily the current sustainable yield.Depth:
borrdjupandtotaldjupmay differ; some wells have missing depth values.Groundwater level:
vattennivamay reflect conditions at the time of drilling only.Address geocoding: The returned point is a geocoded address centroid, not a cadastral parcel boundary.
Date precision: Drilling dates may be partial (year only) or absent.
Source language: All source field names and values are in Swedish. Use
explain_fieldfor translations and definitions.
Future Extension to Other SGU Datasets
The architecture is designed to support additional SGU datasets:
Add a new collection identifier in
field_defs.pyor a new field definitions moduleCreate collection-specific tools in
src/mcp_sgu/tools/following the same patternRegister the new tools in
app.pyUpdate
SGU_BASE_URLor add a newSGU_*_BASE_URLconfiguration variable
Candidate future datasets from SGU OGC APIs:
jordarter— Quaternary deposits / soil typesberggrundsgeologi— Bedrock geologygrundvatten— Groundwater monitoring stationsgeofysik— Geophysical survey data
Known Limitations
Live SGU API not verified in this environment: The SGU API (
api.sgu.se) was not reachable from the build environment. Field names and API behaviour are based on the OGC API Features standard and SGU documentation. Field names should be verified against the live API before production use.In-memory state: Cache and exports are stored in process memory; lost on restart.
No persistent storage: Export files are not stored in Cloud Storage in this version.
Single region: Configured for
europe-north1(Stockholm); other regions require config changes.Stateless MCP: Uses
stateless_http=Truewhich means no server-side session state is maintained between MCP requests.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/deanmcgowan/mcp-sgu-open-data'
If you have feedback or need assistance with the MCP directory API, please join our Discord server