Skip to main content
Glama

Lemonade Stand MCP Server

by jimmcq
server.js•13.7 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { v4 as uuidv4 } from 'uuid'; console.error("Initializing Lemonade Stand MCP server"); // Create server const server = new Server( { name: 'lemonade-stand', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, prompts: {} } } ); // Game state const games = new Map(); // Generate weather const generateWeather = () => { const temp = Math.floor(Math.random() * 40) + 50; // 50-90°F const conditions = ['Sunny', 'Partly Cloudy', 'Cloudy', 'Rainy']; const condition = conditions[Math.floor(Math.random() * conditions.length)]; return { temp, condition }; }; // Create new game const createNewGame = () => ({ day: 1, money: 20.00, inventory: { cups: 0, lemons: 0, sugar: 0, ice: 0 }, purchaseHistory: [], // Keep track of purchases for proper cost calculation pricePerCup: 0.25, weather: generateWeather(), status: 'buying' }); // Handle buy supplies const handleBuySupplies = (gameState, purchases) => { const prices = { cups: 0.05, lemons: 0.10, sugar: 0.08, ice: 0.02 }; let totalCost = 0; const newInventory = { ...gameState.inventory }; const purchaseRecord = { ...purchases, date: gameState.day }; for (const item of ['cups', 'lemons', 'sugar', 'ice']) { const amount = purchases[item] || 0; const cost = amount * prices[item]; totalCost += cost; newInventory[item] += amount; purchaseRecord[`${item}Cost`] = cost; } if (totalCost > gameState.money) { return { success: false, message: "Not enough money!" }; } purchaseRecord.totalCost = totalCost; return { success: true, gameState: { ...gameState, money: gameState.money - totalCost, inventory: newInventory, purchaseHistory: [...gameState.purchaseHistory, purchaseRecord], status: 'pricing' } }; }; // Calculate potential customers const calculatePotentialCustomers = (weather, price) => { let customers = Math.floor(weather.temp / 3); if (weather.condition === 'Sunny') customers += 15; if (weather.condition === 'Partly Cloudy') customers += 10; if (weather.condition === 'Cloudy') customers += 5; if (weather.condition === 'Rainy') customers -= 10; // Price elasticity if (price <= 0.15) customers += 10; if (price > 0.50) customers -= Math.floor((price - 0.50) * 30); return Math.max(0, customers); }; // Make recipe const makeRecipe = (gameState) => { const cupsPerPitcher = 10; const lemonsPerPitcher = 4; const sugarPerPitcher = 4; const icePerPitcher = 15; const possiblePitchers = Math.min( Math.floor(gameState.inventory.lemons / lemonsPerPitcher), Math.floor(gameState.inventory.sugar / sugarPerPitcher), Math.floor(gameState.inventory.ice / icePerPitcher) ); const maxCups = Math.min( possiblePitchers * cupsPerPitcher, gameState.inventory.cups ); return { maxCups, lemonsUsed: Math.floor((maxCups / cupsPerPitcher) * lemonsPerPitcher), sugarUsed: Math.floor((maxCups / cupsPerPitcher) * sugarPerPitcher), iceUsed: Math.floor((maxCups / cupsPerPitcher) * icePerPitcher) }; }; // Calculate cost per cup based on actual ingredients used const calculateCostPerCup = () => { const prices = { cups: 0.05, lemons: 0.10, sugar: 0.08, ice: 0.02 }; const cupsPerPitcher = 10; const lemonsPerPitcher = 4; const sugarPerPitcher = 4; const icePerPitcher = 15; // Calculate cost of ingredients per pitcher const pitcherCost = (lemonsPerPitcher * prices.lemons) + (sugarPerPitcher * prices.sugar) + (icePerPitcher * prices.ice); // Calculate cost per cup including the cup itself return (pitcherCost / cupsPerPitcher) + prices.cups; }; // Handle sell lemonade const handleSellLemonade = (gameState) => { const recipe = makeRecipe(gameState); const potentialCustomers = calculatePotentialCustomers(gameState.weather, gameState.pricePerCup); const actualSales = Math.min(potentialCustomers, recipe.maxCups); const revenue = actualSales * gameState.pricePerCup; const newInventory = { cups: gameState.inventory.cups - actualSales, lemons: gameState.inventory.lemons - recipe.lemonsUsed, sugar: gameState.inventory.sugar - recipe.sugarUsed, ice: 0 // ice melts every day }; // Calculate the actual cost per cup based on recipe const actualCostPerCup = calculateCostPerCup(); const dailyCost = actualSales * actualCostPerCup; const dailyProfit = revenue - dailyCost; return { success: true, dailyResults: { sales: actualSales, revenue: revenue, cost: dailyCost, profit: dailyProfit, costPerCup: actualCostPerCup, potentialCustomers: potentialCustomers, unsatisfiedCustomers: Math.max(0, potentialCustomers - actualSales) }, gameState: { ...gameState, money: gameState.money + revenue, // Revenue is added, not profit inventory: newInventory, status: 'reporting' } }; }; // Handle next day const handleNextDay = (gameState) => { if (gameState.day >= 14) { return { success: true, gameState: { ...gameState, status: 'gameOver' } }; } return { success: true, gameState: { ...gameState, day: gameState.day + 1, weather: generateWeather(), status: 'buying', inventory: { ...gameState.inventory, ice: 0 // ice melts daily } } }; }; // Set up tools - only actions that modify state server.setRequestHandler(ListToolsRequestSchema, async () => { console.error('Listing tools'); return { tools: [ { name: "start_game", description: "Start a new lemonade stand game", inputSchema: { type: "object", properties: {} } }, { name: "buy_supplies", description: "Purchase supplies for the lemonade stand", inputSchema: { type: "object", properties: { gameId: { type: "string", description: "The game ID" }, cups: { type: "number", description: "Number of cups to buy" }, lemons: { type: "number", description: "Number of lemons to buy" }, sugar: { type: "number", description: "Amount of sugar to buy" }, ice: { type: "number", description: "Amount of ice to buy" } }, required: ["gameId"] } }, { name: "set_price", description: "Set the price per cup of lemonade", inputSchema: { type: "object", properties: { gameId: { type: "string", description: "The game ID" }, price: { type: "number", description: "Price per cup in dollars" } }, required: ["gameId", "price"] } }, { name: "sell_lemonade", description: "Open for business and see today's results", inputSchema: { type: "object", properties: { gameId: { type: "string", description: "The game ID" } }, required: ["gameId"] } }, { name: "next_day", description: "Advance to the next day", inputSchema: { type: "object", properties: { gameId: { type: "string", description: "The game ID" } }, required: ["gameId"] } } ] }; }); // Handle tool calls - only actions that modify state server.setRequestHandler(CallToolRequestSchema, async (request) => { console.error(`Tool call received: ${request.params.name}`); switch (request.params.name) { case 'start_game': { const gameId = uuidv4(); const initialGameState = createNewGame(); games.set(gameId, initialGameState); return { content: [{ type: "text", text: JSON.stringify({ gameId, gameState: initialGameState }) }] }; } case 'buy_supplies': { const buyGame = games.get(request.params.arguments?.gameId); if (!buyGame) { throw new McpError(ErrorCode.InvalidRequest, "Game not found"); } const result = handleBuySupplies(buyGame, request.params.arguments); if (result.success) { games.set(request.params.arguments.gameId, result.gameState); } return { content: [{ type: "text", text: JSON.stringify(result) }] }; } case 'set_price': { const priceGame = games.get(request.params.arguments?.gameId); if (!priceGame) { throw new McpError(ErrorCode.InvalidRequest, "Game not found"); } priceGame.pricePerCup = parseFloat(request.params.arguments.price); priceGame.status = 'selling'; games.set(request.params.arguments.gameId, priceGame); return { content: [{ type: "text", text: JSON.stringify({ success: true, gameState: priceGame }) }] }; } case 'sell_lemonade': { const sellGame = games.get(request.params.arguments?.gameId); if (!sellGame) { throw new McpError(ErrorCode.InvalidRequest, "Game not found"); } const sellResult = handleSellLemonade(sellGame); if (sellResult.success) { games.set(request.params.arguments.gameId, sellResult.gameState); } return { content: [{ type: "text", text: JSON.stringify(sellResult) }] }; } case 'next_day': { const nextDayGame = games.get(request.params.arguments?.gameId); if (!nextDayGame) { throw new McpError(ErrorCode.InvalidRequest, "Game not found"); } const nextDayResult = handleNextDay(nextDayGame); if (nextDayResult.success) { games.set(request.params.arguments.gameId, nextDayResult.gameState); } return { content: [{ type: "text", text: JSON.stringify(nextDayResult) }] }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } }); // Set up resources - read-only data access server.setRequestHandler(ListResourcesRequestSchema, async () => { console.error('Listing resources'); const resources = []; // Add a resource for each active game for (const [gameId, gameState] of games.entries()) { resources.push({ uri: `game://${gameId}`, name: `Game ${gameId}`, description: `Game state for Day ${gameState.day}, $${gameState.money.toFixed(2)} balance`, mimeType: "application/json" }); } return { resources }; }); // Handle resource reading - for reading game state server.setRequestHandler(ReadResourceRequestSchema, async (request) => { console.error(`Reading resource: ${request.params.uri}`); const gameMatch = request.params.uri.match(/^game:\/\/(.+)$/); if (gameMatch) { const gameId = gameMatch[1]; const gameState = games.get(gameId); if (!gameState) { throw new McpError(ErrorCode.InvalidRequest, "Game not found"); } return { contents: [{ uri: request.params.uri, mimeType: "application/json", text: JSON.stringify({ gameState }, null, 2) }] }; } throw new McpError(ErrorCode.InvalidRequest, "Unknown resource URI"); }); // Set up prompts server.setRequestHandler(ListPromptsRequestSchema, async () => { console.error('Listing prompts'); return { prompts: [ { name: "start_lemonade_game", description: "Start a new Lemonade Stand game with an introduction", arguments: [] } ] }; }); // Handle prompt retrieval server.setRequestHandler(GetPromptRequestSchema, async (request) => { console.error(`Getting prompt: ${request.params.name}`); if (request.params.name === "start_lemonade_game") { return { messages: [ { role: "user", content: { type: "text", text: "I want to play the Lemonade Stand game." } }, { role: "assistant", content: { type: "text", text: `Welcome to Lemonade Stand Tycoon! šŸ‹ You're about to embark on a 14-day journey to build your lemonade empire. Your goal is to make as much profit as possible by: 1. Buying supplies wisely 2. Setting the right price for your lemonade 3. Responding to weather conditions You start with $20. Each day, you'll need to: - Check the weather forecast - Buy cups, lemons, sugar, and ice - Set your lemonade price - See how many customers you attract Remember: Ice melts daily, so buy just what you need! Each cup of lemonade requires: - 1 cup (5Ā¢) - 0.4 lemons (4Ā¢) - 0.4 sugar (3.2Ā¢) - 1.5 ice (3Ā¢) Total cost per cup: ~15.2Ā¢ Let me start a new game for you...` } } ] }; } throw new McpError(ErrorCode.InvalidRequest, "Unknown prompt"); }); // Main execution const transport = new StdioServerTransport(); server.connect(transport).then(() => { console.error("Lemonade Stand MCP server running on stdio"); }).catch(error => { console.error("Failed to start server:", error); process.exit(1); });

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/jimmcq/Lemonade-Stand-MCP-Server'

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