DoorDash CLI MCP Server
Enables interaction with DoorDash through the DoorDash CLI, providing tools for searching restaurants, browsing menus, managing carts, previewing and submitting orders, handling payment methods, and applying promotions.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@DoorDash CLI MCP ServerFind sushi restaurants in San Francisco"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
DoorDash CLI MCP server
Local Streamable HTTP MCP wrapper for DoorDash CLI. It lets an MCP client use DoorDash through the macOS CLI and the credentials in the user's Keychain.
The server exposes the authenticated ordering services in DoorDash CLI v0.2.1.
login, help, and version remain local CLI operations.
Setup
Requirements:
macOS on Apple Silicon
Node.js 22 or newer
DoorDash CLI v0.2.1 for Darwin ARM64
The DoorDash release is local vendor material and is not committed. Copy the
extracted release into ./doordash-cli/, then point the tracked root shim at
its executable:
mkdir -p ./doordash-cli
cp -R /path/to/extracted/doordash-cli-release/. ./doordash-cli/
ln -sfn doordash-cli/dd-cli-v0.2.1-darwin-arm64 ./dd-cli
chmod +x ./doordash-cli/dd-cli-v0.2.1-darwin-arm64The executable must now be:
./doordash-cli/dd-cli-v0.2.1-darwin-arm64Sign in locally and install dependencies:
./dd-cli login
npm install
cp .env.example .envGenerate a secret with openssl rand -hex 32, paste it into
ADMIN_ACCESS_TOKEN in .env, then start the server:
npm startOpen http://127.0.0.1:8787/ and enter the admin secret. Generate an MCP
bearer token, copy it immediately, and put it in the MCP client's
Authorization/Bearer-token setting. Only the MCP token's SHA-256 hash is
stored.
The MCP endpoint is:
http://127.0.0.1:8787/mcpAll MCP requests require a valid bearer token.
Open WebUI in Docker
Listen on the Mac's network interface:
HOST=0.0.0.0In Open WebUI configure:
Type:
MCP (Streamable HTTP)URL:
http://host.docker.internal:8787/mcpAuthentication: Bearer token generated by the local UI
Open WebUI caches tool schemas. After upgrading this service, disconnect and reconnect the MCP integration before starting a new chat; otherwise the model may keep sending an obsolete input shape.
The admin UI, token APIs, and raw activity log require the admin secret. A
successful web login receives an HttpOnly session cookie. Scripts can instead
send Authorization: Bearer <ADMIN_ACCESS_TOKEN>. The /mcp endpoint keeps
its separate MCP bearer tokens.
Related MCP server: grubhub-mcp
Token permissions
Each token has its own Checkout & card details checkbox.
Unchecked tokens receive the complete non-purchase tool set. Checked tokens also receive:
list_payment_methodsorder_submit
Changing the checkbox changes that token immediately. There is no global purchase switch. Revoking a token invalidates it immediately.
Payment output contains only card brand, last four digits, expiry, and default status. DoorDash CLI cannot return a full card number or CVC, and the wrapper does not expose provider/payment IDs.
order_submit is deliberately obnoxious:
It requires the exact
submit_contextreturned bypreview_order.It requires an explicit tip in dollars, including an explicit zero.
It requires the confirmed default card, account default, or work budget.
It requires explicit PIN-handoff acknowledgement when the preview says a delivery PIN is required.
It re-previews immediately before submission and stops on drift.
It records the cart before spawning the purchase command.
It never retries a cart with any recorded submission attempt.
It polls
order_statusafter DoorDash accepts the submission.
A bearer token authenticates a caller. It is not purchase consent; the tool's confirmation contract is still mandatory.
MCP tools
Every tool advertises an MCP outputSchema with a success/error union. Calls
return a readable result plus the same machine payload through both
client-compatible channels:
content[0].textis a short readable summary.content[1].textis minified JSON, exactly equal toJSON.stringify(structuredContent).structuredContentis the stable, versioned machine result.
The advertised output schemas are intentionally shallow: they name the fields needed for the next tool without repeating the full response grammar 25 times. Every actual response is still checked against the strict internal contract before it is returned.
Typed errors also return the JSON text copy and isError: true.
structuredContent.error contains a stable code, retryable: false, and,
when recovery is possible, the exact recovery_tool and
recovery_arguments. Do not repeat the failed call; perform the one stated
recovery action instead.
State-changing tools are serialized inside the service. A cart, promo, address, preview, reorder, or submission cannot slip between purchase revalidation and the purchase command. If a non-idempotent command loses its upstream result, the error names one inspection tool; it never tells the caller to repeat the write.
Cart preflight failures are the one deliberate exception: they return the
cart shape with items: [], complete item_errors, and isError: true.
That means no cart write occurred. A DoorDash partial-add response instead has
both successful items and item_errors; those successful lines are already
in the cart and must never be sent again.
The public tools/list schemas use strict snake_case fields. Copy response
fields such as address_id, store_id, menu_id, item_id, cart_uuid,
cart_item_id, order_uuid, and budget_id directly into the next tool.
Unknown fields are rejected. Older camelCase and cents-based inputs remain
runtime compatibility aliases, but new callers should not use them.
Restaurant stores, items, and cart lines retain upstream menu_id values. A
cart also exposes top-level menu_id when every line agrees, so a failed menu
fetch does not erase a working restaurant-item handoff.
The contract stays compact. Money is a floating-point dollar number rounded to
two decimals. An ETA is one delivery_time string, including a range such as
"25-35 min". Distance is one display string, preferring miles when DoorDash
provides them. Unavailable optional fields are omitted. Checkout, tracking, and
group-cart links appear only when DoorDash supplied them.
Typed tools discard unknown CLI response fields instead of leaking the upstream payload. See MCP response examples for complete wire examples.
Addresses
list_addressesset_default_address
Address changes are account-wide. DoorDash CLI has no per-cart address override, and checkout URLs do not pin an address. Delivery carts, previews, and submissions use the account-wide default address.
Discovery and catalogs
search_restaurantsfind_nearby_storesget_store_detailsget_menufind_itemsget_item_detailsbuild_grocery_list
find_items searches grocery and retail catalogs only. A known restaurant
store_id is rejected before any retail catalog call and directs the caller to
get_menu; never retry find_items for that store.
search_restaurants and find_nearby_stores always call list_addresses and
use the coordinates of the account-wide default address. They do not accept a
location override. The wrapper returns an error instead of guessing when
DoorDash has no marked default or the default has no coordinates.
get_menu requires store_id. It returns the authoritative menu_id as
output for later item-detail and cart calls. Its optional query filters a
restaurant menu to the requested dish name. The wrapper makes exactly one
read-only dd-cli menu --store-id call and never reads or changes cart state.
If DoorDash cannot return that menu, the operation fails instead of fabricating
a partial menu from order history.
get_item_details routes i_-prefixed IDs, plus bare historical item IDs
paired with a restaurant menu_id, through the restaurant modifier endpoint.
For an i_ ID with no returned menu ID, the wrapper uses store_id as the
endpoint's internal lookup context but does not publish it as menu_id; cart
calls still require an authoritative menu ID. Other item IDs use grocery or
retail details. On a large modifier tree, pass
option_queries such as ["Ranch"] to receive root choices plus compact
matching paths instead of dumping the entire tree.
When the full-menu endpoint succeeds, get_menu returns every valid item and
category supplied by DoorDash, without modifier trees. A zero-match query on
that successful menu says not to repeat it unchanged: try one broader dish
name or inspect the full menu once without a query. That broader-search advice
does not apply when the menu call fails upstream.
Carts
list_cartsshow_cartadd_cart_itemsremove_cart_itemdelete_cart
The add operation is additive and non-idempotent. Send the complete requested
batch once. Every line requires the exact menu item_id and exact menu name;
names are labels, not customization. Before one DoorDash cart write, the
wrapper fetches current details for every restaurant item, including bare
historical IDs paired with a restaurant menu_id, and validates the full
batch. A required group with one possible choice is selected automatically. A
preference with multiple choices is never guessed.
Put requested choices per line in structured requested_options entries:
{"name":"Ranch","quantity":2,"option_id":"o_ranch_sauce"}. name is
required; omit quantity for one (maximum 100), and include option_id when
the same name appears in more than one branch. If DoorDash reuses one
option_id across groups, use the qualified name returned by the error, such
as Sauce Ranch. A group name can select its unambiguous Yes option. Omit an
optional add-on to decline it; never invent a "No ..."
option. An unmatched or unreachable choice blocks the full batch. An ambiguous
choice such as Ranch sauce versus Ranch dressing returns both candidates; ask
the user, then resend the chosen option_id. Resolution stays within the
selected parent branch.
Use get_item_details when the choices are not already known. Menu results
omit modifier trees. Item details keeps large responses bounded with
option_queries, while cart preflight resolves the full tree internally.
Malformed trees or selectable options without IDs still fail closed before any
cart write.
Exact choices may instead be copied into nested_options as
{"option_id":"o_...","name":"Chosen option"}. Do not pass modifier group IDs
such as e_....
Ordinary selections stay flat. If a selected option exposes another modifier
group, put the child selections in that option's options array.
If preflight returns items: [] plus item_errors, no cart change occurred.
Resolve every reported line from the user's stated choices, or ask the user.
Then retry the complete batch once. Never repeat unchanged input.
An unavailable item explicitly says not to retry; choose another item or use
DoorDash checkout.
Cart errors return only relevant choices or ambiguity candidates. Call
get_item_details with that item_id, menu_id, and focused
option_queries when more context is needed.
If DoorDash instead returns successful items plus item_errors, the update
was partial. Call show_cart, then add only the failed lines using the returned
cart_uuid. Resending an added line or the full original batch duplicates it.
When DoorDash reports an error for repeated copies of the same item but omits
the modifier variant, the error has ambiguous: true and lists every candidate
request line with its selected options. Inspect the cart and add only a variant
confirmed missing; never guess which candidate failed.
After a successful add, add_cart_items automatically creates and returns a
browser checkout_url. If DoorDash adds the items but link creation fails, the
tool preserves the cart result and tells the caller to use
create_checkout_link.
One add_cart_items call accepts at most 20 complete request lines and checks
at most four distinct item-detail records concurrently.
When cart_uuid is omitted, the wrapper checks for an active cart at that store
before adding. An empty cart left by an earlier failed add is reused
automatically. A nonempty cart returns ACTIVE_CART_EXISTS with
recovery_tool: show_cart and its exact cart_uuid instead of silently
duplicating items. Inspect it, then return its checkout link when it already
matches, explicitly extend it using its cart_uuid, or ask whether to replace
it. delete_cart requires the exact confirmation "DELETE CART" after the
user chooses replacement.
remove_cart_item also fails closed. For a replacement, first add and verify
the new line, then pass its cart_item_id as replacement_cart_item_id; the
tool verifies that both the old and replacement lines exist before removing
anything. For a true deletion with no replacement, pass
confirm_delete_without_replacement: true instead. Provide exactly one of
those proofs. On success, the tool returns a freshly hydrated cart confirming
that the old line is gone and, for a replacement, the new line remains. If the
mutation or hydration outcome is unknown, follow the returned one-time
show_cart action and do not retry the removal.
When extending a cart_uuid, omit fulfillment to preserve its current mode,
or copy show_cart.fulfillment when the user explicitly chose delivery or
pickup.
list_carts returns at most 25 carts and 10 lines per cart; call show_cart
for one cart's detail. Cart detail and add results return at most 100 lines.
items_truncation states exactly how many lines were omitted.
Group-cart spend_limit is a dollar amount with at most two decimal places. It
requires group_cart: true and cannot be used while extending cart_uuid.
Orders
list_ordersreorderpreview_ordercreate_checkout_linkget_receiptorder_statusorder_submit— permission-gated
list_orders returns at most 25 orders with 10 item lines each and marks
omitted lines with items_truncation; call get_receipt for one order's
itemized detail.
reorder first inspects the source order and active same-store carts. It
refuses to merge into a nonempty cart, performs the upstream reorder exactly
once, then hydrates the result with show_cart and reports item, quantity, and
modifier differences from the source. Success always contains the verified
hydrated cart. If hydration or the mutation outcome is unknown, follow the one
returned inspection action; never issue a second reorder blindly.
Preview supports scheduled orders, delivery/pickup, Priority delivery, credit
opt-out, and work-benefit budgets. It returns an exact submit_context:
Omit fulfillment to preserve the cart's current delivery/pickup mode. Passing
delivery or pickup explicitly changes that mode.
{
"cart_uuid": "cart-123",
"preview_token": "bHy-Nx9_OVxS4mVXGF8TF08f-HmEiEvPl-9mM9pHUaQ.sxoJtkJslOz0zqrsIs4uuss1vX8cPgTWuvHCAScEilk",
"expected_total_before_tip": 25.01,
"expected_delivery_address": "123 Main St, Oakland, CA 94611",
"fulfillment": "delivery",
"priority": false,
"apply_credits": true,
"pin_handoff_required": false
}preview_token binds the confirmed cart contents, every setting returned in
submit_context, and the selected work budget's identity, rules, and remaining
balance. Changing any of that requires a new preview; tip, payment confirmation,
and expense details are added afterward.
Copy cart_uuid, preview_token, expected_total_before_tip,
expected_delivery_address, fulfillment, priority, apply_credits, and
pin_handoff_required from submit_context; also copy scheduled_time when
present. Add the user's confirmed tip in dollars, tip_confirmed: true,
payment confirmation, and confirmation: "PLACE ORDER". For pickup, copy
expected_delivery_address: null when the preview returns no delivery address.
When pin_handoff_required is true, ask the user to accept handing the
delivery PIN to the Dasher, then add pin_handoff_acknowledged: true. For a
work budget, call preview_order again with the selected budget_id, then
copy work_benefits.team_id and that budget's budget_id, name, and
optional team_account_id; use
payment_confirmation: {"type":"work_budget","name":"..."} and include any
required expense code or notes. For a personal card, call
list_payment_methods and copy brand and last4 from the is_default card
after the user confirms it.
Use account_default only when that call cannot identify the default, browser
checkout was offered, and the user explicitly accepts the unseen account
default.
Payments and promotions
list_payment_methods— permission-gatedlist_promosapply_promoremove_promo
Every CLI invocation automatically uses root --json-output and appends:
--intent cli-usageCLI limitations
DoorDash CLI v0.2.1 does not support:
Adding or changing saved payment methods
Per-cart delivery addresses
Creating or deleting saved addresses
Setting an existing cart line to an absolute quantity
Merchant tips for pickup
Agent checkout for restricted or age-gated items
Browser checkout is the fallback for those cases.
Storage and activity
SQLite is used only for bearer-token hashes and the duplicate-submission ledger:
./.data/doordash-mcp.sqliteThe directory is ignored by Git. DoorDash owns carts and orders; the macOS Keychain owns CLI credentials.
The dashboard keeps the last 100 MCP-routed CLI calls in memory. Commands and CLI results are stored raw and completely unredacted, including coordinates, addresses, URLs, payment metadata, and error details. Anyone with admin dashboard or raw activity-log access can read them. The log resets on restart. Direct terminal calls do not appear.
Authenticated raw activity JSON:
http://127.0.0.1:8787/activityConfiguration
Variable | Default | Purpose |
|
| Listen address |
|
| Listen port |
| required | Admin UI and API secret; minimum 16 characters |
|
| DoorDash executable |
|
| Per-command timeout |
|
| Token and submission SQLite database |
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables running MCP tools against local MLX models on your Mac, with hardware-aware configuration, CLI streaming, and a dashboard for routing and monitoring.5,309Inno Setup
- AlicenseNot gradedqualityCmaintenanceProvides programmatic access to Grubhub's food delivery platform, enabling restaurant search, menu browsing, cart management, order placement, and delivery tracking through MCP tools.MIT
- AlicenseNot gradedqualityBmaintenanceLocal-first macOS MCP server for searching files, running approved Shortcuts, managing Homebrew packages, and working with local models, with read-only defaults and optional gated mutations.MIT
- AlicenseAqualityBmaintenanceA local MCP server that exposes macOS automation actions (AppleScript + CLIs) as tools, enabling MCP clients on your Mac to control apps, system settings, and more.39MIT
Related MCP Connectors
MCP connector for iMessage & Contacts via a local Mac agent + Vercel relay
MCP connector that lets ChatGPT list, search, and run your Apple Shortcuts via a local Mac agent
A paid remote MCP for CLI tool MCP, built to return verdicts, receipts, usage logs, and audit-ready
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/francisschmaltz/doordash-cli-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server