# Directory Structure
```
├── .gitignore
├── package.json
├── readme.md
├── showSettings.js
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules
2 | build
3 | package-lock.json
4 | yarn.lock
```
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Server Guide
2 |
3 | To build the MCP server, run:
4 |
5 | ```
6 | npm install && npm run build
7 | ```
8 |
9 | This will compile the typescript files and produce a build directory plus it will output the json you can copy/paste into your MCP client (Claude Desktop, Windsurf, Cursor, etc.)
10 |
11 | If all things go well, this will produce an output similar to this:
12 |
13 | ```json
14 | {
15 | "mcpServers": {
16 | "doordash": {
17 | "command": "node",
18 | "args": [
19 | "<thePathToYour>/build/index.js"
20 | ],
21 | "env": [
22 | {
23 | "DOORDASH_API_KEY": "<REPLACE>"
24 | }
25 | ]
26 | }
27 | }
28 | }
29 | ```
30 |
31 | ## Support & Feedback
32 | If things do not compile, or you have more advanced needs, please reach out to me at, [email protected].
33 |
34 | ## Sharing
35 |
36 | If you have found value in this service please share it on social media. You can tag me [@jordandalton](https://x.com/jordankdalton) on X, or [jdcarnivore](https://www.reddit.com/user/jdcarnivore) on Reddit.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "allowSyntheticDefaultImports": true,
13 | "moduleDetection": "force",
14 | "allowJs": true
15 | },
16 | "include": ["src/**/*"],
17 | "exclude": ["node_modules"]
18 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "doordash-mcp-server",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && chmod 755 build/index.js && node showSettings"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "description": "",
13 | "dependencies": {
14 | "@modelcontextprotocol/sdk": "^1.8.0",
15 | "axios": "^1.8.4",
16 | "jsonwebtoken": "^9.0.2",
17 | "zod": "^3.24.2"
18 | },
19 | "devDependencies": {
20 | "@types/node": "^22.14.0",
21 | "typescript": "^5.8.3"
22 | }
23 | }
24 |
```
--------------------------------------------------------------------------------
/showSettings.js:
--------------------------------------------------------------------------------
```javascript
1 | import path from 'path';
2 | import fs from 'fs';
3 |
4 | const filePath = path.join(process.cwd(), 'build', 'index.js');
5 |
6 | // look for any env variables inside the file
7 | const env = {};
8 |
9 | // Read the file
10 | const fileContent = fs.readFileSync(filePath, 'utf-8');
11 |
12 | // Find all env variables
13 | const envRegex = /process\.env\.(\w+)/g;
14 | let match;
15 | while ((match = envRegex.exec(fileContent)) !== null) {
16 | const row = { [match[1]]: '<REPLACE>' };
17 | Object.assign(env, row);
18 | }
19 |
20 | const structure = {
21 | mcpServers : {
22 | "doordash" : {
23 | command : "node",
24 | args : [filePath],
25 | env : env
26 | }
27 | }
28 | }
29 |
30 | console.log('Copy/Paste into your MCP client:');
31 | console.log(
32 | JSON.stringify(structure, null, 2)
33 | );
34 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3 | import { z } from "zod";
4 | import axios from 'axios';
5 | import jwt from "jsonwebtoken";
6 |
7 | const developer_id = process.env.DOORDASH_DEVELOPER_ID;
8 | const key_id = process.env.DOORDASH_KEY_ID;
9 | const signing_secret = process.env.DOORDASH_SIGNING_SECRET;
10 |
11 | // Create an MCP server
12 | const server = new McpServer({
13 | name: "DoorDashDriveAPI",
14 | version: "1.0.0"
15 | });
16 |
17 | const issuedAt = Math.floor(Date.now() / 1000);
18 | const expirationDuration = 300;
19 |
20 | const jwtData = {
21 | aud: 'doordash',
22 | iss: developer_id,
23 | kid: key_id,
24 | exp: issuedAt + expirationDuration,
25 | iat: issuedAt,
26 | }
27 |
28 | const token = jwt.sign(
29 | jwtData,
30 | Buffer.from(signing_secret, 'base64'),
31 | {
32 | algorithm: "HS256",
33 | header: {
34 | alg: "HS256",
35 | 'dd-ver': 'DD-JWT-V1'
36 | }
37 | } as unknown as jwt.SignOptions
38 | );
39 |
40 | // Helper function to make API requests with authentication
41 | async function makeApiRequest(url: string, method: string, data?: any) {
42 | const headers = {
43 | Authorization: `Bearer ${token}`,
44 | 'Content-Type': 'application/json',
45 | };
46 | try {
47 | const response = await axios({
48 | url: url,
49 | method: method,
50 | data: data,
51 | headers
52 | });
53 | return response.data;
54 | } catch (error) {
55 | console.error("API request failed:", error);
56 | throw error; // Re-throw the error for the tool to handle
57 | }
58 | }
59 |
60 | // Tool: Create Quote
61 | server.tool("create_quote",
62 | {
63 | external_delivery_id: z.string(),
64 | locale: z.string().optional(),
65 | order_fulfillment_method: z.string().optional(),
66 | origin_facility_id: z.string().optional(),
67 | pickup_address: z.string().optional(),
68 | pickup_business_name: z.string().optional(),
69 | pickup_phone_number: z.string().optional(),
70 | pickup_instructions: z.string().optional(),
71 | pickup_reference_tag: z.string().optional(),
72 | pickup_external_business_id: z.string().optional(),
73 | pickup_external_store_id: z.string().optional(),
74 | pickup_verification_metadata: z.record(z.any()).optional(),
75 | dropoff_address: z.string(),
76 | dropoff_business_name: z.string().optional(),
77 | dropoff_location: z.record(z.any()).optional(),
78 | dropoff_phone_number: z.string(),
79 | dropoff_instructions: z.string().optional(),
80 | dropoff_contact_given_name: z.string().optional(),
81 | dropoff_contact_family_name: z.string().optional(),
82 | dropoff_contact_send_notifications: z.boolean().optional(),
83 | dropoff_options: z.record(z.any()).optional(),
84 | dropoff_address_components: z.record(z.any()).optional(),
85 | dropoff_pin_code_verification_metadata: z.record(z.any()).optional(),
86 | shopping_options: z.record(z.any()).optional(),
87 | order_value: z.number().optional(),
88 | items: z.array(z.record(z.any())).optional(),
89 | pickup_time: z.string().optional(),
90 | dropoff_time: z.string().optional(),
91 | pickup_window: z.record(z.any()).optional(),
92 | dropoff_window: z.record(z.any()).optional(),
93 | customer_expected_sla: z.any().optional(),
94 | expires_by: z.any().optional(),
95 | shipping_label_metadata: z.record(z.any()).optional(),
96 | contactless_dropoff: z.boolean().optional(),
97 | action_if_undeliverable: z.string().optional(),
98 | tip: z.number().optional(),
99 | order_contains: z.record(z.any()).optional(),
100 | dasher_allowed_vehicles: z.array(z.string()).optional(),
101 | dropoff_requires_signature: z.boolean().optional(),
102 | promotion_id: z.string().optional(),
103 | dropoff_cash_on_delivery: z.number().optional(),
104 | order_route_type: z.string().optional(),
105 | order_route_items: z.array(z.string()).optional()
106 | },
107 | async (params) => {
108 | try {
109 | const response = await makeApiRequest(
110 | 'https://openapi.doordash.com/drive/v2/quotes',
111 | 'POST',
112 | params
113 | );
114 | return {
115 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
116 | };
117 | } catch (error: any) {
118 | return {
119 | content: [{ type: "text", text: `Error: ${error.message}` }]
120 | };
121 | }
122 | }
123 | );
124 |
125 | // Tool: Accept Quote
126 | server.tool("accept_quote",
127 | {
128 | external_delivery_id: z.string(),
129 | tip: z.number().optional(),
130 | dropoff_phone_number: z.string().optional()
131 | },
132 | async ({ external_delivery_id, tip, dropoff_phone_number }) => {
133 | try {
134 | const response = await makeApiRequest(
135 | `https://openapi.doordash.com/drive/v2/quotes/${external_delivery_id}/accept`,
136 | 'POST',
137 | { tip: tip, dropoff_phone_number: dropoff_phone_number }
138 | );
139 | return {
140 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
141 | };
142 | } catch (error: any) {
143 | return {
144 | content: [{ type: "text", text: `Error: ${error.message}` }]
145 | };
146 | }
147 | }
148 | );
149 |
150 | // Tool: Create Delivery
151 | server.tool("create_delivery",
152 | {
153 | external_delivery_id: z.string(),
154 | locale: z.string().optional(),
155 | order_fulfillment_method: z.string().optional(),
156 | origin_facility_id: z.string().optional(),
157 | pickup_address: z.string().optional(),
158 | pickup_business_name: z.string().optional(),
159 | pickup_phone_number: z.string().optional(),
160 | pickup_instructions: z.string().optional(),
161 | pickup_reference_tag: z.string().optional(),
162 | pickup_external_business_id: z.string().optional(),
163 | pickup_external_store_id: z.string().optional(),
164 | pickup_verification_metadata: z.record(z.any()).optional(),
165 | dropoff_address: z.string(),
166 | dropoff_business_name: z.string().optional(),
167 | dropoff_location: z.record(z.any()).optional(),
168 | dropoff_phone_number: z.string(),
169 | dropoff_instructions: z.string().optional(),
170 | dropoff_contact_given_name: z.string().optional(),
171 | dropoff_contact_family_name: z.string().optional(),
172 | dropoff_contact_send_notifications: z.boolean().optional(),
173 | dropoff_options: z.record(z.any()).optional(),
174 | dropoff_address_components: z.record(z.any()).optional(),
175 | dropoff_pin_code_verification_metadata: z.record(z.any()).optional(),
176 | shopping_options: z.record(z.any()).optional(),
177 | order_value: z.number().optional(),
178 | items: z.array(z.record(z.any())).optional(),
179 | pickup_time: z.string().optional(),
180 | dropoff_time: z.string().optional(),
181 | pickup_window: z.record(z.any()).optional(),
182 | dropoff_window: z.record(z.any()).optional(),
183 | customer_expected_sla: z.any().optional(),
184 | expires_by: z.any().optional(),
185 | shipping_label_metadata: z.record(z.any()).optional(),
186 | contactless_dropoff: z.boolean().optional(),
187 | action_if_undeliverable: z.string().optional(),
188 | tip: z.number().optional(),
189 | order_contains: z.record(z.any()).optional(),
190 | dasher_allowed_vehicles: z.array(z.string()).optional(),
191 | dropoff_requires_signature: z.boolean().optional(),
192 | promotion_id: z.string().optional(),
193 | dropoff_cash_on_delivery: z.number().optional(),
194 | order_route_type: z.string().optional(),
195 | order_route_items: z.array(z.string()).optional()
196 | },
197 | async (params) => {
198 | try {
199 | const response = await makeApiRequest(
200 | 'https://openapi.doordash.com/drive/v2/deliveries',
201 | 'POST',
202 | params
203 | );
204 | return {
205 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
206 | };
207 | } catch (error: any) {
208 | return {
209 | content: [{ type: "text", text: `Error: ${error.message}` }]
210 | };
211 | }
212 | }
213 | );
214 |
215 | // Tool: Get Delivery
216 | server.tool("get_delivery",
217 | {
218 | external_delivery_id: z.string()
219 | },
220 | async ({ external_delivery_id }) => {
221 | try {
222 | const response = await makeApiRequest(
223 | `https://openapi.doordash.com/drive/v2/deliveries/${external_delivery_id}`,
224 | 'GET'
225 | );
226 | return {
227 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
228 | };
229 | } catch (error: any) {
230 | return {
231 | content: [{ type: "text", text: `Error: ${error.message}` }]
232 | };
233 | }
234 | }
235 | );
236 |
237 | // Tool: Update Delivery
238 | server.tool("update_delivery",
239 | {
240 | external_delivery_id: z.string(),
241 | pickup_address: z.string().optional(),
242 | pickup_business_name: z.string().optional(),
243 | pickup_phone_number: z.string().optional(),
244 | pickup_instructions: z.string().optional(),
245 | pickup_reference_tag: z.string().optional(),
246 | pickup_external_business_id: z.string().optional(),
247 | pickup_external_store_id: z.string().optional(),
248 | pickup_verification_metadata: z.record(z.any()).optional(),
249 | dropoff_address: z.string().optional(),
250 | dropoff_business_name: z.string().optional(),
251 | dropoff_location: z.record(z.any()).optional(),
252 | dropoff_phone_number: z.string().optional(),
253 | dropoff_instructions: z.string().optional(),
254 | dropoff_contact_given_name: z.string().optional(),
255 | dropoff_contact_family_name: z.string().optional(),
256 | dropoff_contact_send_notifications: z.boolean().optional(),
257 | dropoff_options: z.record(z.any()).optional(),
258 | dropoff_address_components: z.record(z.any()).optional(),
259 | dropoff_pin_code_verification_metadata: z.record(z.any()).optional(),
260 | contactless_dropoff: z.boolean().optional(),
261 | action_if_undeliverable: z.string().optional(),
262 | tip: z.number().optional(),
263 | order_contains: z.record(z.any()).optional(),
264 | dasher_allowed_vehicles: z.array(z.string()).optional(),
265 | dropoff_requires_signature: z.boolean().optional(),
266 | promotion_id: z.string().optional(),
267 | dropoff_cash_on_delivery: z.number().optional(),
268 | order_route_type: z.string().optional(),
269 | order_route_items: z.array(z.string()).optional(),
270 | order_value: z.number().optional(),
271 | items: z.array(z.record(z.any())).optional(),
272 | pickup_time: z.string().optional(),
273 | dropoff_time: z.string().optional(),
274 | pickup_window: z.record(z.any()).optional(),
275 | dropoff_window: z.record(z.any()).optional(),
276 | customer_expected_sla: z.any().optional(),
277 | expires_by: z.any().optional(),
278 | shipping_label_metadata: z.record(z.any()).optional(),
279 | },
280 | async ({ external_delivery_id, ...params }) => {
281 | try {
282 | const response = await makeApiRequest(
283 | `https://openapi.doordash.com/drive/v2/deliveries/${external_delivery_id}`,
284 | 'PATCH',
285 | params
286 | );
287 | return {
288 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
289 | };
290 | } catch (error: any) {
291 | return {
292 | content: [{ type: "text", text: `Error: ${error.message}` }]
293 | };
294 | }
295 | }
296 | );
297 |
298 | // Tool: Cancel Delivery
299 | server.tool("cancel_delivery",
300 | {
301 | external_delivery_id: z.string()
302 | },
303 | async ({ external_delivery_id }) => {
304 | try {
305 | const response = await makeApiRequest(
306 | `https://openapi.doordash.com/drive/v2/deliveries/${external_delivery_id}/cancel`,
307 | 'PUT'
308 | );
309 | return {
310 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
311 | };
312 | } catch (error: any) {
313 | return {
314 | content: [{ type: "text", text: `Error: ${error.message}` }]
315 | };
316 | }
317 | }
318 | );
319 |
320 | // Tool: Get Items Substitution Recommendation
321 | server.tool("get_items_substitution_recommendation",
322 | {
323 | pickup_external_business_id: z.string(),
324 | pickup_external_store_id: z.string(),
325 | items: z.array(z.record(z.any())),
326 | customer: z.record(z.any()).optional()
327 | },
328 | async (params) => {
329 | try {
330 | const response = await makeApiRequest(
331 | 'https://openapi.doordash.com/drive/v2/items_substitution_recommendation',
332 | 'POST',
333 | params
334 | );
335 | return {
336 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
337 | };
338 | } catch (error: any) {
339 | return {
340 | content: [{ type: "text", text: `Error: ${error.message}` }]
341 | };
342 | }
343 | }
344 | );
345 |
346 | // Tool: Create Checkout Audit Signal
347 | server.tool("create_checkout_audit_signal",
348 | {
349 | external_delivery_id: z.string(),
350 | is_audit_successful: z.boolean(),
351 | audit_period: z.record(z.any()).optional(),
352 | requested_audit_item_count: z.number().optional(),
353 | audited_item_count: z.number().optional(),
354 | successful_audit_items: z.array(z.record(z.any())).optional(),
355 | failed_audit_items: z.array(z.record(z.any())).optional(),
356 | checkout_audit_status: z.string().optional()
357 | },
358 | async (params) => {
359 | try {
360 | const response = await makeApiRequest(
361 | 'https://openapi.doordash.com/drive/v2/checkout_audit_signal',
362 | 'POST',
363 | params
364 | );
365 | return {
366 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
367 | };
368 | } catch (error: any) {
369 | return {
370 | content: [{ type: "text", text: `Error: ${error.message}` }]
371 | };
372 | }
373 | }
374 | );
375 |
376 | // Tool: Get Business
377 | server.tool("get_business",
378 | {
379 | external_business_id: z.string()
380 | },
381 | async ({ external_business_id }) => {
382 | try {
383 | const response = await makeApiRequest(
384 | `https://openapi.doordash.com/developer/v1/businesses/${external_business_id}`,
385 | 'GET'
386 | );
387 | return {
388 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
389 | };
390 | } catch (error: any) {
391 | return {
392 | content: [{ type: "text", text: `Error: ${error.message}` }]
393 | };
394 | }
395 | }
396 | );
397 |
398 | // Tool: Update Business
399 | server.tool("update_business",
400 | {
401 | external_business_id: z.string(),
402 | name: z.string().optional(),
403 | description: z.string().optional(),
404 | activation_status: z.string().optional()
405 | },
406 | async ({ external_business_id, name, description, activation_status }) => {
407 | try {
408 | const response = await makeApiRequest(
409 | `https://openapi.doordash.com/developer/v1/businesses/${external_business_id}`,
410 | 'PATCH',
411 | { name: name, description: description, activation_status: activation_status }
412 | );
413 | return {
414 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
415 | };
416 | } catch (error: any) {
417 | return {
418 | content: [{ type: "text", text: `Error: ${error.message}` }]
419 | };
420 | }
421 | }
422 | );
423 |
424 | // Tool: List Businesses
425 | server.tool("list_businesses",
426 | {
427 | activationStatus: z.string().optional(),
428 | continuationToken: z.string().optional()
429 | },
430 | async ({ activationStatus, continuationToken }) => {
431 | try {
432 | let url = 'https://openapi.doordash.com/developer/v1/businesses';
433 | const params = new URLSearchParams();
434 | if (activationStatus) {
435 | params.append('activationStatus', activationStatus);
436 | }
437 | if (continuationToken) {
438 | params.append('continuationToken', continuationToken);
439 | }
440 | if (params.toString()) {
441 | url += `?${params.toString()}`;
442 | }
443 | const response = await makeApiRequest(url, 'GET');
444 | return {
445 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
446 | };
447 | } catch (error: any) {
448 | return {
449 | content: [{ type: "text", text: `Error: ${error.message}` }]
450 | };
451 | }
452 | }
453 | );
454 |
455 | // Tool: Get Store
456 | server.tool("get_store",
457 | {
458 | external_business_id: z.string(),
459 | external_store_id: z.string()
460 | },
461 | async ({ external_business_id, external_store_id }) => {
462 | try {
463 | const response = await makeApiRequest(
464 | `https://openapi.doordash.com/developer/v1/businesses/${external_business_id}/stores/${external_store_id}`,
465 | 'GET'
466 | );
467 | return {
468 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
469 | };
470 | } catch (error: any) {
471 | return {
472 | content: [{ type: "text", text: `Error: ${error.message}` }]
473 | };
474 | }
475 | }
476 | );
477 |
478 | // Tool: Update Store
479 | server.tool("update_store",
480 | {
481 | external_business_id: z.string(),
482 | external_store_id: z.string(),
483 | name: z.string().optional(),
484 | phone_number: z.string().optional(),
485 | address: z.string().optional()
486 | },
487 | async ({ external_business_id, external_store_id, name, phone_number, address }) => {
488 | try {
489 | const response = await makeApiRequest(
490 | `https://openapi.doordash.com/developer/v1/businesses/${external_business_id}/stores/${external_store_id}`,
491 | 'PATCH',
492 | { name: name, phone_number: phone_number, address: address }
493 | );
494 | return {
495 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
496 | };
497 | } catch (error: any) {
498 | return {
499 | content: [{ type: "text", text: `Error: ${error.message}` }]
500 | };
501 | }
502 | }
503 | );
504 |
505 | // Tool: Create Store
506 | server.tool("create_store",
507 | {
508 | external_business_id: z.string(),
509 | external_store_id: z.string(),
510 | name: z.string(),
511 | phone_number: z.string(),
512 | address: z.string()
513 | },
514 | async ({ external_business_id, external_store_id, name, phone_number, address }) => {
515 | try {
516 | const response = await makeApiRequest(
517 | `https://openapi.doordash.com/developer/v1/businesses/${external_business_id}/stores`,
518 | 'POST',
519 | { external_store_id: external_store_id, name: name, phone_number: phone_number, address: address }
520 | );
521 | return {
522 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
523 | };
524 | } catch (error: any) {
525 | return {
526 | content: [{ type: "text", text: `Error: ${error.message}` }]
527 | };
528 | }
529 | }
530 | );
531 |
532 | // Tool: List Stores
533 | server.tool("list_stores",
534 | {
535 | external_business_id: z.string(),
536 | activationStatus: z.string().optional(),
537 | continuationToken: z.string().optional()
538 | },
539 | async ({ external_business_id, activationStatus, continuationToken }) => {
540 | try {
541 | let url = `https://openapi.doordash.com/developer/v1/businesses/${external_business_id}/stores`;
542 | const params = new URLSearchParams();
543 | if (activationStatus) {
544 | params.append('activationStatus', activationStatus);
545 | }
546 | if (continuationToken) {
547 | params.append('continuationToken', continuationToken);
548 | }
549 | if (params.toString()) {
550 | url += `?${params.toString()}`;
551 | }
552 | const response = await makeApiRequest(url, 'GET');
553 | return {
554 | content: [{ type: "text", text: String(JSON.stringify(response)) }]
555 | };
556 | } catch (error: any) {
557 | return {
558 | content: [{ type: "text", text: `Error: ${error.message}` }]
559 | };
560 | }
561 | }
562 | );
563 |
564 | // Start receiving messages on stdin and sending messages on stdout
565 | const transport = new StdioServerTransport();
566 | await server.connect(transport);
```