diff --git a/README.md b/README.md index 72da370..39f9025 100644 --- a/README.md +++ b/README.md @@ -833,6 +833,26 @@ await mcpClient.callTool({ See [examples/slack-with-auth](./examples/slack-with-auth) for a complete working example. +### E-commerce Product Search + +A self-contained example with a mock product database (20 products, 5 categories). + +**What it demonstrates:** +- `@Tool` — search with filters, pagination, sorting, product details, recommendations +- `@Resource` — full product catalog as a JSON data endpoint +- `@Prompt` — AI shopping assistant template + +```bash +cd examples/ecommerce-search +npm install +npm start +# Dashboard: http://localhost:8080 +# MCP endpoint: http://localhost:8080/mcp +``` + +See [examples/ecommerce-search](./examples/ecommerce-search) for full documentation and curl examples. + +## Development
Development (Click to expand) diff --git a/examples/ecommerce-search/README.md b/examples/ecommerce-search/README.md new file mode 100644 index 0000000..f53d972 --- /dev/null +++ b/examples/ecommerce-search/README.md @@ -0,0 +1,204 @@ +# E-commerce Product Search Example + +A complete LeanMCP example demonstrating product search over a mock database with full-text search, filtering, pagination, sorting, and product recommendations. + +## Features Demonstrated + +- **All three MCP primitives**: `@Tool`, `@Resource`, and `@Prompt` decorators +- Type-safe input schemas with `@SchemaConstraint` and `@Optional` +- Full-text search across multiple fields +- Multi-criteria filtering (category, brand, price range, stock status) +- Pagination with metadata (page count, hasNext/hasPrevious) +- Sorting (price asc/desc, rating, newest) +- Similarity-based product recommendations +- Data modeling with TypeScript interfaces + +## Installation + +```bash +npm install +``` + +## Running + +```bash +npm start +``` + +The server starts on `http://localhost:8080`: +- **MCP endpoint**: `http://localhost:8080/mcp` +- **Dashboard UI**: `http://localhost:8080` (test tools visually) +- **Health check**: `http://localhost:8080/health` + +## Registered Tools + +| Tool | Description | +|------|-------------| +| `searchProducts` | Full-text search with category, brand, price, stock filters + pagination | +| `getProductDetails` | Get complete details for a single product by ID | +| `getCategories` | List all categories with product counts and price ranges | +| `getProductRecommendations` | Get similar products based on shared category and tags | + +## Resource + +| Resource | Description | +|----------|-------------| +| `productCatalog` | Full product catalog as JSON (all products, categories, brands) | + +## Prompt + +| Prompt | Description | +|--------|-------------| +| `productSearchAssistant` | AI shopping assistant prompt with store context | + +## Testing + +### Search Products + +```bash +curl -s -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "searchProducts", + "arguments": { + "query": "wireless", + "page": 1, + "pageSize": 5 + } + } + }' +``` + +### Search with Filters + +```bash +curl -s -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "searchProducts", + "arguments": { + "category": "Electronics", + "minPrice": 50, + "maxPrice": 500, + "inStock": true, + "sortBy": "price_asc" + } + } + }' +``` + +### Get Product Details + +```bash +curl -s -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "getProductDetails", + "arguments": { + "productId": "prod-001" + } + } + }' +``` + +### Get Categories + +```bash +curl -s -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 4, + "method": "tools/call", + "params": { + "name": "getCategories", + "arguments": {} + } + }' +``` + +### Get Recommendations + +```bash +curl -s -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "getProductRecommendations", + "arguments": { + "productId": "prod-001", + "limit": 3 + } + } + }' +``` + +## Project Structure + +``` +ecommerce-search/ +├── main.ts # Entry point - starts MCP server +├── package.json # Dependencies +├── tsconfig.json # TypeScript config +├── README.md # This file +└── mcp/ + ├── data/ + │ └── products.ts # Mock product database (20 products) + └── products/ + └── index.ts # Product search service (Tools, Resource, Prompt) +``` + +## Key Concepts + +### Pagination + +The `searchProducts` tool returns paginated results with metadata: + +```json +{ + "products": [...], + "pagination": { + "page": 1, + "pageSize": 10, + "totalResults": 20, + "totalPages": 2, + "hasNextPage": true, + "hasPreviousPage": false + } +} +``` + +### Input Validation + +Input schemas use `@SchemaConstraint` for validation: + +```typescript +@Optional() +@SchemaConstraint({ + description: 'Results per page', + minimum: 1, + maximum: 50, + default: 10 +}) +pageSize?: number; +``` + +## Learn More + +- [Main README](../../README.md) +- [LeanMCP Core Package](../../packages/core/) diff --git a/examples/ecommerce-search/main.ts b/examples/ecommerce-search/main.ts new file mode 100644 index 0000000..048387c --- /dev/null +++ b/examples/ecommerce-search/main.ts @@ -0,0 +1,15 @@ +import { createHTTPServer } from "@leanmcp/core"; + +// Services are automatically discovered from ./mcp directory +await createHTTPServer({ + name: "ecommerce-search-server", + version: "1.0.0", + port: 8080, + cors: true, + logging: true +}); + +console.log("\nE-commerce Product Search MCP Server"); +console.log(" MCP Endpoint: http://localhost:8080/mcp"); +console.log(" Dashboard: http://localhost:8080"); +console.log(" Health check: http://localhost:8080/health"); diff --git a/examples/ecommerce-search/mcp/data/products.ts b/examples/ecommerce-search/mcp/data/products.ts new file mode 100644 index 0000000..fc8e1d3 --- /dev/null +++ b/examples/ecommerce-search/mcp/data/products.ts @@ -0,0 +1,333 @@ +// ============================================================================ +// Mock Product Database +// ============================================================================ + +export interface Product { + id: string; + name: string; + description: string; + category: string; + price: number; + currency: string; + brand: string; + rating: number; + reviewCount: number; + inStock: boolean; + tags: string[]; + imageUrl: string; + createdAt: string; +} + +export const products: Product[] = [ + { + id: 'prod-001', + name: 'Wireless Noise-Cancelling Headphones', + description: 'Premium over-ear headphones with active noise cancellation, 30-hour battery life, and hi-res audio support.', + category: 'Electronics', + price: 299.99, + currency: 'USD', + brand: 'SoundMax', + rating: 4.7, + reviewCount: 2341, + inStock: true, + tags: ['wireless', 'noise-cancelling', 'bluetooth', 'audio', 'headphones'], + imageUrl: 'https://example.com/images/headphones-001.jpg', + createdAt: '2024-08-15T10:00:00Z' + }, + { + id: 'prod-002', + name: 'Organic Cotton T-Shirt', + description: 'Soft, breathable organic cotton t-shirt available in multiple colors. Sustainably sourced and ethically made.', + category: 'Clothing', + price: 29.99, + currency: 'USD', + brand: 'EcoWear', + rating: 4.3, + reviewCount: 578, + inStock: true, + tags: ['organic', 'cotton', 'sustainable', 'casual', 't-shirt'], + imageUrl: 'https://example.com/images/tshirt-002.jpg', + createdAt: '2024-09-01T08:30:00Z' + }, + { + id: 'prod-003', + name: 'Stainless Steel Water Bottle', + description: 'Double-walled insulated water bottle that keeps drinks cold for 24 hours or hot for 12 hours. BPA-free.', + category: 'Home & Kitchen', + price: 34.95, + currency: 'USD', + brand: 'HydroLife', + rating: 4.6, + reviewCount: 1892, + inStock: true, + tags: ['insulated', 'stainless-steel', 'bpa-free', 'eco-friendly', 'water-bottle'], + imageUrl: 'https://example.com/images/bottle-003.jpg', + createdAt: '2024-07-20T14:00:00Z' + }, + { + id: 'prod-004', + name: 'The Art of Clean Code', + description: 'A comprehensive guide to writing maintainable, scalable, and elegant software. Covers design patterns, refactoring, and testing.', + category: 'Books', + price: 42.00, + currency: 'USD', + brand: 'TechPress', + rating: 4.8, + reviewCount: 3456, + inStock: true, + tags: ['programming', 'software', 'clean-code', 'design-patterns', 'bestseller'], + imageUrl: 'https://example.com/images/book-004.jpg', + createdAt: '2024-03-10T09:00:00Z' + }, + { + id: 'prod-005', + name: 'Yoga Mat Pro', + description: 'Extra-thick 6mm yoga mat with non-slip surface. Includes carrying strap and alignment markers.', + category: 'Sports', + price: 49.99, + currency: 'USD', + brand: 'FlexFit', + rating: 4.5, + reviewCount: 892, + inStock: true, + tags: ['yoga', 'fitness', 'non-slip', 'exercise', 'mat'], + imageUrl: 'https://example.com/images/yogamat-005.jpg', + createdAt: '2024-06-05T11:30:00Z' + }, + { + id: 'prod-006', + name: '4K Ultra HD Smart TV 55"', + description: 'Stunning 4K display with HDR10+ support, built-in streaming apps, and voice control. Dolby Atmos audio.', + category: 'Electronics', + price: 649.99, + currency: 'USD', + brand: 'VisionTech', + rating: 4.4, + reviewCount: 1567, + inStock: true, + tags: ['4k', 'smart-tv', 'hdr', 'streaming', 'dolby-atmos'], + imageUrl: 'https://example.com/images/tv-006.jpg', + createdAt: '2024-10-01T16:00:00Z' + }, + { + id: 'prod-007', + name: 'Merino Wool Sweater', + description: 'Luxuriously soft merino wool sweater. Temperature regulating, moisture-wicking, and odor resistant.', + category: 'Clothing', + price: 89.99, + currency: 'USD', + brand: 'AlpineKnit', + rating: 4.6, + reviewCount: 423, + inStock: true, + tags: ['merino', 'wool', 'sweater', 'winter', 'premium'], + imageUrl: 'https://example.com/images/sweater-007.jpg', + createdAt: '2024-09-15T10:00:00Z' + }, + { + id: 'prod-008', + name: 'Cast Iron Dutch Oven', + description: 'Enameled cast iron dutch oven, 6-quart capacity. Perfect for braising, baking, and slow cooking.', + category: 'Home & Kitchen', + price: 79.99, + currency: 'USD', + brand: 'IronChef', + rating: 4.9, + reviewCount: 2678, + inStock: false, + tags: ['cast-iron', 'dutch-oven', 'cooking', 'braising', 'premium'], + imageUrl: 'https://example.com/images/dutchoven-008.jpg', + createdAt: '2024-04-22T13:00:00Z' + }, + { + id: 'prod-009', + name: 'Machine Learning Fundamentals', + description: 'Learn machine learning from scratch with hands-on Python examples. Covers neural networks, NLP, and computer vision.', + category: 'Books', + price: 54.99, + currency: 'USD', + brand: 'TechPress', + rating: 4.5, + reviewCount: 1234, + inStock: true, + tags: ['machine-learning', 'python', 'ai', 'data-science', 'textbook'], + imageUrl: 'https://example.com/images/book-009.jpg', + createdAt: '2024-05-18T08:00:00Z' + }, + { + id: 'prod-010', + name: 'Resistance Bands Set', + description: 'Set of 5 resistance bands with varying tension levels. Includes door anchor, handles, and ankle straps.', + category: 'Sports', + price: 24.99, + currency: 'USD', + brand: 'FlexFit', + rating: 4.3, + reviewCount: 2100, + inStock: true, + tags: ['resistance-bands', 'fitness', 'home-workout', 'strength-training', 'portable'], + imageUrl: 'https://example.com/images/bands-010.jpg', + createdAt: '2024-08-28T09:30:00Z' + }, + { + id: 'prod-011', + name: 'Wireless Mechanical Keyboard', + description: 'Compact 75% mechanical keyboard with hot-swappable switches, RGB backlighting, and multi-device Bluetooth.', + category: 'Electronics', + price: 129.99, + currency: 'USD', + brand: 'KeyCraft', + rating: 4.7, + reviewCount: 934, + inStock: true, + tags: ['mechanical', 'keyboard', 'wireless', 'rgb', 'bluetooth'], + imageUrl: 'https://example.com/images/keyboard-011.jpg', + createdAt: '2024-11-03T12:00:00Z' + }, + { + id: 'prod-012', + name: 'Running Shoes Ultra Boost', + description: 'Lightweight running shoes with responsive cushioning and breathable mesh upper. Great for daily training.', + category: 'Sports', + price: 139.99, + currency: 'USD', + brand: 'StrideMax', + rating: 4.4, + reviewCount: 1876, + inStock: true, + tags: ['running', 'shoes', 'cushioning', 'breathable', 'training'], + imageUrl: 'https://example.com/images/shoes-012.jpg', + createdAt: '2024-07-14T10:30:00Z' + }, + { + id: 'prod-013', + name: 'Ceramic Pour-Over Coffee Maker', + description: 'Handcrafted ceramic dripper for pour-over coffee. Produces a clean, flavorful cup every time.', + category: 'Home & Kitchen', + price: 38.00, + currency: 'USD', + brand: 'BrewCraft', + rating: 4.7, + reviewCount: 645, + inStock: true, + tags: ['coffee', 'pour-over', 'ceramic', 'handcrafted', 'brewing'], + imageUrl: 'https://example.com/images/coffee-013.jpg', + createdAt: '2024-06-30T15:00:00Z' + }, + { + id: 'prod-014', + name: 'Slim Fit Denim Jeans', + description: 'Classic slim fit jeans made from premium stretch denim. Comfortable all-day wear with modern styling.', + category: 'Clothing', + price: 69.99, + currency: 'USD', + brand: 'DenimCo', + rating: 4.2, + reviewCount: 1345, + inStock: true, + tags: ['denim', 'jeans', 'slim-fit', 'stretch', 'casual'], + imageUrl: 'https://example.com/images/jeans-014.jpg', + createdAt: '2024-08-05T11:00:00Z' + }, + { + id: 'prod-015', + name: 'USB-C Portable Charger 20000mAh', + description: 'High-capacity portable charger with USB-C PD fast charging. Charges a phone 4+ times. LED power indicator.', + category: 'Electronics', + price: 45.99, + currency: 'USD', + brand: 'PowerVault', + rating: 4.5, + reviewCount: 3210, + inStock: true, + tags: ['portable-charger', 'usb-c', 'fast-charging', 'power-bank', 'travel'], + imageUrl: 'https://example.com/images/charger-015.jpg', + createdAt: '2024-09-20T08:00:00Z' + }, + { + id: 'prod-016', + name: 'Adjustable Dumbbell Set', + description: 'Space-saving adjustable dumbbells, 5-52.5 lbs per hand. Quick-lock mechanism for fast weight changes.', + category: 'Sports', + price: 349.99, + currency: 'USD', + brand: 'IronGrip', + rating: 4.8, + reviewCount: 567, + inStock: false, + tags: ['dumbbells', 'adjustable', 'strength-training', 'home-gym', 'weights'], + imageUrl: 'https://example.com/images/dumbbells-016.jpg', + createdAt: '2024-10-12T14:30:00Z' + }, + { + id: 'prod-017', + name: 'Bamboo Cutting Board Set', + description: 'Set of 3 organic bamboo cutting boards in different sizes. Knife-friendly, antimicrobial, and easy to clean.', + category: 'Home & Kitchen', + price: 27.99, + currency: 'USD', + brand: 'GreenHome', + rating: 4.4, + reviewCount: 890, + inStock: true, + tags: ['bamboo', 'cutting-board', 'kitchen', 'eco-friendly', 'antimicrobial'], + imageUrl: 'https://example.com/images/cuttingboard-017.jpg', + createdAt: '2024-05-25T10:00:00Z' + }, + { + id: 'prod-018', + name: 'API Design Patterns', + description: 'Master the art of designing robust, scalable APIs. Covers REST, GraphQL, gRPC, and event-driven architectures.', + category: 'Books', + price: 48.99, + currency: 'USD', + brand: 'TechPress', + rating: 4.6, + reviewCount: 789, + inStock: true, + tags: ['api', 'rest', 'graphql', 'architecture', 'design-patterns'], + imageUrl: 'https://example.com/images/book-018.jpg', + createdAt: '2024-07-08T09:00:00Z' + }, + { + id: 'prod-019', + name: 'Waterproof Hiking Jacket', + description: 'Lightweight, packable waterproof jacket with sealed seams. Perfect for hiking, camping, and travel.', + category: 'Clothing', + price: 119.99, + currency: 'USD', + brand: 'TrailBlazer', + rating: 4.5, + reviewCount: 654, + inStock: true, + tags: ['waterproof', 'hiking', 'jacket', 'outdoor', 'packable'], + imageUrl: 'https://example.com/images/jacket-019.jpg', + createdAt: '2024-10-25T13:00:00Z' + }, + { + id: 'prod-020', + name: 'Smart Fitness Watch', + description: 'Advanced fitness tracker with heart rate monitoring, GPS, sleep tracking, and 7-day battery life.', + category: 'Electronics', + price: 199.99, + currency: 'USD', + brand: 'FitTech', + rating: 4.3, + reviewCount: 2456, + inStock: true, + tags: ['smartwatch', 'fitness', 'gps', 'heart-rate', 'health'], + imageUrl: 'https://example.com/images/watch-020.jpg', + createdAt: '2024-11-10T10:00:00Z' + } +]; + +// ============================================================================ +// Helper exports +// ============================================================================ + +export const categories: string[] = [...new Set(products.map(p => p.category))].sort(); +export const brands: string[] = [...new Set(products.map(p => p.brand))].sort(); +export const priceRange = { + min: Math.min(...products.map(p => p.price)), + max: Math.max(...products.map(p => p.price)) +}; diff --git a/examples/ecommerce-search/mcp/products/index.ts b/examples/ecommerce-search/mcp/products/index.ts new file mode 100644 index 0000000..c11d266 --- /dev/null +++ b/examples/ecommerce-search/mcp/products/index.ts @@ -0,0 +1,384 @@ +import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core"; +import { products, categories, brands, priceRange } from "../data/products.js"; + +// ============================================================================ +// Input Schema Classes +// ============================================================================ + +class SearchProductsInput { + @Optional() + @SchemaConstraint({ + description: 'Search query (matches name, description, and tags)', + default: '' + }) + query?: string; + + @Optional() + @SchemaConstraint({ + description: 'Filter by category', + enum: ['Electronics', 'Clothing', 'Home & Kitchen', 'Books', 'Sports'] + }) + category?: string; + + @Optional() + @SchemaConstraint({ + description: 'Filter by brand name' + }) + brand?: string; + + @Optional() + @SchemaConstraint({ + description: 'Minimum price', + minimum: 0 + }) + minPrice?: number; + + @Optional() + @SchemaConstraint({ + description: 'Maximum price', + minimum: 0 + }) + maxPrice?: number; + + @Optional() + @SchemaConstraint({ + description: 'Only show in-stock items' + }) + inStock?: boolean; + + @Optional() + @SchemaConstraint({ + description: 'Sort results', + enum: ['price_asc', 'price_desc', 'rating', 'newest'], + default: 'rating' + }) + sortBy?: string; + + @Optional() + @SchemaConstraint({ + description: 'Page number (1-indexed)', + minimum: 1, + default: 1 + }) + page?: number; + + @Optional() + @SchemaConstraint({ + description: 'Results per page', + minimum: 1, + maximum: 50, + default: 10 + }) + pageSize?: number; +} + +class GetProductDetailsInput { + @SchemaConstraint({ + description: 'Product ID (e.g., prod-001)', + minLength: 1 + }) + productId!: string; +} + +class GetRecommendationsInput { + @SchemaConstraint({ + description: 'Product ID to get recommendations for', + minLength: 1 + }) + productId!: string; + + @Optional() + @SchemaConstraint({ + description: 'Maximum number of recommendations to return', + minimum: 1, + maximum: 10, + default: 5 + }) + limit?: number; +} + +class ProductSearchAssistantInput { + @SchemaConstraint({ + description: 'What the customer is looking for' + }) + query!: string; + + @Optional() + @SchemaConstraint({ + description: 'Customer budget (e.g., "under $50", "$100-$300")' + }) + budget?: string; + + @Optional() + @SchemaConstraint({ + description: 'Any customer preferences or requirements' + }) + preferences?: string; +} + +// ============================================================================ +// Product Search Service +// ============================================================================ + +/** + * E-commerce Product Search Service + * + * Demonstrates LeanMCP decorators with all three MCP primitives: + * - @Tool: searchProducts, getProductDetails, getCategories, getProductRecommendations + * - @Resource: productCatalog + * - @Prompt: productSearchAssistant + */ +export class ProductSearchService { + + // ========================================================================== + // Tools + // ========================================================================== + + /** + * Search products with full-text query, filters, sorting, and pagination + */ + @Tool({ + description: 'Search products with full-text query, category/brand/price filters, sorting, and pagination', + inputClass: SearchProductsInput + }) + async searchProducts(args: SearchProductsInput) { + let results = [...products]; + + // Full-text search across name, description, and tags + if (args.query) { + const query = args.query.toLowerCase(); + results = results.filter(p => + p.name.toLowerCase().includes(query) || + p.description.toLowerCase().includes(query) || + p.tags.some(t => t.toLowerCase().includes(query)) + ); + } + + // Apply filters + if (args.category) { + results = results.filter(p => p.category === args.category); + } + if (args.brand) { + results = results.filter(p => p.brand.toLowerCase() === args.brand!.toLowerCase()); + } + if (args.minPrice !== undefined) { + results = results.filter(p => p.price >= args.minPrice!); + } + if (args.maxPrice !== undefined) { + results = results.filter(p => p.price <= args.maxPrice!); + } + if (args.inStock !== undefined) { + results = results.filter(p => p.inStock === args.inStock); + } + + // Sort + const sortBy = args.sortBy || 'rating'; + switch (sortBy) { + case 'price_asc': + results.sort((a, b) => a.price - b.price); + break; + case 'price_desc': + results.sort((a, b) => b.price - a.price); + break; + case 'rating': + results.sort((a, b) => b.rating - a.rating); + break; + case 'newest': + results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + break; + } + + // Paginate + const page = args.page || 1; + const pageSize = args.pageSize || 10; + const totalResults = results.length; + const totalPages = Math.ceil(totalResults / pageSize); + const startIndex = (page - 1) * pageSize; + const paginatedResults = results.slice(startIndex, startIndex + pageSize); + + return { + products: paginatedResults, + pagination: { + page, + pageSize, + totalResults, + totalPages, + hasNextPage: page < totalPages, + hasPreviousPage: page > 1 + }, + filters: { + query: args.query || null, + category: args.category || null, + brand: args.brand || null, + minPrice: args.minPrice ?? null, + maxPrice: args.maxPrice ?? null, + inStock: args.inStock ?? null, + sortBy + } + }; + } + + /** + * Get full details for a single product by ID + */ + @Tool({ + description: 'Get full details for a single product by its ID', + inputClass: GetProductDetailsInput + }) + async getProductDetails(args: GetProductDetailsInput) { + const product = products.find(p => p.id === args.productId); + + if (!product) { + return { + error: `Product not found: ${args.productId}`, + availableIds: products.map(p => p.id) + }; + } + + return { + product, + relatedCategories: categories.filter(c => c !== product.category), + sameBrandCount: products.filter(p => p.brand === product.brand && p.id !== product.id).length + }; + } + + /** + * List all product categories with counts + */ + @Tool({ + description: 'List all product categories with product counts and price ranges' + }) + async getCategories() { + const categoryDetails = categories.map(category => { + const categoryProducts = products.filter(p => p.category === category); + const prices = categoryProducts.map(p => p.price); + return { + name: category, + productCount: categoryProducts.length, + inStockCount: categoryProducts.filter(p => p.inStock).length, + priceRange: { + min: Math.min(...prices), + max: Math.max(...prices) + }, + topBrands: [...new Set(categoryProducts.map(p => p.brand))] + }; + }); + + return { + categories: categoryDetails, + totalCategories: categories.length, + totalProducts: products.length + }; + } + + /** + * Get product recommendations based on category and tags + */ + @Tool({ + description: 'Get product recommendations similar to a given product, based on shared category and tags', + inputClass: GetRecommendationsInput + }) + async getProductRecommendations(args: GetRecommendationsInput) { + const sourceProduct = products.find(p => p.id === args.productId); + + if (!sourceProduct) { + return { + error: `Product not found: ${args.productId}`, + availableIds: products.map(p => p.id) + }; + } + + const limit = args.limit || 5; + + // Score each product by similarity (shared category + shared tags) + const scored = products + .filter(p => p.id !== sourceProduct.id) + .map(p => { + let score = 0; + if (p.category === sourceProduct.category) score += 3; + const sharedTags = p.tags.filter(t => sourceProduct.tags.includes(t)); + score += sharedTags.length; + // Bonus for similar price range (within 30%) + const priceDiff = Math.abs(p.price - sourceProduct.price) / sourceProduct.price; + if (priceDiff < 0.3) score += 1; + return { product: p, score, sharedTags }; + }) + .sort((a, b) => b.score - a.score) + .slice(0, limit); + + return { + sourceProduct: { + id: sourceProduct.id, + name: sourceProduct.name, + category: sourceProduct.category, + tags: sourceProduct.tags + }, + recommendations: scored.map(s => ({ + product: s.product, + similarityScore: s.score, + sharedTags: s.sharedTags + })) + }; + } + + // ========================================================================== + // Resource + // ========================================================================== + + /** + * Full product catalog as a data resource + */ + @Resource({ + description: 'Full product catalog with all products, categories, and brands', + mimeType: 'application/json' + }) + async productCatalog() { + return { + contents: [{ + uri: 'ecommerce://catalog', + mimeType: 'application/json', + text: JSON.stringify({ + products, + totalProducts: products.length, + categories, + brands, + priceRange + }, null, 2) + }] + }; + } + + // ========================================================================== + // Prompt + // ========================================================================== + + /** + * AI shopping assistant prompt template + */ + @Prompt({ + description: 'Generate a prompt for an AI shopping assistant to help find products', + inputClass: ProductSearchAssistantInput + }) + async productSearchAssistant(args: ProductSearchAssistantInput) { + const budgetInfo = args.budget ? `\nBudget: ${args.budget}` : ''; + const prefsInfo = args.preferences ? `\nPreferences: ${args.preferences}` : ''; + + return { + messages: [{ + role: 'user' as const, + content: { + type: 'text' as const, + text: `You are a helpful shopping assistant for an e-commerce store. Help the customer find the right products. + +Available categories: ${categories.join(', ')} +Available brands: ${brands.join(', ')} +Price range: $${priceRange.min} - $${priceRange.max} + +Customer request: ${args.query}${budgetInfo}${prefsInfo} + +Use the searchProducts tool to find matching products. If the customer needs more details, use getProductDetails. For similar items, use getProductRecommendations.` + } + }] + }; + } +} diff --git a/examples/ecommerce-search/package.json b/examples/ecommerce-search/package.json new file mode 100644 index 0000000..061e675 --- /dev/null +++ b/examples/ecommerce-search/package.json @@ -0,0 +1,21 @@ +{ + "name": "ecommerce-search", + "version": "0.1.0", + "description": "E-commerce product search with pagination, filtering, and recommendations", + "type": "module", + "scripts": { + "dev": "leanmcp dev", + "start": "leanmcp start", + "build": "leanmcp build", + "start:node": "node dist/main.js" + }, + "dependencies": { + "@leanmcp/core": "latest" + }, + "devDependencies": { + "@leanmcp/cli": "latest", + "@types/node": "^20.0.0", + "tsx": "^4.20.3", + "typescript": "^5.6.3" + } +} \ No newline at end of file diff --git a/examples/ecommerce-search/tsconfig.json b/examples/ecommerce-search/tsconfig.json new file mode 100644 index 0000000..2f8809f --- /dev/null +++ b/examples/ecommerce-search/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "dist" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file