Company Logo
MCIP

Build AI Agent

Transform any AI into a shopping expert. Connect to MCIP, maintain context across conversations, and let your agent search, compare, and manage carts like a seasoned personal shopper.

The Agent Mental Model

Think of your AI agent as a personal shopping assistant at a high-end department store. The assistant doesn't just answer questions—they remember your preferences, carry your selections, suggest alternatives, and guide you through checkout. That's exactly what we're building.

MCIP provides the shopping expertise (semantic search, cart management, multi-store access). Your agent provides the conversational intelligence (understanding intent, maintaining context, making recommendations). Together, they create something neither could achieve alone.


Part 1: Understanding the Integration

How Agents Talk to MCIP

MCIP exposes tools through the Model Context Protocol (MCP). Your agent discovers available tools, then calls them as needed during conversations:

User: "Find me a gaming laptop under $1500"

Agent: [Understands intent: product search]

Agent → MCIP: search_product({query: "gaming laptop", maxPrice: 1500})

MCIPAgent: [Returns 8 products with scores]

Agent: "I found 8 gaming laptops under $1500. The top pick is..."

Available MCP Tools

MCIP provides five core tools for commerce operations:

ToolPurposeWhen to Use
search_productSemantic product searchUser asks to find, browse, or compare products
add_to_cartAdd item to session cartUser wants to save or buy a product
view_cartShow current cart contentsUser asks "what's in my cart?"
update_cart_itemChange quantity or removeUser says "remove that" or "I need 3"
clear_cartEmpty entire cartUser wants to start over

Part 2: Basic Integration

Discovering Tools

First, your agent needs to know what MCIP can do:

// src/mcip-client.ts

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

export class MCIPClient {
  private client: Client;
  private sessionId: string;

  constructor() {
    this.client = new Client(
      { name: "shopping-agent", version: "1.0.0" },
      { capabilities: { tools: {} } }
    );
    this.sessionId = this.generateSessionId();
  }

  async connect(): Promise<void> {
    const transport = new StdioClientTransport({
      command: "node",
      args: ["path/to/mcip-server"],
    });

    await this.client.connect(transport);
    console.log("Connected to MCIP");
  }

  async discoverTools(): Promise<Tool[]> {
    const response = await this.client.listTools();
    return response.tools;
  }

  private generateSessionId(): string {
    return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

Now let's search for products:

// Continuing MCIPClient class

async searchProducts(query: string, options?: SearchOptions): Promise<Product[]> {
  const result = await this.client.callTool({
    name: "search_product",
    arguments: {
      sessionId: this.sessionId,
      query: query,
      filters: {
        maxPrice: options?.maxPrice,
        minPrice: options?.minPrice,
        categories: options?.categories,
        inStock: options?.inStock ?? true,
      },
      limit: options?.limit ?? 10,
    },
  });

  return this.parseSearchResults(result);
}

private parseSearchResults(result: any): Product[] {
  // MCP returns content as an array of content blocks
  const content = result.content[0];
  
  if (content.type === "text") {
    return JSON.parse(content.text).products;
  }
  
  return [];
}

Complete Working Example

Here's a minimal agent that can search and respond:

// src/simple-agent.ts

import { MCIPClient } from "./mcip-client";
import OpenAI from "openai";

const openai = new OpenAI();
const mcip = new MCIPClient();

async function handleUserMessage(message: string): Promise<string> {
  // Step 1: Understand the user's intent
  const intentResponse = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [
      {
        role: "system",
        content: `You are a shopping assistant. Analyze the user's message and respond with JSON:
          {"intent": "search" | "add_to_cart" | "view_cart" | "general",
           "query": "extracted search terms if searching",
           "filters": {"maxPrice": number | null, "category": string | null}}`
      },
      { role: "user", content: message }
    ],
    response_format: { type: "json_object" },
  });

  const intent = JSON.parse(intentResponse.choices[0].message.content!);

  // Step 2: Execute the appropriate action
  let context = "";
  
  if (intent.intent === "search") {
    const products = await mcip.searchProducts(intent.query, intent.filters);
    context = formatProductsForLLM(products);
  } else if (intent.intent === "view_cart") {
    const cart = await mcip.viewCart();
    context = formatCartForLLM(cart);
  }

  // Step 3: Generate natural response
  const response = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [
      {
        role: "system",
        content: `You are a helpful shopping assistant. Use the provided product/cart data to answer naturally. Be concise but informative.`
      },
      { role: "user", content: message },
      { role: "assistant", content: `[Available data: ${context}]` }
    ],
  });

  return response.choices[0].message.content!;
}

