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 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":"AAAA;;;;;;;;;GASG;AAIH,4EAA4E;AAC5E,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAOD,MAAM,OAAO,SAAS;IAIS;IAHZ,IAAI,CAAS;IACb,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEvD,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;QACzC,IAAI,CAAC,IAAI,GAAG,GAAG,MAAM,CAAC,UAAU,sBAAsB,CAAC;IACzD,CAAC;IAED,wEAAwE;IACxE,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,IAAY,EAAE,SAAsD,EAAE;QACjF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;gBAAE,SAAS;YAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAE5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;YAC7B,OAAO,GAAG,CAAC,IAAS,CAAC;QACvB,CAAC;QAED,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE;gBACtB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE;aACtE,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,6EAA6E;YAC7E,MAAM,IAAI,YAAY,CACpB,+BAA+B,IAAI,CAAC,MAAM,CAAC,UAAU,gBAAgB;gBACnE,kEAAkE,CACrE,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,GAAG,IAAI,CAAC;QACjD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,MAAW,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAa;QACvC,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE;gBAClC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,MAAM,EAAE,kBAAkB;oBAC1B,cAAc,EAAE,kBAAkB;oBAClC,YAAY,EAAE,cAAc;iBAC7B;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,YAAY,CACpB,+BAA+B,IAAI,CAAC,MAAM,CAAC,UAAU,gBAAgB;gBACnE,kEAAkE,CACrE,CAAC;QACJ,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,MAAW,CAAC;IACrB,CAAC;IAED;;;OAGG;IACK,aAAa,CAAC,MAAc,EAAE,IAAa;QACjD,MAAM,SAAS,GACb,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,SAAS,IAAI,IAAI;YACnD,CAAC,CAAC,MAAM,CAAE,IAA6B,CAAC,OAAO,CAAC;YAChD,CAAC,CAAC,EAAE,CAAC;QAET,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,OAAO,SAAS;gBACd,CAAC,CAAC,cAAc,SAAS,EAAE;gBAC3B,CAAC,CAAC,uEAAuE,CAAC;QAC9E,CAAC;QACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,OAAO,SAAS;gBACd,CAAC,CAAC,oBAAoB,SAAS,EAAE;gBACjC,CAAC,CAAC,8DAA8D,CAAC;QACrE,CAAC;QACD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,+CAA+C,MAAM,MAAM,SAAS,EAAE,CAAC;QAChF,CAAC;QACD,OAAO,+DAA+D,MAAM,IAAI,CAAC;IACnF,CAAC;CACF"}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -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"}