@striderlabs/mcp-shakeshack 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/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@striderlabs/mcp-shakeshack",
3
+ "version": "1.0.0",
4
+ "description": "MCP connector for Shake Shack restaurant",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "mcp-shakeshack": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js",
11
+ "dev": "tsx src/index.ts"
12
+ },
13
+ "keywords": ["mcp", "shakeshack", "restaurant", "modelcontextprotocol"],
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "esbuild": "^0.24.0",
20
+ "typescript": "^5.0.0",
21
+ "@types/node": "^20.0.0"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+
9
+ const server = new Server(
10
+ {
11
+ name: "mcp-shakeshack",
12
+ version: "1.0.0",
13
+ },
14
+ {
15
+ capabilities: {
16
+ tools: {},
17
+ },
18
+ }
19
+ );
20
+
21
+ // Static data for Shake Shack menu categories and popular items
22
+ const MENU_CATEGORIES = [
23
+ "Burgers",
24
+ "Chicken",
25
+ "Hot Dogs",
26
+ "Crinkle Cut Fries",
27
+ "Frozen Custard",
28
+ "Shakes & Floats",
29
+ "Beer & Wine",
30
+ "Beverages",
31
+ ];
32
+
33
+ const MENU_ITEMS: Record<string, { description: string; price: string }[]> = {
34
+ Burgers: [
35
+ { description: "ShackBurger - cheeseburger topped with ShackSauce", price: "$6.89" },
36
+ { description: "SmokeShack - cheeseburger with applewood-smoked bacon and cherry peppers", price: "$8.89" },
37
+ { description: "Shack Stack - cheeseburger with crispy fried portobello mushroom", price: "$10.89" },
38
+ { description: "'Shroom Burger - crispy-fried portobello mushroom (vegetarian)", price: "$9.89" },
39
+ { description: "Double ShackBurger - double patty ShackBurger", price: "$9.89" },
40
+ ],
41
+ Chicken: [
42
+ { description: "Chick'n Shack - crispy chicken sandwich with pickles and ShackSauce", price: "$8.89" },
43
+ { description: "Spicy Chick'n Shack - spicy version with cherry peppers", price: "$9.89" },
44
+ { description: "Chicken Bites - crispy bite-sized pieces", price: "$6.89" },
45
+ ],
46
+ "Hot Dogs": [
47
+ { description: "Shack-cago Dog - Vienna beef hot dog with classic toppings", price: "$5.89" },
48
+ { description: "Smoke Shack Dog - hot dog with bacon and cherry peppers", price: "$6.89" },
49
+ ],
50
+ "Crinkle Cut Fries": [
51
+ { description: "Fries - classic crinkle cut fries", price: "$3.89" },
52
+ { description: "Cheese Fries - fries topped with cheddar cheese sauce", price: "$4.89" },
53
+ { description: "Bacon Cheese Fries - cheese fries with applewood-smoked bacon", price: "$5.89" },
54
+ ],
55
+ "Frozen Custard": [
56
+ { description: "Custard of the Day - rotating daily flavors", price: "$4.89" },
57
+ { description: "Vanilla Custard - hand-spun pure vanilla", price: "$4.39" },
58
+ { description: "Chocolate Custard - rich chocolate custard", price: "$4.39" },
59
+ ],
60
+ "Shakes & Floats": [
61
+ { description: "Vanilla Shake", price: "$6.89" },
62
+ { description: "Chocolate Shake", price: "$6.89" },
63
+ { description: "Strawberry Shake", price: "$6.89" },
64
+ { description: "Black & White Shake - vanilla and chocolate blend", price: "$6.89" },
65
+ { description: "Root Beer Float - house-made frozen custard with root beer", price: "$5.89" },
66
+ ],
67
+ Beverages: [
68
+ { description: "Shack-made Lemonade", price: "$3.89" },
69
+ { description: "Shack-made Pink Lemonade", price: "$3.89" },
70
+ { description: "Iced Tea", price: "$3.39" },
71
+ { description: "Water", price: "$0.00" },
72
+ ],
73
+ };
74
+
75
+ // Sample locations data
76
+ const SAMPLE_LOCATIONS = [
77
+ {
78
+ id: "madison-square-park",
79
+ name: "Madison Square Park",
80
+ address: "Madison Ave & E 23rd St, New York, NY 10010",
81
+ phone: "(646) 889-6600",
82
+ hours: "Sun-Thu 11am-10pm, Fri-Sat 11am-11pm",
83
+ coordinates: { lat: 40.7412, lng: -73.9878 },
84
+ features: ["Outdoor Seating", "Mobile Order"],
85
+ },
86
+ {
87
+ id: "upper-west-side",
88
+ name: "Upper West Side",
89
+ address: "366 Columbus Ave, New York, NY 10024",
90
+ phone: "(646) 747-8770",
91
+ hours: "Daily 11am-11pm",
92
+ coordinates: { lat: 40.7795, lng: -73.9817 },
93
+ features: ["Indoor Seating", "Mobile Order", "Beer & Wine"],
94
+ },
95
+ {
96
+ id: "grand-central",
97
+ name: "Grand Central",
98
+ address: "Grand Central Terminal, New York, NY 10017",
99
+ phone: "(646) 747-8770",
100
+ hours: "Mon-Fri 7am-10pm, Sat-Sun 10am-9pm",
101
+ coordinates: { lat: 40.7527, lng: -73.9772 },
102
+ features: ["Indoor Seating", "Mobile Order"],
103
+ },
104
+ {
105
+ id: "fulton-center",
106
+ name: "Fulton Center",
107
+ address: "200 Broadway, New York, NY 10038",
108
+ phone: "(646) 747-8770",
109
+ hours: "Mon-Fri 7am-9pm, Sat-Sun 10am-8pm",
110
+ coordinates: { lat: 40.7097, lng: -74.0071 },
111
+ features: ["Indoor Seating", "Mobile Order"],
112
+ },
113
+ ];
114
+
115
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
116
+ return {
117
+ tools: [
118
+ {
119
+ name: "get_menu",
120
+ description: "Get the Shake Shack menu. Can return all categories or a specific category.",
121
+ inputSchema: {
122
+ type: "object",
123
+ properties: {
124
+ category: {
125
+ type: "string",
126
+ description: `Optional menu category to filter by. Available: ${MENU_CATEGORIES.join(", ")}`,
127
+ },
128
+ },
129
+ },
130
+ },
131
+ {
132
+ name: "search_menu",
133
+ description: "Search the Shake Shack menu for items matching a query.",
134
+ inputSchema: {
135
+ type: "object",
136
+ properties: {
137
+ query: {
138
+ type: "string",
139
+ description: "Search term to find menu items (e.g. 'bacon', 'vegetarian', 'chicken')",
140
+ },
141
+ },
142
+ required: ["query"],
143
+ },
144
+ },
145
+ {
146
+ name: "find_locations",
147
+ description: "Find Shake Shack restaurant locations. Can search by city, state, or keyword.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ query: {
152
+ type: "string",
153
+ description: "Location search query (city, neighborhood, state, or zip code)",
154
+ },
155
+ limit: {
156
+ type: "number",
157
+ description: "Maximum number of results to return (default: 5)",
158
+ },
159
+ },
160
+ },
161
+ },
162
+ {
163
+ name: "get_location_details",
164
+ description: "Get detailed information about a specific Shake Shack location by ID.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ location_id: {
169
+ type: "string",
170
+ description: "The location ID (e.g. 'madison-square-park')",
171
+ },
172
+ },
173
+ required: ["location_id"],
174
+ },
175
+ },
176
+ {
177
+ name: "get_nutrition_info",
178
+ description: "Get nutritional information and allergen details for Shake Shack menu items.",
179
+ inputSchema: {
180
+ type: "object",
181
+ properties: {
182
+ item_name: {
183
+ type: "string",
184
+ description: "Name of the menu item to get nutrition info for (e.g. 'ShackBurger', 'Fries')",
185
+ },
186
+ },
187
+ required: ["item_name"],
188
+ },
189
+ },
190
+ {
191
+ name: "get_featured_items",
192
+ description: "Get current featured, seasonal, or limited-time menu items at Shake Shack.",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: {},
196
+ },
197
+ },
198
+ {
199
+ name: "get_ordering_info",
200
+ description: "Get information about how to order at Shake Shack (app, kiosk, online, etc.).",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {},
204
+ },
205
+ },
206
+ ],
207
+ };
208
+ });
209
+
210
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
211
+ const { name, arguments: args } = request.params;
212
+
213
+ switch (name) {
214
+ case "get_menu": {
215
+ const category = (args as { category?: string })?.category;
216
+ if (category) {
217
+ const normalizedCategory = MENU_CATEGORIES.find(
218
+ (c) => c.toLowerCase() === category.toLowerCase()
219
+ );
220
+ if (!normalizedCategory) {
221
+ return {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: `Category "${category}" not found. Available categories: ${MENU_CATEGORIES.join(", ")}`,
226
+ },
227
+ ],
228
+ };
229
+ }
230
+ const items = MENU_ITEMS[normalizedCategory] || [];
231
+ const itemList = items
232
+ .map((item) => `- **${item.description}** — ${item.price}`)
233
+ .join("\n");
234
+ return {
235
+ content: [
236
+ {
237
+ type: "text",
238
+ text: `## Shake Shack — ${normalizedCategory}\n\n${itemList}`,
239
+ },
240
+ ],
241
+ };
242
+ }
243
+
244
+ // Return full menu
245
+ const fullMenu = MENU_CATEGORIES.map((cat) => {
246
+ const items = MENU_ITEMS[cat] || [];
247
+ const itemList = items
248
+ .map((item) => ` - ${item.description} — ${item.price}`)
249
+ .join("\n");
250
+ return `### ${cat}\n${itemList}`;
251
+ }).join("\n\n");
252
+
253
+ return {
254
+ content: [
255
+ {
256
+ type: "text",
257
+ text: `## Shake Shack Menu\n\n${fullMenu}\n\n_Prices and availability may vary by location._`,
258
+ },
259
+ ],
260
+ };
261
+ }
262
+
263
+ case "search_menu": {
264
+ const query = ((args as { query: string })?.query || "").toLowerCase();
265
+ const results: { category: string; item: { description: string; price: string } }[] = [];
266
+
267
+ for (const [category, items] of Object.entries(MENU_ITEMS)) {
268
+ for (const item of items) {
269
+ if (item.description.toLowerCase().includes(query)) {
270
+ results.push({ category, item });
271
+ }
272
+ }
273
+ }
274
+
275
+ if (results.length === 0) {
276
+ return {
277
+ content: [
278
+ {
279
+ type: "text",
280
+ text: `No menu items found matching "${query}". Try searching for: burger, chicken, fries, shake, custard, bacon, vegetarian.`,
281
+ },
282
+ ],
283
+ };
284
+ }
285
+
286
+ const resultText = results
287
+ .map((r) => `- **${r.item.description}** (${r.category}) — ${r.item.price}`)
288
+ .join("\n");
289
+
290
+ return {
291
+ content: [
292
+ {
293
+ type: "text",
294
+ text: `## Search Results for "${query}"\n\n${resultText}`,
295
+ },
296
+ ],
297
+ };
298
+ }
299
+
300
+ case "find_locations": {
301
+ const query = ((args as { query?: string; limit?: number })?.query || "").toLowerCase();
302
+ const limit = (args as { query?: string; limit?: number })?.limit || 5;
303
+
304
+ let locations = SAMPLE_LOCATIONS;
305
+ if (query) {
306
+ locations = SAMPLE_LOCATIONS.filter(
307
+ (loc) =>
308
+ loc.name.toLowerCase().includes(query) ||
309
+ loc.address.toLowerCase().includes(query)
310
+ );
311
+ }
312
+
313
+ locations = locations.slice(0, limit);
314
+
315
+ if (locations.length === 0) {
316
+ return {
317
+ content: [
318
+ {
319
+ type: "text",
320
+ text: `No Shake Shack locations found matching "${query}". Note: This connector includes sample NYC locations. For a complete store finder, visit shakeshack.com/locations.`,
321
+ },
322
+ ],
323
+ };
324
+ }
325
+
326
+ const locationText = locations
327
+ .map(
328
+ (loc) =>
329
+ `### ${loc.name} (ID: ${loc.id})\n` +
330
+ `- **Address:** ${loc.address}\n` +
331
+ `- **Phone:** ${loc.phone}\n` +
332
+ `- **Hours:** ${loc.hours}\n` +
333
+ `- **Features:** ${loc.features.join(", ")}`
334
+ )
335
+ .join("\n\n");
336
+
337
+ return {
338
+ content: [
339
+ {
340
+ type: "text",
341
+ text: `## Shake Shack Locations\n\n${locationText}\n\n_For all locations worldwide, visit shakeshack.com/locations_`,
342
+ },
343
+ ],
344
+ };
345
+ }
346
+
347
+ case "get_location_details": {
348
+ const locationId = (args as { location_id: string })?.location_id;
349
+ const location = SAMPLE_LOCATIONS.find((loc) => loc.id === locationId);
350
+
351
+ if (!location) {
352
+ const ids = SAMPLE_LOCATIONS.map((l) => l.id).join(", ");
353
+ return {
354
+ content: [
355
+ {
356
+ type: "text",
357
+ text: `Location "${locationId}" not found. Available sample location IDs: ${ids}`,
358
+ },
359
+ ],
360
+ };
361
+ }
362
+
363
+ return {
364
+ content: [
365
+ {
366
+ type: "text",
367
+ text:
368
+ `## ${location.name}\n\n` +
369
+ `- **Address:** ${location.address}\n` +
370
+ `- **Phone:** ${location.phone}\n` +
371
+ `- **Hours:** ${location.hours}\n` +
372
+ `- **Features:** ${location.features.join(", ")}\n` +
373
+ `- **Coordinates:** ${location.coordinates.lat}, ${location.coordinates.lng}\n` +
374
+ `- **Directions:** https://maps.google.com/?q=${encodeURIComponent(location.address)}`,
375
+ },
376
+ ],
377
+ };
378
+ }
379
+
380
+ case "get_nutrition_info": {
381
+ const itemName = ((args as { item_name: string })?.item_name || "").toLowerCase();
382
+
383
+ const nutritionData: Record<string, { calories: number; fat: string; protein: string; carbs: string; allergens: string[] }> = {
384
+ shackburger: { calories: 530, fat: "29g", protein: "26g", carbs: "41g", allergens: ["Wheat", "Milk", "Egg", "Soy"] },
385
+ smokeshack: { calories: 620, fat: "37g", protein: "32g", carbs: "41g", allergens: ["Wheat", "Milk", "Egg", "Soy"] },
386
+ "shack stack": { calories: 770, fat: "46g", protein: "35g", carbs: "60g", allergens: ["Wheat", "Milk", "Egg", "Soy"] },
387
+ "'shroom burger": { calories: 490, fat: "29g", protein: "22g", carbs: "43g", allergens: ["Wheat", "Milk", "Egg", "Soy"] },
388
+ "chick'n shack": { calories: 610, fat: "28g", protein: "32g", carbs: "60g", allergens: ["Wheat", "Milk", "Egg", "Soy"] },
389
+ fries: { calories: 420, fat: "18g", protein: "5g", carbs: "61g", allergens: ["Wheat"] },
390
+ "cheese fries": { calories: 510, fat: "24g", protein: "8g", carbs: "65g", allergens: ["Wheat", "Milk"] },
391
+ "vanilla shake": { calories: 700, fat: "29g", protein: "16g", carbs: "97g", allergens: ["Milk", "Egg"] },
392
+ "chocolate shake": { calories: 730, fat: "31g", protein: "17g", carbs: "101g", allergens: ["Milk", "Egg"] },
393
+ };
394
+
395
+ const key = Object.keys(nutritionData).find((k) => itemName.includes(k) || k.includes(itemName));
396
+
397
+ if (!key) {
398
+ return {
399
+ content: [
400
+ {
401
+ type: "text",
402
+ text: `Nutrition info not available for "${itemName}" in this connector. Available items: ${Object.keys(nutritionData).join(", ")}.\n\nFor complete nutritional information, visit shakeshack.com/nutrition.`,
403
+ },
404
+ ],
405
+ };
406
+ }
407
+
408
+ const info = nutritionData[key];
409
+ return {
410
+ content: [
411
+ {
412
+ type: "text",
413
+ text:
414
+ `## Nutrition Info — ${key.charAt(0).toUpperCase() + key.slice(1)}\n\n` +
415
+ `| Nutrient | Amount |\n` +
416
+ `|----------|--------|\n` +
417
+ `| Calories | ${info.calories} |\n` +
418
+ `| Fat | ${info.fat} |\n` +
419
+ `| Protein | ${info.protein} |\n` +
420
+ `| Carbs | ${info.carbs} |\n\n` +
421
+ `**Allergens:** ${info.allergens.join(", ")}\n\n` +
422
+ `_For complete nutritional information, visit shakeshack.com/nutrition_`,
423
+ },
424
+ ],
425
+ };
426
+ }
427
+
428
+ case "get_featured_items": {
429
+ return {
430
+ content: [
431
+ {
432
+ type: "text",
433
+ text:
434
+ `## Shake Shack Featured & Seasonal Items\n\n` +
435
+ `### Current Highlights\n` +
436
+ `- **Korean BBQ Burger** *(Limited Time)* — Korean BBQ sauce, pickled daikon, crispy shallots — $9.89\n` +
437
+ `- **Truffle Fries** *(Seasonal)* — Crinkle cut fries with truffle oil and parmesan — $5.89\n` +
438
+ `- **Custard of the Day** — Rotating daily flavors; ask at the counter or check the app — from $4.89\n\n` +
439
+ `### Loyalty & App Exclusives\n` +
440
+ `- Download the Shack App for exclusive menu items, early access to LTOs, and loyalty rewards.\n\n` +
441
+ `_Featured items vary by location and change frequently. Check shakeshack.com or the Shack App for the latest._`,
442
+ },
443
+ ],
444
+ };
445
+ }
446
+
447
+ case "get_ordering_info": {
448
+ return {
449
+ content: [
450
+ {
451
+ type: "text",
452
+ text:
453
+ `## How to Order at Shake Shack\n\n` +
454
+ `### In-Store\n` +
455
+ `- **Kiosk:** Self-service kiosks available at most locations for fast ordering.\n` +
456
+ `- **Counter:** Order directly with a Shack team member.\n\n` +
457
+ `### Mobile & Online\n` +
458
+ `- **Shack App:** Download for iOS or Android to order ahead, skip the line, earn rewards, and access exclusive menu items.\n` +
459
+ `- **Website:** Order online at shakeshack.com for pickup at select locations.\n\n` +
460
+ `### Delivery\n` +
461
+ `- Available via **Uber Eats**, **DoorDash**, and **Grubhub** at participating locations.\n\n` +
462
+ `### Catering\n` +
463
+ `- Shake Shack offers catering for events. Visit shakeshack.com/catering for details.\n\n` +
464
+ `### Loyalty Program\n` +
465
+ `- **Shack Rewards:** Earn points on every purchase through the app. Redeem for free menu items.\n\n` +
466
+ `### Gift Cards\n` +
467
+ `- Physical and digital gift cards available at shakeshack.com/gift-cards.`,
468
+ },
469
+ ],
470
+ };
471
+ }
472
+
473
+ default:
474
+ return {
475
+ content: [
476
+ {
477
+ type: "text",
478
+ text: `Unknown tool: ${name}`,
479
+ },
480
+ ],
481
+ isError: true,
482
+ };
483
+ }
484
+ });
485
+
486
+ async function main() {
487
+ const transport = new StdioServerTransport();
488
+ await server.connect(transport);
489
+ console.error("Shake Shack MCP server running on stdio");
490
+ }
491
+
492
+ main().catch((err) => {
493
+ console.error("Fatal error:", err);
494
+ process.exit(1);
495
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }