oebb-mcp-server 0.1.1__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.
- oebb_mcp_server-0.1.1/.github/dependabot.yml +21 -0
- oebb_mcp_server-0.1.1/.github/workflows/release.yml +90 -0
- oebb_mcp_server-0.1.1/.github/workflows/validate.yml +49 -0
- oebb_mcp_server-0.1.1/.gitignore +22 -0
- oebb_mcp_server-0.1.1/CHANGELOG.md +13 -0
- oebb_mcp_server-0.1.1/CLAUDE.md +58 -0
- oebb_mcp_server-0.1.1/LICENSE +21 -0
- oebb_mcp_server-0.1.1/PKG-INFO +137 -0
- oebb_mcp_server-0.1.1/README.md +112 -0
- oebb_mcp_server-0.1.1/docs/README.md +72 -0
- oebb_mcp_server-0.1.1/docs/domain/OVERVIEW.md +99 -0
- oebb_mcp_server-0.1.1/docs/domain/README.md +6 -0
- oebb_mcp_server-0.1.1/docs/tech/ARCHITECTURE.md +103 -0
- oebb_mcp_server-0.1.1/docs/tech/CONVENTIONS.md +92 -0
- oebb_mcp_server-0.1.1/docs/tech/README.md +9 -0
- oebb_mcp_server-0.1.1/docs/tech/TECH-STACK.md +73 -0
- oebb_mcp_server-0.1.1/docs/tech/TESTING.md +85 -0
- oebb_mcp_server-0.1.1/pyproject.toml +82 -0
- oebb_mcp_server-0.1.1/server.json +22 -0
- oebb_mcp_server-0.1.1/src/oebb_mcp_server/__init__.py +10 -0
- oebb_mcp_server-0.1.1/src/oebb_mcp_server/_version.py +24 -0
- oebb_mcp_server-0.1.1/src/oebb_mcp_server/const.py +15 -0
- oebb_mcp_server-0.1.1/src/oebb_mcp_server/oebb_api.py +542 -0
- oebb_mcp_server-0.1.1/src/oebb_mcp_server/py.typed +0 -0
- oebb_mcp_server-0.1.1/src/oebb_mcp_server/server.py +145 -0
- oebb_mcp_server-0.1.1/tests/test_oebb_api.py +799 -0
- oebb_mcp_server-0.1.1/tests/test_oebb_api_integration.py +109 -0
- oebb_mcp_server-0.1.1/uv.lock +1268 -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,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@v6
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0 # hatch-vcs needs full history + tags to derive the version
|
|
15
|
+
- uses: astral-sh/setup-uv@v8.3.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/*.tar.gz | sed -E 's|.*/oebb_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@v6
|
|
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@v6
|
|
14
|
+
- uses: actions/setup-python@v6
|
|
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@v6
|
|
26
|
+
with:
|
|
27
|
+
fetch-depth: 0 # hatch-vcs derives the version from git history + tags
|
|
28
|
+
- uses: actions/setup-python@v6
|
|
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,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial release
|
|
6
|
+
- MCP server with 4 tools: `search_station`, `station_board`, `trip_search`, `service_alerts`
|
|
7
|
+
- Station search by name with coordinates and IDs
|
|
8
|
+
- Live departures/arrivals at any OeBB station
|
|
9
|
+
- Trip search with time planning, departure/arrival mode, and direct-only filter
|
|
10
|
+
- Service alerts and disruption information with product type filtering
|
|
11
|
+
- First public distribution: published to [PyPI](https://pypi.org/project/oebb-mcp-server/) (installable via `uvx oebb-mcp-server`) and listed in the [official MCP Registry](https://registry.modelcontextprotocol.io)
|
|
12
|
+
- Tag-driven release pipeline: PyPI Trusted Publishing (OIDC), GitHub Release, and MCP Registry publish on `v*` tags
|
|
13
|
+
- Version single-sourced from git tags via `hatch-vcs`; added PyPI metadata (authors, URLs, classifiers, keywords) and a `py.typed` marker
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# OeBB MCP Server
|
|
2
|
+
> MCP server for OeBB (Austrian Federal Railways) train data, usable by LLMs via the Model Context Protocol.
|
|
3
|
+
|
|
4
|
+
## Quick Reference
|
|
5
|
+
- **Lint**: `ruff check .`
|
|
6
|
+
- **Format**: `ruff format .`
|
|
7
|
+
- **Test (unit)**: `pytest tests/ -v -m "not integration"`
|
|
8
|
+
- **Test (integration, real API)**: `pytest tests/ -v -m integration`
|
|
9
|
+
- **Run server**: `uvx --from . oebb-mcp-server` or `python -m oebb_mcp_server.server`
|
|
10
|
+
- **Validate (CI)**: Ruff + pytest unit tests (all must pass via `gate` job)
|
|
11
|
+
|
|
12
|
+
## Architecture Overview
|
|
13
|
+
Thin adapter: FastMCP presentation layer wrapping a pure async OeBB API client. Purely functional, no classes. All code in `src/oebb_mcp_server/`.
|
|
14
|
+
|
|
15
|
+
- `server.py` -- FastMCP tool registration, session lifecycle, stdio entry point
|
|
16
|
+
- `oebb_api.py` -- pure async HTTP client for OeBB Scotty API (independently testable)
|
|
17
|
+
- `const.py` -- all constants (endpoint, auth, client config)
|
|
18
|
+
|
|
19
|
+
Data flow: MCP tool call -> `server.py` handler -> `oebb_api.async_oebb_*()` -> OeBB Scotty API -> JSON -> MCP tool result.
|
|
20
|
+
|
|
21
|
+
See [Architecture](docs/tech/ARCHITECTURE.md) for module boundaries and data flow detail.
|
|
22
|
+
|
|
23
|
+
## Tech Stack
|
|
24
|
+
- Python 3.12+, `from __future__ import annotations` in every file
|
|
25
|
+
- `mcp[cli]` (FastMCP) for MCP server framework
|
|
26
|
+
- `aiohttp` for async HTTP
|
|
27
|
+
- `ruff` for linting/formatting, `pytest` + `pytest-asyncio` for testing
|
|
28
|
+
- `uv` for environment management, `hatchling` build backend
|
|
29
|
+
- GitHub Actions CI (validate on push/PR)
|
|
30
|
+
|
|
31
|
+
See [Tech Stack](docs/tech/TECH-STACK.md) for full detail.
|
|
32
|
+
|
|
33
|
+
## Core Conventions
|
|
34
|
+
- All async functions use `async_` prefix; MCP tools use plain `verb_noun`
|
|
35
|
+
- Constants in `const.py` only -- no inline magic values
|
|
36
|
+
- Logger: `_LOGGER = logging.getLogger(__name__)` with `%s` formatting (not f-strings)
|
|
37
|
+
- Import order: `__future__` -> stdlib -> third-party -> local
|
|
38
|
+
- Errors signaled via sentinel dict `{"message": "..."}`, not exceptions
|
|
39
|
+
- OeBB Scotty API is reverse-engineered -- response structure may change without notice
|
|
40
|
+
|
|
41
|
+
See [Conventions](docs/tech/CONVENTIONS.md) for naming tables and full rules.
|
|
42
|
+
|
|
43
|
+
## Business Domain
|
|
44
|
+
Read-only MCP gateway to OeBB live train data. Four tools: station search, station board (departures/arrivals), trip search (connections), and service alerts (disruptions). All data fetched live from the OeBB Scotty HAFAS API with no local caching.
|
|
45
|
+
|
|
46
|
+
See [Domain Overview](docs/domain/OVERVIEW.md) for concepts, feature boundaries, and HAFAS terminology.
|
|
47
|
+
|
|
48
|
+
## Structural Risks
|
|
49
|
+
- OeBB Scotty API is reverse-engineered and undocumented -- breaking changes possible without notice
|
|
50
|
+
- Hardcoded auth token in `const.py` -- rotation requires code change
|
|
51
|
+
- Per-call `aiohttp.ClientSession` creation -- no connection pooling
|
|
52
|
+
- No unit tests for `server.py` tool handlers
|
|
53
|
+
- All data flows as untyped `dict[str, Any]` -- no compile-time shape guarantees
|
|
54
|
+
|
|
55
|
+
## Detailed Guides
|
|
56
|
+
- [Technical Context](docs/tech/README.md) -- architecture, tech stack, conventions, testing
|
|
57
|
+
- [Domain Context](docs/domain/README.md) -- business domain, entities, terminology, integrations
|
|
58
|
+
- [Documentation Guide](docs/README.md) -- how to maintain these docs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 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,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oebb-mcp-server
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: MCP server for OeBB (Austrian Federal Railways) train data
|
|
5
|
+
Project-URL: Homepage, https://github.com/slettmayer/oebb-mcp-server
|
|
6
|
+
Project-URL: Repository, https://github.com/slettmayer/oebb-mcp-server
|
|
7
|
+
Project-URL: Issues, https://github.com/slettmayer/oebb-mcp-server/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/slettmayer/oebb-mcp-server/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Stefan Lettmayer
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: austria,hafas,mcp,model-context-protocol,oebb,railway,scotty,train
|
|
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: mcp[cli]>=1.0.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# oebb-mcp-server
|
|
27
|
+
|
|
28
|
+
<!-- mcp-name: io.github.slettmayer/oebb-mcp-server -->
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/oebb-mcp-server/)
|
|
31
|
+
[](https://pypi.org/project/oebb-mcp-server/)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
|
|
34
|
+
MCP server for [OeBB](https://www.oebb.at) (Austrian Federal Railways) train data. Query Austrian train stations, departures, connections, and service alerts directly from LLMs via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
### Claude Desktop
|
|
39
|
+
|
|
40
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"oebb": {
|
|
46
|
+
"command": "uvx",
|
|
47
|
+
"args": ["oebb-mcp-server"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Claude Code
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add oebb -- uvx oebb-mcp-server
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### From source (development)
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"oebb": {
|
|
65
|
+
"command": "uvx",
|
|
66
|
+
"args": ["--from", "/path/to/oebb-mcp-server", "oebb-mcp-server"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Tools
|
|
73
|
+
|
|
74
|
+
### `search_station`
|
|
75
|
+
|
|
76
|
+
Search OeBB stations by name. Returns matching stations with IDs, coordinates, and types.
|
|
77
|
+
|
|
78
|
+
| Parameter | Type | Default | Description |
|
|
79
|
+
|-----------|------|---------|-------------|
|
|
80
|
+
| `query` | string | required | Station name (e.g. "Wien Hbf") |
|
|
81
|
+
| `max_results` | int | 10 | Maximum results |
|
|
82
|
+
|
|
83
|
+
### `station_board`
|
|
84
|
+
|
|
85
|
+
Fetch live departures or arrivals at a station. Provide either `station_id` or `station_name`.
|
|
86
|
+
|
|
87
|
+
| Parameter | Type | Default | Description |
|
|
88
|
+
|-----------|------|---------|-------------|
|
|
89
|
+
| `station_id` | string | — | OeBB station ID (e.g. "1190100") |
|
|
90
|
+
| `station_name` | string | — | Station name (auto-resolved) |
|
|
91
|
+
| `board_type` | string | "DEP" | "DEP" for departures, "ARR" for arrivals |
|
|
92
|
+
| `max_journeys` | int | 10 | Maximum journeys |
|
|
93
|
+
|
|
94
|
+
### `trip_search`
|
|
95
|
+
|
|
96
|
+
Search train connections between two stations. Supports time planning and direct-only filtering.
|
|
97
|
+
|
|
98
|
+
| Parameter | Type | Default | Description |
|
|
99
|
+
|-----------|------|---------|-------------|
|
|
100
|
+
| `from_station_id` | string | — | Departure station ID |
|
|
101
|
+
| `from_station_name` | string | — | Departure station name |
|
|
102
|
+
| `to_station_id` | string | — | Arrival station ID |
|
|
103
|
+
| `to_station_name` | string | — | Arrival station name |
|
|
104
|
+
| `max_connections` | int | 5 | Maximum connections |
|
|
105
|
+
| `time` | string | now | ISO 8601 time (e.g. "2026-04-15T08:00:00") |
|
|
106
|
+
| `time_mode` | string | "departure" | "departure" or "arrival" |
|
|
107
|
+
| `direct_only` | bool | false | Only direct connections |
|
|
108
|
+
|
|
109
|
+
### `service_alerts`
|
|
110
|
+
|
|
111
|
+
Fetch current OeBB service alerts and disruptions.
|
|
112
|
+
|
|
113
|
+
| Parameter | Type | Default | Description |
|
|
114
|
+
|-----------|------|---------|-------------|
|
|
115
|
+
| `max_alerts` | int | 20 | Maximum alerts |
|
|
116
|
+
| `product_filter` | int | 65535 | Product bitmask (1=ICE/RJX, 2=IC/EC, 4=NJ, 8=D/EN, 16=REX/R, 32=S-Bahn, 64=Bus, 128=Ferry, 256=U-Bahn, 512=Tram, 4096=private operators like Westbahn/RegioJet, 65535=all) |
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Install dependencies
|
|
122
|
+
uv sync
|
|
123
|
+
|
|
124
|
+
# Lint & format
|
|
125
|
+
ruff check .
|
|
126
|
+
ruff format .
|
|
127
|
+
|
|
128
|
+
# Run unit tests
|
|
129
|
+
pytest tests/ -v -m "not integration"
|
|
130
|
+
|
|
131
|
+
# Run integration tests (hits real OeBB API)
|
|
132
|
+
pytest tests/ -v -m integration
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# oebb-mcp-server
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.slettmayer/oebb-mcp-server -->
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/oebb-mcp-server/)
|
|
6
|
+
[](https://pypi.org/project/oebb-mcp-server/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
MCP server for [OeBB](https://www.oebb.at) (Austrian Federal Railways) train data. Query Austrian train stations, departures, connections, and service alerts directly from LLMs via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
### Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"oebb": {
|
|
21
|
+
"command": "uvx",
|
|
22
|
+
"args": ["oebb-mcp-server"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Claude Code
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
claude mcp add oebb -- uvx oebb-mcp-server
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### From source (development)
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"oebb": {
|
|
40
|
+
"command": "uvx",
|
|
41
|
+
"args": ["--from", "/path/to/oebb-mcp-server", "oebb-mcp-server"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Tools
|
|
48
|
+
|
|
49
|
+
### `search_station`
|
|
50
|
+
|
|
51
|
+
Search OeBB stations by name. Returns matching stations with IDs, coordinates, and types.
|
|
52
|
+
|
|
53
|
+
| Parameter | Type | Default | Description |
|
|
54
|
+
|-----------|------|---------|-------------|
|
|
55
|
+
| `query` | string | required | Station name (e.g. "Wien Hbf") |
|
|
56
|
+
| `max_results` | int | 10 | Maximum results |
|
|
57
|
+
|
|
58
|
+
### `station_board`
|
|
59
|
+
|
|
60
|
+
Fetch live departures or arrivals at a station. Provide either `station_id` or `station_name`.
|
|
61
|
+
|
|
62
|
+
| Parameter | Type | Default | Description |
|
|
63
|
+
|-----------|------|---------|-------------|
|
|
64
|
+
| `station_id` | string | — | OeBB station ID (e.g. "1190100") |
|
|
65
|
+
| `station_name` | string | — | Station name (auto-resolved) |
|
|
66
|
+
| `board_type` | string | "DEP" | "DEP" for departures, "ARR" for arrivals |
|
|
67
|
+
| `max_journeys` | int | 10 | Maximum journeys |
|
|
68
|
+
|
|
69
|
+
### `trip_search`
|
|
70
|
+
|
|
71
|
+
Search train connections between two stations. Supports time planning and direct-only filtering.
|
|
72
|
+
|
|
73
|
+
| Parameter | Type | Default | Description |
|
|
74
|
+
|-----------|------|---------|-------------|
|
|
75
|
+
| `from_station_id` | string | — | Departure station ID |
|
|
76
|
+
| `from_station_name` | string | — | Departure station name |
|
|
77
|
+
| `to_station_id` | string | — | Arrival station ID |
|
|
78
|
+
| `to_station_name` | string | — | Arrival station name |
|
|
79
|
+
| `max_connections` | int | 5 | Maximum connections |
|
|
80
|
+
| `time` | string | now | ISO 8601 time (e.g. "2026-04-15T08:00:00") |
|
|
81
|
+
| `time_mode` | string | "departure" | "departure" or "arrival" |
|
|
82
|
+
| `direct_only` | bool | false | Only direct connections |
|
|
83
|
+
|
|
84
|
+
### `service_alerts`
|
|
85
|
+
|
|
86
|
+
Fetch current OeBB service alerts and disruptions.
|
|
87
|
+
|
|
88
|
+
| Parameter | Type | Default | Description |
|
|
89
|
+
|-----------|------|---------|-------------|
|
|
90
|
+
| `max_alerts` | int | 20 | Maximum alerts |
|
|
91
|
+
| `product_filter` | int | 65535 | Product bitmask (1=ICE/RJX, 2=IC/EC, 4=NJ, 8=D/EN, 16=REX/R, 32=S-Bahn, 64=Bus, 128=Ferry, 256=U-Bahn, 512=Tram, 4096=private operators like Westbahn/RegioJet, 65535=all) |
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Install dependencies
|
|
97
|
+
uv sync
|
|
98
|
+
|
|
99
|
+
# Lint & format
|
|
100
|
+
ruff check .
|
|
101
|
+
ruff format .
|
|
102
|
+
|
|
103
|
+
# Run unit tests
|
|
104
|
+
pytest tests/ -v -m "not integration"
|
|
105
|
+
|
|
106
|
+
# Run integration tests (hits real OeBB API)
|
|
107
|
+
pytest tests/ -v -m integration
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Documentation Contributing Guide
|
|
2
|
+
|
|
3
|
+
> Rules for maintaining the AI assistant documentation. Follow these when adding, updating, or restructuring any `.md` files in this system. These rules apply to both human developers and AI sessions.
|
|
4
|
+
|
|
5
|
+
## Why This Architecture Exists
|
|
6
|
+
|
|
7
|
+
Claude reads `CLAUDE.md` on every conversation start. If it's bloated with detail, it wastes context window on content that may not be relevant to the task. The architecture solves this:
|
|
8
|
+
|
|
9
|
+
- **`CLAUDE.md`** is loaded every time -- must stay concise (index + critical one-liners)
|
|
10
|
+
- **`docs/tech/*.md`** are technical documentation files loaded on demand -- only when a task touches that topic. README.md in that folder contains an index.
|
|
11
|
+
- **`docs/domain/*.md`** are domain documentation files loaded on demand -- only when a task touches that topic. README.md in that folder contains an index.
|
|
12
|
+
- **Directory-specific `CLAUDE.md`** files are loaded when working in that directory
|
|
13
|
+
|
|
14
|
+
This means the AI gets the right information at the right time without burning context on irrelevant detail.
|
|
15
|
+
|
|
16
|
+
## File Locations and Scope
|
|
17
|
+
|
|
18
|
+
| Location | Purpose | Loaded when |
|
|
19
|
+
|----------|---------|-------------|
|
|
20
|
+
| `/CLAUDE.md` | Concise index: tech stack, critical warnings (one-liners), links to detailed docs, quick reference | Every conversation |
|
|
21
|
+
| `/docs/tech/README.md` | Index file for technical topic guides | AI reads the file when the task is related to a technical topic to find the correct file |
|
|
22
|
+
| `/docs/tech/*.md` | Detailed technical topic guides (one topic per file) | AI reads the file when the task is related to the technical topic |
|
|
23
|
+
| `/docs/domain/README.md` | Index file for domain topic guides | AI reads the file when the task is related to a domain topic to find the correct file |
|
|
24
|
+
| `/docs/domain/*.md` | Detailed domain topic guides (one topic per file) | AI reads the file when the task is related to the domain |
|
|
25
|
+
| `src/**/CLAUDE.md` | Conventions and documentation for a specific directory within the source code | Working in a specific folder of the source code |
|
|
26
|
+
|
|
27
|
+
### What goes where
|
|
28
|
+
|
|
29
|
+
- **Rule in `CLAUDE.md`**: one-liner that fits in a bullet point (with a link to detail)
|
|
30
|
+
- **Detail in `docs/tech`**: anything technical that needs explanation, examples, tables, checklists, or code blocks
|
|
31
|
+
- **Detail in `docs/domain`**: anything domain specific that needs explanation, examples, tables, checklists, or code blocks
|
|
32
|
+
- **Directory-specific `CLAUDE.md`**: conventions that only apply when working in that specific directory
|
|
33
|
+
|
|
34
|
+
## Naming Conventions
|
|
35
|
+
|
|
36
|
+
- `/docs` files: `UPPERCASE-KEBAB-CASE.md` (e.g., `PACKAGE-GUIDELINES.md`, `STATE-MANAGEMENT.md`)
|
|
37
|
+
- Directory-specific files: always named `CLAUDE.md`
|
|
38
|
+
- Keep names descriptive and short (2-3 words max)
|
|
39
|
+
|
|
40
|
+
## File Size Guidelines
|
|
41
|
+
|
|
42
|
+
- **`/CLAUDE.md`**: aim for <150 lines -- if it grows beyond this, content is leaking in that should be in `docs/`
|
|
43
|
+
- **`/docs/**/*.md`**: aim for <300 lines per file -- if a doc grows beyond this, consider splitting into two focused files
|
|
44
|
+
- **Directory-specific `CLAUDE.md`**: aim for <100 lines -- these should be tightly scoped
|
|
45
|
+
|
|
46
|
+
## Checklist: Adding a New Doc
|
|
47
|
+
|
|
48
|
+
1. Create the file in `/docs/tech` for technology focused docs and `/docs/domain` for domain focused docs, both following the naming convention
|
|
49
|
+
2. Add a new entry to the `README.md` file of the corresponding folder.
|
|
50
|
+
3. If there's a critical rule, add a one-liner to the `ALWAYS` or `DO NOT USE` block in `/CLAUDE.md` with a link to the new doc
|
|
51
|
+
4. Add cross-references from related docs (e.g., a new payment doc should link from `README.md`)
|
|
52
|
+
5. Use relative links between `/docs/` files (e.g., `[INTEGRATIONS.md](INTEGRATIONS.md)`)
|
|
53
|
+
6. Use relative links from `/CLAUDE.md` (e.g., `[PACKAGE-GUIDELINES.md](/docs/tech/PACKAGE-GUIDELINES.md)`)
|
|
54
|
+
|
|
55
|
+
## Checklist: Updating an Existing Doc
|
|
56
|
+
|
|
57
|
+
1. Keep changes within the doc's defined scope -- don't let a doc grow to cover unrelated topics
|
|
58
|
+
2. If new content doesn't fit the existing doc's scope, create a new doc instead
|
|
59
|
+
3. **Never move detailed content into `/CLAUDE.md`** -- add a one-liner + link instead
|
|
60
|
+
4. Update cross-references if you rename sections or files
|
|
61
|
+
5. Check that the Documentation Index entry in `/CLAUDE.md` still accurately describes the doc's purpose
|
|
62
|
+
|
|
63
|
+
## Common Mistakes to Avoid
|
|
64
|
+
|
|
65
|
+
| Mistake | What to do instead |
|
|
66
|
+
|---------|-------------------|
|
|
67
|
+
| Inlining detailed content in `/CLAUDE.md` | Create/update a doc in `docs/`, add a one-liner + link |
|
|
68
|
+
| Creating a doc without indexing it | Always add to the Documentation Index table in `/CLAUDE.md` |
|
|
69
|
+
| Putting directory-specific rules in `/docs/` | Use the directory's `CLAUDE.md` file instead |
|
|
70
|
+
| Using absolute paths in links | Use relative paths so links work regardless of where the repo is cloned |
|
|
71
|
+
| Duplicating content across multiple docs | Put it in one place, cross-reference from others |
|
|
72
|
+
| Letting a doc grow unbounded | Split into focused files when it exceeds ~300 lines |
|