transcapi-mcp 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.
@@ -0,0 +1,6 @@
1
+ venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ *.egg-info/
6
+ .env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TransCAPI
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,87 @@
1
+ Metadata-Version: 2.5
2
+ Name: transcapi-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for TransCAPI: live bus and rail departures, vehicles, disruptions and journey planning for Great Britain
5
+ Project-URL: Homepage, https://www.transcapi.com
6
+ Project-URL: Documentation, https://www.transcapi.com/docs
7
+ Project-URL: Repository, https://github.com/tcdataconsultants/transcapi-mcp
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: bus,great britain,mcp,rail,transcapi,transport,uk
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: httpx>=0.27
13
+ Requires-Dist: mcp<3,>=2.2
14
+ Description-Content-Type: text/markdown
15
+
16
+ # TransCAPI MCP server
17
+
18
+ Lets AI assistants such as Claude answer questions about public transport in
19
+ Great Britain with live data: *"When's the next train from Leeds to York, and
20
+ is it on time?"*, *"Where's the 52 bus?"*, *"Is anything disrupting my route
21
+ home?"*
22
+
23
+ It connects an assistant to [TransCAPI](https://www.transcapi.com), which
24
+ covers England, Scotland and Wales: stops and places, bus and rail departures
25
+ with live running, live bus positions, disruptions and journey planning.
26
+
27
+ ## Setup
28
+
29
+ 1. Get a free API key at <https://www.transcapi.com/signup> (1,000 requests a
30
+ day, every endpoint).
31
+ 2. Add the server to your MCP client. For Claude Desktop, in
32
+ `claude_desktop_config.json`:
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "transcapi": {
38
+ "command": "uvx",
39
+ "args": ["transcapi-mcp"],
40
+ "env": { "TRANSCAPI_API_KEY": "your-key" }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ For Claude Code:
47
+
48
+ ```bash
49
+ claude mcp add transcapi --env TRANSCAPI_API_KEY=your-key -- uvx transcapi-mcp
50
+ ```
51
+
52
+ Or install it yourself with `pip install transcapi-mcp` and run
53
+ `transcapi-mcp`.
54
+
55
+ ## Tools
56
+
57
+ | Tool | What it answers |
58
+ |---|---|
59
+ | `search_places` | Find a town, village, postcode or stop by name |
60
+ | `nearby_stops` | Bus, tram and rail stops near a point, with which way the buses go |
61
+ | `nearby_stations` | Rail stations near a point |
62
+ | `bus_departures` | What's leaving (or arriving at) a bus stop, with live times for tracked buses |
63
+ | `rail_departures` | Live trains at a station: expected times, platforms, cancellations, delay reasons |
64
+ | `train_service` | Every station one train calls at, live for today |
65
+ | `bus_service` | Every stop one bus journey calls at, and where the bus is now |
66
+ | `live_buses` | Buses on the road near a point, with delays where tracked |
67
+ | `disruptions` | Planned works, incidents and line status, by area, line or a journey's route |
68
+ | `plan_journey` | Public transport journeys between two points (South and West Yorkshire and West Sussex for now) |
69
+
70
+ All tools are read-only.
71
+
72
+ ## Configuration
73
+
74
+ | Variable | |
75
+ |---|---|
76
+ | `TRANSCAPI_API_KEY` | Required. Your TransCAPI key. |
77
+ | `TRANSCAPI_BASE_URL` | Optional. Defaults to `https://api.transcapi.com`. |
78
+
79
+ ## Attribution
80
+
81
+ TransCAPI is built on official open data. If you show its data to other
82
+ people, credit the sources as described at
83
+ <https://www.transcapi.com/docs/data-sources>.
84
+
85
+ ## Licence
86
+
87
+ MIT.
@@ -0,0 +1,72 @@
1
+ # TransCAPI MCP server
2
+
3
+ Lets AI assistants such as Claude answer questions about public transport in
4
+ Great Britain with live data: *"When's the next train from Leeds to York, and
5
+ is it on time?"*, *"Where's the 52 bus?"*, *"Is anything disrupting my route
6
+ home?"*
7
+
8
+ It connects an assistant to [TransCAPI](https://www.transcapi.com), which
9
+ covers England, Scotland and Wales: stops and places, bus and rail departures
10
+ with live running, live bus positions, disruptions and journey planning.
11
+
12
+ ## Setup
13
+
14
+ 1. Get a free API key at <https://www.transcapi.com/signup> (1,000 requests a
15
+ day, every endpoint).
16
+ 2. Add the server to your MCP client. For Claude Desktop, in
17
+ `claude_desktop_config.json`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "transcapi": {
23
+ "command": "uvx",
24
+ "args": ["transcapi-mcp"],
25
+ "env": { "TRANSCAPI_API_KEY": "your-key" }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ For Claude Code:
32
+
33
+ ```bash
34
+ claude mcp add transcapi --env TRANSCAPI_API_KEY=your-key -- uvx transcapi-mcp
35
+ ```
36
+
37
+ Or install it yourself with `pip install transcapi-mcp` and run
38
+ `transcapi-mcp`.
39
+
40
+ ## Tools
41
+
42
+ | Tool | What it answers |
43
+ |---|---|
44
+ | `search_places` | Find a town, village, postcode or stop by name |
45
+ | `nearby_stops` | Bus, tram and rail stops near a point, with which way the buses go |
46
+ | `nearby_stations` | Rail stations near a point |
47
+ | `bus_departures` | What's leaving (or arriving at) a bus stop, with live times for tracked buses |
48
+ | `rail_departures` | Live trains at a station: expected times, platforms, cancellations, delay reasons |
49
+ | `train_service` | Every station one train calls at, live for today |
50
+ | `bus_service` | Every stop one bus journey calls at, and where the bus is now |
51
+ | `live_buses` | Buses on the road near a point, with delays where tracked |
52
+ | `disruptions` | Planned works, incidents and line status, by area, line or a journey's route |
53
+ | `plan_journey` | Public transport journeys between two points (South and West Yorkshire and West Sussex for now) |
54
+
55
+ All tools are read-only.
56
+
57
+ ## Configuration
58
+
59
+ | Variable | |
60
+ |---|---|
61
+ | `TRANSCAPI_API_KEY` | Required. Your TransCAPI key. |
62
+ | `TRANSCAPI_BASE_URL` | Optional. Defaults to `https://api.transcapi.com`. |
63
+
64
+ ## Attribution
65
+
66
+ TransCAPI is built on official open data. If you show its data to other
67
+ people, credit the sources as described at
68
+ <https://www.transcapi.com/docs/data-sources>.
69
+
70
+ ## Licence
71
+
72
+ MIT.
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "transcapi-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for TransCAPI: live bus and rail departures, vehicles, disruptions and journey planning for Great Britain"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ dependencies = ["mcp>=2.2,<3", "httpx>=0.27"]
13
+ keywords = ["mcp", "transport", "bus", "rail", "uk", "great britain", "transcapi"]
14
+
15
+ [project.urls]
16
+ Homepage = "https://www.transcapi.com"
17
+ Documentation = "https://www.transcapi.com/docs"
18
+ Repository = "https://github.com/tcdataconsultants/transcapi-mcp"
19
+
20
+ [project.scripts]
21
+ transcapi-mcp = "transcapi_mcp.server:main"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["src/transcapi_mcp"]
@@ -0,0 +1,3 @@
1
+ """MCP server for TransCAPI, Great Britain's public transport API."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,197 @@
1
+ """MCP server exposing TransCAPI to AI assistants.
2
+
3
+ Each tool is one TransCAPI endpoint, described so an assistant can chain them:
4
+ find a place or stop, then ask what is leaving it, then follow one bus or
5
+ train. Responses are the API's own JSON, so every field an assistant sees is
6
+ documented at https://www.transcapi.com/docs.
7
+
8
+ Needs a TransCAPI key in TRANSCAPI_API_KEY (free at
9
+ https://www.transcapi.com/signup). TRANSCAPI_BASE_URL overrides the API
10
+ address.
11
+ """
12
+ import json
13
+ import logging
14
+ import os
15
+
16
+ import httpx
17
+ from mcp.server.mcpserver import MCPServer
18
+ from mcp.server.mcpserver.exceptions import ToolError
19
+ from mcp.types import ToolAnnotations
20
+
21
+ from transcapi_mcp import __version__
22
+
23
+ # httpx logs every request at INFO; on an MCP client's log that is noise.
24
+ logging.getLogger("httpx").setLevel(logging.WARNING)
25
+
26
+ BASE_URL = os.environ.get("TRANSCAPI_BASE_URL", "https://api.transcapi.com").rstrip("/")
27
+
28
+ INSTRUCTIONS = """\
29
+ TransCAPI covers public transport in Great Britain (England, Scotland, Wales).
30
+
31
+ How to use these tools well:
32
+ - Resolve names first. Use search_places for a town, postcode or stop name,
33
+ and nearby_stops / nearby_stations for a location. Bus stops are identified
34
+ by `atcocode`, rail stations by 3-letter CRS code (LDS = Leeds).
35
+ - Stops sharing a name are told apart by `towards` (where the buses go).
36
+ Mention it when there is more than one.
37
+ - Every departure has `source`: `darwin` or `avl` or `siri_vm` means live,
38
+ `gtfs_scheduled` means timetable only. Say which when reporting a time.
39
+ - To follow one bus or train, pass its `trip_id` to bus_service or
40
+ train_service.
41
+ - `null` means unknown, not zero. A `422 outside_coverage` from plan_journey
42
+ means that area is not covered, not that there is no route.
43
+ - Apps showing this data must credit its sources; see
44
+ https://www.transcapi.com/docs/data-sources.
45
+ """
46
+
47
+ mcp = MCPServer(
48
+ name="transcapi",
49
+ title="TransCAPI",
50
+ description="Live bus and rail departures, vehicles, disruptions and journey "
51
+ "planning for Great Britain.",
52
+ instructions=INSTRUCTIONS,
53
+ website_url="https://www.transcapi.com",
54
+ version=__version__,
55
+ )
56
+
57
+ READ_ONLY = ToolAnnotations(read_only_hint=True, open_world_hint=True, idempotent_hint=True)
58
+
59
+
60
+ class TransCAPIError(ToolError):
61
+ """An API error, carrying the API's own JSON error body.
62
+
63
+ A ToolError, so the client gets is_error with this message; any other
64
+ exception would reach the model only as "Error executing tool"."""
65
+
66
+
67
+ def _get(path, **params):
68
+ key = os.environ.get("TRANSCAPI_API_KEY")
69
+ if not key:
70
+ raise TransCAPIError(json.dumps({"error": {
71
+ "code": "missing_api_key",
72
+ "message": "Set TRANSCAPI_API_KEY. Keys are free at https://www.transcapi.com/signup",
73
+ }}))
74
+ params = {k: v for k, v in params.items() if v is not None}
75
+ try:
76
+ resp = httpx.get(
77
+ f"{BASE_URL}{path}",
78
+ params=params,
79
+ headers={"X-Api-Key": key, "User-Agent": f"transcapi-mcp/{__version__}"},
80
+ timeout=45,
81
+ )
82
+ except httpx.HTTPError as exc:
83
+ raise TransCAPIError(json.dumps({"error": {"code": "unreachable", "message": str(exc)}}))
84
+ # Raised rather than returned so the client marks the result as an error;
85
+ # the API's JSON is kept as the message because it says what was wrong
86
+ # ("outside_coverage", "not_found") in a way an assistant can act on.
87
+ if resp.status_code >= 400:
88
+ raise TransCAPIError(resp.text)
89
+ return resp.text
90
+
91
+
92
+ @mcp.tool(annotations=READ_ONLY)
93
+ def search_places(query: str, lat: float | None = None, lon: float | None = None,
94
+ limit: int = 10) -> str:
95
+ """Find towns, villages, postcodes (full or partial, e.g. "S1") and stops by
96
+ name. Places sharing a name are ranked by size. Give lat/lon to sort by
97
+ distance from there."""
98
+ return _get("/v1/places.json", query=query, lat=lat, lon=lon, limit=limit)
99
+
100
+
101
+ @mcp.tool(annotations=READ_ONLY)
102
+ def nearby_stops(lat: float, lon: float, radius: int = 400, limit: int = 20) -> str:
103
+ """Bus, tram and rail stops within `radius` metres of a point, nearest first.
104
+ Each has an `atcocode` for bus_departures and `towards` saying which way its
105
+ buses go."""
106
+ return _get("/v1/bus/stops.json", lat=lat, lon=lon, radius=radius, limit=limit)
107
+
108
+
109
+ @mcp.tool(annotations=READ_ONLY)
110
+ def nearby_stations(lat: float, lon: float, radius: int = 3000, limit: int = 10) -> str:
111
+ """Rail stations within `radius` metres of a point, with their CRS codes for
112
+ rail_departures."""
113
+ return _get("/v1/rail/stations.json", lat=lat, lon=lon, radius=radius, limit=limit)
114
+
115
+
116
+ @mcp.tool(annotations=READ_ONLY)
117
+ def bus_departures(atcocode: str, board: str = "departures", minutes_ahead: int = 60,
118
+ limit: int = 15) -> str:
119
+ """What is leaving (or, with board="arrivals" or "both", arriving at) a bus
120
+ stop. Tracked buses have an expected time and `delay_seconds`; others show
121
+ the timetabled time with source `gtfs_scheduled`."""
122
+ return _get(f"/v1/bus/stop_timetables/{atcocode}.json", type=board,
123
+ to_offset=minutes_ahead, limit=limit, live="true")
124
+
125
+
126
+ @mcp.tool(annotations=READ_ONLY)
127
+ def rail_departures(crs_code: str, board: str = "departures", minutes_ahead: int = 60,
128
+ limit: int = 15) -> str:
129
+ """Live trains leaving (or arriving at, with board="arrivals" or "both") a
130
+ station: expected times, platforms, cancellations and delay reasons from
131
+ National Rail, plus station messages. `crs_code` is the 3-letter code,
132
+ e.g. LDS for Leeds."""
133
+ return _get(f"/v1/rail/station_timetables/{crs_code.upper()}.json", type=board,
134
+ to_offset=minutes_ahead, limit=limit)
135
+
136
+
137
+ @mcp.tool(annotations=READ_ONLY)
138
+ def train_service(trip_id: str) -> str:
139
+ """Every station one train calls at. For a train running today, each call has
140
+ live status, expected and actual times, and the platform at its next
141
+ station. Take `trip_id` from rail_departures."""
142
+ return _get(f"/v1/rail/services/{trip_id}.json")
143
+
144
+
145
+ @mcp.tool(annotations=READ_ONLY)
146
+ def bus_service(trip_id: str) -> str:
147
+ """Every stop one bus journey calls at, the days it runs, and where the bus is
148
+ now if it is running. Take `trip_id` from bus_departures or live_buses."""
149
+ return _get(f"/v1/bus/services/{trip_id}.json")
150
+
151
+
152
+ @mcp.tool(annotations=READ_ONLY)
153
+ def live_buses(lat: float, lon: float, radius: int = 1000, line: str | None = None,
154
+ operator: str | None = None) -> str:
155
+ """Buses on the road within `radius` metres (up to 10000) of a point, nearest
156
+ first, each linked to the journey it is working (`trip_id`) and with
157
+ `delay_seconds` where tracked. Filter by `line` (e.g. "52") or operator."""
158
+ return _get("/v1/bus/vehicles.json", lat=lat, lon=lon, radius=radius,
159
+ line=line, operator=operator)
160
+
161
+
162
+ @mcp.tool(annotations=READ_ONLY)
163
+ def disruptions(lat: float | None = None, lon: float | None = None, radius: int | None = None,
164
+ mode: str | None = None, operator: str | None = None, line: str | None = None,
165
+ path: str | None = None, path_radius: int | None = None,
166
+ limit: int = 20) -> str:
167
+ """Planned works, incidents and line status: bus and tram across Great Britain,
168
+ plus Tube, DLR, Overground, Elizabeth line and London Trams. Filter by area
169
+ (lat/lon/radius), mode, operator or line. To ask what affects a particular
170
+ journey, pass its route as `path` (an encoded polyline, e.g. a leg's
171
+ `leg_geometry` from plan_journey): line numbers repeat across towns, so this
172
+ is more reliable than `line`."""
173
+ return _get("/v1/disruptions.json", lat=lat, lon=lon, radius=radius, mode=mode,
174
+ operator=operator, line=line, path=path, path_radius=path_radius,
175
+ limit=limit)
176
+
177
+
178
+ @mcp.tool(annotations=READ_ONLY)
179
+ def plan_journey(from_lat: float, from_lon: float, to_lat: float, to_lon: float,
180
+ datetime: str | None = None, arrive_by: bool = False,
181
+ include_steps: bool = False, limit: int = 3) -> str:
182
+ """Public transport journeys between two points, with walking, fares and live
183
+ positions of buses already running. `datetime` is ISO 8601 (default now);
184
+ `arrive_by` treats it as the latest arrival. Currently covers South and West
185
+ Yorkshire and West Sussex: elsewhere it returns 422 outside_coverage, which
186
+ means not covered rather than no route."""
187
+ return _get("/v1/journey.json", from_lat=from_lat, from_lon=from_lon, to_lat=to_lat,
188
+ to_lon=to_lon, datetime=datetime, arrive_by=str(arrive_by).lower(),
189
+ include_steps=str(include_steps).lower(), limit=limit)
190
+
191
+
192
+ def main():
193
+ mcp.run()
194
+
195
+
196
+ if __name__ == "__main__":
197
+ main()