AI Agents, Skills, MCPs
& Multi-Agent Systems

A complete, practical guide with real working Node.js code using the Gemini API. You'll go from zero to building production-ready AI systems.

Node.js Gemini API MCP Protocol Multi-Agent

šŸ¤– What is an AI Agent?

An AI Agent is an AI program that can think, decide, and act to achieve a goal — not just answer questions. Unlike a regular chatbot that only talks, an agent can use tools, run code, browse the web, send emails, and take real actions in the world.

Real-Life Analogy: Think of a regular chatbot as a very smart encyclopedia — it knows a lot and can answer questions. An AI Agent is like hiring a personal assistant. You say "book me a flight to Dubai next Friday under $500" and they actually do it — they search flights, compare prices, fill in forms, and confirm the booking. They act, not just answer.
šŸ’¬

Regular Chatbot (No Agent)

You: "What's the weather in Lahore?"
Bot: "I cannot access real-time data."

Stuck. Can't do anything.

šŸš€

AI Agent (With Tools)

You: "What's the weather in Lahore?"
Agent: calls weather API tool
Agent: "It's 28°C and sunny right now!"

Acts. Gets real data.

The 3 Parts of Every Agent

1

Brain (LLM)

The AI model (like Gemini, GPT-4, Claude) that reads your request, reasons about it, and decides what to do next.

2

Tools

Functions the agent can call — like searching Google, running code, reading files, calling APIs, or sending emails.

3

Memory / Context

The conversation history and any stored information the agent remembers from past steps or past conversations.

🧠 How Agents Think — The ReAct Loop

Agents use a loop called ReAct (Reason + Act). They don't just do one thing — they keep looping: think → act → observe the result → think again → act again... until the task is done.

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ ReAct Loop │ │ │ │ User Request ──► [ LLM THINKS ] │ │ │ │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ │ Do I need a tool? │ │ │ │ YES ──► Call Tool │ │ │ │ NO ──► Give Answer │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ │ (tool result) │ │ [ LLM OBSERVES ] │ │ │ │ │ Is goal achieved? │ │ NO ──► Loop again │ │ YES ──► Final Answer │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Real Example: "Book me a restaurant in Lahore tonight"

1

Think

Agent reasons: "I need to find restaurants. I'll use the search tool."

2

Act

Calls: search_restaurants({city: "Lahore", cuisine: "any", date: "tonight"})

3

Observe

Gets back: List of 5 restaurants with availability

4

Think Again

"I have options. I need to book one. I'll use the booking tool."

5

Act Again

Calls: book_restaurant({id: "xyz", time: "7pm", party: 2})

6

Done!

Returns: "Booked Haveli Restaurant at 7pm for 2 people. Confirmation: #HR2024"

šŸ”§ Tools — Giving Agents Superpowers

A tool is just a JavaScript function that the agent can call. You describe what it does, and the AI decides when and how to call it. This is the most fundamental concept in agents.

Key Insight
You write normal JS functions. You describe them to the AI in a special format (called a "function declaration"). The AI reads the description, decides to call it, and you execute the function. The result goes back to the AI.

Example: A Calculator Agent

Let's build a simple agent that can do math by giving it calculator tools.

JavaScript 01_calculator_agent.js
// npm install @google/generative-ai
const { GoogleGenerativeAI } = require("@google/generative-ai");

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// STEP 1: Define your actual JavaScript functions (tools)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const tools = {
  add: ({ a, b }) => a + b,
  subtract: ({ a, b }) => a - b,
  multiply: ({ a, b }) => a * b,
  divide: ({ a, b }) => {
    if (b === 0) return "Error: Cannot divide by zero";
    return a / b;
  },
  power: ({ base, exponent }) => Math.pow(base, exponent),
};

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// STEP 2: Describe your tools to the AI (function declarations)
// This is how the AI knows what tools exist and how to use them
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const toolDeclarations = [
  {
    functionDeclarations: [
      {
        name: "add",
        description: "Add two numbers together",
        parameters: {
          type: "OBJECT",
          properties: {
            a: { type: "NUMBER", description: "First number" },
            b: { type: "NUMBER", description: "Second number" },
          },
          required: ["a", "b"],
        },
      },
      {
        name: "multiply",
        description: "Multiply two numbers",
        parameters: {
          type: "OBJECT",
          properties: {
            a: { type: "NUMBER", description: "First number" },
            b: { type: "NUMBER", description: "Second number" },
          },
          required: ["a", "b"],
        },
      },
      {
        name: "divide",
        description: "Divide number a by number b",
        parameters: {
          type: "OBJECT",
          properties: {
            a: { type: "NUMBER", description: "Dividend" },
            b: { type: "NUMBER", description: "Divisor" },
          },
          required: ["a", "b"],
        },
      },
      {
        name: "power",
        description: "Raise base to the power of exponent",
        parameters: {
          type: "OBJECT",
          properties: {
            base: { type: "NUMBER", description: "The base number" },
            exponent: { type: "NUMBER", description: "The exponent" },
          },
          required: ["base", "exponent"],
        },
      },
    ],
  },
];

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// STEP 3: The Agent Loop
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function runAgent(userMessage) {
  const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash",
    tools: toolDeclarations,
  });

  const chat = model.startChat();
  console.log(`\nšŸ‘¤ User: ${userMessage}`);

  let response = await chat.sendMessage(userMessage);

  // The Agent Loop — keeps running until no more tool calls
  while (true) {
    const candidate = response.response.candidates[0];
    const parts = candidate.content.parts;
    const functionCalls = parts.filter(p => p.functionCall);

    // If no function calls, we're done — return the text answer
    if (functionCalls.length === 0) {
      const text = parts.map(p => p.text || "").join("");
      console.log(`\nšŸ¤– Agent: ${text}`);
      return text;
    }

    // Execute each tool call and collect results
    const toolResults = [];
    for (const part of functionCalls) {
      const { name, args } = part.functionCall;
      console.log(`  šŸ”§ Calling tool: ${name}(${JSON.stringify(args)})`);

      const toolFn = tools[name];
      if (!toolFn) throw new Error(`Unknown tool: ${name}`);

      const result = toolFn(args);
      console.log(`  āœ… Result: ${result}`);

      toolResults.push({
        functionResponse: {
          name: name,
          response: { result },
        },
      });
    }

    // Send results back to the AI so it can continue thinking
    response = await chat.sendMessage(toolResults);
  }
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Test it!
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function main() {
  await runAgent("What is (5 + 3) * 4, and then divide that by 2^3?");
  // Agent will call: add(5,3)=8, multiply(8,4)=32, power(2,3)=8, divide(32,8)=4
}
main();

