Skip to main content
Glama
tas1337

MCP A2A AP2 Food Delivery & Payments

by tas1337
tools.ts7.86 kB
// ============================================================================= // 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, }; }

Latest Blog Posts

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/tas1337/mcp-a2a-ap2-im-hungry'

If you have feedback or need assistance with the MCP directory API, please join our Discord server