import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// Tipo de producto — en producción esto viene de tu ORM o API client
interface Product {
id: string;
sku: string;
name: string;
price: number;
stock: number;
status: 'active' | 'disabled' | 'out_of_stock';
}
// Simulamos una capa de servicio real
async function fetchProducts(filters: {
sku?: string;
status?: string;
limit?: number;
}): Promise<Product[]> {
// Aquí iría tu llamada a la API de Magento, tu ORM de Symfony, etc.
const baseUrl = process.env.API_BASE_URL;
const apiKey = process.env.API_KEY;
const params = new URLSearchParams();
if (filters.sku) params.append('sku', filters.sku);
if (filters.status) params.append('status', filters.status);
if (filters.limit) params.append('limit', String(filters.limit));
const response = await fetch(`${baseUrl}/products?${params}`, {
headers: { 'X-API-Key': apiKey ?? '' },
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
async function updateProductStock(
sku: string,
quantity: number
): Promise<{ success: boolean; message: string }> {
const baseUrl = process.env.API_BASE_URL;
const apiKey = process.env.API_KEY;
const response = await fetch(`${baseUrl}/products/${sku}/stock`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey ?? '',
},
body: JSON.stringify({ quantity }),
});
if (!response.ok) {
throw new Error(`Stock update failed: ${response.status}`);
}
return response.json();
}
// Crear el servidor MCP
const server = new McpServer({
name: 'product-catalog-mcp',
version: '1.0.0',
});
// Tool 1: consultar productos
server.tool(
'search_products',
'Busca productos en el catálogo por SKU, estado o con límite de resultados',
{
sku: z.string().optional().describe('SKU exacto del producto'),
status: z
.enum(['active', 'disabled', 'out_of_stock'])
.optional()
.describe('Estado del producto'),
limit: z
.number()
.int()
.min(1)
.max(100)
.default(10)
.describe('Número máximo de resultados'),
},
async ({ sku, status, limit }) => {
const products = await fetchProducts({ sku, status, limit });
return {
content: [
{
type: 'text',
text: JSON.stringify(products, null, 2),
},
],
};
}
);
// Tool 2: actualizar stock
server.tool(
'update_stock',
'Actualiza la cantidad en stock de un producto por su SKU',
{
sku: z.string().describe('SKU del producto a actualizar'),
quantity: z
.number()
.int()
.min(0)
.describe('Nueva cantidad en stock'),
},
async ({ sku, quantity }) => {
const result = await updateProductStock(sku, quantity);
return {
content: [
{
type: 'text',
text: result.success
? `Stock actualizado correctamente: ${result.message}`
: `Error al actualizar: ${result.message}`,
},
],
};
}
);
// Resource: documentación de la API
server.resource(
'api-docs',
'docs://product-api',
async (uri) => ({
contents: [
{
uri: uri.href,
text: `# Product Catalog API\n\nEndpoints disponibles:\n- GET /products — lista productos con filtros\n- PATCH /products/:sku/stock — actualiza stock\n\nAutenticación: header X-API-Key requerido en todas las llamadas.`,
},
],
})
);
// Arrancar con transport stdio
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Product Catalog MCP server running on stdio');