What Just Happened?

The AI saw your complex math question, broke it into steps, called 4 different tools in sequence, and combined the results. You wrote normal JS functions — the AI figured out when and how to call them.

šŸŽ“ Skills — Teaching Agents What They Know

A skill is a group of related tools + the knowledge/instructions about how to use them. Think of it as a "specialization" you give to an agent. An agent can have many skills.

Real-Life Analogy: Imagine hiring a new employee. They're smart (the LLM). You then train them: "Here's how we handle customer complaints" + give them access to the CRM system, email, and refund tools. That training + tools = a Skill. You can teach them multiple skills: customer service, billing, technical support.

A Skill Has 3 Parts

šŸ“‹

System Prompt / Instructions

The "training manual" — text that tells the agent how to behave, what it knows, what tone to use, rules to follow.

šŸ”§

Tools

The functions the agent can call to actually do things — like reading a database, calling an API, sending a message.

šŸ“š

Knowledge

Context about the domain — FAQs, product details, policies — often passed in the system prompt or as retrieved documents.

šŸŽÆ

Scope

What the agent should and shouldn't do — "Only handle billing questions, escalate everything else."

Example: Building a Customer Support Agent with Skills

Let's build a real customer support agent with multiple skills: order tracking, refund processing, and product info.

JavaScript 02_skilled_agent.js
const { GoogleGenerativeAI } = require("@google/generative-ai");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// FAKE DATABASE (in production, this hits a real DB)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const db = {
  orders: {
    "ORD-001": { status: "shipped", item: "iPhone Case", eta: "Feb 28", price: 25 },
    "ORD-002": { status: "processing", item: "Laptop Stand", eta: "Mar 2", price: 45 },
    "ORD-003": { status: "delivered", item: "USB Hub", eta: "delivered", price: 35 },
  },
  products: {
    "iphone-case": { name: "iPhone Case", price: 25, stock: 150, description: "Premium leather case" },
    "laptop-stand": { name: "Laptop Stand", price: 45, stock: 30, description: "Adjustable aluminum stand" },
  },
  refunds: [],
};

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SKILL 1: Order Tracking
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const orderTrackingSkill = {
  name: "Order Tracking",
  instructions: `
    You are an order tracking specialist. When customers ask about their orders,
    use the track_order tool to get real-time status. Be empathetic and clear.
    If an order is delayed, apologize and offer to help.
    Always end with asking if there's anything else you can help with.
  `,
  tools: {
    track_order: ({ order_id }) => {
      const order = db.orders[order_id];
      if (!order) return { error: `Order ${order_id} not found` };
      return order;
    },
    list_customer_orders: ({ customer_email }) => {
      // In real app, query by email
      return Object.entries(db.orders).map(([id, o]) => ({ id, ...o }));
    },
  },
  toolDeclarations: [
    {
      name: "track_order",
      description: "Get the current status of an order by its ID",
      parameters: {
        type: "OBJECT",
        properties: {
          order_id: { type: "STRING", description: "The order ID like ORD-001" },
        },
        required: ["order_id"],
      },
    },
    {
      name: "list_customer_orders",
      description: "List all orders for a customer by their email",
      parameters: {
        type: "OBJECT",
        properties: {
          customer_email: { type: "STRING", description: "Customer email address" },
        },
        required: ["customer_email"],
      },
    },
  ],
};

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SKILL 2: Refund Processing
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const refundSkill = {
  name: "Refund Processing",
  instructions: `
    You handle refund requests. Rules:
    - Only refund delivered orders
    - Maximum refund is the original price
    - Always verify the order exists before refunding
    - Be professional and apologetic for any inconvenience
    - Refunds take 3-5 business days
  `,
  tools: {
    process_refund: ({ order_id, reason }) => {
      const order = db.orders[order_id];
      if (!order) return { success: false, error: "Order not found" };
      if (order.status !== "delivered") {
        return { success: false, error: "Can only refund delivered orders" };
      }
      const refundId = `REF-${Date.now()}`;
      db.refunds.push({ refundId, order_id, reason, amount: order.price, date: new Date() });
      return { success: true, refundId, amount: order.price, eta: "3-5 business days" };
    },
  },
  toolDeclarations: [
    {
      name: "process_refund",
      description: "Process a refund for a delivered order",
      parameters: {
        type: "OBJECT",
        properties: {
          order_id: { type: "STRING", description: "The order ID to refund" },
          reason: { type: "STRING", description: "Reason for refund" },
        },
        required: ["order_id", "reason"],
      },
    },
  ],
};

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SKILL 3: Product Info
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const productSkill = {
  name: "Product Information",
  instructions: `
    You are a product expert. Help customers find the right product.
    Always mention price and stock availability.
    Suggest related products when relevant.
    Be enthusiastic about the products but honest.
  `,
  tools: {
    get_product: ({ product_id }) => {
      return db.products[product_id] || { error: "Product not found" };
    },
    search_products: ({ query }) => {
      return Object.entries(db.products)
        .filter(([id, p]) => p.name.toLowerCase().includes(query.toLowerCase()))
        .map(([id, p]) => ({ id, ...p }));
    },
  },
  toolDeclarations: [
    {
      name: "get_product",
      description: "Get details about a specific product",
      parameters: {
        type: "OBJECT",
        properties: {
          product_id: { type: "STRING", description: "Product ID like iphone-case" },
        },
        required: ["product_id"],
      },
    },
    {
      name: "search_products",
      description: "Search products by keyword",
      parameters: {
        type: "OBJECT",
        properties: {
          query: { type: "STRING", description: "Search query" },
        },
        required: ["query"],
      },
    },
  ],
};

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// AGENT FACTORY: Combine skills into one agent
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function createAgent(skills) {
  // Merge all skills' instructions
  const systemInstruction = `
    You are a helpful customer support agent for TechStore.
    You have the following capabilities:
    ${skills.map(s => `- ${s.name}: ${s.instructions}`).join('\n')}
    
    Always be polite, empathetic, and efficient.
    If a customer's issue is outside your capabilities, say so honestly.
  `;

  // Merge all skills' tool functions
  const allToolFns = skills.reduce((acc, skill) => ({
    ...acc,
    ...skill.tools,
  }), {});

  // Merge all tool declarations
  const allDeclarations = [{
    functionDeclarations: skills.flatMap(s => s.toolDeclarations),
  }];

  return { systemInstruction, allToolFns, allDeclarations };
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Run the skilled agent
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function runSkilledAgent(userMessage) {
  const { systemInstruction, allToolFns, allDeclarations } = createAgent([
    orderTrackingSkill,
    refundSkill,
    productSkill,
  ]);

  const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash",
    systemInstruction,
    tools: allDeclarations,
  });

  const chat = model.startChat();
  console.log(`\nšŸ‘¤ Customer: ${userMessage}`);

  let response = await chat.sendMessage(userMessage);

  while (true) {
    const parts = response.response.candidates[0].content.parts;
    const functionCalls = parts.filter(p => p.functionCall);

    if (functionCalls.length === 0) {
      console.log(`\nšŸ¤– Support Agent: ${parts.map(p => p.text || "").join("")}`);
      return;
    }

    const toolResults = [];
    for (const part of functionCalls) {
      const { name, args } = part.functionCall;
      console.log(`  šŸ”§ Using skill: ${name}(${JSON.stringify(args)})`);
      const result = allToolFns[name](args);
      toolResults.push({ functionResponse: { name, response: result } });
    }

    response = await chat.sendMessage(toolResults);
  }
}

