gtfs-one-mcp 1.3.0
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.
- package/LICENSE +15 -0
- package/README.md +190 -0
- package/dist/api-client.d.ts +38 -0
- package/dist/api-client.js +133 -0
- package/dist/api-client.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.js +121 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +27 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +9 -0
- package/dist/tools.js +431 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/gtfs-one.config.example.json +7 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (C) Digital Mountaineers
|
|
2
|
+
|
|
3
|
+
This program is free software; you can redistribute it and/or modify it under
|
|
4
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
5
|
+
Foundation; either version 2 of the License, or (at your option) any later
|
|
6
|
+
version.
|
|
7
|
+
|
|
8
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
9
|
+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
10
|
+
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
11
|
+
|
|
12
|
+
You should have received a copy of the GNU General Public License along with
|
|
13
|
+
this program; if not, see <https://www.gnu.org/licenses/gpl-2.0.html>.
|
|
14
|
+
|
|
15
|
+
SPDX-License-Identifier: GPL-2.0-or-later
|
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# gtfs-one-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server that turns any
|
|
4
|
+
[GTFS One](https://gtfs.one) transit site into a set of tools for AI
|
|
5
|
+
assistants. Point it at an agency's WordPress site and Claude, ChatGPT, or any
|
|
6
|
+
other MCP-compatible client can answer rider questions from the agency's **live**
|
|
7
|
+
GTFS and GTFS-Realtime data — nearest stops, next departures, routes, live bus
|
|
8
|
+
positions, and service alerts.
|
|
9
|
+
|
|
10
|
+
It talks to the site's public REST API (`/wp-json/gtfs-one/v1/*`) — no plugins,
|
|
11
|
+
no database access, no credentials required. One MCP server covers one transit site
|
|
12
|
+
(which may host multiple agency feeds).
|
|
13
|
+
|
|
14
|
+
> **Why this exists:** the only other transit MCP servers are single-agency,
|
|
15
|
+
> hard-coded hobby projects. This one works with *any* GTFS One install, driven
|
|
16
|
+
> entirely by configuration.
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
| Tool | What it does |
|
|
21
|
+
|------|--------------|
|
|
22
|
+
| `list_feeds` | List the transit feeds (agencies) configured on the site |
|
|
23
|
+
| `find_nearby_stops` | Nearest stops to a lat/lon, with serving routes and next departures |
|
|
24
|
+
| `search_stops` | Find stops by name, landmark, or stop code |
|
|
25
|
+
| `get_stop_departures` | Next departures from a specific stop, across all routes |
|
|
26
|
+
| `get_route_map` | A route's shape (path geometry) and the stops it serves |
|
|
27
|
+
| `get_system_map` | Every route in the system with its stops |
|
|
28
|
+
| `get_service_alerts` | Active GTFS-Realtime service alerts (empty = no active alerts) |
|
|
29
|
+
| `get_live_vehicles` | Real-time vehicle positions (empty = none reporting right now) |
|
|
30
|
+
| `geocode_address` | Address / place name → coordinates, biased to the service area |
|
|
31
|
+
|
|
32
|
+
The realtime tools (`get_service_alerts`, `get_live_vehicles`) **never error on
|
|
33
|
+
missing data** — an empty result means "nothing active right now," not "no
|
|
34
|
+
service." The tool descriptions tell the AI this explicitly.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Node.js 18 or newer
|
|
39
|
+
- A transit website running GTFS One 1.5+ with its REST API publicly reachable
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
Provide settings via a JSON config file **or** environment variables (env vars win
|
|
44
|
+
where both are set). Only the site URL is required.
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
// gtfs-one.config.json
|
|
48
|
+
{
|
|
49
|
+
"gtfs_one_url": "https://your-agency-site.org",
|
|
50
|
+
"feed_id": "default",
|
|
51
|
+
"cache_ttl_seconds": 30,
|
|
52
|
+
"agency_name": "Your Transit Agency",
|
|
53
|
+
"agency_description": "Public transit serving ... (cities, landmarks, region)."
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Setting | Env var | Default | Notes |
|
|
58
|
+
|---------|---------|---------|-------|
|
|
59
|
+
| `gtfs_one_url` | `GTFS_ONE_URL` | — (required) | Base URL of the GTFS One site |
|
|
60
|
+
| `feed_id` | `GTFS_ONE_FEED_ID` | `default` | Default feed so the AI needn't pass one each call |
|
|
61
|
+
| `cache_ttl_seconds` | `GTFS_ONE_CACHE_TTL` | `30` | Local response cache; protects the WP site |
|
|
62
|
+
| `agency_name` | `GTFS_ONE_AGENCY_NAME` | — | Shown in server metadata |
|
|
63
|
+
| `agency_description` | `GTFS_ONE_AGENCY_DESCRIPTION` | — | Service-area context for the AI |
|
|
64
|
+
|
|
65
|
+
Pass a non-default config path with `--config /path/to/config.json` or the
|
|
66
|
+
`GTFS_ONE_CONFIG` env var.
|
|
67
|
+
|
|
68
|
+
## Use with Claude Desktop
|
|
69
|
+
|
|
70
|
+
Edit `claude_desktop_config.json` (Settings → Developer → Edit Config) and add:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"gtfs-one-transit": {
|
|
76
|
+
"command": "npx",
|
|
77
|
+
"args": ["-y", "gtfs-one-mcp"],
|
|
78
|
+
"env": {
|
|
79
|
+
"GTFS_ONE_URL": "https://your-agency-site.org",
|
|
80
|
+
"GTFS_ONE_FEED_ID": "default",
|
|
81
|
+
"GTFS_ONE_AGENCY_NAME": "Your Transit Agency"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Restart Claude Desktop, then ask: *"What bus stops are near \<a place in the
|
|
89
|
+
service area\>?"* or *"When's the next bus from \<stop name\>?"*
|
|
90
|
+
|
|
91
|
+
> **Windows users:** Claude Desktop on Windows can't launch `npx` directly (it's a
|
|
92
|
+
> `.cmd` shim, not an executable), so the server silently fails to start and Claude
|
|
93
|
+
> falls back to web search. Wrap the command in `cmd` instead:
|
|
94
|
+
>
|
|
95
|
+
> ```json
|
|
96
|
+
> {
|
|
97
|
+
> "mcpServers": {
|
|
98
|
+
> "gtfs-one-transit": {
|
|
99
|
+
> "command": "cmd",
|
|
100
|
+
> "args": ["/c", "npx", "-y", "gtfs-one-mcp"],
|
|
101
|
+
> "env": {
|
|
102
|
+
> "GTFS_ONE_URL": "https://your-agency-site.org",
|
|
103
|
+
> "GTFS_ONE_FEED_ID": "default",
|
|
104
|
+
> "GTFS_ONE_AGENCY_NAME": "Your Transit Agency"
|
|
105
|
+
> }
|
|
106
|
+
> }
|
|
107
|
+
> }
|
|
108
|
+
> }
|
|
109
|
+
> ```
|
|
110
|
+
>
|
|
111
|
+
> After editing, fully **quit** Claude Desktop (right-click the system-tray icon →
|
|
112
|
+
> Quit — closing the window isn't enough) and reopen it.
|
|
113
|
+
|
|
114
|
+
## Use with ChatGPT / other MCP clients
|
|
115
|
+
|
|
116
|
+
Any client that launches a local stdio MCP server works the same way — run
|
|
117
|
+
`npx -y gtfs-one-mcp` with the same environment variables, or install globally:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install -g gtfs-one-mcp
|
|
121
|
+
GTFS_ONE_URL=https://your-agency-site.org gtfs-one-mcp
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Remote / hosted connector (Streamable HTTP)
|
|
125
|
+
|
|
126
|
+
The stdio setup above only works in clients that launch a **local process** (classic
|
|
127
|
+
Claude Desktop, Cursor, etc.). The **Claude apps that use remote connectors** — the
|
|
128
|
+
newer desktop app and **claude.ai on the web** — instead add an MCP server by **URL**.
|
|
129
|
+
For those, run the server in **HTTP mode** and host it somewhere with a public HTTPS
|
|
130
|
+
address; then add that URL as a custom connector.
|
|
131
|
+
|
|
132
|
+
Run it in HTTP mode:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
GTFS_ONE_URL=https://your-agency-site.org \
|
|
136
|
+
GTFS_ONE_AGENCY_NAME="Your Transit Agency" \
|
|
137
|
+
PORT=3000 \
|
|
138
|
+
gtfs-one-mcp-http # or: npm run start:http
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This serves the MCP endpoint at **`/mcp`** (and a `/healthz` check). Extra env:
|
|
142
|
+
|
|
143
|
+
| Env | Default | Notes |
|
|
144
|
+
|-----|---------|-------|
|
|
145
|
+
| `PORT` | `3000` | Most hosts inject this automatically |
|
|
146
|
+
| `MCP_AUTH_TOKEN` | — | Optional. If set, clients must send `Authorization: Bearer <token>`. Leave unset for an open server — the transit data is public. |
|
|
147
|
+
|
|
148
|
+
### Deploy
|
|
149
|
+
|
|
150
|
+
- **Docker:** a `Dockerfile` is included — `docker build -t gtfs-one-mcp . && docker run -p 3000:3000 -e GTFS_ONE_URL=… -e GTFS_ONE_AGENCY_NAME=… gtfs-one-mcp`
|
|
151
|
+
- **Render / Railway / Fly.io (Node):** build `npm install && npm run build`, start `npm run start:http`, set the env vars. A `render.yaml` blueprint is included; Render gives you HTTPS automatically.
|
|
152
|
+
- **Your own VPS:** run behind nginx/Caddy with TLS, proxying to the Node port.
|
|
153
|
+
|
|
154
|
+
The data is public and read-only, so an open endpoint is fine; add `MCP_AUTH_TOKEN`
|
|
155
|
+
if you'd rather gate it.
|
|
156
|
+
|
|
157
|
+
### Add it to Claude
|
|
158
|
+
|
|
159
|
+
In the Claude desktop app or claude.ai: **Settings → Connectors → Add custom
|
|
160
|
+
connector**, give it a name, and paste your server's URL ending in **`/mcp`**
|
|
161
|
+
(e.g. `https://gtfs-one-mcp.onrender.com/mcp`). Then ask a transit question and
|
|
162
|
+
you'll see the `gtfs-one-transit` tools used.
|
|
163
|
+
|
|
164
|
+
## Local development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm install
|
|
168
|
+
npm run build # compile TypeScript to dist/
|
|
169
|
+
node test/smoke.mjs # exercise all 9 tools against a live site
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## How it fits together
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Local (stdio):
|
|
176
|
+
AI client ──MCP/stdio──► gtfs-one-mcp ──HTTPS──► GTFS One REST API
|
|
177
|
+
(Desktop) (local process) /wp-json/gtfs-one/v1/*
|
|
178
|
+
|
|
179
|
+
Remote (Streamable HTTP):
|
|
180
|
+
AI client ──MCP/HTTPS──► gtfs-one-mcp-http ──HTTPS──► GTFS One REST API
|
|
181
|
+
(web/app) (hosted service /mcp) /wp-json/gtfs-one/v1/*
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Both transports share the same nine tools and config — pick stdio for local
|
|
185
|
+
clients (Claude Desktop, Cursor) or HTTP for connector-based clients (the Claude
|
|
186
|
+
app, claude.ai web, ChatGPT).
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
GPL-2.0-or-later © Digital Mountaineers
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for the GTFS One REST API.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Build namespaced URLs and inject the default feed_id.
|
|
6
|
+
* - Cache GET responses in-process for `cacheTtlSeconds` (spec §1.9: keep it to
|
|
7
|
+
* ~1 call per 30s per unique query so we never hammer the agency's WP site).
|
|
8
|
+
* - Turn failures into human-readable messages (spec §1.6) rather than leaking
|
|
9
|
+
* raw JSON or stack traces to the AI.
|
|
10
|
+
*/
|
|
11
|
+
import type { Config } from "./config.js";
|
|
12
|
+
/** A failure we want the AI to read as prose (not a thrown stack trace). */
|
|
13
|
+
export declare class GtfsApiError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class ApiClient {
|
|
17
|
+
private readonly config;
|
|
18
|
+
private readonly base;
|
|
19
|
+
private readonly cache;
|
|
20
|
+
constructor(config: Config);
|
|
21
|
+
/** The configured default feed, used when a tool call omits feed_id. */
|
|
22
|
+
get defaultFeedId(): string;
|
|
23
|
+
/**
|
|
24
|
+
* GET a REST path with query params. Empty/undefined params are dropped.
|
|
25
|
+
* Results are cached by full URL for the configured TTL.
|
|
26
|
+
*/
|
|
27
|
+
get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
|
|
28
|
+
/**
|
|
29
|
+
* POST a REST path with a JSON body. Not cached (plans are query-specific and
|
|
30
|
+
* cheap to recompute server-side). Same error-to-prose handling as get().
|
|
31
|
+
*/
|
|
32
|
+
post<T>(path: string, body: unknown): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Convert a non-2xx response into a readable sentence. WordPress REST errors
|
|
35
|
+
* carry `{ code, message }`; surface the message when present.
|
|
36
|
+
*/
|
|
37
|
+
private describeError;
|
|
38
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for the GTFS One REST API.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Build namespaced URLs and inject the default feed_id.
|
|
6
|
+
* - Cache GET responses in-process for `cacheTtlSeconds` (spec §1.9: keep it to
|
|
7
|
+
* ~1 call per 30s per unique query so we never hammer the agency's WP site).
|
|
8
|
+
* - Turn failures into human-readable messages (spec §1.6) rather than leaking
|
|
9
|
+
* raw JSON or stack traces to the AI.
|
|
10
|
+
*/
|
|
11
|
+
/** A failure we want the AI to read as prose (not a thrown stack trace). */
|
|
12
|
+
export class GtfsApiError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "GtfsApiError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class ApiClient {
|
|
19
|
+
config;
|
|
20
|
+
base;
|
|
21
|
+
cache = new Map();
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.base = `${config.gtfsOneUrl}/wp-json/gtfs-one/v1`;
|
|
25
|
+
}
|
|
26
|
+
/** The configured default feed, used when a tool call omits feed_id. */
|
|
27
|
+
get defaultFeedId() {
|
|
28
|
+
return this.config.feedId;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* GET a REST path with query params. Empty/undefined params are dropped.
|
|
32
|
+
* Results are cached by full URL for the configured TTL.
|
|
33
|
+
*/
|
|
34
|
+
async get(path, params = {}) {
|
|
35
|
+
const url = new URL(this.base + path);
|
|
36
|
+
for (const [key, value] of Object.entries(params)) {
|
|
37
|
+
if (value === undefined || value === "")
|
|
38
|
+
continue;
|
|
39
|
+
url.searchParams.set(key, String(value));
|
|
40
|
+
}
|
|
41
|
+
const href = url.toString();
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const hit = this.cache.get(href);
|
|
44
|
+
if (hit && hit.expires > now) {
|
|
45
|
+
return hit.data;
|
|
46
|
+
}
|
|
47
|
+
let res;
|
|
48
|
+
try {
|
|
49
|
+
res = await fetch(href, {
|
|
50
|
+
headers: { Accept: "application/json", "User-Agent": "gtfs-one-mcp" },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Network-level failure: the WP site is unreachable. Spec §1.6 exact intent.
|
|
55
|
+
throw new GtfsApiError(`The transit data service at ${this.config.gtfsOneUrl} is currently ` +
|
|
56
|
+
`unavailable. The rider should check the agency website directly.`);
|
|
57
|
+
}
|
|
58
|
+
const text = await res.text();
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = text ? JSON.parse(text) : null;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
parsed = null;
|
|
65
|
+
}
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
throw new GtfsApiError(this.describeError(res.status, parsed));
|
|
68
|
+
}
|
|
69
|
+
const ttlMs = this.config.cacheTtlSeconds * 1000;
|
|
70
|
+
if (ttlMs > 0) {
|
|
71
|
+
this.cache.set(href, { expires: now + ttlMs, data: parsed });
|
|
72
|
+
}
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* POST a REST path with a JSON body. Not cached (plans are query-specific and
|
|
77
|
+
* cheap to recompute server-side). Same error-to-prose handling as get().
|
|
78
|
+
*/
|
|
79
|
+
async post(path, body) {
|
|
80
|
+
let res;
|
|
81
|
+
try {
|
|
82
|
+
res = await fetch(this.base + path, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
Accept: "application/json",
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
"User-Agent": "gtfs-one-mcp",
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new GtfsApiError(`The transit data service at ${this.config.gtfsOneUrl} is currently ` +
|
|
94
|
+
`unavailable. The rider should check the agency website directly.`);
|
|
95
|
+
}
|
|
96
|
+
const raw = await res.text();
|
|
97
|
+
let parsed;
|
|
98
|
+
try {
|
|
99
|
+
parsed = raw ? JSON.parse(raw) : null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
parsed = null;
|
|
103
|
+
}
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new GtfsApiError(this.describeError(res.status, parsed));
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Convert a non-2xx response into a readable sentence. WordPress REST errors
|
|
111
|
+
* carry `{ code, message }`; surface the message when present.
|
|
112
|
+
*/
|
|
113
|
+
describeError(status, body) {
|
|
114
|
+
const wpMessage = body && typeof body === "object" && "message" in body
|
|
115
|
+
? String(body.message)
|
|
116
|
+
: "";
|
|
117
|
+
if (status === 404) {
|
|
118
|
+
return wpMessage
|
|
119
|
+
? `Not found: ${wpMessage}`
|
|
120
|
+
: "That transit record was not found. Double-check the stop or route id.";
|
|
121
|
+
}
|
|
122
|
+
if (status === 400) {
|
|
123
|
+
return wpMessage
|
|
124
|
+
? `Invalid request: ${wpMessage}`
|
|
125
|
+
: "The request was invalid. Check the parameters and try again.";
|
|
126
|
+
}
|
|
127
|
+
if (wpMessage) {
|
|
128
|
+
return `The transit data service returned an error (${status}): ${wpMessage}`;
|
|
129
|
+
}
|
|
130
|
+
return `The transit data service returned an unexpected error (HTTP ${status}).`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for the GTFS One MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (later wins where they overlap):
|
|
5
|
+
* 1. A JSON config file — path from `--config <path>`, else $GTFS_ONE_CONFIG,
|
|
6
|
+
* else ./gtfs-one.config.json if it exists.
|
|
7
|
+
* 2. Environment variables (GTFS_ONE_URL, GTFS_ONE_FEED_ID, ...).
|
|
8
|
+
*
|
|
9
|
+
* Only `gtfs_one_url` is strictly required. Everything else has a sane default
|
|
10
|
+
* so a single-agency setup can run with just the URL.
|
|
11
|
+
*/
|
|
12
|
+
export interface Config {
|
|
13
|
+
/** Base URL of the WordPress site running GTFS One (no trailing slash). */
|
|
14
|
+
gtfsOneUrl: string;
|
|
15
|
+
/** Default feed to use when the AI doesn't specify one. */
|
|
16
|
+
feedId: string;
|
|
17
|
+
/** Local response cache lifetime, in seconds (protects the WP site). */
|
|
18
|
+
cacheTtlSeconds: number;
|
|
19
|
+
/** Human label for the agency this server covers (used in server metadata). */
|
|
20
|
+
agencyName: string;
|
|
21
|
+
/** Free-text description of the agency's service area (used in metadata). */
|
|
22
|
+
agencyDescription: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function loadConfig(): Config;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for the GTFS One MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (later wins where they overlap):
|
|
5
|
+
* 1. A JSON config file — path from `--config <path>`, else $GTFS_ONE_CONFIG,
|
|
6
|
+
* else ./gtfs-one.config.json if it exists.
|
|
7
|
+
* 2. Environment variables (GTFS_ONE_URL, GTFS_ONE_FEED_ID, ...).
|
|
8
|
+
*
|
|
9
|
+
* Only `gtfs_one_url` is strictly required. Everything else has a sane default
|
|
10
|
+
* so a single-agency setup can run with just the URL.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
function readConfigFile() {
|
|
15
|
+
// --config <path> takes precedence, then $GTFS_ONE_CONFIG, then a local default.
|
|
16
|
+
const argIdx = process.argv.indexOf("--config");
|
|
17
|
+
const fromArg = argIdx !== -1 && process.argv[argIdx + 1] ? process.argv[argIdx + 1] : undefined;
|
|
18
|
+
const candidate = fromArg ||
|
|
19
|
+
process.env.GTFS_ONE_CONFIG ||
|
|
20
|
+
(existsSync("gtfs-one.config.json") ? "gtfs-one.config.json" : undefined);
|
|
21
|
+
if (!candidate)
|
|
22
|
+
return {};
|
|
23
|
+
const path = resolve(candidate);
|
|
24
|
+
if (!existsSync(path)) {
|
|
25
|
+
throw new Error(`Config file not found: ${path}`);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
throw new Error(`Could not parse config file ${path}: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function loadConfig() {
|
|
35
|
+
const file = readConfigFile();
|
|
36
|
+
const gtfsOneUrl = (process.env.GTFS_ONE_URL || file.gtfs_one_url || "").trim();
|
|
37
|
+
if (!gtfsOneUrl) {
|
|
38
|
+
throw new Error("Missing GTFS One site URL. Set `gtfs_one_url` in your config file or the " +
|
|
39
|
+
"GTFS_ONE_URL environment variable (e.g. https://your-agency-site.org).");
|
|
40
|
+
}
|
|
41
|
+
const ttlRaw = process.env.GTFS_ONE_CACHE_TTL ?? file.cache_ttl_seconds;
|
|
42
|
+
const cacheTtlSeconds = Number.isFinite(Number(ttlRaw)) ? Number(ttlRaw) : 30;
|
|
43
|
+
return {
|
|
44
|
+
// Strip any trailing slash so we can concatenate the REST namespace cleanly.
|
|
45
|
+
gtfsOneUrl: gtfsOneUrl.replace(/\/+$/, ""),
|
|
46
|
+
feedId: (process.env.GTFS_ONE_FEED_ID || file.feed_id || "default").trim(),
|
|
47
|
+
cacheTtlSeconds: cacheTtlSeconds >= 0 ? cacheTtlSeconds : 30,
|
|
48
|
+
agencyName: (process.env.GTFS_ONE_AGENCY_NAME ||
|
|
49
|
+
file.agency_name ||
|
|
50
|
+
"this transit agency").trim(),
|
|
51
|
+
agencyDescription: (process.env.GTFS_ONE_AGENCY_DESCRIPTION ||
|
|
52
|
+
file.agency_description ||
|
|
53
|
+
"").trim(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,SAAS,cAAc;IACrB,iFAAiF;IACjF,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,OAAO,GACX,MAAM,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,MAAM,SAAS,GACb,OAAO;QACP,OAAO,CAAC,GAAG,CAAC,eAAe;QAC3B,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAE5E,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAE1B,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAkB,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,+BAA+B,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CACjE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAE9B,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,2EAA2E;YACzE,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,iBAAiB,CAAC;IACxE,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9E,OAAO;QACL,6EAA6E;QAC7E,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1C,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC,IAAI,EAAE;QAC1E,eAAe,EAAE,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;QAC5D,UAAU,EAAE,CACV,OAAO,CAAC,GAAG,CAAC,oBAAoB;YAChC,IAAI,CAAC,WAAW;YAChB,qBAAqB,CACtB,CAAC,IAAI,EAAE;QACR,iBAAiB,EAAE,CACjB,OAAO,CAAC,GAAG,CAAC,2BAA2B;YACvC,IAAI,CAAC,kBAAkB;YACvB,EAAE,CACH,CAAC,IAAI,EAAE;KACT,CAAC;AACJ,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GTFS One MCP server — Streamable HTTP entry point (remote / hosted transport).
|
|
4
|
+
*
|
|
5
|
+
* This lets the server run as a public service that AI clients add as a "custom
|
|
6
|
+
* connector" by URL — Claude (desktop + web), ChatGPT, etc. — with no local
|
|
7
|
+
* install. It implements the MCP Streamable HTTP transport with per-client
|
|
8
|
+
* sessions, exposes a single `/mcp` endpoint (POST to send, GET for the SSE
|
|
9
|
+
* stream, DELETE to end a session), plus `/healthz`.
|
|
10
|
+
*
|
|
11
|
+
* Config is the same as the stdio server (GTFS_ONE_URL, etc.). Extra env:
|
|
12
|
+
* PORT - listen port (default 3000; most hosts inject this)
|
|
13
|
+
* MCP_AUTH_TOKEN - optional. If set, clients must send `Authorization:
|
|
14
|
+
* Bearer <token>`. Leave unset for an open server (the
|
|
15
|
+
* underlying transit data is public anyway).
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GTFS One MCP server — Streamable HTTP entry point (remote / hosted transport).
|
|
4
|
+
*
|
|
5
|
+
* This lets the server run as a public service that AI clients add as a "custom
|
|
6
|
+
* connector" by URL — Claude (desktop + web), ChatGPT, etc. — with no local
|
|
7
|
+
* install. It implements the MCP Streamable HTTP transport with per-client
|
|
8
|
+
* sessions, exposes a single `/mcp` endpoint (POST to send, GET for the SSE
|
|
9
|
+
* stream, DELETE to end a session), plus `/healthz`.
|
|
10
|
+
*
|
|
11
|
+
* Config is the same as the stdio server (GTFS_ONE_URL, etc.). Extra env:
|
|
12
|
+
* PORT - listen port (default 3000; most hosts inject this)
|
|
13
|
+
* MCP_AUTH_TOKEN - optional. If set, clients must send `Authorization:
|
|
14
|
+
* Bearer <token>`. Leave unset for an open server (the
|
|
15
|
+
* underlying transit data is public anyway).
|
|
16
|
+
*/
|
|
17
|
+
import express from "express";
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
20
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
+
import { loadConfig } from "./config.js";
|
|
22
|
+
import { ApiClient } from "./api-client.js";
|
|
23
|
+
import { createServer } from "./server.js";
|
|
24
|
+
const config = loadConfig();
|
|
25
|
+
const api = new ApiClient(config); // shared across sessions → shared response cache
|
|
26
|
+
const PORT = Number(process.env.PORT || 3000);
|
|
27
|
+
const AUTH = (process.env.MCP_AUTH_TOKEN || "").trim();
|
|
28
|
+
const app = express();
|
|
29
|
+
app.use(express.json({ limit: "1mb" }));
|
|
30
|
+
// CORS — required for browser-based MCP clients (e.g. claude.ai web). The
|
|
31
|
+
// session id travels in the `mcp-session-id` header, so it must be allowed and
|
|
32
|
+
// exposed. Data is public, so origin is open.
|
|
33
|
+
app.use((req, res, next) => {
|
|
34
|
+
res.setHeader("Access-Control-Allow-Origin", req.headers.origin || "*");
|
|
35
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
36
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, mcp-session-id, mcp-protocol-version, last-event-id");
|
|
37
|
+
res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
|
|
38
|
+
res.setHeader("Vary", "Origin");
|
|
39
|
+
if (req.method === "OPTIONS") {
|
|
40
|
+
res.sendStatus(204);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
next();
|
|
44
|
+
});
|
|
45
|
+
/** Optional bearer-token gate. Returns true if the request may proceed. */
|
|
46
|
+
function authorized(req, res) {
|
|
47
|
+
if (!AUTH)
|
|
48
|
+
return true;
|
|
49
|
+
if (req.headers.authorization === `Bearer ${AUTH}`)
|
|
50
|
+
return true;
|
|
51
|
+
res.status(401).json({
|
|
52
|
+
jsonrpc: "2.0",
|
|
53
|
+
error: { code: -32001, message: "Unauthorized" },
|
|
54
|
+
id: null,
|
|
55
|
+
});
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
app.get("/healthz", (_req, res) => {
|
|
59
|
+
res.json({ ok: true, agency: config.agencyName, site: config.gtfsOneUrl });
|
|
60
|
+
});
|
|
61
|
+
app.get("/", (_req, res) => {
|
|
62
|
+
res.type("text/plain").send(`gtfs-one-mcp (Streamable HTTP) for ${config.agencyName}. ` +
|
|
63
|
+
`MCP endpoint: POST ${"/mcp"}. Add this server's URL as a custom connector in your AI client.`);
|
|
64
|
+
});
|
|
65
|
+
// Live sessions, keyed by the transport's session id.
|
|
66
|
+
const transports = new Map();
|
|
67
|
+
const sid = (req) => {
|
|
68
|
+
const h = req.headers["mcp-session-id"];
|
|
69
|
+
return Array.isArray(h) ? h[0] : h;
|
|
70
|
+
};
|
|
71
|
+
// POST /mcp — client → server messages. An initialize request with no session
|
|
72
|
+
// id spins up a fresh session+server; everything else must carry a known id.
|
|
73
|
+
app.post("/mcp", async (req, res) => {
|
|
74
|
+
if (!authorized(req, res))
|
|
75
|
+
return;
|
|
76
|
+
const id = sid(req);
|
|
77
|
+
let transport = id ? transports.get(id) : undefined;
|
|
78
|
+
if (!transport) {
|
|
79
|
+
if (id || !isInitializeRequest(req.body)) {
|
|
80
|
+
res.status(400).json({
|
|
81
|
+
jsonrpc: "2.0",
|
|
82
|
+
error: { code: -32000, message: "Bad Request: no valid session ID for a non-initialize request" },
|
|
83
|
+
id: null,
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
transport = new StreamableHTTPServerTransport({
|
|
88
|
+
sessionIdGenerator: () => randomUUID(),
|
|
89
|
+
onsessioninitialized: (newId) => {
|
|
90
|
+
transports.set(newId, transport);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
transport.onclose = () => {
|
|
94
|
+
if (transport.sessionId)
|
|
95
|
+
transports.delete(transport.sessionId);
|
|
96
|
+
};
|
|
97
|
+
const server = createServer(config, api);
|
|
98
|
+
await server.connect(transport);
|
|
99
|
+
}
|
|
100
|
+
await transport.handleRequest(req, res, req.body);
|
|
101
|
+
});
|
|
102
|
+
// GET /mcp (open the SSE stream) and DELETE /mcp (end the session) both operate
|
|
103
|
+
// on an existing session.
|
|
104
|
+
async function existingSession(req, res) {
|
|
105
|
+
if (!authorized(req, res))
|
|
106
|
+
return;
|
|
107
|
+
const id = sid(req);
|
|
108
|
+
const transport = id ? transports.get(id) : undefined;
|
|
109
|
+
if (!transport) {
|
|
110
|
+
res.status(400).send("Invalid or missing session ID");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await transport.handleRequest(req, res);
|
|
114
|
+
}
|
|
115
|
+
app.get("/mcp", existingSession);
|
|
116
|
+
app.delete("/mcp", existingSession);
|
|
117
|
+
app.listen(PORT, () => {
|
|
118
|
+
console.error(`gtfs-one-mcp ready (http) — listening on :${PORT}, agency: ${config.agencyName}, ` +
|
|
119
|
+
`site: ${config.gtfsOneUrl}${AUTH ? " [auth required]" : ""}`);
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GTFS One MCP server — stdio entry point.
|
|
4
|
+
*
|
|
5
|
+
* Loads configuration, builds the server, and speaks MCP over stdio (the
|
|
6
|
+
* transport Claude Desktop / Cursor / ChatGPT desktop use to launch a local
|
|
7
|
+
* server). For the hosted/remote transport see `http.ts`. All diagnostics go to
|
|
8
|
+
* stderr so they never corrupt the stdout JSON-RPC stream.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GTFS One MCP server — stdio entry point.
|
|
4
|
+
*
|
|
5
|
+
* Loads configuration, builds the server, and speaks MCP over stdio (the
|
|
6
|
+
* transport Claude Desktop / Cursor / ChatGPT desktop use to launch a local
|
|
7
|
+
* server). For the hosted/remote transport see `http.ts`. All diagnostics go to
|
|
8
|
+
* stderr so they never corrupt the stdout JSON-RPC stream.
|
|
9
|
+
*/
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { loadConfig } from "./config.js";
|
|
12
|
+
import { createServer } from "./server.js";
|
|
13
|
+
async function main() {
|
|
14
|
+
const config = loadConfig();
|
|
15
|
+
const server = createServer(config);
|
|
16
|
+
const transport = new StdioServerTransport();
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
// Visible in the client's MCP logs; harmless on stderr.
|
|
19
|
+
console.error(`gtfs-one-mcp ready (stdio) — agency: ${config.agencyName}, site: ${config.gtfsOneUrl}, ` +
|
|
20
|
+
`default feed: ${config.feedId}`);
|
|
21
|
+
}
|
|
22
|
+
main().catch((err) => {
|
|
23
|
+
console.error(`gtfs-one-mcp failed to start: ${err.message}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,wDAAwD;IACxD,OAAO,CAAC,KAAK,CACX,wCAAwC,MAAM,CAAC,UAAU,WAAW,MAAM,CAAC,UAAU,IAAI;QACvF,iBAAiB,MAAM,CAAC,MAAM,EAAE,CACnC,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,iCAAkC,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|