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.
Files changed (39) hide show
  1. geosphere_mcp_server-0.1.0/.github/dependabot.yml +21 -0
  2. geosphere_mcp_server-0.1.0/.github/workflows/auto-release.yml +48 -0
  3. geosphere_mcp_server-0.1.0/.github/workflows/release.yml +90 -0
  4. geosphere_mcp_server-0.1.0/.github/workflows/validate.yml +49 -0
  5. geosphere_mcp_server-0.1.0/.gitignore +22 -0
  6. geosphere_mcp_server-0.1.0/CHANGELOG.md +14 -0
  7. geosphere_mcp_server-0.1.0/CLAUDE.md +62 -0
  8. geosphere_mcp_server-0.1.0/LICENSE +21 -0
  9. geosphere_mcp_server-0.1.0/PKG-INFO +208 -0
  10. geosphere_mcp_server-0.1.0/README.md +182 -0
  11. geosphere_mcp_server-0.1.0/docs/README.md +72 -0
  12. geosphere_mcp_server-0.1.0/docs/domain/OVERVIEW.md +118 -0
  13. geosphere_mcp_server-0.1.0/docs/domain/README.md +6 -0
  14. geosphere_mcp_server-0.1.0/docs/tech/ARCHITECTURE.md +123 -0
  15. geosphere_mcp_server-0.1.0/docs/tech/CONVENTIONS.md +95 -0
  16. geosphere_mcp_server-0.1.0/docs/tech/README.md +9 -0
  17. geosphere_mcp_server-0.1.0/docs/tech/TECH-STACK.md +77 -0
  18. geosphere_mcp_server-0.1.0/docs/tech/TESTING.md +97 -0
  19. geosphere_mcp_server-0.1.0/pyproject.toml +82 -0
  20. geosphere_mcp_server-0.1.0/server.json +22 -0
  21. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/__init__.py +14 -0
  22. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/_version.py +24 -0
  23. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/condition.py +162 -0
  24. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/const.py +287 -0
  25. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/format.py +501 -0
  26. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/geosphere_api.py +167 -0
  27. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/openmeteo_api.py +135 -0
  28. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/py.typed +0 -0
  29. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/server.py +235 -0
  30. geosphere_mcp_server-0.1.0/src/geosphere_mcp_server/weather.py +474 -0
  31. geosphere_mcp_server-0.1.0/tests/__init__.py +1 -0
  32. geosphere_mcp_server-0.1.0/tests/test_condition.py +184 -0
  33. geosphere_mcp_server-0.1.0/tests/test_format.py +290 -0
  34. geosphere_mcp_server-0.1.0/tests/test_geosphere_api.py +259 -0
  35. geosphere_mcp_server-0.1.0/tests/test_integration.py +94 -0
  36. geosphere_mcp_server-0.1.0/tests/test_openmeteo_api.py +233 -0
  37. geosphere_mcp_server-0.1.0/tests/test_server.py +277 -0
  38. geosphere_mcp_server-0.1.0/tests/test_weather.py +444 -0
  39. 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,22 @@
1
+ # Python
2
+ _version.py
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.pyo
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .pytest_cache/
12
+
13
+ # Environment
14
+ .venv/
15
+
16
+ # macOS
17
+ .DS_Store
18
+ ._*
19
+
20
+ # IDE
21
+ .vscode/
22
+ .idea/
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/geosphere-mcp-server.svg)](https://pypi.org/project/geosphere-mcp-server/)
32
+ [![Python](https://img.shields.io/pypi/pyversions/geosphere-mcp-server.svg)](https://pypi.org/project/geosphere-mcp-server/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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