// Test all 3 skills!
async function main() {
  await runSkilledAgent("Where is my order ORD-001?");
  await runSkilledAgent("I want a refund for ORD-003, it was broken");
  await runSkilledAgent("Do you have any laptop accessories?");
}
main();

šŸ”Œ MCP — Model Context Protocol

MCP (Model Context Protocol) is a standard way for AI agents to connect to external tools and data sources. Think of it as USB for AI — just like USB lets any device connect to any computer, MCP lets any AI agent connect to any tool server.

Real-Life Analogy: Before USB, every device had a different connector. You needed a special cable for your mouse, another for your keyboard, another for your printer. USB standardized it. MCP does the same for AI tools — instead of every AI app writing custom integrations, there's one standard way to expose tools, and any AI can use them.

MCP Architecture

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ MCP Architecture │ │ │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ │ AI Client │◄──────►│ MCP Server │ │ │ │ (Claude, │ JSON │ (Your custom server) │ │ │ │ Cursor, │ RPC │ │ │ │ │ Your App) │ │ Tools: │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ - read_file() │ │ │ │ - query_database() │ │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ - send_email() │ │ │ │ MCP Server │ │ - search_web() │ │ │ │ (GitHub) │ │ │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ Resources: │ │ │ │ - /docs/api.md │ │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ - /data/users.csv │ │ │ │ MCP Server │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ │ (Postgres) │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

MCP Has 3 Things It Can Expose

TypeWhat It IsExample
ToolsFunctions the AI can callsend_email(), query_db()
ResourcesData/files the AI can readcompany_docs.md, user_data.json
PromptsPre-built prompt templates"Summarize this document as..."

Build Your Own MCP Server from Scratch

Let's build an MCP server that exposes a file system and a "todo" database to any AI that connects to it.

JavaScript 03_mcp_server.js
// npm install @modelcontextprotocol/sdk
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
const {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} = require("@modelcontextprotocol/sdk/types.js");
const fs = require("fs");
const path = require("path");

// In-memory Todo Database
let todos = [
  { id: 1, text: "Buy groceries", done: false, priority: "high" },
  { id: 2, text: "Write project report", done: false, priority: "medium" },
  { id: 3, text: "Call dentist", done: true, priority: "low" },
];

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Create the MCP Server
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const server = new Server(
  { name: "todo-server", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {} } }
);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// LIST TOOLS: Tell any connected AI what tools are available
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "add_todo",
      description: "Add a new todo item to the list",
      inputSchema: {
        type: "object",
        properties: {
          text: { type: "string", description: "The todo task text" },
          priority: {
            type: "string",
            enum: ["low", "medium", "high"],
            description: "Priority level",
          },
        },
        required: ["text"],
      },
    },
    {
      name: "list_todos",
      description: "List all todos, optionally filtered by status or priority",
      inputSchema: {
        type: "object",
        properties: {
          filter: {
            type: "string",
            enum: ["all", "done", "pending"],
            description: "Filter by completion status",
          },
        },
      },
    },
    {
      name: "complete_todo",
      description: "Mark a todo as completed",
      inputSchema: {
        type: "object",
        properties: {
          id: { type: "number", description: "The todo ID to complete" },
        },
        required: ["id"],
      },
    },
    {
      name: "delete_todo",
      description: "Delete a todo item",
      inputSchema: {
        type: "object",
        properties: {
          id: { type: "number", description: "The todo ID to delete" },
        },
        required: ["id"],
      },
    },
    {
      name: "read_file",
      description: "Read the contents of a file",
      inputSchema: {
        type: "object",
        properties: {
          filepath: { type: "string", description: "Path to the file" },
        },
        required: ["filepath"],
      },
    },
    {
      name: "write_file",
      description: "Write content to a file",
      inputSchema: {
        type: "object",
        properties: {
          filepath: { type: "string", description: "Path to write the file" },
          content: { type: "string", description: "Content to write" },
        },
        required: ["filepath", "content"],
      },
    },
  ],
}));

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// CALL TOOL: Handle when AI calls a tool
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case "add_todo": {
      const newTodo = {
        id: todos.length + 1,
        text: args.text,
        done: false,
        priority: args.priority || "medium",
      };
      todos.push(newTodo);
      return { content: [{ type: "text", text: `Added todo: ${JSON.stringify(newTodo)}` }] };
    }

    case "list_todos": {
      let filtered = todos;
      if (args.filter === "done") filtered = todos.filter(t => t.done);
      if (args.filter === "pending") filtered = todos.filter(t => !t.done);
      return { content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }] };
    }

    case "complete_todo": {
      const todo = todos.find(t => t.id === args.id);
      if (!todo) return { content: [{ type: "text", text: `Todo ${args.id} not found` }] };
      todo.done = true;
      return { content: [{ type: "text", text: `Completed: "${todo.text}"` }] };
    }

    case "delete_todo": {
      const idx = todos.findIndex(t => t.id === args.id);
      if (idx === -1) return { content: [{ type: "text", text: `Todo ${args.id} not found` }] };
      const [removed] = todos.splice(idx, 1);
      return { content: [{ type: "text", text: `Deleted: "${removed.text}"` }] };
    }

    case "read_file": {
      try {
        const content = fs.readFileSync(args.filepath, "utf-8");
        return { content: [{ type: "text", text: content }] };
      } catch (e) {
        return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
      }
    }

    case "write_file": {
      try {
        fs.writeFileSync(args.filepath, args.content, "utf-8");
        return { content: [{ type: "text", text: `Written to ${args.filepath}` }] };
      } catch (e) {
        return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
      }
    }

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// LIST RESOURCES: Expose data files as readable resources
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "todo://database",
      name: "Current Todos",
      description: "The complete list of todos in JSON format",
      mimeType: "application/json",
    },
    {
      uri: "file://docs/readme.md",
      name: "App Documentation",
      description: "README file for this application",
      mimeType: "text/markdown",
    },
  ],
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  if (uri === "todo://database") {
    return {
      contents: [{
        uri,
        mimeType: "application/json",
        text: JSON.stringify(todos, null, 2),
      }],
    };
  }

  throw new Error(`Resource not found: ${uri}`);
});

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Start the server (communicates via stdin/stdout)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Todo MCP Server running on stdio");
}
main();

