@striderlabs/mcp-spothero 1.0.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/dist/index.js +21266 -0
- package/dist/index.js.map +7 -0
- package/esbuild.config.js +23 -0
- package/package.json +25 -0
- package/src/index.ts +573 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { build } from "esbuild";
|
|
2
|
+
|
|
3
|
+
const isWatch = process.argv.includes("--watch");
|
|
4
|
+
|
|
5
|
+
const ctx = await build({
|
|
6
|
+
entryPoints: ["src/index.ts"],
|
|
7
|
+
bundle: true,
|
|
8
|
+
platform: "node",
|
|
9
|
+
target: "node18",
|
|
10
|
+
format: "esm",
|
|
11
|
+
outfile: "dist/index.js",
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
minify: false,
|
|
14
|
+
banner: {
|
|
15
|
+
js: "#!/usr/bin/env node",
|
|
16
|
+
},
|
|
17
|
+
external: [],
|
|
18
|
+
logLevel: "info",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!isWatch) {
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@striderlabs/mcp-spothero",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for SpotHero parking reservations",
|
|
5
|
+
"author": "Strider Labs <hello@striderlabs.ai>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"striderlabs-mcp-spothero": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "node esbuild.config.js",
|
|
13
|
+
"dev": "node esbuild.config.js --watch",
|
|
14
|
+
"start": "node dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"esbuild": "^0.24.0",
|
|
22
|
+
"typescript": "^5.6.0"
|
|
23
|
+
},
|
|
24
|
+
"mcpName": "io.github.striderlabs/spothero"
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
Tool,
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// --- Types ---
|
|
12
|
+
|
|
13
|
+
interface SpotHeroConfig {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
authToken?: string;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SearchParkingArgs {
|
|
20
|
+
location: string;
|
|
21
|
+
starts: string;
|
|
22
|
+
ends: string;
|
|
23
|
+
radius_miles?: number;
|
|
24
|
+
vehicle_type?: "car" | "motorcycle" | "suv";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface GetSpotDetailsArgs {
|
|
28
|
+
facility_id: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface GetAvailabilityArgs {
|
|
32
|
+
facility_id: string;
|
|
33
|
+
starts: string;
|
|
34
|
+
ends: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ReserveSpotArgs {
|
|
38
|
+
rate_id: string;
|
|
39
|
+
facility_id: string;
|
|
40
|
+
starts: string;
|
|
41
|
+
ends: string;
|
|
42
|
+
vehicle?: {
|
|
43
|
+
make?: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
color?: string;
|
|
46
|
+
license_plate?: string;
|
|
47
|
+
state?: string;
|
|
48
|
+
};
|
|
49
|
+
payment_method_id?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface CancelReservationArgs {
|
|
53
|
+
reservation_id: string;
|
|
54
|
+
reason?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface GetReservationsArgs {
|
|
58
|
+
status?: "upcoming" | "past" | "cancelled" | "all";
|
|
59
|
+
limit?: number;
|
|
60
|
+
offset?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface GetNearbySpotsArgs {
|
|
64
|
+
lat: number;
|
|
65
|
+
lng: number;
|
|
66
|
+
radius_miles?: number;
|
|
67
|
+
starts?: string;
|
|
68
|
+
ends?: string;
|
|
69
|
+
limit?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface GetMonthlyParkingArgs {
|
|
73
|
+
location: string;
|
|
74
|
+
radius_miles?: number;
|
|
75
|
+
vehicle_type?: "car" | "motorcycle" | "suv";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface GetEventParkingArgs {
|
|
79
|
+
event_id?: string;
|
|
80
|
+
venue_name?: string;
|
|
81
|
+
event_date?: string;
|
|
82
|
+
radius_miles?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- SpotHero API Client ---
|
|
86
|
+
|
|
87
|
+
const BASE_URL = "https://api.spothero.com/v2";
|
|
88
|
+
|
|
89
|
+
function getConfig(): SpotHeroConfig {
|
|
90
|
+
const apiKey = process.env.SPOTHERO_API_KEY ?? "";
|
|
91
|
+
const authToken = process.env.SPOTHERO_AUTH_TOKEN;
|
|
92
|
+
return {
|
|
93
|
+
apiKey,
|
|
94
|
+
authToken,
|
|
95
|
+
baseUrl: process.env.SPOTHERO_BASE_URL ?? BASE_URL,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildHeaders(config: SpotHeroConfig): Record<string, string> {
|
|
100
|
+
const headers: Record<string, string> = {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
Accept: "application/json",
|
|
103
|
+
"X-SpotHero-Api-Key": config.apiKey,
|
|
104
|
+
"User-Agent": "SpotHero-MCP/1.0",
|
|
105
|
+
};
|
|
106
|
+
if (config.authToken) {
|
|
107
|
+
headers["Authorization"] = `Bearer ${config.authToken}`;
|
|
108
|
+
}
|
|
109
|
+
return headers;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function apiGet(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<unknown> {
|
|
113
|
+
const config = getConfig();
|
|
114
|
+
const url = new URL(`${config.baseUrl}${path}`);
|
|
115
|
+
if (params) {
|
|
116
|
+
for (const [key, value] of Object.entries(params)) {
|
|
117
|
+
if (value !== undefined) {
|
|
118
|
+
url.searchParams.set(key, String(value));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const response = await fetch(url.toString(), {
|
|
123
|
+
method: "GET",
|
|
124
|
+
headers: buildHeaders(config),
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const errorText = await response.text();
|
|
128
|
+
throw new Error(`SpotHero API error ${response.status}: ${errorText}`);
|
|
129
|
+
}
|
|
130
|
+
return response.json();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function apiPost(path: string, body: unknown): Promise<unknown> {
|
|
134
|
+
const config = getConfig();
|
|
135
|
+
const response = await fetch(`${config.baseUrl}${path}`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: buildHeaders(config),
|
|
138
|
+
body: JSON.stringify(body),
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
const errorText = await response.text();
|
|
142
|
+
throw new Error(`SpotHero API error ${response.status}: ${errorText}`);
|
|
143
|
+
}
|
|
144
|
+
return response.json();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function apiDelete(path: string, body?: unknown): Promise<unknown> {
|
|
148
|
+
const config = getConfig();
|
|
149
|
+
const response = await fetch(`${config.baseUrl}${path}`, {
|
|
150
|
+
method: "DELETE",
|
|
151
|
+
headers: buildHeaders(config),
|
|
152
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const errorText = await response.text();
|
|
156
|
+
throw new Error(`SpotHero API error ${response.status}: ${errorText}`);
|
|
157
|
+
}
|
|
158
|
+
if (response.status === 204) return { success: true };
|
|
159
|
+
return response.json();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- Tool Handlers ---
|
|
163
|
+
|
|
164
|
+
async function searchParking(args: SearchParkingArgs): Promise<unknown> {
|
|
165
|
+
return apiGet("/search/transient", {
|
|
166
|
+
location: args.location,
|
|
167
|
+
starts: args.starts,
|
|
168
|
+
ends: args.ends,
|
|
169
|
+
radius: args.radius_miles ?? 1.0,
|
|
170
|
+
vehicle_type: args.vehicle_type ?? "car",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function getSpotDetails(args: GetSpotDetailsArgs): Promise<unknown> {
|
|
175
|
+
return apiGet(`/facilities/${args.facility_id}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function getAvailability(args: GetAvailabilityArgs): Promise<unknown> {
|
|
179
|
+
return apiGet(`/facilities/${args.facility_id}/availability`, {
|
|
180
|
+
starts: args.starts,
|
|
181
|
+
ends: args.ends,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function reserveSpot(args: ReserveSpotArgs): Promise<unknown> {
|
|
186
|
+
return apiPost("/reservations", {
|
|
187
|
+
rate_id: args.rate_id,
|
|
188
|
+
facility_id: args.facility_id,
|
|
189
|
+
starts: args.starts,
|
|
190
|
+
ends: args.ends,
|
|
191
|
+
vehicle: args.vehicle ?? {},
|
|
192
|
+
payment_method_id: args.payment_method_id,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function cancelReservation(args: CancelReservationArgs): Promise<unknown> {
|
|
197
|
+
return apiDelete(`/reservations/${args.reservation_id}`, {
|
|
198
|
+
reason: args.reason ?? "user_requested",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function getReservations(args: GetReservationsArgs): Promise<unknown> {
|
|
203
|
+
return apiGet("/reservations", {
|
|
204
|
+
status: args.status ?? "all",
|
|
205
|
+
limit: args.limit ?? 20,
|
|
206
|
+
offset: args.offset ?? 0,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function getNearbySpots(args: GetNearbySpotsArgs): Promise<unknown> {
|
|
211
|
+
return apiGet("/search/nearby", {
|
|
212
|
+
lat: args.lat,
|
|
213
|
+
lng: args.lng,
|
|
214
|
+
radius: args.radius_miles ?? 0.5,
|
|
215
|
+
starts: args.starts,
|
|
216
|
+
ends: args.ends,
|
|
217
|
+
limit: args.limit ?? 20,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function getMonthlyParking(args: GetMonthlyParkingArgs): Promise<unknown> {
|
|
222
|
+
return apiGet("/search/monthly", {
|
|
223
|
+
location: args.location,
|
|
224
|
+
radius: args.radius_miles ?? 1.0,
|
|
225
|
+
vehicle_type: args.vehicle_type ?? "car",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function getEventParking(args: GetEventParkingArgs): Promise<unknown> {
|
|
230
|
+
if (args.event_id) {
|
|
231
|
+
return apiGet(`/events/${args.event_id}/parking`);
|
|
232
|
+
}
|
|
233
|
+
return apiGet("/search/events", {
|
|
234
|
+
venue: args.venue_name,
|
|
235
|
+
event_date: args.event_date,
|
|
236
|
+
radius: args.radius_miles ?? 0.5,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// --- Tool Definitions ---
|
|
241
|
+
|
|
242
|
+
const TOOLS: Tool[] = [
|
|
243
|
+
{
|
|
244
|
+
name: "search_parking",
|
|
245
|
+
description:
|
|
246
|
+
"Search for parking spots near a location or address. Returns available facilities with rates and details.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
location: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: "Address, city, or landmark to search near (e.g. '200 W Madison St, Chicago, IL')",
|
|
253
|
+
},
|
|
254
|
+
starts: {
|
|
255
|
+
type: "string",
|
|
256
|
+
description: "Parking start time in ISO 8601 format (e.g. '2026-03-14T09:00:00')",
|
|
257
|
+
},
|
|
258
|
+
ends: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "Parking end time in ISO 8601 format (e.g. '2026-03-14T17:00:00')",
|
|
261
|
+
},
|
|
262
|
+
radius_miles: {
|
|
263
|
+
type: "number",
|
|
264
|
+
description: "Search radius in miles (default: 1.0)",
|
|
265
|
+
},
|
|
266
|
+
vehicle_type: {
|
|
267
|
+
type: "string",
|
|
268
|
+
enum: ["car", "motorcycle", "suv"],
|
|
269
|
+
description: "Type of vehicle (default: car)",
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
required: ["location", "starts", "ends"],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "get_spot_details",
|
|
277
|
+
description:
|
|
278
|
+
"Get detailed information about a specific parking facility including amenities, hours, entry/exit instructions, and contact info.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
facility_id: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "The SpotHero facility ID",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
required: ["facility_id"],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "get_availability",
|
|
292
|
+
description:
|
|
293
|
+
"Check real-time availability and pricing for a parking facility for specific dates and times.",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
facility_id: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "The SpotHero facility ID",
|
|
300
|
+
},
|
|
301
|
+
starts: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "Desired start time in ISO 8601 format",
|
|
304
|
+
},
|
|
305
|
+
ends: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "Desired end time in ISO 8601 format",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
required: ["facility_id", "starts", "ends"],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "reserve_spot",
|
|
315
|
+
description:
|
|
316
|
+
"Make a parking reservation at a SpotHero facility. Requires authentication and a valid payment method on file.",
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
rate_id: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "The rate ID from availability results",
|
|
323
|
+
},
|
|
324
|
+
facility_id: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "The SpotHero facility ID",
|
|
327
|
+
},
|
|
328
|
+
starts: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Parking start time in ISO 8601 format",
|
|
331
|
+
},
|
|
332
|
+
ends: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "Parking end time in ISO 8601 format",
|
|
335
|
+
},
|
|
336
|
+
vehicle: {
|
|
337
|
+
type: "object",
|
|
338
|
+
description: "Vehicle information",
|
|
339
|
+
properties: {
|
|
340
|
+
make: { type: "string" },
|
|
341
|
+
model: { type: "string" },
|
|
342
|
+
color: { type: "string" },
|
|
343
|
+
license_plate: { type: "string" },
|
|
344
|
+
state: { type: "string", description: "State abbreviation (e.g. 'IL')" },
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
payment_method_id: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "Payment method ID from user's account (uses default if omitted)",
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
required: ["rate_id", "facility_id", "starts", "ends"],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "cancel_reservation",
|
|
357
|
+
description:
|
|
358
|
+
"Cancel an existing parking reservation. Refund eligibility depends on the cancellation policy.",
|
|
359
|
+
inputSchema: {
|
|
360
|
+
type: "object",
|
|
361
|
+
properties: {
|
|
362
|
+
reservation_id: {
|
|
363
|
+
type: "string",
|
|
364
|
+
description: "The reservation ID to cancel",
|
|
365
|
+
},
|
|
366
|
+
reason: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "Optional cancellation reason",
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
required: ["reservation_id"],
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: "get_reservations",
|
|
376
|
+
description:
|
|
377
|
+
"List the user's parking reservations. Requires authentication. Can filter by status.",
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
status: {
|
|
382
|
+
type: "string",
|
|
383
|
+
enum: ["upcoming", "past", "cancelled", "all"],
|
|
384
|
+
description: "Filter by reservation status (default: all)",
|
|
385
|
+
},
|
|
386
|
+
limit: {
|
|
387
|
+
type: "number",
|
|
388
|
+
description: "Number of results to return (default: 20, max: 100)",
|
|
389
|
+
},
|
|
390
|
+
offset: {
|
|
391
|
+
type: "number",
|
|
392
|
+
description: "Pagination offset (default: 0)",
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: "get_nearby_spots",
|
|
399
|
+
description:
|
|
400
|
+
"Find parking spots near specific GPS coordinates. Useful when you have lat/lng rather than an address.",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
lat: {
|
|
405
|
+
type: "number",
|
|
406
|
+
description: "Latitude coordinate",
|
|
407
|
+
},
|
|
408
|
+
lng: {
|
|
409
|
+
type: "number",
|
|
410
|
+
description: "Longitude coordinate",
|
|
411
|
+
},
|
|
412
|
+
radius_miles: {
|
|
413
|
+
type: "number",
|
|
414
|
+
description: "Search radius in miles (default: 0.5)",
|
|
415
|
+
},
|
|
416
|
+
starts: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description: "Optional start time filter in ISO 8601 format",
|
|
419
|
+
},
|
|
420
|
+
ends: {
|
|
421
|
+
type: "string",
|
|
422
|
+
description: "Optional end time filter in ISO 8601 format",
|
|
423
|
+
},
|
|
424
|
+
limit: {
|
|
425
|
+
type: "number",
|
|
426
|
+
description: "Max results to return (default: 20)",
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
required: ["lat", "lng"],
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "get_monthly_parking",
|
|
434
|
+
description:
|
|
435
|
+
"Search for monthly parking passes near a location. Returns facilities offering monthly contracts.",
|
|
436
|
+
inputSchema: {
|
|
437
|
+
type: "object",
|
|
438
|
+
properties: {
|
|
439
|
+
location: {
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Address or area to search (e.g. 'downtown Chicago')",
|
|
442
|
+
},
|
|
443
|
+
radius_miles: {
|
|
444
|
+
type: "number",
|
|
445
|
+
description: "Search radius in miles (default: 1.0)",
|
|
446
|
+
},
|
|
447
|
+
vehicle_type: {
|
|
448
|
+
type: "string",
|
|
449
|
+
enum: ["car", "motorcycle", "suv"],
|
|
450
|
+
description: "Type of vehicle (default: car)",
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
required: ["location"],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "get_event_parking",
|
|
458
|
+
description:
|
|
459
|
+
"Find parking for a specific event or venue. Can search by SpotHero event ID or venue name and date.",
|
|
460
|
+
inputSchema: {
|
|
461
|
+
type: "object",
|
|
462
|
+
properties: {
|
|
463
|
+
event_id: {
|
|
464
|
+
type: "string",
|
|
465
|
+
description: "SpotHero event ID (if known)",
|
|
466
|
+
},
|
|
467
|
+
venue_name: {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "Venue or stadium name (e.g. 'Wrigley Field', 'United Center')",
|
|
470
|
+
},
|
|
471
|
+
event_date: {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "Date of the event in ISO 8601 format (e.g. '2026-04-01')",
|
|
474
|
+
},
|
|
475
|
+
radius_miles: {
|
|
476
|
+
type: "number",
|
|
477
|
+
description: "Search radius in miles (default: 0.5)",
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
// --- MCP Server Setup ---
|
|
485
|
+
|
|
486
|
+
const server = new Server(
|
|
487
|
+
{
|
|
488
|
+
name: "spothero",
|
|
489
|
+
version: "1.0.0",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
capabilities: {
|
|
493
|
+
tools: {},
|
|
494
|
+
},
|
|
495
|
+
}
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
499
|
+
tools: TOOLS,
|
|
500
|
+
}));
|
|
501
|
+
|
|
502
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
503
|
+
const { name, arguments: args } = request.params;
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
let result: unknown;
|
|
507
|
+
|
|
508
|
+
switch (name) {
|
|
509
|
+
case "search_parking":
|
|
510
|
+
result = await searchParking(args as SearchParkingArgs);
|
|
511
|
+
break;
|
|
512
|
+
case "get_spot_details":
|
|
513
|
+
result = await getSpotDetails(args as GetSpotDetailsArgs);
|
|
514
|
+
break;
|
|
515
|
+
case "get_availability":
|
|
516
|
+
result = await getAvailability(args as GetAvailabilityArgs);
|
|
517
|
+
break;
|
|
518
|
+
case "reserve_spot":
|
|
519
|
+
result = await reserveSpot(args as ReserveSpotArgs);
|
|
520
|
+
break;
|
|
521
|
+
case "cancel_reservation":
|
|
522
|
+
result = await cancelReservation(args as CancelReservationArgs);
|
|
523
|
+
break;
|
|
524
|
+
case "get_reservations":
|
|
525
|
+
result = await getReservations(args as GetReservationsArgs);
|
|
526
|
+
break;
|
|
527
|
+
case "get_nearby_spots":
|
|
528
|
+
result = await getNearbySpots(args as GetNearbySpotsArgs);
|
|
529
|
+
break;
|
|
530
|
+
case "get_monthly_parking":
|
|
531
|
+
result = await getMonthlyParking(args as GetMonthlyParkingArgs);
|
|
532
|
+
break;
|
|
533
|
+
case "get_event_parking":
|
|
534
|
+
result = await getEventParking(args as GetEventParkingArgs);
|
|
535
|
+
break;
|
|
536
|
+
default:
|
|
537
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
text: JSON.stringify(result, null, 2),
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
} catch (error) {
|
|
549
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
550
|
+
return {
|
|
551
|
+
content: [
|
|
552
|
+
{
|
|
553
|
+
type: "text",
|
|
554
|
+
text: `Error: ${message}`,
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
isError: true,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// --- Start ---
|
|
563
|
+
|
|
564
|
+
async function main() {
|
|
565
|
+
const transport = new StdioServerTransport();
|
|
566
|
+
await server.connect(transport);
|
|
567
|
+
console.error("SpotHero MCP server running on stdio");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
main().catch((err) => {
|
|
571
|
+
console.error("Fatal error:", err);
|
|
572
|
+
process.exit(1);
|
|
573
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"resolveJsonModule": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|