geosphere-mcp-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geosphere_mcp_server-0.1.0/.github/dependabot.yml +21 -0
- geosphere_mcp_server-0.1.0/.github/workflows/auto-release.yml +48 -0
- geosphere_mcp_server-0.1.0/.github/workflows/release.yml +90 -0
- geosphere_mcp_server-0.1.0/.github/workflows/validate.yml +49 -0
- geosphere_mcp_server-0.1.0/.gitignore +22 -0
- geosphere_mcp_server-0.1.0/CHANGELOG.md +14 -0
- geosphere_mcp_server-0.1.0/CLAUDE.md +62 -0
- geosphere_mcp_server-0.1.0/LICENSE +21 -0
- geosphere_mcp_server-0.1.0/PKG-INFO +208 -0
- geosphere_mcp_server-0.1.0/README.md +182 -0
- geosphere_mcp_server-0.1.0/docs/README.md +72 -0
- geosphere_mcp_server-0.1.0/docs/domain/OVERVIEW.md +118 -0
- geosphere_mcp_server-0.1.0/docs/domain/README.md +6 -0
- geosphere_mcp_server-0.1.0/docs/tech/ARCHITECTURE.md +123 -0
- geosphere_mcp_server-0.1.0/docs/tech/CONVENTIONS.md +95 -0
- geosphere_mcp_server-0.1.0/docs/tech/README.md +9 -0
- geosphere_mcp_server-0.1.0/docs/tech/TECH-STACK.md +77 -0
- geosphere_mcp_server-0.1.0/docs/tech/TESTING.md +97 -0
- geosphere_mcp_server-0.1.0/pyproject.toml +82 -0
- geosphere_mcp_server-0.1.0/server.json +22 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/__init__.py +14 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/_version.py +24 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/condition.py +162 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/const.py +287 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/format.py +501 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/geosphere_api.py +167 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/openmeteo_api.py +135 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/py.typed +0 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/server.py +235 -0
- geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/weather.py +474 -0
- geosphere_mcp_server-0.1.0/tests/__init__.py +1 -0
- geosphere_mcp_server-0.1.0/tests/test_condition.py +184 -0
- geosphere_mcp_server-0.1.0/tests/test_format.py +290 -0
- geosphere_mcp_server-0.1.0/tests/test_geosphere_api.py +259 -0
- geosphere_mcp_server-0.1.0/tests/test_integration.py +94 -0
- geosphere_mcp_server-0.1.0/tests/test_openmeteo_api.py +233 -0
- geosphere_mcp_server-0.1.0/tests/test_server.py +277 -0
- geosphere_mcp_server-0.1.0/tests/test_weather.py +444 -0
- geosphere_mcp_server-0.1.0/uv.lock +1319 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
# Python dependencies (managed via uv / uv.lock)
|
|
4
|
+
- package-ecosystem: "uv"
|
|
5
|
+
directory: "/"
|
|
6
|
+
schedule:
|
|
7
|
+
interval: "weekly"
|
|
8
|
+
groups:
|
|
9
|
+
python-dependencies:
|
|
10
|
+
patterns:
|
|
11
|
+
- "*"
|
|
12
|
+
|
|
13
|
+
# GitHub Actions used in workflows
|
|
14
|
+
- package-ecosystem: "github-actions"
|
|
15
|
+
directory: "/"
|
|
16
|
+
schedule:
|
|
17
|
+
interval: "weekly"
|
|
18
|
+
groups:
|
|
19
|
+
github-actions:
|
|
20
|
+
patterns:
|
|
21
|
+
- "*"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Auto Release (Dependabot)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
workflow_dispatch: {} # manual "cut the next patch release" trigger
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
tag:
|
|
10
|
+
# Runs for a manual dispatch, or for a merged Dependabot PR from the uv
|
|
11
|
+
# (Python dependency) ecosystem โ those change the published package.
|
|
12
|
+
# github-actions bumps do not, so they merge without cutting a release.
|
|
13
|
+
if: >-
|
|
14
|
+
github.event_name == 'workflow_dispatch' ||
|
|
15
|
+
(github.event.pull_request.merged == true &&
|
|
16
|
+
github.event.pull_request.user.login == 'dependabot[bot]' &&
|
|
17
|
+
startsWith(github.event.pull_request.head.ref, 'dependabot/uv/'))
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Generate GitHub App token
|
|
21
|
+
id: app-token
|
|
22
|
+
uses: actions/create-github-app-token@v3
|
|
23
|
+
with:
|
|
24
|
+
app-id: ${{ secrets.GH_ACTION_APP_ID }}
|
|
25
|
+
private-key: ${{ secrets.GH_ACTION_APP_PRIVATE_KEY }}
|
|
26
|
+
- uses: actions/checkout@v7
|
|
27
|
+
with:
|
|
28
|
+
ref: main
|
|
29
|
+
fetch-depth: 0 # need all tags + the merge commit
|
|
30
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
31
|
+
- name: Compute next patch version
|
|
32
|
+
id: ver
|
|
33
|
+
run: |
|
|
34
|
+
latest=$(git tag -l 'v*' --sort=-v:refname | head -n1)
|
|
35
|
+
latest=${latest:-v0.0.0}
|
|
36
|
+
IFS=. read -r major minor patch <<< "${latest#v}"
|
|
37
|
+
next="v${major}.${minor}.$((patch + 1))"
|
|
38
|
+
echo "next=$next" >> "$GITHUB_OUTPUT"
|
|
39
|
+
echo "Next release: $next (from ${latest})"
|
|
40
|
+
- name: Create and push tag
|
|
41
|
+
env:
|
|
42
|
+
NEXT: ${{ steps.ver.outputs.next }}
|
|
43
|
+
REASON: ${{ github.event_name == 'workflow_dispatch' && 'manual dispatch' || format('Dependabot PR {0}', github.event.pull_request.number) }}
|
|
44
|
+
run: |
|
|
45
|
+
git config user.name "github-actions[bot]"
|
|
46
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
47
|
+
git tag -a "$NEXT" -m "Automated release $NEXT ($REASON)"
|
|
48
|
+
git push origin "$NEXT"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Build distributions
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0 # hatch-vcs needs full history + tags to derive the version
|
|
15
|
+
- uses: astral-sh/setup-uv@v9.0.0
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- name: Build sdist and wheel
|
|
19
|
+
run: uv build
|
|
20
|
+
- name: Verify tag matches built version
|
|
21
|
+
run: |
|
|
22
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
23
|
+
BUILT=$(ls dist/geosphere_mcp_server-*.tar.gz | sed -E 's|.*/geosphere_mcp_server-(.*)\.tar\.gz|\1|')
|
|
24
|
+
echo "tag=$TAG_VERSION built=$BUILT"
|
|
25
|
+
if [ "$TAG_VERSION" != "$BUILT" ]; then
|
|
26
|
+
echo "::error::Tag $TAG_VERSION does not match built version $BUILT"
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
- uses: actions/upload-artifact@v7
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
pypi-publish:
|
|
35
|
+
name: Publish to PyPI
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment: pypi
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write # OIDC for PyPI Trusted Publishing
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/download-artifact@v8
|
|
43
|
+
with:
|
|
44
|
+
name: dist
|
|
45
|
+
path: dist/
|
|
46
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
47
|
+
|
|
48
|
+
github-release:
|
|
49
|
+
name: Create GitHub Release
|
|
50
|
+
needs: pypi-publish
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
permissions:
|
|
53
|
+
contents: write
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/download-artifact@v8
|
|
56
|
+
with:
|
|
57
|
+
name: dist
|
|
58
|
+
path: dist/
|
|
59
|
+
- name: Create release
|
|
60
|
+
env:
|
|
61
|
+
GH_TOKEN: ${{ github.token }}
|
|
62
|
+
run: |
|
|
63
|
+
gh release create "$GITHUB_REF_NAME" \
|
|
64
|
+
--repo "$GITHUB_REPOSITORY" \
|
|
65
|
+
--title "$GITHUB_REF_NAME" \
|
|
66
|
+
--generate-notes \
|
|
67
|
+
dist/*
|
|
68
|
+
|
|
69
|
+
mcp-registry:
|
|
70
|
+
name: Publish to MCP Registry
|
|
71
|
+
needs: pypi-publish
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
id-token: write # OIDC proves ownership of the io.github.<owner> namespace
|
|
75
|
+
contents: read
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v7
|
|
78
|
+
- name: Set server.json version from tag
|
|
79
|
+
run: |
|
|
80
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
81
|
+
jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' \
|
|
82
|
+
server.json > server.tmp && mv server.tmp server.json
|
|
83
|
+
cat server.json
|
|
84
|
+
- name: Install mcp-publisher
|
|
85
|
+
run: |
|
|
86
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
87
|
+
- name: Authenticate to MCP Registry
|
|
88
|
+
run: ./mcp-publisher login github-oidc
|
|
89
|
+
- name: Publish to MCP Registry
|
|
90
|
+
run: ./mcp-publisher publish
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Validate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
ruff:
|
|
10
|
+
name: Ruff (lint + format)
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v7
|
|
14
|
+
- uses: actions/setup-python@v7
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: pip install ruff
|
|
18
|
+
- run: ruff check .
|
|
19
|
+
- run: ruff format . --check
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
name: Tests
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v7
|
|
26
|
+
with:
|
|
27
|
+
fetch-depth: 0 # hatch-vcs derives the version from git history + tags
|
|
28
|
+
- uses: actions/setup-python@v7
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- run: pip install hatchling && pip install -e . && pip install pytest pytest-asyncio
|
|
32
|
+
- run: pytest tests/ -v -m "not integration"
|
|
33
|
+
|
|
34
|
+
gate:
|
|
35
|
+
name: gate
|
|
36
|
+
needs: [ruff, test]
|
|
37
|
+
if: always()
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- name: Check validation results
|
|
41
|
+
run: |
|
|
42
|
+
if [[ "${{ needs.ruff.result }}" == "success" && \
|
|
43
|
+
"${{ needs.test.result }}" == "success" ]]; then
|
|
44
|
+
echo "All checks passed"
|
|
45
|
+
exit 0
|
|
46
|
+
else
|
|
47
|
+
echo "One or more checks failed"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-07-22
|
|
4
|
+
|
|
5
|
+
- Initial release
|
|
6
|
+
- MCP server with 3 tools: `get_current_weather`, `get_hourly_forecast`, `get_daily_forecast`
|
|
7
|
+
- High-resolution weather for Austria and the Alps from the GeoSphere Austria Dataset API: AROME forecast (~60 h hourly), INCA analysis, INCA nowcast, and C-LAEF ensemble precipitation probability
|
|
8
|
+
- Automatic worldwide fallback to Open-Meteo when a point is outside GeoSphere coverage (current + hourly tools); daily forecast (1-16 days) always via Open-Meteo
|
|
9
|
+
- Location input is plain decimal `latitude`/`longitude` (the calling LLM geocodes place names)
|
|
10
|
+
- HA-style condition vocabulary derived physically on the GeoSphere path and mapped from WMO weather codes on the Open-Meteo path
|
|
11
|
+
- Compact emoji-markdown output (metric units), each response naming its data source
|
|
12
|
+
- First public distribution: published to [PyPI](https://pypi.org/project/geosphere-mcp-server/) (installable via `uvx geosphere-mcp-server`) and listed in the [official MCP Registry](https://registry.modelcontextprotocol.io)
|
|
13
|
+
- Tag-driven release pipeline: PyPI Trusted Publishing (OIDC), GitHub Release, and MCP Registry publish on `v*` tags
|
|
14
|
+
- Version single-sourced from git tags via `hatch-vcs`; PyPI metadata (authors, URLs, classifiers, keywords) and a `py.typed` marker
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# GeoSphere MCP Server
|
|
2
|
+
> MCP server for weather forecasts, usable by LLMs via the Model Context Protocol. High-resolution GeoSphere Austria data (AROME/INCA/C-LAEF) for Austria and the Alps, Open-Meteo worldwide.
|
|
3
|
+
|
|
4
|
+
## Quick Reference
|
|
5
|
+
- **Lint**: `ruff check .`
|
|
6
|
+
- **Format**: `ruff format .`
|
|
7
|
+
- **Test (unit)**: `pytest tests/ -v -m "not integration"`
|
|
8
|
+
- **Test (integration, live APIs)**: `pytest tests/ -v -m integration`
|
|
9
|
+
- **Run server**: `uvx --from . geosphere-mcp-server` or `python -m geosphere_mcp_server.server`
|
|
10
|
+
- **Validate (CI)**: Ruff + pytest unit tests (all must pass via `gate` job)
|
|
11
|
+
|
|
12
|
+
## Architecture Overview
|
|
13
|
+
FastMCP presentation layer over pure async API clients and pure derivation/rendering helpers. Purely functional -- no classes outside the `FastMCP` instance. All code in `src/geosphere_mcp_server/`.
|
|
14
|
+
|
|
15
|
+
- `server.py` -- FastMCP tool registration (3 tools), session lifecycle, stdio entry point, sentinel error lines
|
|
16
|
+
- `weather.py` -- merge chain, hourly assembly, POP mapping, unit conversions (orchestration)
|
|
17
|
+
- `geosphere_api.py` -- pure async client for the GeoSphere Dataset API
|
|
18
|
+
- `openmeteo_api.py` -- pure async client for Open-Meteo (current/hourly/daily)
|
|
19
|
+
- `condition.py` -- pure condition derivation (ported from ha-geosphere-next)
|
|
20
|
+
- `format.py` -- emoji-markdown renderers for the three tools
|
|
21
|
+
- `const.py` -- all constants (URLs, resource IDs, parameter lists, thresholds, WMO->condition map)
|
|
22
|
+
|
|
23
|
+
Data flow: MCP tool call -> `server.py` handler -> `weather.py` orchestration -> `geosphere_api`/`openmeteo_api` -> live API -> derived via `condition.py` -> rendered by `format.py` -> markdown string.
|
|
24
|
+
|
|
25
|
+
See [Architecture](docs/tech/ARCHITECTURE.md) for module boundaries and data flow detail.
|
|
26
|
+
|
|
27
|
+
## Tech Stack
|
|
28
|
+
- Python 3.12+, `from __future__ import annotations` in every file
|
|
29
|
+
- `mcp[cli]` (FastMCP) for MCP server framework
|
|
30
|
+
- `aiohttp` for async HTTP, `astral` for day/night
|
|
31
|
+
- `ruff` for linting/formatting, `pytest` + `pytest-asyncio` for testing
|
|
32
|
+
- `uv` for environment management, `hatchling` + `hatch-vcs` build backend
|
|
33
|
+
- GitHub Actions CI (validate on push/PR)
|
|
34
|
+
|
|
35
|
+
See [Tech Stack](docs/tech/TECH-STACK.md) for full detail.
|
|
36
|
+
|
|
37
|
+
## Core Conventions
|
|
38
|
+
- Module-level async functions -- no client classes
|
|
39
|
+
- Constants in `const.py` only -- no inline magic values
|
|
40
|
+
- Logger: `_LOGGER = logging.getLogger(__name__)` with `%s` formatting (not f-strings)
|
|
41
|
+
- Import order: `__future__` -> stdlib -> third-party -> local
|
|
42
|
+
- API modules raise typed exceptions; the server layer catches them and returns a short markdown error line -- tools never raise
|
|
43
|
+
- GeoSphere out-of-domain is not an error: it triggers the transparent Open-Meteo fallback
|
|
44
|
+
|
|
45
|
+
See [Conventions](docs/tech/CONVENTIONS.md) for naming tables and full rules.
|
|
46
|
+
|
|
47
|
+
## Business Domain
|
|
48
|
+
Weather MCP gateway. Three tools -- `get_current_weather`, `get_hourly_forecast`, `get_daily_forecast` -- matching the OWM server surface they replace. GeoSphere Austria's gridded datasets (AROME ~60 h, INCA analysis/nowcast, C-LAEF ensemble) drive current + hourly for Austria/the Alps; points outside coverage fall back to Open-Meteo automatically, and daily is always Open-Meteo (worldwide, 1-16 days). A shared HA-style condition vocabulary is derived physically on the GeoSphere path and mapped from WMO codes on the Open-Meteo path.
|
|
49
|
+
|
|
50
|
+
See [Domain Overview](docs/domain/OVERVIEW.md) for datasets, coverage, condition derivation, and attribution.
|
|
51
|
+
|
|
52
|
+
## Structural Risks
|
|
53
|
+
- GeoSphere dataset resource IDs are versioned -- a catalog rotation breaks the server until IDs in `const.py` are bumped
|
|
54
|
+
- No GeoSphere forecast beyond ~60 h -- longer horizons must go through Open-Meteo
|
|
55
|
+
- Per-call `aiohttp.ClientSession` creation -- no connection pooling
|
|
56
|
+
- `condition.py` duplicates HA `ATTR_CONDITION_*` string literals (to stay import-free) -- could drift if HA renames a condition
|
|
57
|
+
- Rate limits (GeoSphere 5 req/s, 240 req/h) shared across all callers -- no server-side quota tracking
|
|
58
|
+
|
|
59
|
+
## Detailed Guides
|
|
60
|
+
- [Technical Context](docs/tech/README.md) -- architecture, tech stack, conventions, testing
|
|
61
|
+
- [Domain Context](docs/domain/README.md) -- datasets, coverage, condition derivation, integrations
|
|
62
|
+
- [Documentation Guide](docs/README.md) -- how to maintain these docs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stefan Lettmayer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geosphere-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for weather forecasts: GeoSphere Austria high-resolution data (AROME/INCA/C-LAEF) for Austria and the Alps, Open-Meteo worldwide.
|
|
5
|
+
Project-URL: Homepage, https://github.com/slettmayer/geosphere-mcp-server
|
|
6
|
+
Project-URL: Repository, https://github.com/slettmayer/geosphere-mcp-server
|
|
7
|
+
Project-URL: Issues, https://github.com/slettmayer/geosphere-mcp-server/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/slettmayer/geosphere-mcp-server/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Stefan Lettmayer
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: austria,forecast,geosphere,home-assistant,mcp,open-meteo,weather
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: aiohttp>=3.0.0
|
|
23
|
+
Requires-Dist: astral>=3.2
|
|
24
|
+
Requires-Dist: mcp[cli]>=1.28.1
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# geosphere-mcp-server
|
|
28
|
+
|
|
29
|
+
<!-- mcp-name: io.github.slettmayer/geosphere-mcp-server -->
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/geosphere-mcp-server/)
|
|
32
|
+
[](https://pypi.org/project/geosphere-mcp-server/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
MCP server for weather: current conditions, hourly forecasts, and multi-day outlooks for **any location worldwide**, via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
36
|
+
|
|
37
|
+
In **Austria and the Alpine region** it serves high-resolution [GeoSphere Austria](https://www.geosphere.at) data โ the AROME numerical forecast, the INCA analysis/nowcast, and the C-LAEF ensemble for precipitation probability. **Everywhere else** it falls back automatically to [Open-Meteo](https://open-meteo.com), so it is a drop-in worldwide weather source. Every response states which source produced it.
|
|
38
|
+
|
|
39
|
+
Output is compact emoji-markdown with metric units โ built for smart-home and voice-assistant LLM pipelines where a terse, readable answer beats a JSON blob. Weather conditions are derived from physical parameters and reported with the Home Assistant condition vocabulary (`sunny`, `partlycloudy`, `rainy`, `snowy`, โฆ).
|
|
40
|
+
|
|
41
|
+
## Coverage
|
|
42
|
+
|
|
43
|
+
| Where | `get_current_weather` | `get_hourly_forecast` | `get_daily_forecast` |
|
|
44
|
+
|-------|-----------------------|-----------------------|----------------------|
|
|
45
|
+
| Austria | GeoSphere INCA + nowcast + AROME | GeoSphere AROME (โค60 h) + C-LAEF probability | Open-Meteo (1โ16 days) |
|
|
46
|
+
| Alps (non-AT) | GeoSphere AROME only | GeoSphere AROME (โค60 h) + C-LAEF probability | Open-Meteo (1โ16 days) |
|
|
47
|
+
| Rest of world | Open-Meteo | Open-Meteo (โค48 h) | Open-Meteo (1โ16 days) |
|
|
48
|
+
|
|
49
|
+
Coverage is detected automatically: the server tries GeoSphere first and falls back to Open-Meteo when the point is outside the AROME grid โ no bounding box to configure. The daily forecast always uses Open-Meteo (GeoSphere publishes no forecasts beyond ~60 h).
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
Pass **decimal latitude/longitude** to every tool. There is no geocoder in the server โ the calling LLM geocodes city names to coordinates itself.
|
|
54
|
+
|
|
55
|
+
### Claude Desktop
|
|
56
|
+
|
|
57
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"geosphere": {
|
|
63
|
+
"command": "uvx",
|
|
64
|
+
"args": ["geosphere-mcp-server"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Claude Code
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
claude mcp add geosphere -- uvx geosphere-mcp-server
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### From source (development)
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"geosphere": {
|
|
82
|
+
"command": "uvx",
|
|
83
|
+
"args": ["--from", "/path/to/geosphere-mcp-server", "geosphere-mcp-server"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Home Assistant
|
|
90
|
+
|
|
91
|
+
It is a standard stdio MCP server, so it runs anywhere an stdio MCP server can be hosted โ including alongside Home Assistant's Assist pipeline (register it the same way as any other stdio MCP server). Give the voice agent the coordinates of the places it should answer for, or let it geocode names.
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
### `get_current_weather`
|
|
96
|
+
|
|
97
|
+
Current conditions for a point. GeoSphere (INCA/AROME) inside coverage, Open-Meteo elsewhere.
|
|
98
|
+
|
|
99
|
+
| Parameter | Type | Default | Description |
|
|
100
|
+
|-----------|------|---------|-------------|
|
|
101
|
+
| `latitude` | float | required | Decimal latitude (e.g. `48.2208`) |
|
|
102
|
+
| `longitude` | float | required | Decimal longitude (e.g. `16.3738`) |
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
# Current Weather at 48.2208, 16.3738
|
|
106
|
+
|
|
107
|
+
๐ก๏ธ Temperature: 24.7ยฐC (feels like 24.2ยฐC)
|
|
108
|
+
๐ค๏ธ Condition: partlycloudy
|
|
109
|
+
๐ง Humidity: 52%
|
|
110
|
+
๐จ Wind: 2.9 m/s from 135ยฐ (gusts 7 m/s)
|
|
111
|
+
๐ง๏ธ Precipitation (last hour): 0 mm
|
|
112
|
+
๐ Pressure: 1016 hPa
|
|
113
|
+
โ๏ธ Cloud cover: 30%
|
|
114
|
+
๐ Timezone: Europe/Vienna (CEST)
|
|
115
|
+
๐ก Source: GeoSphere (INCA + nowcast + AROME) โ observed 15:20
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Outside GeoSphere coverage the same tool answers from Open-Meteo (sunrise/sunset and the point's own timezone included):
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
# Current Weather at 38.7223, -9.1393
|
|
122
|
+
|
|
123
|
+
๐ก๏ธ Temperature: 26.1ยฐC (feels like 27.0ยฐC)
|
|
124
|
+
๐ค๏ธ Condition: cloudy
|
|
125
|
+
๐ง Humidity: 58%
|
|
126
|
+
๐จ Wind: 4.5 m/s from 315ยฐ (gusts 9 m/s)
|
|
127
|
+
๐ Pressure: 1014 hPa
|
|
128
|
+
โ๏ธ Cloud cover: 90%
|
|
129
|
+
๐
Sunrise: 06:24
|
|
130
|
+
๐ Sunset: 20:52
|
|
131
|
+
๐ Timezone: Europe/Lisbon (WEST)
|
|
132
|
+
๐ก Source: Open-Meteo โ observed 14:00
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `get_hourly_forecast`
|
|
136
|
+
|
|
137
|
+
Hour-by-hour forecast. GeoSphere AROME (with C-LAEF precipitation probability) up to ~60 h inside coverage; Open-Meteo up to 48 h elsewhere.
|
|
138
|
+
|
|
139
|
+
| Parameter | Type | Default | Description |
|
|
140
|
+
|-----------|------|---------|-------------|
|
|
141
|
+
| `latitude` | float | required | Decimal latitude (e.g. `48.2208`) |
|
|
142
|
+
| `longitude` | float | required | Decimal longitude (e.g. `16.3738`) |
|
|
143
|
+
| `hours` | int | 24 | Forecast hours (clamped to 1โ60 on GeoSphere, 1โ48 on the fallback) |
|
|
144
|
+
| `start` | string | now | Optional ISO 8601 start (e.g. `2026-07-22T15:00`); forecast begins at/after this instant |
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
# 3-Hour Forecast for 48.2208, 16.3738
|
|
148
|
+
|
|
149
|
+
AROME model, reference 2026-07-22 12:00 CEST ยท Source: GeoSphere (AROME + C-LAEF ensemble)
|
|
150
|
+
|
|
151
|
+
15:00: 24.7ยฐC โ partlycloudy, wind 3 m/s
|
|
152
|
+
16:00: 23.9ยฐC โ rainy, 1.2 mm (70% chance), wind 4 m/s
|
|
153
|
+
17:00: 22.5ยฐC โ cloudy, wind 3 m/s
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Requesting more hours than the AROME horizon provides appends a note suggesting `get_daily_forecast` for days further ahead. Dry hours omit the precipitation and probability parts.
|
|
157
|
+
|
|
158
|
+
### `get_daily_forecast`
|
|
159
|
+
|
|
160
|
+
Multi-day outlook, always from Open-Meteo (worldwide, including Austria).
|
|
161
|
+
|
|
162
|
+
| Parameter | Type | Default | Description |
|
|
163
|
+
|-----------|------|---------|-------------|
|
|
164
|
+
| `latitude` | float | required | Decimal latitude (e.g. `48.2208`) |
|
|
165
|
+
| `longitude` | float | required | Decimal longitude (e.g. `16.3738`) |
|
|
166
|
+
| `days` | int | 7 | Forecast days (clamped to 1โ16) |
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
# 3-Day Forecast for 48.2208, 16.3738
|
|
170
|
+
|
|
171
|
+
Source: Open-Meteo (Europe/Vienna)
|
|
172
|
+
|
|
173
|
+
Wed 2026-07-22: 16โ27ยฐC โ partlycloudy, wind up to 9 m/s
|
|
174
|
+
Thu 2026-07-23: 15โ22ยฐC โ rainy, 4.2 mm (80% chance), wind up to 15 m/s
|
|
175
|
+
Fri 2026-07-24: 14โ26ยฐC โ sunny, wind up to 8 m/s
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Data sources & attribution
|
|
179
|
+
|
|
180
|
+
- **GeoSphere Austria Dataset API** โ AROME forecast, INCA analysis/nowcast, C-LAEF ensemble. Data licensed under [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). ยฉ GeoSphere Austria.
|
|
181
|
+
- **Open-Meteo** โ worldwide forecast API. Data licensed under [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). ยฉ Open-Meteo.
|
|
182
|
+
|
|
183
|
+
Both APIs are keyless and intended for **non-commercial** use. When you redistribute their data, keep the attribution.
|
|
184
|
+
|
|
185
|
+
## Rate limits
|
|
186
|
+
|
|
187
|
+
The GeoSphere Dataset API allows **5 requests/second and 240 requests/hour**. Each current/hourly call issues a small burst of concurrent requests; on an HTTP 429 the server retries once (when the API asks for a short wait) and otherwise returns a rate-limit notice โ `get_daily_forecast` keeps working through Open-Meteo in that case. Open-Meteo has its own generous free-tier limits.
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Install dependencies (creates .venv from the locked versions)
|
|
193
|
+
uv sync
|
|
194
|
+
|
|
195
|
+
# Lint & format
|
|
196
|
+
ruff check .
|
|
197
|
+
ruff format .
|
|
198
|
+
|
|
199
|
+
# Run unit tests
|
|
200
|
+
pytest -m "not integration"
|
|
201
|
+
|
|
202
|
+
# Run integration tests (hits the live GeoSphere + Open-Meteo APIs)
|
|
203
|
+
pytest -m integration
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT
|