Table of contents

What is the Difference Between MCP Client and MCP Server?

Understanding the distinction between MCP clients and servers is fundamental to working with the Model Context Protocol (MCP). These two components form the backbone of MCP's architecture, enabling AI assistants to interact with external tools, data sources, and web scraping capabilities. This guide provides a comprehensive technical breakdown of both components, their roles, responsibilities, and how they work together in web scraping workflows.

Core Architecture Overview

The Model Context Protocol follows a client-server architecture pattern similar to HTTP, but specifically designed for AI-tool integration. This architecture creates a clear separation of concerns:

  • MCP Server: Provides capabilities (tools, resources, prompts)
  • MCP Client: Consumes and orchestrates these capabilities
  • MCP Host: The application that embeds the client (like Claude Desktop, VS Code, or custom apps)
┌──────────────────────────────────────────────────────────┐
│                        MCP Host                          │
│                  (e.g., Claude Desktop)                  │
│  ┌────────────────────────────────────────────────────┐  │
│  │                   MCP Client                       │  │
│  │  - Manages connections to multiple servers        │  │
│  │  - Routes requests from AI to appropriate server  │  │
│  │  - Aggregates capabilities from all servers       │  │
│  └──────────────┬─────────────────────────────────────┘  │
└─────────────────┼────────────────────────────────────────┘
                  │ MCP Protocol
         ┌────────┴────────┬───────────────┬──────────────┐
         ▼                 ▼               ▼              ▼
  ┌─────────────┐   ┌─────────────┐   ┌──────────┐   ┌──────────┐
  │ MCP Server  │   │ MCP Server  │   │   MCP    │   │   MCP    │
  │  (Scraper)  │   │   (DB)      │   │ Server   │   │ Server   │
  │             │   │             │   │  (File)  │   │  (API)   │
  └─────────────┘   └─────────────┘   └──────────┘   └──────────┘

MCP Server: The Capability Provider

An MCP server is a program that exposes specific functionality through the standardized MCP protocol. Think of it as a microservice that makes tools, data, and operations available to AI assistants.

Key Responsibilities

  1. Expose Tools: Functions that the AI can execute (e.g., scrape a webpage, parse HTML)
  2. Provide Resources: Data sources that the AI can read (e.g., cached content, configuration)
  3. Offer Prompts: Pre-configured templates for common workflows
  4. Handle Requests: Process incoming requests from clients and return results
  5. Manage State: Maintain session state, rate limits, and resource management

MCP Server Implementation Example (Python)

Here's a complete MCP server that provides web scraping capabilities:

import asyncio
import httpx
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource
from bs4 import BeautifulSoup

# Initialize the MCP server
app = Server("webscraping-server")

# Define available tools
@app.list_tools()
async def list_tools() -> list[Tool]:
    """Expose web scraping tools to clients"""
    return [
        Tool(
            name="fetch_html",
            description="Fetch HTML content from a URL",
            inputSchema={
                "type": "object",
                "properties": {
                    "url": {
                        "type": "string",
                        "description": "The URL to scrape"
                    },
                    "timeout": {
                        "type": "number",
                        "description": "Request timeout in seconds"
                    }
                },
                "required": ["url"]
            }
        ),
        Tool(
            name="extract_text",
            description="Extract clean text from HTML",
            inputSchema={
                "type": "object",
                "properties": {
                    "html": {
                        "type": "string",
                        "description": "HTML content to parse"
                    },
                    "selector": {
                        "type": "string",
                        "description": "CSS selector to filter content"
                    }
                },
                "required": ["html"]
            }
        )
    ]

# Implement tool execution
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """Execute tools requested by the client"""
    if name == "fetch_html":
        async with httpx.AsyncClient() as client:
            response = await client.get(
                arguments["url"],
                timeout=arguments.get("timeout", 30)
            )
            return [TextContent(
                type="text",
                text=response.text
            )]

    elif name == "extract_text":
        soup = BeautifulSoup(arguments["html"], 'html.parser')
        if "selector" in arguments:
            elements = soup.select(arguments["selector"])
            text = "\n".join([el.get_text().strip() for el in elements])
        else:
            text = soup.get_text()

        return [TextContent(
            type="text",
            text=text
        )]

# Provide resources
@app.list_resources()
async def list_resources() -> list[Resource]:
    """Expose data resources to clients"""
    return [
        Resource(
            uri="scraper://config",
            name="Scraper Configuration",
            mimeType="application/json",
            description="Current scraper settings"
        )
    ]