function formatProductsForLLM(products: Product[]): string {
  return products.map((p, i) => 
    `${i + 1}. ${p.name} - $${p.price.amount} (${p.score.toFixed(2)} relevance)`
  ).join("\n");
}

Part 3: Session Management

Why Sessions Matter

Without sessions, every interaction starts fresh. The agent forgets the laptop you loved, the items in your cart, and the preferences you mentioned. Sessions fix this by maintaining state across the conversation.

Session Lifecycle

// src/session-manager.ts

export class SessionManager {
  private sessions: Map<string, SessionData> = new Map();
  private readonly TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

  createSession(userId: string): string {
    const sessionId = `sess_${userId}_${Date.now()}`;
    
    this.sessions.set(sessionId, {
      id: sessionId,
      userId,
      createdAt: new Date(),
      lastActivity: new Date(),
      context: {
        searchHistory: [],
        viewedProducts: [],
        preferences: {},
      },
    });

    return sessionId;
  }

  getSession(sessionId: string): SessionData | null {
    const session = this.sessions.get(sessionId);
    
    if (!session) return null;
    
    // Check if expired
    const age = Date.now() - session.lastActivity.getTime();
    if (age > this.TTL_MS) {
      this.sessions.delete(sessionId);
      return null;
    }

    // Update last activity
    session.lastActivity = new Date();
    return session;
  }

  updateContext(sessionId: string, updates: Partial<SessionContext>): void {
    const session = this.getSession(sessionId);
    if (session) {
      session.context = { ...session.context, ...updates };
    }
  }
}

interface SessionData {
  id: string;
  userId: string;
  createdAt: Date;
  lastActivity: Date;
  context: SessionContext;
}

interface SessionContext {
  searchHistory: SearchQuery[];
  viewedProducts: string[];  // Product IDs
  preferences: UserPreferences;
}

Maintaining Conversation Context

Track what the user has seen and discussed:

// Enhanced agent with context awareness

class ContextAwareAgent {
  private mcip: MCIPClient;
  private sessions: SessionManager;
  private conversationHistory: Map<string, Message[]> = new Map();

  async handleMessage(sessionId: string, message: string): Promise<string> {
    const session = this.sessions.getSession(sessionId);
    if (!session) {
      throw new Error("Session expired. Please start a new conversation.");
    }

    // Get conversation history
    const history = this.conversationHistory.get(sessionId) || [];
    
    // Add user message
    history.push({ role: "user", content: message });

    // Build context from session
    const contextPrompt = this.buildContextPrompt(session);

    // Generate response with full context
    const response = await this.generateResponse(contextPrompt, history);

    // Update session based on what happened
    await this.updateSessionFromInteraction(session, message, response);

    // Save to history
    history.push({ role: "assistant", content: response });
    this.conversationHistory.set(sessionId, history.slice(-20)); // Keep last 20

    return response;
  }

  private buildContextPrompt(session: SessionData): string {
    const { searchHistory, viewedProducts, preferences } = session.context;
    
    let context = "User context:\n";
    
    if (searchHistory.length > 0) {
      const recent = searchHistory.slice(-3);
      context += `- Recent searches: ${recent.map(s => s.query).join(", ")}\n`;
    }
    
    if (viewedProducts.length > 0) {
      context += `- Recently viewed ${viewedProducts.length} products\n`;
    }
    
    if (preferences.priceRange) {
      context += `- Preferred price range: $${preferences.priceRange.min}-$${preferences.priceRange.max}\n`;
    }
    
    if (preferences.brands?.length) {
      context += `- Preferred brands: ${preferences.brands.join(", ")}\n`;
    }

    return context;
  }
}

Part 4: Cart Management

Natural Cart Interactions

Users don't say "execute add_to_cart with product ID xyz". They say "I'll take that one" or "add the second laptop to my cart". Your agent needs to bridge this gap.

// src/cart-handler.ts

export class CartHandler {
  private mcip: MCIPClient;
  private recentProducts: Map<string, Product[]> = new Map(); // sessionId -> products

  async handleCartIntent(
    sessionId: string,
    intent: CartIntent
  ): Promise<CartResult> {
    switch (intent.action) {
      case "add":
        return this.handleAdd(sessionId, intent);
      case "remove":
        return this.handleRemove(sessionId, intent);
      case "update_quantity":
        return this.handleQuantityUpdate(sessionId, intent);
      case "view":
        return this.handleView(sessionId);
      case "clear":
        return this.handleClear(sessionId);
    }
  }