MCP Client — Connect Your Agent to the MCP Server

JavaScript 04_mcp_client_agent.js
const { Client } = require("@modelcontextprotocol/sdk/client/index.js");
const { StdioClientTransport } = require("@modelcontextprotocol/sdk/client/stdio.js");
const { GoogleGenerativeAI } = require("@google/generative-ai");

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

async function main() {
  // Step 1: Connect to the MCP server
  const mcpClient = new Client({ name: "gemini-agent", version: "1.0.0" }, {});
  const transport = new StdioClientTransport({
    command: "node",
    args: ["./03_mcp_server.js"],
  });
  await mcpClient.connect(transport);

  // Step 2: Discover available tools from the MCP server
  const { tools: mcpTools } = await mcpClient.listTools();
  console.log("Available MCP tools:", mcpTools.map(t => t.name));

  // Step 3: Convert MCP tool format to Gemini format
  const geminiTools = [{
    functionDeclarations: mcpTools.map(tool => ({
      name: tool.name,
      description: tool.description,
      parameters: tool.inputSchema,
    })),
  }];

  // Step 4: Create Gemini model with MCP tools
  const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash",
    tools: geminiTools,
    systemInstruction: "You are a helpful assistant that manages todos and files. Use the available tools.",
  });

  // Step 5: Run agent loop, dispatching tool calls to MCP server
  async function chat(message) {
    console.log(`\nšŸ‘¤ User: ${message}`);
    const chatSession = model.startChat();
    let response = await chatSession.sendMessage(message);

    while (true) {
      const parts = response.response.candidates[0].content.parts;
      const calls = parts.filter(p => p.functionCall);

      if (!calls.length) {
        console.log(`šŸ¤– Agent: ${parts.map(p => p.text || "").join("")}`);
        return;
      }

      const results = [];
      for (const p of calls) {
        const { name, args } = p.functionCall;
        console.log(`  šŸ“” MCP call: ${name}(${JSON.stringify(args)})`);

        // Execute tool ON THE MCP SERVER (not locally!)
        const mcpResult = await mcpClient.callTool({ name, arguments: args });
        const resultText = mcpResult.content[0].text;
        console.log(`  āœ… MCP result: ${resultText}`);

        results.push({ functionResponse: { name, response: { result: resultText } } });
      }

      response = await chatSession.sendMessage(results);
    }
  }

  await chat("Show me all my pending todos");
  await chat("Add a high priority todo: Deploy the new website");
  await chat("Mark todo #1 as done and then show all todos");

  await mcpClient.close();
}
main();
Why MCP Matters
Once you build an MCP server, any MCP-compatible AI client can use it — Claude Desktop, Cursor, Continue.dev, or your own app. You write the server once, and it works everywhere. This is the power of standardization.

āš™ļø Build Your First Full Agent

Let's build a real, complete agent — a Research Assistant that can search the web (simulated), read URLs, summarize content, and save notes to files. This is a production-quality pattern.