# Run the server
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="webscraping-server",
                server_version="1.0.0"
            )
        )

if __name__ == "__main__":
    asyncio.run(main())

MCP Server Implementation Example (JavaScript/TypeScript)

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";

// Create server instance
const server = new Server(
  {
    name: "webscraping-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
      resources: {},
      prompts: {}
    },
  }
);

// Define tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "scrape_url",
        description: "Scrape content from a URL with optional JavaScript rendering",
        inputSchema: {
          type: "object",
          properties: {
            url: {
              type: "string",
              description: "URL to scrape",
            },
            wait_for: {
              type: "string",
              description: "CSS selector to wait for",
            },
            use_proxy: {
              type: "boolean",
              description: "Whether to use proxy rotation",
            },
          },
          required: ["url"],
        },
      },
      {
        name: "parse_html",
        description: "Parse HTML and extract data using selectors",
        inputSchema: {
          type: "object",
          properties: {
            html: {
              type: "string",
              description: "HTML content to parse",
            },
            selectors: {
              type: "object",
              description: "Field names mapped to CSS selectors",
            },
          },
          required: ["html", "selectors"],
        },
      },
    ],
  };
});

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "scrape_url") {
    try {
      const response = await axios.get(args.url, {
        headers: {
          'User-Agent': 'Mozilla/5.0 (compatible; MCPServer/1.0)',
        },
        timeout: 30000,
      });

      return {
        content: [
          {
            type: "text",
            text: response.data,
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error scraping URL: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }

  if (name === "parse_html") {
    const { load } = await import("cheerio");
    const $ = load(args.html);
    const results = {};

    for (const [field, selector] of Object.entries(args.selectors)) {
      const elements = $(selector);
      results[field] = elements.map((i, el) => $(el).text().trim()).get();
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(results, null, 2),
        },
      ],
    };
  }

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

// Define resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "scraper://stats",
        name: "Scraping Statistics",
        mimeType: "application/json",
        description: "Current session scraping statistics",
      },
    ],
  };
});

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

main().catch(console.error);

MCP Client: The Capability Consumer

An MCP client is the component that connects to one or more MCP servers and makes their capabilities available to the AI assistant or host application. The client acts as an orchestrator and request router.

Key Responsibilities

  1. Connect to Servers: Establish and maintain connections to multiple MCP servers
  2. Discover Capabilities: Query servers for available tools, resources, and prompts
  3. Route Requests: Direct AI requests to the appropriate server
  4. Aggregate Results: Combine capabilities from multiple servers into a unified interface
  5. Manage Lifecycle: Handle connection initialization, reconnection, and cleanup
  6. Handle Errors: Implement retry logic and error recovery

MCP Client Implementation Example (Python)

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from typing import List, Dict, Any

class WebScrapingMCPClient:
    """MCP Client for web scraping operations"""

    def __init__(self, server_command: str, server_args: List[str]):
        self.server_params = StdioServerParameters(
            command=server_command,
            args=server_args
        )
        self.session = None
        self.available_tools = []

    async def connect(self):
        """Establish connection to the MCP server"""
        try:
            self.read, self.write = await stdio_client(
                self.server_params
            ).__aenter__()

            self.session = await ClientSession(
                self.read,
                self.write
            ).__aenter__()

            # Initialize the session
            await self.session.initialize()

            # Discover available tools
            tools_response = await self.session.list_tools()
            self.available_tools = tools_response.tools

            print(f"Connected to MCP server with {len(self.available_tools)} tools")
            return True

        except Exception as e:
            print(f"Failed to connect: {e}")
            return False

    async def scrape_website(self, url: str, **options) -> str:
        """Use the server's scraping tool"""
        if not self.session:
            raise RuntimeError("Not connected to server")

        result = await self.session.call_tool(
            "scrape_url",
            arguments={
                "url": url,
                **options
            }
        )

        return result.content[0].text

    async def extract_data(self, html: str, selectors: Dict[str, str]) -> Dict[str, Any]:
        """Use the server's parsing tool"""
        if not self.session:
            raise RuntimeError("Not connected to server")

        result = await self.session.call_tool(
            "parse_html",
            arguments={
                "html": html,
                "selectors": selectors
            }
        )

        import json
        return json.loads(result.content[0].text)

    async def get_tools(self) -> List[str]:
        """List all available tools from the server"""
        return [tool.name for tool in self.available_tools]

    async def disconnect(self):
        """Close the connection"""
        if self.session:
            await self.session.__aexit__(None, None, None)

