// =============================================================================
// MCP TOOLS (A2A + AP2)
// =============================================================================
// Food tools use A2A to call agents.
// place_order also uses AP2 to process payment first!
// =============================================================================
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import type {
Restaurant,
Menu,
MenuItem,
DeliveryEstimate,
OrderItem,
OrderStatus,
PlaceOrderArgs,
PlaceOrderResult,
} from './interfaces.js';
import { searchRestaurants, getMenu, getDeliveryEstimate, placeOrder, checkOrderStatus } from './a2a-client.js';
import { readUserLocation, readUserPreferences } from './resources.js';
import { handleProcessPayment } from './ap2-tools.js';
// =============================================================================
// TOOL DEFINITIONS
// =============================================================================
export const searchRestaurantsTool: Tool = {
name: 'search_restaurants',
description: 'Search restaurants. Location auto-filled. DO NOT ask user - just search based on what they want.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query (e.g., "burger", "pizza")' },
},
required: ['query'],
},
};
export const getMenuTool: Tool = {
name: 'get_menu',
description: 'Get menu. Use this to find items and prices, then place_order.',
inputSchema: {
type: 'object',
properties: {
restaurantId: { type: 'string', description: 'Restaurant ID' },
},
required: ['restaurantId'],
},
};
export const searchMenuItemsTool: Tool = {
name: 'search_menu_items',
description: 'Search dishes across all restaurants. Pick the best match for user.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Dish name to search for' },
limit: { type: 'number', description: 'Maximum results (default: 20)' },
},
required: ['query'],
},
};
export const checkDeliveryEstimateTool: Tool = {
name: 'check_delivery_estimate',
description: 'Get delivery time and fee. Use to pick cheapest/fastest if user asks.',
inputSchema: {
type: 'object',
properties: {
restaurantId: { type: 'string', description: 'Restaurant ID' },
},
required: ['restaurantId'],
},
};
export const placeOrderTool: Tool = {
name: 'place_order',
description: 'Place order and process payment. BEFORE calling this, you MUST show order summary (restaurant, items, total with delivery fee) and ask "Should I place this order?" Only call AFTER user says yes/approve.',
inputSchema: {
type: 'object',
properties: {
restaurantId: { type: 'string', description: 'Restaurant ID' },
items: {
type: 'array',
description: 'Items to order',
items: {
type: 'object',
properties: {
itemId: { type: 'string' },
quantity: { type: 'number' },
},
},
},
totalAmount: { type: 'number', description: 'Total = item price + delivery fee' },
paymentMethod: {
type: 'string',
description: 'Default: credit_card',
enum: ['credit_card', 'debit_card', 'paypal', 'apple_pay', 'google_pay'],
},
},
required: ['restaurantId', 'items', 'totalAmount', 'paymentMethod'],
},
};
export const checkOrderStatusTool: Tool = {
name: 'check_order_status',
description: 'Check status of an order',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string', description: 'Order ID' },
},
required: ['orderId'],
},
};
// =============================================================================
// HANDLERS
// =============================================================================
async function getLocation(): Promise<string> {
const data = JSON.parse(await readUserLocation());
return data.address || '123 Main St, San Francisco, CA';
}
async function getUserId(): Promise<string> {
const data = JSON.parse(await readUserPreferences());
return data.userId || 'user-001';
}
export async function handleSearchRestaurants(args: { query: string }): Promise<Restaurant[]> {
const location = await getLocation();
const results = await searchRestaurants(args.query, location);
return results.sort((a, b) => b.rating - a.rating);
}
export async function handleGetMenu(args: { restaurantId: string }): Promise<Menu> {
return await getMenu(args.restaurantId);
}
export async function handleSearchMenuItems(args: { query: string; limit?: number }): Promise<Array<MenuItem & { restaurantId: string; restaurantName: string; service: string }>> {
const restaurants = await handleSearchRestaurants({ query: args.query });
const allItems: Array<MenuItem & { restaurantId: string; restaurantName: string; service: string }> = [];
const queryLower = args.query.toLowerCase();
for (const restaurant of restaurants.slice(0, 10)) {
const menu = await handleGetMenu({ restaurantId: restaurant.id });
const matching = menu.items.filter(
item => item.name.toLowerCase().includes(queryLower) || item.description?.toLowerCase().includes(queryLower)
);
matching.forEach(item => {
allItems.push({
...item,
restaurantId: restaurant.id,
restaurantName: restaurant.name,
service: restaurant.service,
});
});
}
return allItems.slice(0, args.limit || 20);
}
export async function handleCheckDeliveryEstimate(args: { restaurantId: string }): Promise<DeliveryEstimate> {
const location = await getLocation();
const result = await getDeliveryEstimate(args.restaurantId, location);
const service = args.restaurantId.startsWith('dd-') ? 'doordash'
: args.restaurantId.startsWith('ue-') ? 'ubereats'
: 'grubhub';
return {
restaurantId: args.restaurantId,
restaurantName: 'Unknown',
estimatedTime: result.estimatedTime,
deliveryFee: result.deliveryFee,
minimumOrder: result.minimumOrder,
service: service as 'doordash' | 'ubereats' | 'grubhub',
};
}
export async function handlePlaceOrder(args: PlaceOrderArgs): Promise<PlaceOrderResult> {
const address = await getLocation();
const userId = await getUserId();
const tempOrderId = `order-${Date.now()}`;
const paymentResult = await handleProcessPayment({
amount: args.totalAmount,
currency: 'USD',
paymentMethod: args.paymentMethod,
orderId: tempOrderId,
userId: userId,
});
if (paymentResult.status !== 'completed') {
throw new Error(`Payment failed: ${paymentResult.status}`);
}
const orderResult = await placeOrder(args.restaurantId, args.items, address);
return {
orderId: orderResult.orderId,
status: orderResult.status,
service: orderResult.service,
payment: {
transactionId: paymentResult.transactionId,
status: paymentResult.status,
amount: paymentResult.amount,
currency: paymentResult.currency,
},
mandate: {
userId: paymentResult.mandate.userId,
agentId: paymentResult.mandate.agentId,
action: paymentResult.mandate.action,
maxAmount: paymentResult.mandate.maxAmount,
currency: paymentResult.mandate.currency,
orderId: paymentResult.mandate.orderId,
issuedAt: paymentResult.mandate.issuedAt,
expiresAt: paymentResult.mandate.expiresAt,
signature: paymentResult.mandate.signature,
},
};
}
export async function handleCheckOrderStatus(args: { orderId: string }): Promise<OrderStatus> {
const result = await checkOrderStatus(args.orderId);
return {
orderId: result.orderId,
status: result.status,
currentStage: result.currentStage,
service: result.service,
};
}