JavaScript 05_research_agent.js
const { GoogleGenerativeAI } = require("@google/generative-ai");
const fs = require("fs");
const https = require("https");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// TOOLS for the Research Agent
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Tool 1: Web search (using DuckDuckGo instant answers API)
async function web_search({ query }) {
  return new Promise((resolve) => {
    const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1`;
    https.get(url, (res) => {
      let data = "";
      res.on("data", d => data += d);
      res.on("end", () => {
        try {
          const json = JSON.parse(data);
          const results = {
            abstract: json.AbstractText || "No abstract available",
            source: json.AbstractSource || "",
            topics: (json.RelatedTopics || []).slice(0, 5).map(t => t.Text || "").filter(Boolean),
            answer: json.Answer || "",
          };
          resolve(results);
        } catch {
          resolve({ error: "Search failed", query });
        }
      });
    }).on("error", () => resolve({ error: "Network error", query }));
  });
}

// Tool 2: Save notes to a file
function save_note({ filename, content }) {
  const filepath = `./research_notes/${filename}`;
  if (!fs.existsSync("./research_notes")) {
    fs.mkdirSync("./research_notes");
  }
  fs.writeFileSync(filepath, content, "utf-8");
  return { success: true, filepath, size: content.length };
}

// Tool 3: Read saved notes
function read_note({ filename }) {
  const filepath = `./research_notes/${filename}`;
  if (!fs.existsSync(filepath)) return { error: `File ${filename} not found` };
  return { content: fs.readFileSync(filepath, "utf-8") };
}

// Tool 4: List saved notes
function list_notes() {
  if (!fs.existsSync("./research_notes")) return { files: [] };
  return { files: fs.readdirSync("./research_notes") };
}

// Tool 5: Get current datetime
function get_datetime() {
  return { datetime: new Date().toISOString(), timestamp: Date.now() };
}

const toolMap = { web_search, save_note, read_note, list_notes, get_datetime };

const toolDeclarations = [{
  functionDeclarations: [
    {
      name: "web_search",
      description: "Search the web for information on any topic",
      parameters: {
        type: "OBJECT",
        properties: { query: { type: "STRING", description: "Search query" } },
        required: ["query"],
      },
    },
    {
      name: "save_note",
      description: "Save research notes to a file for later reference",
      parameters: {
        type: "OBJECT",
        properties: {
          filename: { type: "STRING", description: "Filename like research.md" },
          content: { type: "STRING", description: "Content to save" },
        },
        required: ["filename", "content"],
      },
    },
    {
      name: "read_note",
      description: "Read a previously saved note file",
      parameters: {
        type: "OBJECT",
        properties: {
          filename: { type: "STRING", description: "Filename to read" },
        },
        required: ["filename"],
      },
    },
    {
      name: "list_notes",
      description: "List all saved research note files",
      parameters: { type: "OBJECT", properties: {} },
    },
    {
      name: "get_datetime",
      description: "Get the current date and time",
      parameters: { type: "OBJECT", properties: {} },
    },
  ],
}];

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// The Research Agent — Full implementation
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class ResearchAgent {
  constructor() {
    this.model = genAI.getGenerativeModel({
      model: "gemini-2.5-flash",
      tools: toolDeclarations,
      systemInstruction: `
        You are a research assistant. Your job is to:
        1. Search for information on topics the user asks about
        2. Synthesize the information clearly
        3. Save important findings to notes when asked
        4. Be factual, cite sources when available
        5. Always search BEFORE answering questions about current events or facts
        
        When saving notes, use descriptive filenames like "ai-trends-2024.md"
      `,
    });
    this.chat = this.model.startChat();
    this.stepCount = 0;
  }

  async run(message) {
    console.log(`\n${"=".repeat(60)}`);
    console.log(`šŸ‘¤ User: ${message}`);
    console.log(`${"=".repeat(60)}`);

    let response = await this.chat.sendMessage(message);
    this.stepCount = 0;

    while (this.stepCount < 10) { // Safety limit
      this.stepCount++;
      const parts = response.response.candidates[0].content.parts;
      const calls = parts.filter(p => p.functionCall);

      if (!calls.length) {
        const text = parts.map(p => p.text || "").join("");
        console.log(`\nšŸ¤– Research Agent:\n${text}`);
        return text;
      }

      const results = [];
      for (const p of calls) {
        const { name, args } = p.functionCall;
        console.log(`\n  šŸ” Step ${this.stepCount}: ${name}(${JSON.stringify(args)})`);

        const fn = toolMap[name];
        const result = await Promise.resolve(fn(args));
        console.log(`  āœ… Done`);

        results.push({ functionResponse: { name, response: result } });
      }

      response = await this.chat.sendMessage(results);
    }
  }
}

async function main() {
  const agent = new ResearchAgent();

  await agent.run("Search for information about Node.js and save a summary note called 'nodejs-research.md'");
  await agent.run("What notes have I saved so far?");
  await agent.run("Read my nodejs-research.md file");
}
main();

🌐 Multi-Agent Systems

A multi-agent system is when you have multiple specialized agents working together — each does what it's best at, and they coordinate to solve complex problems.

Real-Life Analogy: Think of a hospital. A patient comes in. The receptionist agent registers them. The triage agent assesses urgency. The diagnosis agent (doctor) determines the condition. The pharmacy agent prescribes medication. Each is specialized. A coordinator agent (head nurse) routes work between them. No single agent does everything — they collaborate.

Multi-Agent Patterns

PatternHow It WorksUse Case
Orchestrator/WorkerOne boss agent delegates tasks to worker agentsComplex research, content pipelines
PipelineAgents in sequence — output of one is input to nextData processing, document transformation
DebateTwo agents argue/review each other's workCode review, fact checking, writing quality
ParallelMultiple agents work simultaneously, results mergedResearch from multiple sources
SupervisorOne agent monitors and corrects othersQuality assurance, safety checking

Real Example: Content Creation Pipeline

3 agents working in sequence: Researcher → Writer → Editor

JavaScript 06_multi_agent_pipeline.js
const { GoogleGenerativeAI } = require("@google/generative-ai");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Helper: Simple LLM call (no tools needed for some agents)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function llmCall(systemPrompt, userMessage) {
  const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash",
    systemInstruction: systemPrompt,
  });
  const result = await model.generateContent(userMessage);
  return result.response.text();
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// AGENT 1: Researcher — Gathers key points on a topic
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function researcherAgent(topic) {
  console.log(`\nšŸ“š AGENT 1 (Researcher): Researching "${topic}"...`);

  const researchNotes = await llmCall(
    `You are a research specialist. Given a topic, produce:
     1. Key facts (5-7 bullet points)
     2. Recent trends
     3. Important statistics
     4. Common misconceptions
     Be factual and specific. Format as structured notes.`,
    `Research topic: ${topic}`
  );

  console.log(`āœ… Researcher done. Notes: ${researchNotes.length} chars`);
  return researchNotes;
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// AGENT 2: Writer — Turns research into an article
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function writerAgent(topic, researchNotes, style) {
  console.log(`\nāœļø  AGENT 2 (Writer): Writing ${style} article...`);

  const article = await llmCall(
    `You are a professional content writer. Given research notes, write a compelling article.
     Writing style: ${style}
     Requirements:
     - Engaging headline
     - Strong opening paragraph
     - 3-4 body sections with headers
     - Clear conclusion
     - Natural, flowing prose
     Do NOT use bullet points in the final article.`,
    `Topic: ${topic}\n\nResearch Notes:\n${researchNotes}`
  );

  console.log(`āœ… Writer done. Article: ${article.length} chars`);
  return article;
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// AGENT 3: Editor — Reviews and improves the article
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function editorAgent(article) {
  console.log(`\nšŸ” AGENT 3 (Editor): Reviewing and improving...`);

  const edited = await llmCall(
    `You are a senior editor. Review the article and:
     1. Fix any grammatical errors
     2. Improve clarity and flow
     3. Strengthen the opening and closing
     4. Ensure consistent tone
     5. Add a compelling meta description (1-2 sentences) at the top
     Return the improved article only. No editorial commentary.`,
    `Review and improve this article:\n\n${article}`
  );

  console.log(`āœ… Editor done.`);
  return edited;
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ORCHESTRATOR: Coordinates the pipeline
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function contentPipeline({ topic, style = "informative blog post" }) {
  console.log(`\nšŸš€ Starting Content Pipeline`);
  console.log(`šŸ“Œ Topic: ${topic}`);
  console.log(`šŸŽØ Style: ${style}`);

  const startTime = Date.now();

  // STEP 1: Research
  const research = await researcherAgent(topic);

  // STEP 2: Write (uses research from step 1)
  const draft = await writerAgent(topic, research, style);

  // STEP 3: Edit (uses draft from step 2)
  const finalArticle = await editorAgent(draft);

  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
  console.log(`\nā±ļø  Pipeline completed in ${elapsed}s`);
  console.log(`\n${"═".repeat(60)}`);
  console.log(`šŸ“° FINAL ARTICLE:`);
  console.log(`${"═".repeat(60)}\n`);
  console.log(finalArticle);

  return { research, draft, finalArticle };
}

contentPipeline({
  topic: "The impact of AI agents on software development in 2024",
  style: "engaging tech blog post for developers",
});

Advanced: Orchestrator Agent with Dynamic Worker Routing

JavaScript 07_orchestrator_pattern.js
const { GoogleGenerativeAI } = require("@google/generative-ai");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// WORKER AGENTS (specialized mini-agents)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const workers = {
  code_agent: async (task) => {
    const model = genAI.getGenerativeModel({
      model: "gemini-2.5-flash",
      systemInstruction: "You are an expert programmer. Write clean, working code with comments. Always include example usage.",
    });
    const result = await model.generateContent(task);
    return result.response.text();
  },

  analysis_agent: async (task) => {
    const model = genAI.getGenerativeModel({
      model: "gemini-2.5-flash",
      systemInstruction: "You are a data analyst. Analyze information, find patterns, and provide clear insights with numbers.",
    });
    const result = await model.generateContent(task);
    return result.response.text();
  },

  writing_agent: async (task) => {
    const model = genAI.getGenerativeModel({
      model: "gemini-2.5-flash",
      systemInstruction: "You are a professional writer. Write clear, engaging, well-structured content.",
    });
    const result = await model.generateContent(task);
    return result.response.text();
  },

  math_agent: async (task) => {
    const model = genAI.getGenerativeModel({
      model: "gemini-2.5-flash",
      systemInstruction: "You are a mathematician. Solve problems step by step, showing all work clearly.",
    });
    const result = await model.generateContent(task);
    return result.response.text();
  },
};

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ORCHESTRATOR: Analyzes task, delegates to right worker(s)
// Can run workers in parallel for speed!
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class Orchestrator {
  constructor() {
    // Orchestrator has ONE special tool: call_worker
    this.model = genAI.getGenerativeModel({
      model: "gemini-2.5-flash",
      tools: [{
        functionDeclarations: [{
          name: "call_worker",
          description: "Delegate a subtask to a specialized worker agent",
          parameters: {
            type: "OBJECT",
            properties: {
              worker: {
                type: "STRING",
                enum: ["code_agent", "analysis_agent", "writing_agent", "math_agent"],
                description: "Which specialized agent to use",
              },
              task: {
                type: "STRING",
                description: "The specific task for this worker",
              },
              priority: {
                type: "STRING",
                enum: ["parallel", "sequential"],
                description: "Run in parallel with other calls or wait for result first",
              },
            },
            required: ["worker", "task"],
          },
        }],
      }],
      systemInstruction: `
        You are an orchestrator AI. Break complex user requests into subtasks
        and delegate each to the right specialist agent using call_worker.
        
        Available workers:
        - code_agent: Writing code, debugging, programming tasks
        - analysis_agent: Data analysis, research synthesis, pattern finding
        - writing_agent: Articles, emails, documentation, creative writing  
        - math_agent: Calculations, formulas, statistics
        
        For independent subtasks, call multiple workers (they can run in parallel).
        Combine all results into a comprehensive final answer.
      `,
    });
  }

  async run(userRequest) {
    console.log(`\n${"═".repeat(60)}`);
    console.log(`šŸŽÆ Orchestrator received: "${userRequest}"`);
    console.log(`${"═".repeat(60)}`);

    const chat = this.model.startChat();
    let response = await chat.sendMessage(userRequest);
    const workerResults = {};

    while (true) {
      const parts = response.response.candidates[0].content.parts;
      const calls = parts.filter(p => p.functionCall);

      if (!calls.length) {
        const finalAnswer = parts.map(p => p.text || "").join("");
        console.log(`\nšŸ† FINAL ANSWER:\n${finalAnswer}`);
        return finalAnswer;
      }

      // Run all worker calls in parallel!
      console.log(`\nšŸ“‹ Dispatching ${calls.length} worker(s) in parallel...`);
      const workerPromises = calls.map(async (p) => {
        const { worker, task } = p.functionCall.args;
        console.log(`  šŸ¤– → ${worker}: "${task.substring(0, 60)}..."`);
        const result = await workers[worker](task);
        console.log(`  āœ… ${worker} completed`);
        return { name: "call_worker", result: { worker, result } };
      });

      const results = await Promise.all(workerPromises);
      const toolResults = results.map(r => ({
        functionResponse: { name: r.name, response: r.result },
      }));

      response = await chat.sendMessage(toolResults);
    }
  }
}

async function main() {
  const orchestrator = new Orchestrator();

  // Complex request requiring multiple agents
  await orchestrator.run(`
    I want to build a Node.js REST API for a todo app. Please:
    1. Analyze what endpoints I'll need and why
    2. Write the actual Express.js code for the API
    3. Write developer documentation for the API
    4. Calculate how many requests per second a single Node.js instance can handle
  `);
}
main();

šŸ—ļø Real-World Projects

Project 1: Personal Email Assistant Agent

JavaScript 08_email_agent.js
const { GoogleGenerativeAI } = require("@google/generative-ai");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// Simulated email inbox (in real app, use nodemailer + IMAP)
const inbox = [
  {
    id: 1, from: "[email protected]", subject: "Q4 Report Review",
    body: "Please review and send feedback on the Q4 report by Friday.",
    date: "2024-02-26", read: false, priority: "high"
  },
  {
    id: 2, from: "[email protected]", subject: "Weekly Tech Digest",
    body: "This week in tech: AI advances, new frameworks, and more...",
    date: "2024-02-25", read: false, priority: "low"
  },
  {
    id: 3, from: "[email protected]", subject: "Project Update Needed",
    body: "Hi, we need an update on the project status. What's the current progress?",
    date: "2024-02-25", read: false, priority: "high"
  },
];

const sentEmails = [];

const emailTools = {
  list_emails: ({ filter }) => {
    let emails = inbox;
    if (filter === "unread") emails = inbox.filter(e => !e.read);
    if (filter === "high_priority") emails = inbox.filter(e => e.priority === "high");
    return emails.map(({ body, ...e }) => e); // Don't include full body in list
  },
  read_email: ({ id }) => {
    const email = inbox.find(e => e.id === id);
    if (!email) return { error: `Email ${id} not found` };
    email.read = true;
    return email;
  },
  compose_reply: ({ email_id, context }) => {
    const email = inbox.find(e => e.id === email_id);
    if (!email) return { error: "Email not found" };
    return {
      to: email.from,
      subject: `Re: ${email.subject}`,
      original_email: email.body,
      suggestion_context: context,
    };
  },
  send_email: ({ to, subject, body }) => {
    const sent = { id: sentEmails.length + 1, to, subject, body, sentAt: new Date().toISOString() };
    sentEmails.push(sent);
    return { success: true, message: `Email sent to ${to}`, emailId: sent.id };
  },
  mark_as_read: ({ id }) => {
    const email = inbox.find(e => e.id === id);
    if (email) email.read = true;
    return { success: true };
  },
  summarize_thread: ({ ids }) => {
    const emails = inbox.filter(e => ids.includes(e.id));
    return emails;// Agent will summarize these
  },
};

const emailToolDeclarations = [{
  functionDeclarations: [
    { name: "list_emails", description: "List emails in inbox, with optional filter",
      parameters: { type: "OBJECT", properties: {
        filter: { type: "STRING", enum: ["all", "unread", "high_priority"] }
      }}},
    { name: "read_email", description: "Read the full content of an email",
      parameters: { type: "OBJECT", properties: { id: { type: "NUMBER" } }, required: ["id"] }},
    { name: "send_email", description: "Send an email",
      parameters: { type: "OBJECT", properties: {
        to: { type: "STRING" }, subject: { type: "STRING" }, body: { type: "STRING" }
      }, required: ["to", "subject", "body"] }},
    { name: "compose_reply", description: "Get context needed to compose a reply to an email",
      parameters: { type: "OBJECT", properties: {
        email_id: { type: "NUMBER" },
        context: { type: "STRING", description: "What the reply should say" }
      }, required: ["email_id", "context"] }},
  ],
}];

async function emailAgent(command) {
  const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash",
    tools: emailToolDeclarations,
    systemInstruction: `
      You are a professional email assistant. You can:
      - Read and manage emails
      - Draft and send replies
      - Summarize email threads
      - Prioritize important emails
      
      When replying to clients or bosses, be professional and concise.
      When sending emails, draft thoughtful responses.
      Always confirm before sending important emails.
    `,
  });

  const chat = model.startChat();
  console.log(`\nšŸ“§ Command: ${command}`);

  let resp = await chat.sendMessage(command);
  while (true) {
    const parts = resp.response.candidates[0].content.parts;
    const calls = parts.filter(p => p.functionCall);
    if (!calls.length) { console.log(`šŸ¤–: ${parts.map(p=>p.text||"").join("")}`); return; }
    const results = calls.map(p => {
      const {name,args} = p.functionCall;
      console.log(`  šŸ“¬ ${name}(${JSON.stringify(args)})`);
      return { functionResponse: { name, response: emailTools[name](args) }};
    });
    resp = await chat.sendMessage(results);
  }
}

async function main() {
  await emailAgent("Show me my high priority emails and summarize what I need to do");
  await emailAgent("Reply to the client email saying we're 70% complete and on track for next week");
}
main();

Project 2: SQL Database Agent

JavaScript 09_sql_agent.js
// npm install @google/generative-ai better-sqlite3
const { GoogleGenerativeAI } = require("@google/generative-ai");
const Database = require("better-sqlite3");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

// Set up a real SQLite database
const db = new Database(":memory:");

// Create tables and seed data
db.exec(`
  CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT, department TEXT, salary INTEGER, hire_date TEXT
  );
  CREATE TABLE sales (
    id INTEGER PRIMARY KEY, employee_id INTEGER,
    amount REAL, product TEXT, sale_date TEXT,
    FOREIGN KEY (employee_id) REFERENCES employees(id)
  );
  INSERT INTO employees VALUES
    (1,'Ali Hassan','Engineering',95000,'2021-03-15'),
    (2,'Sara Khan','Marketing',75000,'2022-01-10'),
    (3,'Ahmed Raza','Engineering',88000,'2020-06-20'),
    (4,'Fatima Ali','Sales',65000,'2023-02-01'),
    (5,'Usman Malik','Engineering',102000,'2019-11-05');
  INSERT INTO sales VALUES
    (1,4,15000,'Enterprise Plan','2024-01-15'),
    (2,2,8500,'Pro Plan','2024-01-20'),
    (3,4,22000,'Enterprise Plan','2024-02-01'),
    (4,2,4200,'Starter Plan','2024-02-10'),
    (5,4,18000,'Enterprise Plan','2024-02-20');
`);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SQL Tools
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const sqlTools = {
  get_schema: () => {
    const tables = db.prepare("SELECT name, sql FROM sqlite_master WHERE type='table'").all();
    return tables;
  },
  run_query: ({ sql }) => {
    try {
      // Safety: only allow SELECT queries
      if (!sql.trim().toUpperCase().startsWith("SELECT")) {
        return { error: "Only SELECT queries are allowed" };
      }
      const results = db.prepare(sql).all();
      return { rows: results, count: results.length };
    } catch (e) {
      return { error: e.message };
    }
  },
};

const sqlToolDeclarations = [{
  functionDeclarations: [
    {
      name: "get_schema",
      description: "Get the database schema (table names and column definitions)",
      parameters: { type: "OBJECT", properties: {} },
    },
    {
      name: "run_query",
      description: "Execute a SELECT SQL query on the database",
      parameters: {
        type: "OBJECT",
        properties: {
          sql: { type: "STRING", description: "The SQL SELECT query to execute" },
        },
        required: ["sql"],
      },
    },
  ],
}];

async function sqlAgent(question) {
  const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash",
    tools: sqlToolDeclarations,
    systemInstruction: `
      You are a SQL expert. When users ask business questions:
      1. First, check the schema to understand the database structure
      2. Write the appropriate SQL query
      3. Run the query and interpret the results
      4. Explain findings in plain English with numbers
      Always show the SQL query you used.
    `,
  });

  const chat = model.startChat();
  console.log(`\nā“ Question: ${question}`);

  let resp = await chat.sendMessage(question);
  while (true) {
    const parts = resp.response.candidates[0].content.parts;
    const calls = parts.filter(p => p.functionCall);
    if (!calls.length) {
      console.log(`\nšŸ“Š Answer:\n${parts.map(p=>p.text||"").join("")}`);
      return;
    }
    const results = calls.map(p => {
      const {name,args} = p.functionCall;
      console.log(`  šŸ—ƒļø  ${name}(${JSON.stringify(args)})`);
      return { functionResponse: { name, response: sqlTools[name](args) }};
    });
    resp = await chat.sendMessage(results);
  }
}

async function main() {
  await sqlAgent("Who are the top earners in Engineering?");
  await sqlAgent("What's the total sales amount per employee? Who's the best salesperson?");
  await sqlAgent("What's the average salary by department?");
}
main();

šŸ“ Patterns & Project Structure

Complete Project Structure

ai-agents-project/ ā”œā”€ā”€ agents/ │ ā”œā”€ā”€ base-agent.js # Reusable agent loop class │ ā”œā”€ā”€ research-agent.js # Research specialist │ ā”œā”€ā”€ customer-agent.js # Customer support │ └── orchestrator.js # Coordinates other agents ā”œā”€ā”€ skills/ │ ā”œā”€ā”€ email-skill.js # Email tools + instructions │ ā”œā”€ā”€ database-skill.js # SQL tools + instructions │ └── file-skill.js # File system tools ā”œā”€ā”€ mcp-servers/ │ ā”œā”€ā”€ todo-server.js # MCP server for todos │ ā”œā”€ā”€ calendar-server.js # MCP server for calendar │ └── search-server.js # MCP server for web search ā”œā”€ā”€ tools/ │ ā”œā”€ā”€ web-search.js │ ā”œā”€ā”€ file-ops.js │ └── calculator.js ā”œā”€ā”€ index.js # Main entry point └── package.json

Reusable Base Agent Class

JavaScript agents/base-agent.js
const { GoogleGenerativeAI } = require("@google/generative-ai");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

/**
 * BaseAgent - Reusable agent class you can extend
 * Usage:
 *   class MyAgent extends BaseAgent {
 *     constructor() {
 *       super({ name: 'MyAgent', systemPrompt: '...', tools: [...], toolFns: {...} });
 *     }
 *   }
 */
class BaseAgent {
  constructor({ name, systemPrompt, tools, toolFns, model = "gemini-2.5-flash", maxSteps = 20 }) {
    this.name = name;
    this.toolFns = toolFns;
    this.maxSteps = maxSteps;
    this.conversationHistory = [];

    this.model = genAI.getGenerativeModel({
      model,
      systemInstruction: systemPrompt,
      tools: tools ? [{ functionDeclarations: tools }] : undefined,
    });

    this.chat = this.model.startChat({ history: this.conversationHistory });
    this.verbose = true;
  }

  log(msg) {
    if (this.verbose) console.log(`[${this.name}] ${msg}`);
  }

  async run(userMessage, context = {}) {
    this.log(`šŸ‘¤ "${userMessage}"`);

    // Inject any extra context into the message
    const fullMessage = context ? `${userMessage}\n\nContext: ${JSON.stringify(context)}` : userMessage;

    let response = await this.chat.sendMessage(fullMessage);
    let steps = 0;

    while (steps++ < this.maxSteps) {
      const parts = response.response.candidates[0].content.parts;
      const calls = parts.filter(p => p.functionCall);

      if (!calls.length) {
        const text = parts.map(p => p.text || "").join("");
        this.log(`šŸ¤– Done in ${steps} steps`);
        return { success: true, response: text, steps };
      }

      const toolResults = [];
      for (const p of calls) {
        const { name, args } = p.functionCall;
        this.log(`šŸ”§ ${name}(${JSON.stringify(args)})`);

        try {
          const fn = this.toolFns[name];
          if (!fn) throw new Error(`Unknown tool: ${name}`);
          const result = await Promise.resolve(fn(args));
          toolResults.push({ functionResponse: { name, response: result } });
        } catch (err) {
          this.log(`āŒ Error: ${err.message}`);
          toolResults.push({ functionResponse: { name, response: { error: err.message } } });
        }
      }

      response = await this.chat.sendMessage(toolResults);
    }

    return { success: false, error: "Max steps reached" };
  }

  // Reset conversation history
  reset() {
    this.chat = this.model.startChat();
  }
}

module.exports = { BaseAgent };

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Example: Create a specialized agent using BaseAgent
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class WeatherAgent extends BaseAgent {
  constructor() {
    super({
      name: "WeatherAgent",
      systemPrompt: "You are a weather assistant. Always check weather before answering.",
      tools: [
        {
          name: "get_weather",
          description: "Get current weather for a city",
          parameters: {
            type: "OBJECT",
            properties: {
              city: { type: "STRING", description: "City name" },
            },
            required: ["city"],
          },
        },
      ],
      toolFns: {
        get_weather: ({ city }) => ({
          city,
          temp: 28,
          condition: "Sunny",
          humidity: "65%",
          wind: "15 km/h",
        }),
      },
    });
  }
}

// Use it:
const weather = new WeatherAgent();
weather.run("What's the weather like in Lahore? Should I bring an umbrella?")
  .then(result => console.log(result.response));

Setup Instructions

Quick Start

mkdir my-agent && cd my-agent
npm init -y
npm install @google/generative-ai @modelcontextprotocol/sdk better-sqlite3
export GEMINI_API_KEY=your_key_here
node 01_calculator_agent.js

šŸŽÆ Summary — The Big Picture

ConceptWhat It IsAnalogyWhen to Use
Agent AI that thinks + acts using tools Personal assistant Any task requiring action, not just answers
Tool A JS function the agent can call A skill or equipment Every time agent needs to do something real
Skill Group of related tools + instructions Job training + tools Organizing agent capabilities by domain
MCP Standard protocol for AI tool servers USB for AI Sharing tools across multiple AI clients
Multi-Agent Multiple specialized agents collaborating A team of experts Complex tasks, parallel work, quality checks
Orchestrator Agent that manages other agents Project manager Coordinating complex workflows