Skip to main content
Glama

Azure Cosmos DB MCP Server

AIAssistantDrawer.tsx11.8 kB
// components/AIAssistantDrawer.tsx "use client"; import { Bot, User } from "lucide-react"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { EMAIL } from "@/models/constants"; import { Input } from "@/components/ui/input"; import ReactMarkdown from "react-markdown"; import { Resizable } from "re-resizable"; import { createMCPClient } from "@/lib/mcp-client"; import { useRouter } from "next/navigation"; type Message = { role: "user" | "assistant"; content: string; }; export default function AIAssistantDrawer() { const [message, setMessage] = useState(""); const [messages, setMessages] = useState<Message[]>([]); const [client, setClient] = useState<Client | null>(null); const [isOpen, setIsOpen] = useState(false); const router = useRouter(); // Add router const [drawerWidth, setDrawerWidth] = useState(650); const [isResizing, setIsResizing] = useState(false); const startXRef = useRef(0); const startWidthRef = useRef(0); // Handle resize functionality useEffect(() => { if (!isResizing) return; const handleMouseMove = (e: MouseEvent) => { const deltaX = startXRef.current - e.clientX; const newWidth = startWidthRef.current + deltaX; // Set min and max width constraints if (newWidth > 400 && newWidth < 1000) { setDrawerWidth(newWidth); } }; const handleMouseUp = () => { setIsResizing(false); }; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; }, [isResizing]); const handleResizeStart = (e: React.MouseEvent) => { e.preventDefault(); startXRef.current = e.clientX; startWidthRef.current = drawerWidth; setIsResizing(true); }; // Initialize client when drawer opens const handleDrawerOpen = async () => { if (client) return; // Already initialized const cosmosDBMcpClient = await createMCPClient(); if (cosmosDBMcpClient) { setClient(cosmosDBMcpClient); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!message.trim() || !client) return; const userMessage: Message = { role: "user", content: message }; setMessages((prev) => [...prev, userMessage]); setMessage(""); try { const toolsData = await client.listTools(); // transform mcp tools into openai tools format // add email property to getOrders tool if not already added const openAITools = toolsData.tools.map((tool) => ({ type: "function" as const, function: { name: tool.name, description: tool.description, parameters: tool.name === "getOrders" ? { ...tool.inputSchema, properties: { ...tool.inputSchema.properties, email: { type: "string", default: EMAIL } } } : tool.inputSchema, strict: false, }, })); console.log("openAI tools", openAITools); const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message, conversationHistory: messages, tools: openAITools, }), }); if (!response.ok) { throw new Error("Failed to get response"); } const r = await response.json(); const result = JSON.parse(r.content); // Handle tool calls if present if (result.choices[0].finish_reason === "tool_calls") { const toolCalls = result.choices[0].message.tool_calls; for (const toolCall of toolCalls) { console.log("toolCall:", toolCall); const toolResult = await client.callTool({ name: toolCall.function.name, arguments: JSON.parse(toolCall.function.arguments), }); try { switch (toolCall.function.name) { case "searchProducts": console.log("Formatting searchProducts output..."); const parsedResult = typeof toolResult === "string" ? JSON.parse(toolResult) : toolResult; // Parse the content structure which contains the product array if (parsedResult && parsedResult.content && Array.isArray(parsedResult.content)) { // Look for text content that contains JSON string of products const textContent = parsedResult.content.find((item: { type: string; text: any }) => item.type === "text" && item.text); if (textContent && textContent.text) { // Parse the JSON string in the text field const productsArray = JSON.parse(textContent.text); if (Array.isArray(productsArray) && productsArray.length > 0) { // Extract product IDs from the array of product objects const productIds = productsArray.filter((product) => product.id).map((product) => product.id); if (productIds.length > 0) { // Navigate to products page with IDs but keep drawer open router.push(`/products?ids=${productIds.join(",")}`); // Create a formatted list of products for display const productsFormatted = ` ${productsArray.map((p) => `\n**${p.name}**: $${p.price}`).join("\n")}`; const formattedResponse: Message = { role: "assistant", content: productsFormatted, }; setMessages((prev) => [...prev, formattedResponse]); } } } } break; case "getOrders": console.log("Formatting getOrders output..."); const ordersResult = typeof toolResult === "string" ? JSON.parse(toolResult) : toolResult; if (ordersResult && ordersResult.content && Array.isArray(ordersResult.content)) { const textContent = ordersResult.content.find((item: { type: string; text: any }) => item.type === "text" && item.text); if (textContent && textContent.text) { const orders = JSON.parse(textContent.text); if (Array.isArray(orders) && orders.length > 0) { const ordersFormatted = `\n# Your Orders:\n\n ${orders .map( (order) => `\n**Order ID**: ${order.id} \n**Date**: ${new Date(order.createdAt).toLocaleDateString()} \n**Status**: ${order.status} \n**Total**: $${order.total.toFixed(2)} \n**Items**: ${order.items.length} items ${order.items.map((item: any) => `\n- ${item.name} (${item.quantity}x) at $${item.price}`).join("\n")}` ) .join("\n\n---\n\n")}`; const formattedResponse: Message = { role: "assistant", content: ordersFormatted, }; setMessages((prev) => [...prev, formattedResponse]); } else { const noOrdersResponse: Message = { role: "assistant", content: "No orders found for this email address.", }; setMessages((prev) => [...prev, noOrdersResponse]); } } } break; default: console.log("No tool was called..."); break; } } catch (parseError) { console.error("Error parsing tool result:", parseError); } } } else if (result.choices[0].message.content) { console.log("Not a tools call, displaying response..."); // Handle normal message response const assistantMessage: Message = { role: "assistant", content: result.choices[0].message.content, }; setMessages((prev) => [...prev, assistantMessage]); } } catch (e) { console.error(e); const errorMessage: Message = { role: "assistant", content: "Sorry, I encountered an error processing your request.", }; setMessages((prev) => [...prev, errorMessage]); } }; const handleOpenChange = (open: boolean) => { setIsOpen(open); if (open) handleDrawerOpen(); }; return ( <Sheet modal={false} open={isOpen} onOpenChange={handleOpenChange}> <SheetTrigger asChild> <Button variant="ghost" size="icon" className="p-2 hover:bg-gray-100 rounded-full"> <Bot className="h-6 w-6" /> </Button> </SheetTrigger> <SheetContent style={{ width: "auto", maxWidth: "none", padding: 0 }} className="overflow-visible" onInteractOutside={(e) => { const target = e.target as HTMLElement; if (!target.closest('[data-state="closed"]')) { e.preventDefault(); } }} > <Resizable defaultSize={{ width: drawerWidth, height: "100%" }} minWidth={400} maxWidth={1000} enable={{ left: true, right: false, top: false, bottom: false }} handleClasses={{ left: "w-2 bg-transparent hover:bg-blue-400 hover:opacity-50 cursor-ew-resize" }} onResizeStop={(e, direction, ref, d) => { setDrawerWidth(drawerWidth + d.width); }} > <div className="p-6 h-full"> <SheetHeader> <SheetTitle>AI Shopping Assistant</SheetTitle> </SheetHeader> <div className="flex flex-col h-[calc(100vh-8rem)]"> <div className="flex-1 overflow-y-auto mb-4 p-4 bg-gray-50 rounded-lg"> {messages.length === 0 ? ( <p className="text-gray-500 text-center mt-4">Ask me anything about our products!</p> ) : ( <div className="space-y-4"> {messages.map((msg, index) => ( <div key={index} className={`flex items-start gap-2 ${msg.role === "assistant" ? "flex-row" : "flex-row-reverse"}`}> <div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">{msg.role === "assistant" ? <Bot className="h-5 w-5" /> : <User className="h-5 w-5" />}</div> <div className={`rounded-lg p-3 max-w-[80%] ${msg.role === "assistant" ? "bg-white border prose prose-sm" : "bg-blue-500 text-white"}`}>{msg.role === "assistant" ? <ReactMarkdown>{msg.content}</ReactMarkdown> : msg.content}</div> </div> ))} </div> )} </div> <form onSubmit={handleSubmit} className="flex gap-2"> <Input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Type your message..." className="flex-1" /> <Button type="submit">Send</Button> </form> </div> </div> </Resizable> </SheetContent> </Sheet> ); }

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/patrice-truong/cosmosdb-mcp'

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