  private async handleAdd(sessionId: string, intent: CartIntent): Promise<CartResult> {
    // Resolve "that one", "the first option", "the Nike shoes"
    const product = await this.resolveProductReference(
      sessionId,
      intent.productReference
    );

    if (!product) {
      return {
        success: false,
        message: "I'm not sure which product you mean. Could you be more specific?",
      };
    }

    // Select variant if needed
    const variant = await this.resolveVariant(product, intent.variantHints);

    // Add to cart
    const result = await this.mcip.addToCart({
      sessionId,
      productId: product.externalId,
      variantId: variant?.sku,
      quantity: intent.quantity || 1,
    });

    return {
      success: true,
      message: `Added ${product.name} to your cart.`,
      cart: result.cart,
    };
  }

  private async resolveProductReference(
    sessionId: string,
    reference: string
  ): Promise<Product | null> {
    const recentProducts = this.recentProducts.get(sessionId) || [];

    // Handle positional references
    const positionMatch = reference.match(/^(first|second|third|\d+)/i);
    if (positionMatch) {
      const positions: Record<string, number> = {
        first: 0, second: 1, third: 2,
        "1": 0, "2": 1, "3": 2,
      };
      const index = positions[positionMatch[1].toLowerCase()] ?? parseInt(positionMatch[1]) - 1;
      return recentProducts[index] || null;
    }

    // Handle "that one", "this" - return most recently discussed
    if (/^(that|this|it)$/i.test(reference)) {
      return recentProducts[0] || null;
    }

    // Handle name/brand matching
    const lowerRef = reference.toLowerCase();
    return recentProducts.find(p =>
      p.name.toLowerCase().includes(lowerRef) ||
      p.brand?.toLowerCase().includes(lowerRef)
    ) || null;
  }

  // Track products shown to user
  recordShownProducts(sessionId: string, products: Product[]): void {
    this.recentProducts.set(sessionId, products);
  }
}

interface CartIntent {
  action: "add" | "remove" | "update_quantity" | "view" | "clear";
  productReference?: string;  // "the first one", "Nike Air Max", "that"
  variantHints?: string[];    // ["size 10", "blue"]
  quantity?: number;
}

Multi-Turn Cart Conversations

Real shopping conversations flow naturally:

// Example conversation flow

// Turn 1
User: "Find me running shoes under $150"
Agent: [Searches, shows 5 options]
       "I found 5 great options! The top picks are:
        1. Nike Air Zoom - $129 (highly rated for cushioning)
        2. Adidas Ultraboost - $140 (great for long distances)
        3. Brooks Ghost - $119 (excellent stability)..."

// Turn 2
User: "Tell me more about the Nike ones"
Agent: [Retrieves product details from context]
       "The Nike Air Zoom features responsive foam cushioning..."

// Turn 3
User: "I'll take them in size 10"
Agent: [Resolves "them" → Nike Air Zoom, variant → size 10]
       "Perfect! I've added the Nike Air Zoom in size 10 to your cart.
        Your cart total is $129. Would you like to continue shopping?"

// Turn 4
User: "Actually, make that 2 pairs"
Agent: [Updates quantity]
       "Done! Updated to 2 pairs. Your new total is $258."

Part 5: Production Considerations

Error Handling

Graceful degradation keeps conversations flowing:

// src/resilient-agent.ts

async function safeToolCall<T>(
  operation: () => Promise<T>,
  fallback: T,
  context: string
): Promise<{ result: T; hadError: boolean }> {
  try {
    const result = await operation();
    return { result, hadError: false };
  } catch (error) {
    console.error(`Error in ${context}:`, error);
    
    // Log for monitoring
    await logError({
      context,
      error: error.message,
      timestamp: new Date(),
    });

    return { result: fallback, hadError: true };
  }
}

// Usage in agent
async function handleSearch(query: string): Promise<string> {
  const { result: products, hadError } = await safeToolCall(
    () => mcip.searchProducts(query),
    [],  // Empty array as fallback
    "product_search"
  );

  if (hadError) {
    return "I'm having trouble searching right now. Could you try again in a moment?";
  }

  if (products.length === 0) {
    return "I couldn't find products matching that description. Could you try different terms?";
  }

  return formatProductResponse(products);
}

Rate Limiting

