import type { McpServer } from "@modelcontextprotocol/server" import type { Prisma } from "@prisma/client" import { prisma } from "@/lib/prisma" import { deleteImagesByUrl } from "@/lib/images" import { defineTool } from "@/lib/mcp/define" import { McpToolError } from "@/lib/mcp/context" import { deleteBarItemSchema, listBarItemsSchema, upsertBarItemSchema, } from "@/lib/mcp/schemas" import { formatBarItemLine, ok } from "@/lib/mcp/render" /** Bar inventory - the bottles on hand, which is what recipe availability is computed from. */ export function registerBarTools(server: McpServer): void { defineTool( server, { name: "list_bar_items", title: "List bar inventory", description: "Everything in the user's home bar, grouped by category, with how much is left of each. Use this to answer what they have on hand; use list_recipes when the question is what they can make.", inputSchema: listBarItemsSchema, scope: "bar:read", readOnly: true, }, async ({ category, includeEmpty }, caller) => { const where: Prisma.BarItemWhereInput = { userId: caller.userId } if (category) where.category = category if (!includeEmpty) where.quantity = { not: "EMPTY" } const items = await prisma.barItem.findMany({ where, orderBy: [{ category: "asc" }, { name: "asc" }], }) const text = items.length ? [...items.map(formatBarItemLine), `${items.length} items`].join("\n") : "The bar is empty." return ok(text, { items: items.map((i) => ({ id: i.id, name: i.name, category: i.category, quantity: i.quantity, notes: i.notes, hasImage: !!i.imageUrl, })), total: items.length, }) } ) defineTool( server, { name: "upsert_bar_item", title: "Add or update a bottle", description: "Add a bottle to the bar, or update one that is already there. Omit id to add; pass the id from list_bar_items to update. Set quantity to EMPTY when something runs out rather than deleting it.", inputSchema: upsertBarItemSchema, scope: "bar:write", }, async ({ id, ...fields }, caller) => { if (id) { const existing = await prisma.barItem.findFirst({ where: { id, userId: caller.userId }, select: { id: true }, }) if (!existing) throw new McpToolError("No bar item with that id.", "not_found") const item = await prisma.barItem.update({ where: { id }, data: fields }) return { result: ok(`Updated ${formatBarItemLine(item)}`, { id: item.id }), recordId: item.id, } } const item = await prisma.barItem.create({ data: { ...fields, userId: caller.userId }, }) return { result: ok(`Added ${formatBarItemLine(item)}`, { id: item.id }), recordId: item.id, } } ) defineTool( server, { name: "delete_bar_item", title: "Remove a bottle", description: "Permanently remove a bottle from the bar. To record that something ran out but is usually stocked, set its quantity to EMPTY with upsert_bar_item instead.", inputSchema: deleteBarItemSchema, scope: "bar:write", destructive: true, }, async ({ id }, caller) => { const item = await prisma.barItem.findFirst({ where: { id, userId: caller.userId }, select: { id: true, name: true, imageUrl: true }, }) if (!item) throw new McpToolError("No bar item with that id.", "not_found") await prisma.barItem.delete({ where: { id } }) await deleteImagesByUrl([item.imageUrl]) return { result: ok(`Removed ${item.name} from the bar.`, { id: item.id }), recordId: item.id, } } ) }