# Usage example
async def main():
    # Client connects to the server
    client = WebScrapingMCPClient(
        server_command="python",
        server_args=["-m", "webscraping_server"]
    )

    if await client.connect():
        # Client requests capabilities from server
        tools = await client.get_tools()
        print(f"Available tools: {tools}")

        # Client uses server's scraping tool
        html = await client.scrape_website(
            "https://example.com",
            wait_for=".content"
        )

        # Client uses server's parsing tool
        data = await client.extract_data(html, {
            "title": "h1",
            "paragraphs": "p"
        })

        print(f"Extracted data: {data}")

        await client.disconnect()

asyncio.run(main())

MCP Client Implementation Example (JavaScript/TypeScript)

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

class WebScrapingMCPClient {
  private client: Client;
  private transport: StdioClientTransport;
  private connected: boolean = false;

  constructor(serverCommand: string, serverArgs: string[]) {
    this.transport = new StdioClientTransport({
      command: serverCommand,
      args: serverArgs,
    });

    this.client = new Client(
      {
        name: "webscraping-client",
        version: "1.0.0",
      },
      {
        capabilities: {
          tools: {},
          resources: {},
        },
      }
    );
  }

  async connect(): Promise<boolean> {
    try {
      await this.client.connect(this.transport);
      this.connected = true;
      console.log("Connected to MCP server");
      return true;
    } catch (error) {
      console.error("Connection failed:", error);
      return false;
    }
  }

  async listAvailableTools() {
    if (!this.connected) {
      throw new Error("Not connected to server");
    }

    const tools = await this.client.listTools();
    return tools.tools.map((tool) => ({
      name: tool.name,
      description: tool.description,
    }));
  }

  async scrapeWebsite(url: string, options: Record<string, any> = {}) {
    if (!this.connected) {
      throw new Error("Not connected to server");
    }

    const result = await this.client.callTool({
      name: "scrape_url",
      arguments: {
        url,
        ...options,
      },
    });

    return result.content[0].text;
  }

  async parseHTML(html: string, selectors: Record<string, string>) {
    if (!this.connected) {
      throw new Error("Not connected to server");
    }

    const result = await this.client.callTool({
      name: "parse_html",
      arguments: {
        html,
        selectors,
      },
    });

    return JSON.parse(result.content[0].text);
  }

  async disconnect() {
    await this.client.close();
    this.connected = false;
  }
}

// Usage
async function main() {
  const client = new WebScrapingMCPClient("node", ["scraping-server.js"]);

  if (await client.connect()) {
    const tools = await client.listAvailableTools();
    console.log("Available tools:", tools);

    const html = await client.scrapeWebsite("https://example.com");
    const data = await client.parseHTML(html, {
      title: "h1",
      description: "meta[name='description']",
    });

    console.log("Extracted data:", data);

    await client.disconnect();
  }
}

main().catch(console.error);

Key Differences Summary

| Aspect | MCP Server | MCP Client | |--------|-----------|-----------| | Role | Provides capabilities | Consumes capabilities | | Responsibilities | Implements tools, exposes resources | Connects to servers, routes requests | | Quantity | Multiple servers per system | One client per host application | | Initialization | Started by client or system | Started by host application | | Transport | Listens on stdio/HTTP/WebSocket | Initiates connections | | State Management | Manages tool-specific state | Manages connection state | | Error Handling | Returns error responses | Handles retries and reconnection | | Dependencies | Specific to tool domain | Generic MCP protocol handling | | Configuration | Internal tool configuration | Server connection configuration | | Lifecycle | Managed by client | Managed by host |

Communication Flow

Understanding how clients and servers interact is crucial for debugging and optimization:

1. Client Discovery Phase
   Client → Server: "What tools do you provide?"
   Server → Client: [list of tools with schemas]

2. Client Request Phase
   Client → Server: "Execute tool X with parameters Y"
   Server → Client: [executes tool]
   Server → Client: [returns result or error]

3. Resource Access Phase
   Client → Server: "Give me resource at URI Z"
   Server → Client: [returns resource content]

Example Communication Sequence

# Client initiates connection
async def communication_example():
    client = WebScrapingMCPClient("python", ["-m", "server"])
    await client.connect()

    # 1. Discovery: Client asks server for capabilities
    tools = await client.session.list_tools()
    # Server responds: [scrape_url, parse_html, ...]

    # 2. Request: Client asks server to execute a tool
    result = await client.session.call_tool(
        "scrape_url",
        arguments={"url": "https://example.com"}
    )
    # Server responds: TextContent with HTML

    # 3. Resource: Client asks for a resource
    resource = await client.session.read_resource(
        uri="scraper://stats"
    )
    # Server responds: Resource content

    await client.disconnect()