Protect both MCIP and external APIs:

// src/rate-limiter.ts

import { RateLimiter } from "limiter";

export class AgentRateLimiter {
  private limiters: Map<string, RateLimiter> = new Map();

  private getLimiter(sessionId: string): RateLimiter {
    if (!this.limiters.has(sessionId)) {
      // 30 requests per minute per session
      this.limiters.set(sessionId, new RateLimiter({
        tokensPerInterval: 30,
        interval: "minute",
      }));
    }
    return this.limiters.get(sessionId)!;
  }

  async checkLimit(sessionId: string): Promise<boolean> {
    const limiter = this.getLimiter(sessionId);
    const hasToken = await limiter.tryRemoveTokens(1);
    
    if (!hasToken) {
      console.warn(`Rate limit exceeded for session ${sessionId}`);
    }
    
    return hasToken;
  }
}

Monitoring and Observability

Track what matters in production:

// src/agent-metrics.ts

import { Counter, Histogram, Gauge } from "prom-client";

export const metrics = {
  // Conversation metrics
  messagesProcessed: new Counter({
    name: "agent_messages_total",
    help: "Total messages processed",
    labelNames: ["intent", "status"],
  }),

  // Tool usage
  toolCalls: new Counter({
    name: "agent_tool_calls_total",
    help: "MCP tool invocations",
    labelNames: ["tool", "status"],
  }),

  // Response times
  responseLatency: new Histogram({
    name: "agent_response_seconds",
    help: "Time to generate response",
    labelNames: ["intent"],
    buckets: [0.5, 1, 2, 5, 10],
  }),

  // Session health
  activeSessions: new Gauge({
    name: "agent_active_sessions",
    help: "Currently active sessions",
  }),

  // Cart metrics
  cartOperations: new Counter({
    name: "agent_cart_operations_total",
    help: "Cart operations performed",
    labelNames: ["operation", "status"],
  }),
};

// Usage
metrics.toolCalls.inc({ tool: "search_product", status: "success" });
metrics.responseLatency.observe({ intent: "search" }, responseTime);

Scaling Considerations

ScaleArchitectureSession StorageNotes
PrototypeSingle instanceIn-memoryFine for development
Small (< 100 concurrent)Single instanceRedisAdd persistence
Medium (100-1000)2-3 instances + LBRedis clusterHorizontal scaling
Large (1000+)Auto-scaling groupRedis cluster + shardingFull production setup

Part 6: Platform-Specific Integration

OpenAI GPT Actions

For ChatGPT plugins and GPT Actions:

# openapi.yaml for GPT Action

openapi: 3.0.0
info:
  title: Shopping Assistant API
  version: 1.0.0
servers:
  - url: https://your-agent.com/api
paths:
  /search:
    post:
      operationId: searchProducts
      summary: Search for products
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  description: Natural language search query
                maxPrice:
                  type: number
      responses:
        '200':
          description: Search results

Claude Tool Use

For Anthropic Claude with tool use:

const tools = [
  {
    name: "search_products",
    description: "Search for products using natural language. Use when user wants to find, browse, or compare products.",
    input_schema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Natural language search query",
        },
        max_price: {
          type: "number",
          description: "Maximum price filter",
        },
      },
      required: ["query"],
    },
  },
  // ... other tools
];

const response = await anthropic.messages.create({
  model: "claude-3-opus-20240229",
  max_tokens: 1024,
  tools: tools,
  messages: [{ role: "user", content: userMessage }],
});

Troubleshooting

"Agent doesn't understand cart references"

Cause: Product context not being tracked

Solution: Ensure you record shown products after each search.

"Sessions expire too quickly"

Cause: TTL too short or activity not being tracked

Solution: Update lastActivity on every interaction and increase TTL if needed.

"Responses are slow"

Cause: Sequential API calls

Solution: Parallelize where possible:

// Instead of sequential
const products = await mcip.searchProducts(query);
const cart = await mcip.viewCart();

// Use parallel
const [products, cart] = await Promise.all([
  mcip.searchProducts(query),
  mcip.viewCart(),
]);

"Cart operations fail silently"

Cause: Session ID mismatch between agent and MCIP

Solution: Verify session ID is passed consistently:

// Always include sessionId in MCIP calls
await mcip.addToCart({
  sessionId: this.currentSessionId,  // Must match across all calls
  productId: product.id,
  quantity: 1,
});

What's Next

You've built an intelligent shopping agent! Here's where to go from here: