@pizzamcp/server
Version:
Simple Domino's Pizza MCP Server
515 lines (514 loc) • 20.2 kB
JavaScript
#!/usr/bin/env node
/**
* MCPizza - Simple MCP Server for Domino's Pizza Ordering
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
// Simple in-memory order state
let currentOrder = {
store: null,
items: [],
customer: null
};
class MCPizzaServer {
server;
constructor() {
this.server = new Server({
name: 'MCPizza',
version: '1.3.2',
}, {
capabilities: {
tools: {},
},
});
this.setupHandlers();
}
setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'find_dominos_store',
description: 'Find the nearest Domino\'s store by address or zip code',
inputSchema: {
type: 'object',
properties: {
address: {
type: 'string',
description: 'Full address or zip code to search near'
}
},
required: ['address']
}
},
{
name: 'search_menu',
description: 'Search for menu items by name or description',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search term (e.g., "pepperoni pizza", "wings")'
}
},
required: ['query']
}
},
{
name: 'add_to_order',
description: 'Add items to pizza order',
inputSchema: {
type: 'object',
properties: {
item_code: {
type: 'string',
description: 'Product code from menu search'
},
quantity: {
type: 'integer',
description: 'Number of items to add',
default: 1
}
},
required: ['item_code']
}
},
{
name: 'view_order',
description: 'View current order contents and total',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'set_customer_info',
description: 'Set customer information for delivery',
inputSchema: {
type: 'object',
properties: {
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email address' },
phone: { type: 'string', description: 'Phone number' },
address: {
type: 'object',
properties: {
street: { type: 'string', description: 'Street address' },
city: { type: 'string', description: 'City' },
state: { type: 'string', description: 'State' },
zip: { type: 'string', description: 'ZIP code' }
},
required: ['street', 'city', 'state', 'zip']
}
},
required: ['firstName', 'lastName', 'email', 'phone', 'address']
}
},
{
name: 'place_order',
description: 'Place the actual pizza order for delivery or pickup',
inputSchema: {
type: 'object',
properties: {
orderType: {
type: 'string',
enum: ['Delivery', 'Carryout'],
description: 'Order type'
},
paymentType: {
type: 'string',
enum: ['Cash', 'CreditCard'],
description: 'Payment method'
},
creditCard: {
type: 'object',
properties: {
number: { type: 'string', description: 'Card number' },
expiration: { type: 'string', description: 'MM/YY format' },
securityCode: { type: 'string', description: 'CVV' },
postalCode: { type: 'string', description: 'Billing ZIP' }
}
}
},
required: ['orderType', 'paymentType']
}
}
]
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (!args) {
throw new Error('No arguments provided');
}
switch (name) {
case 'find_dominos_store':
return await this.findStore(args.address);
case 'search_menu':
return await this.searchMenu(args.query);
case 'add_to_order':
return await this.addToOrder(args.item_code, args.quantity || 1);
case 'view_order':
return await this.viewOrder();
case 'set_customer_info':
return await this.setCustomerInfo(args);
case 'place_order':
return await this.placeOrder(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`
}
]
};
}
});
}
async findStore(address) {
// Use a real store locator API call
try {
const response = await fetch('https://order.dominos.com/power/store-locator', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
s: address,
type: 'Delivery'
})
});
if (!response.ok) {
throw new Error('Store locator service unavailable');
}
const data = await response.json();
if (data.Stores && data.Stores.length > 0) {
const store = data.Stores[0];
currentOrder.store = store;
return {
content: [
{
type: 'text',
text: `Found store: ${store.StoreName}\nAddress: ${store.AddressDescription}\nPhone: ${store.Phone}\nStore ID: ${store.StoreID}`
}
]
};
}
else {
throw new Error('No stores found near that address');
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Store search failed: ${error.message}. Try a different address or zip code.`
}
]
};
}
}
async searchMenu(query) {
if (!currentOrder.store) {
return {
content: [
{
type: 'text',
text: 'Please find a store first using find_dominos_store'
}
]
};
}
try {
const response = await fetch(`https://order.dominos.com/power/store/${currentOrder.store.StoreID}/menu`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error('Menu service unavailable');
}
const menu = await response.json();
const results = [];
// Search through menu categories
for (const [categoryKey, category] of Object.entries(menu.Categories || {})) {
if (typeof category === 'object' && category !== null && 'Products' in category) {
const products = category.Products;
for (const [productCode, product] of Object.entries(products || {})) {
if (typeof product === 'object' && product !== null) {
const name = product.Name || '';
const description = product.Description || '';
if (name.toLowerCase().includes(query.toLowerCase()) ||
description.toLowerCase().includes(query.toLowerCase())) {
results.push({
code: productCode,
name,
description,
category: categoryKey
});
}
}
}
}
}
if (results.length === 0) {
return {
content: [
{
type: 'text',
text: `No menu items found for "${query}". Try searching for "pizza", "wings", or "pasta".`
}
]
};
}
return {
content: [
{
type: 'text',
text: `Found ${results.length} items:\n\n${results.map(item => `• ${item.name} (${item.code})\n ${item.description}`).join('\n\n')}`
}
]
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Menu search failed: ${error.message}`
}
]
};
}
}
async addToOrder(itemCode, quantity) {
if (!currentOrder.store) {
return {
content: [
{
type: 'text',
text: 'Please find a store first using find_dominos_store'
}
]
};
}
currentOrder.items.push({
code: itemCode,
quantity: quantity,
addedAt: new Date().toISOString()
});
return {
content: [
{
type: 'text',
text: `Added ${quantity}x ${itemCode} to your order`
}
]
};
}
async viewOrder() {
if (currentOrder.items.length === 0) {
return {
content: [
{
type: 'text',
text: 'Your order is empty. Use search_menu to find items, then add_to_order to add them.'
}
]
};
}
const itemList = currentOrder.items.map(item => `• ${item.quantity}x ${item.code}`).join('\n');
return {
content: [
{
type: 'text',
text: `Your current order:\n${itemList}\n\nStore: ${currentOrder.store?.StoreName || 'None selected'}\nTotal items: ${currentOrder.items.length}`
}
]
};
}
async setCustomerInfo(info) {
currentOrder.customer = {
firstName: info.firstName,
lastName: info.lastName,
email: info.email,
phone: info.phone,
address: info.address
};
return {
content: [
{
type: 'text',
text: `Customer info set for ${info.firstName} ${info.lastName}`
}
]
};
}
async placeOrder(orderInfo) {
if (!currentOrder.store) {
return {
content: [
{
type: 'text',
text: 'Please find a store first using find_dominos_store'
}
]
};
}
if (!currentOrder.customer) {
return {
content: [
{
type: 'text',
text: 'Please set customer info first using set_customer_info'
}
]
};
}
if (currentOrder.items.length === 0) {
return {
content: [
{
type: 'text',
text: 'Your order is empty. Add items first using add_to_order'
}
]
};
}
try {
// Build the order payload for Domino's API
const orderPayload = {
Order: {
Address: currentOrder.customer.address,
Coupons: [],
CustomerID: "",
Email: currentOrder.customer.email,
Extension: "",
FirstName: currentOrder.customer.firstName,
LastName: currentOrder.customer.lastName,
LanguageCode: "en",
OrderChannel: "OLO",
OrderMethod: "Web",
OrderTaker: null,
Payments: [],
Phone: currentOrder.customer.phone,
PhonePrefix: "",
Products: currentOrder.items.map(item => ({
Code: item.code,
Qty: item.quantity,
ID: 1,
isNew: true,
Options: {}
})),
ServiceMethod: orderInfo.orderType,
SourceOrganizationURI: "order.dominos.com",
StoreID: currentOrder.store.StoreID,
Tags: {},
Version: "1.0",
NoCombine: true,
Partners: {},
OrderInfos: [],
HotspotsLite: false,
OrderSourceCode: "CustomerWebsite",
IP: ""
}
};
// Add payment info
if (orderInfo.paymentType === 'CreditCard' && orderInfo.creditCard) {
orderPayload.Order.Payments = [{
Type: "CreditCard",
Amount: 0, // Will be calculated by Domino's
Number: orderInfo.creditCard.number,
CardType: "VISA", // Should detect this
Expiration: orderInfo.creditCard.expiration,
SecurityCode: orderInfo.creditCard.securityCode,
PostalCode: orderInfo.creditCard.postalCode
}];
}
else if (orderInfo.paymentType === 'Cash') {
orderPayload.Order.Payments = [{
Type: "Cash",
Amount: 0
}];
}
// Price the order first
const priceResponse = await fetch(`https://order.dominos.com/power/price-order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(orderPayload)
});
if (!priceResponse.ok) {
throw new Error('Failed to price order');
}
const pricedOrder = await priceResponse.json();
if (pricedOrder.Status !== 0) {
throw new Error(`Order pricing failed: ${JSON.stringify(pricedOrder.StatusItems)}`);
}
// Place the actual order
const placeResponse = await fetch(`https://order.dominos.com/power/place-order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(pricedOrder.Order)
});
if (!placeResponse.ok) {
throw new Error('Failed to place order');
}
const result = await placeResponse.json();
if (result.Status === 0) {
// Order success - clear the current order
currentOrder.items = [];
return {
content: [
{
type: 'text',
text: `🍕 ORDER PLACED SUCCESSFULLY!\n\nOrder ID: ${result.OrderID}\nEstimated Wait: ${result.EstimatedWaitMinutes} minutes\nTotal: $${result.Order?.Amounts?.Customer || 'Unknown'}\n\nYour pizza is being prepared!`
}
]
};
}
else {
return {
content: [
{
type: 'text',
text: `Order failed: ${JSON.stringify(result.StatusItems)}`
}
]
};
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Failed to place order: ${error.message}`
}
]
};
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('MCPizza MCP Server running...');
}
}
const server = new MCPizzaServer();
server.run().catch(console.error);