Multi-Server Client Architecture

One of the powerful features of MCP is that a single client can connect to multiple servers, each specializing in different capabilities:

class MultiServerMCPClient:
    """Client managing multiple MCP servers"""

    def __init__(self):
        self.servers = {}

    async def add_server(self, name: str, command: str, args: List[str]):
        """Connect to an additional MCP server"""
        server_params = StdioServerParameters(command=command, args=args)

        async with stdio_client(server_params) as (read, write):
            async with ClientSession(read, write) as session:
                await session.initialize()
                self.servers[name] = session

    async def scrape_with_best_server(self, url: str):
        """Route to appropriate server based on URL"""
        if "javascript" in url or "react" in url:
            # Use browser automation server
            return await self.servers["browser"].call_tool(
                "puppeteer_scrape",
                arguments={"url": url}
            )
        else:
            # Use fast HTTP server
            return await self.servers["http"].call_tool(
                "http_scrape",
                arguments={"url": url}
            )

# Usage
async def main():
    client = MultiServerMCPClient()

    # Connect to multiple specialized servers
    await client.add_server("http", "python", ["-m", "http_scraper"])
    await client.add_server("browser", "node", ["browser_scraper.js"])
    await client.add_server("database", "python", ["-m", "db_server"])

    # Client intelligently routes to appropriate server
    result = await client.scrape_with_best_server("https://react-app.com")

Real-World Web Scraping Example

Here's how clients and servers work together in a complete web scraping workflow:

// Server side: Specialized scraping server
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { WebScrapingAI } from "webscraping-ai-sdk";

const scraperAPI = new WebScrapingAI("YOUR_API_KEY");
const server = new Server({ name: "webscraping-ai-server", version: "1.0.0" });

server.setRequestHandler("tools/call", async (request) => {
  if (request.params.name === "ai_scrape") {
    // Server handles the actual scraping logic
    const result = await scraperAPI.getHTML(request.params.arguments.url, {
      js: true,
      proxy: "datacenter",
    });

    return { content: [{ type: "text", text: result }] };
  }
});

// Client side: Orchestrates multiple scraping operations
class ScrapingOrchestrator {
  constructor() {
    this.client = new Client(/* config */);
  }

  async scrapeProductCatalog(urls: string[]) {
    const results = [];

    for (const url of urls) {
      // Client delegates scraping to server
      const html = await this.client.callTool({
        name: "ai_scrape",
        arguments: { url }
      });

      // Client processes the results
      results.push(this.parseProduct(html));
    }

    return results;
  }
}

When to Use Client vs Server

Build an MCP Server when you want to: - Expose web scraping capabilities to AI assistants - Create reusable scraping tools - Set up an MCP server for web scraping - Provide specialized domain knowledge - Manage scraping infrastructure centrally

Build an MCP Client when you want to: - Connect to an MCP server from your application - Orchestrate multiple scraping services - Build custom AI-powered applications - Create specialized workflows - Aggregate capabilities from multiple servers

Best Practices

For MCP Servers

  1. Clear Tool Definitions: Provide detailed descriptions and schemas
  2. Error Handling: Return meaningful error messages
  3. Rate Limiting: Implement internal rate limiting
  4. Resource Management: Clean up resources properly
  5. Logging: Log all tool executions for debugging

For MCP Clients

  1. Connection Management: Handle reconnections gracefully
  2. Timeout Handling: Set appropriate timeouts
  3. Error Recovery: Implement retry logic
  4. Tool Discovery: Always query available tools after connection
  5. Resource Cleanup: Close connections when done

Conclusion

The distinction between MCP clients and servers is fundamental to the Model Context Protocol architecture. Servers provide capabilities (tools, resources, prompts) while clients consume and orchestrate these capabilities. This separation enables:

  • Modularity: Build specialized servers for different tasks
  • Scalability: Connect to multiple servers simultaneously
  • Reusability: Share servers across different client applications
  • Flexibility: Mix and match capabilities as needed

For web scraping specifically, this architecture allows you to build specialized scraping servers that handle complex browser automation, proxy management, and data extraction, while clients orchestrate these capabilities in sophisticated workflows. Whether you're building a simple scraping tool or a complex AI-powered data extraction pipeline, understanding the client-server relationship is essential for effective MCP development.

By properly implementing both components with robust error handling, resource management, and clear interfaces, you can create powerful, maintainable web scraping solutions that leverage the full potential of AI-assisted automation.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon