import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
import { products, orders } from "../shared/data";
// MCP Protocol Types
interface JsonRpcRequest {
jsonrpc: string;
method: string;
params?: any;
id: number | string | null;
}
interface JsonRpcResponse {
jsonrpc: string;
result?: any;
error?: {
code: number;
message: string;
data?: any;
};
id: number | string | null;
}
// Server capabilities and info
const SERVER_INFO = {
name: "azure-product-catalog-server",
version: "1.0.0"
};
const CAPABILITIES = {
tools: {},
resources: {}
};
// Tool definitions
const TOOLS = [
{
name: "get_products",
description: "Retrieve a list of all products in the catalog",
inputSchema: {
type: "object",
properties: {},
required: []
}
},
{
name: "get_product_by_id",
description: "Get details of a specific product by its ID",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "The product ID"
}
},
required: ["id"]
}
},
{
name: "search_products",
description: "Search products by query string (searches name, description, and category)",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query"
}
},
required: ["query"]
}
},
{
name: "get_orders",
description: "Retrieve a list of all orders",
inputSchema: {
type: "object",
properties: {},
required: []
}
},
{
name: "create_order",
description: "Create a new order for a product",
inputSchema: {
type: "object",
properties: {
productId: {
type: "string",
description: "ID of the product to order"
},
quantity: {
type: "number",
description: "Quantity to order"
}
},
required: ["productId", "quantity"]
}
}
];
// Tool handlers
function handleGetProducts(): any {
return {
content: [
{
type: "text",
text: JSON.stringify(products, null, 2)
}
]
};
}
function handleGetProductById(args: { id: string }): any {
const product = products.find(p => p.id === args.id);
if (!product) {
return {
content: [
{
type: "text",
text: JSON.stringify({ error: "Product not found" })
}
],
isError: true
};
}
return {
content: [
{
type: "text",
text: JSON.stringify(product, null, 2)
}
]
};
}
function handleSearchProducts(args: { query: string }): any {
const query = args.query.toLowerCase();
const results = products.filter(p =>
p.name.toLowerCase().includes(query) ||
p.description.toLowerCase().includes(query) ||
p.category.toLowerCase().includes(query)
);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2)
}
]
};
}
function handleGetOrders(): any {
return {
content: [
{
type: "text",
text: JSON.stringify(orders, null, 2)
}
]
};
}
function handleCreateOrder(args: { productId: string; quantity: number }): any {
const product = products.find(p => p.id === args.productId);
if (!product) {
return {
content: [
{
type: "text",
text: JSON.stringify({ error: `Product ${args.productId} not found` })
}
],
isError: true
};
}
const newOrder = {
orderId: `ord-${Date.now()}`,
productId: args.productId,
productName: product.name,
quantity: args.quantity,
unitPrice: product.price,
totalPrice: product.price * args.quantity,
status: "created" as const,
createdAt: new Date().toISOString()
};
orders.push(newOrder);
return {
content: [
{
type: "text",
text: JSON.stringify(newOrder, null, 2)
}
]
};
}
// MCP Request Handlers
function handleInitialize(params: any): any {
return {
protocolVersion: "2024-11-05",
capabilities: CAPABILITIES,
serverInfo: SERVER_INFO
};
}
function handleToolsList(): any {
return { tools: TOOLS };
}
function handleToolsCall(params: { name: string; arguments?: any }): any {
const toolName = params.name;
const args = params.arguments || {};
switch (toolName) {
case "get_products":
return handleGetProducts();
case "get_product_by_id":
return handleGetProductById(args);
case "search_products":
return handleSearchProducts(args);
case "get_orders":
return handleGetOrders();
case "create_order":
return handleCreateOrder(args);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
// Main MCP endpoint handler
async function mcpHandler(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
context.log("MCP request received");
try {
const body = await request.json() as JsonRpcRequest;
context.log(`MCP method: ${body.method}`);
let result: any;
switch (body.method) {
case "initialize":
result = handleInitialize(body.params);
break;
case "tools/list":
result = handleToolsList();
break;
case "tools/call":
result = handleToolsCall(body.params);
break;
case "notifications/initialized":
// This is a notification, no response needed
return {
status: 204,
headers: {
"Content-Type": "application/json"
}
};
default:
const response: JsonRpcResponse = {
jsonrpc: "2.0",
error: {
code: -32601,
message: `Method not found: ${body.method}`
},
id: body.id
};
return {
status: 200,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(response)
};
}
const response: JsonRpcResponse = {
jsonrpc: "2.0",
result,
id: body.id
};
return {
status: 200,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(response)
};
} catch (error: any) {
context.error("MCP error:", error);
const response: JsonRpcResponse = {
jsonrpc: "2.0",
error: {
code: -32603,
message: error.message || "Internal error"
},
id: null
};
return {
status: 200,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(response)
};
}
}
app.http("mcp", {
methods: ["POST"],
authLevel: "function",
handler: mcpHandler
});