     [Blog](https://scrapfly.io/blog)   /  [ai](https://scrapfly.io/blog/tag/ai)   /  [How to Build an MCP Server in Python](https://scrapfly.io/blog/posts/how-to-build-an-mcp-server)   # How to Build an MCP Server in Python

 by [Ziad Shamndy](https://scrapfly.io/blog/author/ziad) Jun 24, 2026 13 min read [\#ai](https://scrapfly.io/blog/tag/ai) 

 [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-build-an-mcp-server "Share on LinkedIn")    

 

 

   

An LLM is only as useful as the data it can reach. Give a model a way to scrape the live web and it stops guessing from stale training data. It reads the page you point it at. The Model Context Protocol (MCP) is the standard that makes this connection clean.

This guide builds an MCP server in Python with [FastMCP](https://github.com/modelcontextprotocol/python-sdk), starting from a one-tool hello-world and growing into a web scraping server.

The scraping tools wrap the Scrapfly SDK, so a model can fetch pages, pull structured data, and capture screenshots without ever hitting a block.

[What Is MCP? Understanding the Model Context ProtocolWhat is MCP? Learn how the Model Context Protocol powers tools like Copilot Studio by giving AI models access to real-time, structured context.](https://scrapfly.io/blog/posts/what-is-mcp-understanding-the-model-context-protocol)



## Key Takeaways

An MCP server gives a large language model live web access through tools you define in plain Python.

- **MCP gives a model three things**: tools to call, resources to read, prompts to reuse
- **FastMCP turns a Python function into a tool**: add one `@mcp.tool()` decorator
- **Wrap the Scrapfly SDK for scraping tools**: `scrape_page`, `extract_data`, `screenshot`
- **Scrapfly ASP stops your tools getting blocked**: 90+ bot systems handled for you
- **Connect over stdio and JSON-RPC**: Claude Desktop and Cursor auto-detect your tools
- **Skip the build with Scrapfly's hosted MCP**: scrape, extract, screenshot, no code

**Get web scraping tips in your inbox**Trusted by 100K+ developers and 30K+ enterprises. Unsubscribe anytime.







## What is the Model Context Protocol (MCP)?

The Model Context Protocol (MCP) is an open standard that lets external tools, APIs, or plugins talk to large language models (LLMs).

An MCP server is a program you run locally or remotely. An LLM host like Claude or Cursor connects to it to call functions, query data, or use prompt templates.

MCP has three building blocks:

- Tools: functions the model can call.
- Resources: static or live files and data the model can request.
- Prompts: templated messages that shape the model's output.

These three pieces cover most of what you need to give a model new abilities. Next, look at how a host and your server exchange messages.



## Understanding the Basics of MCP Communication

Before writing code, it helps to know how a model reaches your server. MCP servers run over transports like `stdio`, `http`, or `websocket`, and a host such as Cursor sends JSON-RPC requests. Your server answers with tool results, prompt content, or resource data.

This design lets the model call your tools or read your files the same way a plugin would. With the message flow clear, the next question is why you would pick MCP over a plain API.

## Why Use MCP Instead of Other APIs?

MCP targets LLMs directly. A REST API needs explicit engineering work to query. MCP plugs straight into the model interface, so your functions become callable as if the model already knew them.

That makes it a strong fit for prototyping, teaching, internal tools, and research interfaces. Before building any tools, set up a clean Python environment.



## Setting Up Your Python Environment

You need Python 3.10 or later. Start by creating a virtual environment to keep the project isolated from other packages:

bash```bash
python -m venv mcp-env
source mcp-env/bin/activate  # On Windows: mcp-env\Scripts\activate
```



Install the MCP SDK. The `mcp` package gives you the server framework, and the `[cli]` extra adds the command-line tools you will use for testing:

bash```bash
pip install mcp "mcp[cli]"
```



You also need the Scrapfly SDK for the scraping tools later in this guide:

bash```bash
pip install scrapfly-sdk
```



To confirm the MCP install, check the version:

bash```bash
mcp version
```



You should see a version number printed back. That covers setup, so build the smallest possible server first.



## Setting Up Your Python Environment

You need Python 3.10 or later. Start by creating a virtual environment to keep the project isolated from other packages:

bash```bash
python -m venv mcp-env
source mcp-env/bin/activate  # On Windows: mcp-env\Scripts\activate
```



Install the MCP SDK. The `mcp` package gives you the server framework, and the `[cli]` extra adds the command-line tools you will use for testing:

bash```bash
pip install mcp "mcp[cli]"
```



You also need the Scrapfly SDK for the scraping tools later in this guide:

bash```bash
pip install scrapfly-sdk
```



To confirm the MCP install, check the version:

bash```bash
mcp version
```



You should see a version number printed back. That covers setup, so build the smallest possible server first.



## Creating Your First MCP Server (Calculator Hello-World)

The simplest MCP server is one tool that does one thing. Create a file named `calculator.py` with a single `add` tool:

python```python
from mcp.server.fastmcp import FastMCP  # FastMCP is the quickstart server base

mcp = FastMCP("Calculator Server")  # name your server

@mcp.tool()  # register the function as a callable tool
def add(a: int, b: int) -> int:
    """Add two numbers and return the result."""
    return a + b

if __name__ == "__main__":
    mcp.run(transport="stdio")  # talk to the host over standard input/output
```



The `@mcp.tool()` decorator tells FastMCP to expose `add` to any connected model. That is a complete, working server. Now swap the toy math for a real job a model needs: reading the live web.



## Build a Web Scraping MCP Server with Scrapfly

A scraping MCP server gives a model live, unblocked web access. Instead of math, the tools fetch pages, extract structured data, and take screenshots.

Each one calls the [Scrapfly API](https://scrapfly.io/web-scraping-api) under the hood, so it handles blocking, proxies, and JavaScript rendering for you.

Start a new file named `scraper.py`. Read your Scrapfly API key from the environment and create one client to share across tools:

python```python
import os
from mcp.server.fastmcp import FastMCP, Image
from scrapfly import ScrapflyClient, ScrapeConfig, ExtractionConfig, ScreenshotConfig

mcp = FastMCP("Web Scraping Server")
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
```



The Scrapfly SDK ships for [Python](https://scrapfly.io/docs/sdk/python), TypeScript, Go, and Rust. You can build the same server in whichever language your stack prefers, though this guide stays in Python.

### A scrape\_page Tool (Web Scraping API + ASP)

This tool fetches a page and returns its HTML. Setting `asp=True` turns on Anti-Scraping Protection, which auto-detects the anti-bot vendor and applies the right bypass. Setting `render_js=True` runs the page through a real browser:

python```python
@mcp.tool()
def scrape_page(url: str) -> str:
    """Fetch a web page and return its HTML, bypassing anti-bot blocking."""
    result = client.scrape(ScrapeConfig(url=url, asp=True, render_js=True))
    return result.scrape_result["content"]
```



Calling it on a live product page returns the full rendered HTML:

python```python
scrape_page("https://web-scraping.dev/product/1")
```



text```text
content length: 38628 chars
<html lang="en"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, ...
```



The model now has the raw page. Most of the time it wants clean fields, not HTML, which is where extraction comes in.



### An extract\_data Tool (AI Extraction API)

This tool scrapes a page and then pulls structured data from it with a plain-English prompt. The [AI Extraction API](https://scrapfly.io/docs/extraction-api/getting-started) turns HTML into JSON without any selectors:

python```python
@mcp.tool()
def extract_data(url: str, prompt: str) -> dict:
    """Scrape a page, then extract structured data from it with an AI prompt."""
    page = client.scrape(ScrapeConfig(url=url, asp=True, render_js=True))
    extraction = client.extract(ExtractionConfig(
        body=page.scrape_result["content"],
        content_type="text/html",
        charset="utf-8",
        extraction_prompt=prompt,
    ))
    return extraction.extraction_result
```



Ask it for the product name, price, and description, and it returns ready-to-use JSON:

python```python
extract_data(
    "https://web-scraping.dev/product/1",
    "Extract the product name, price, and description as JSON.",
)
```



text```text
{'data': {'product_name': 'Box of Chocolate Candy', 'price': '$9.99',
          'description': "Indulge your sweet tooth with our Box of Chocolate Candy..."},
 'content_type': 'application/json'}
```



The model gets clean fields it can reason over directly. For tasks that need the visual page, add a screenshot tool.



### A screenshot Tool (Screenshot API)

This tool captures a full-page screenshot and hands it back as an image the model can view. The [Screenshot API](https://scrapfly.io/docs/screenshot-api/getting-started) renders the page through a real browser, so the capture matches what a person sees:

python```python
@mcp.tool()
def screenshot(url: str) -> Image:
    """Capture a full-page screenshot of a URL and return it as an image."""
    result = client.screenshot(ScreenshotConfig(url=url, capture="fullpage"))
    return Image(data=result.image, format="png")
```



The `Image` class wraps the raw PNG bytes Scrapfly returns, so the host renders the screenshot inline. Run the file the same way as the calculator, and the model gains three tools that read the live web.

To reach the wider AI-agent picture, see our [guide to AI agents for web scraping](https://scrapfly.io/blog/posts/ai-agent-web-scraping).



Scrapfly

#### Scale your web scraping effortlessly

Scrapfly handles proxies, browsers, and anti-bot bypass — so you can focus on data.

[Try Free →](https://scrapfly.io/register)## Connecting and Testing Your MCP Server

The fastest way to test a server is the MCP dev dashboard, a local UI for calling your tools and prompts by hand. Run it against your file:

bash```bash
mcp dev ./scraper.py
```



This launches a local dashboard in your browser. There you can view every registered tool and prompt, fill in parameters, and see results or errors in real time.

Once the tools work, connect the server to a host. To run it from the command line:

bash```bash
mcp run ./scraper.py
```



In Cursor, open `Settings` &gt; `MCP` and click `Add new global MCP server`. Cursor stores server definitions as JSON, and you can add yours manually:

json```json
{
  "mcpServers": {
    "scraper": {
      "command": "python",
      "args": ["path/to/your/scraper.py"],
      "env": { "SCRAPFLY_API_KEY": "YOUR_SCRAPFLY_KEY" }
    }
  }
}
```



The same JSON format works for Claude Desktop. Once connected, the host detects your tools automatically and lists them under your server:

You can then prompt the model in natural language, like `Scrape the product at web-scraping.dev/product/1 and tell me its price`. With the server connected, a little structure keeps the project maintainable.



## Organizing Your MCP Project and Creating Prompts

As your server grows, splitting tools into their own modules keeps it readable. A simple layout separates tools, prompts, and docs:

text```text
mcp-scraper/
├── scraper.py
├── tools/
│   └── scraping.py
├── prompts/
│   └── templates.py
└── docs/
    └── usage.txt
```



Prompts are reusable message templates you define with the `@mcp.prompt()` decorator. This one builds a scraping instruction the model can fill in:

python```python
@mcp.prompt()
def scrape_prompt(url: str, fields: str) -> str:
    """Build a scraping instruction for the model."""
    return f"Scrape {url} and extract these fields as JSON: {fields}."
```



The model invokes the prompt with arguments and gets a consistent instruction back. You can also expose data through resources.



## Exposing Resources

Resources are files or data the model can request, either static or generated on the fly. Define one with the `@mcp.resource()` decorator and a URL template:

python```python
@mcp.resource("scraper://docs/usage")
def get_usage() -> str:
    """Return usage docs for the scraping server."""
    return "Use scrape_page for HTML, extract_data for JSON, screenshot for images."
```



When the model reads `scraper://docs/usage`, the function runs and returns the text. With tools, prompts, and resources in place, look at where a scraping server pays off.



## Real-World Use Cases (Web Scraping First)

A scraping MCP server is most useful when a model needs current facts the web holds but its training data does not. Here are common cases:

- Live research: let a model read pricing, docs, or news pages and answer from what is on the page right now.
- Structured extraction: turn product, listing, or review pages into clean JSON an agent can act on.
- Visual checks: screenshot a page so the model can confirm layout, banners, or rendering.
- Monitoring agents: have a model scrape and compare a page on a schedule to flag changes.

Every one of these needs the same thing underneath: reliable, unblocked access to the page. If you would rather not run a server at all, Scrapfly hosts one for you.



## Don't Want to Build One? Use Scrapfly's Hosted MCP

If you only need scraping tools and not a custom server, Scrapfly ships a hosted MCP you can point a model at directly. It exposes the same scrape, extract, and screenshot capabilities without any code to maintain. See [Scrapfly's hosted MCP](https://scrapfly.io/products/mcp-cloud) for setup.

You can also self-host the same server from the Scrapfly CLI with `scrapfly mcp serve`. Either way, you skip the build and keep the unblocked access. For everything else, the server you built above is yours to extend.

## Power-up with Scrapfly



The scraping tools above work because [Scrapfly's Web Scraping API](https://scrapfly.io/web-scraping-api) handles blocking, proxies, and rendering for you. It is a single HTTP endpoint for collecting web data at scale, with a 99.99% success rate across 130M+ proxies in 190+ countries.

- [Anti-Scraping Protection bypass](https://scrapfly.io/docs/scrape-api/anti-scraping-protection) - automatically defeats Cloudflare, DataDome, PerimeterX, Akamai, and 90+ other bot systems.
- [Smart proxy rotation](https://scrapfly.io/docs/scrape-api/proxy) - residential and datacenter pools with country and ASN level geo-targeting.
- [JavaScript rendering](https://scrapfly.io/docs/scrape-api/javascript-rendering) - render SPAs and JavaScript-heavy pages through real cloud browsers.
- [Format conversion](https://scrapfly.io/docs/scrape-api/getting-started#api_param_format) - return pages as HTML, JSON, clean text, or LLM ready Markdown.
- [AI Extraction](https://scrapfly.io/docs/extraction-api/getting-started) - turn raw HTML into structured JSON with a prompt, no selectors required.

Wrap these in MCP tools and your model gets live web access that does not get blocked.



### Web Scraping API

Scrape any website with our powerful API. Anti-bot bypass, JavaScript rendering, and rotating proxies built-in.



[Try Web Scraping API](https://scrapfly.io/docs/scrape-api/getting-started)



## FAQ

How do I create a resource with parameters?Use the `@mcp.resource()` decorator with a parameter in the URL template, like `scraper://product/{id}`. The function runs with the supplied parameter each time the model reads the resource.







What types can MCP tools return?MCP tools can return strings, numbers, lists, dicts, and binary media through the `Image` class. The return type should match what the calling model or host expects.







Can I use async functions in my tools?Yes, FastMCP supports `async def` tools. They are useful for non-blocking work like fetching from APIs without stalling the server.







How does an MCP scraping server avoid getting blocked?Each tool calls the Scrapfly API with `asp=True`, which detects the anti-bot vendor and applies the right bypass, including proxies, fingerprints, and CAPTCHA solving. Your tool code stays simple while Scrapfly handles the blocking.







Is web scraping with an MCP server legal?Scraping public data is generally legal, but the rules depend on the data, the site's terms, and where you operate. Review the terms of any site you scrape and consult a professional for your specific case.









## Summary

This guide built an MCP server in Python with FastMCP, starting from a single calculator tool and growing into a web scraping server. You saw the core MCP pieces, tools, resources, and prompts, and how each one lets a model interact with your code.

The scraping tools, `scrape_page`, `extract_data`, and `screenshot`, wrap the Scrapfly SDK so a model gets live web access without hitting blocks. You also tested the server with the MCP dev dashboard and connected it to a host like Cursor or Claude Desktop.

You can build your own server or point a model at Scrapfly's hosted MCP. Either way, the result is the same: an LLM that can read the live web on demand.



Legal Disclaimer and PrecautionsThis tutorial covers popular web scraping techniques for education. Interacting with public servers requires diligence and respect:

- Do not scrape at rates that could damage the website.
- Do not scrape data that's not available publicly.
- Do not store PII of EU citizens protected by GDPR.
- Do not repurpose *entire* public datasets which can be illegal in some countries.

Scrapfly does not offer legal advice but these are good general rules to follow. For more you should consult a lawyer.

 

   Table of Contents















 

  Table of Contents- [Key Takeaways](#key-takeaways)
- [What is the Model Context Protocol (MCP)?](#what-is-the-model-context-protocol-mcp)
- [Understanding the Basics of MCP Communication](#understanding-the-basics-of-mcp-communication)
- [Why Use MCP Instead of Other APIs?](#why-use-mcp-instead-of-other-apis)
- [Setting Up Your Python Environment](#setting-up-your-python-environment)
- [Setting Up Your Python Environment](#setting-up-your-python-environment)
- [Creating Your First MCP Server (Calculator Hello-World)](#creating-your-first-mcp-server-calculator-hello-world)
- [Build a Web Scraping MCP Server with Scrapfly](#build-a-web-scraping-mcp-server-with-scrapfly)
- [A scrape\_page Tool (Web Scraping API + ASP)](#a-scrape-page-tool-web-scraping-api-asp)
- [An extract\_data Tool (AI Extraction API)](#an-extract-data-tool-ai-extraction-api)
- [A screenshot Tool (Screenshot API)](#a-screenshot-tool-screenshot-api)
- [Connecting and Testing Your MCP Server](#connecting-and-testing-your-mcp-server)
- [Organizing Your MCP Project and Creating Prompts](#organizing-your-mcp-project-and-creating-prompts)
- [Exposing Resources](#exposing-resources)
- [Real-World Use Cases (Web Scraping First)](#real-world-use-cases-web-scraping-first)
- [Don't Want to Build One? Use Scrapfly's Hosted MCP](#don-t-want-to-build-one-use-scrapfly-s-hosted-mcp)
- [Power-up with Scrapfly](#power-up-with-scrapfly)
- [FAQ](#faq)
- [Summary](#summary)
 
    Join the Newsletter  Get monthly web scraping insights 

 

  



Scale Your Web Scraping

Anti-bot bypass, browser rendering, and rotating proxies, all in one API. Start with 1,000 free credits.

  No credit card required  1,000 free API credits  Anti-bot bypass included 

 [Start Free](https://scrapfly.io/register) [View Docs](https://scrapfly.io/docs/onboarding) 

 Not ready? Get our newsletter instead. 

 

## Explore this Article with AI

 [ ChatGPT ](https://chat.openai.com/?q=Summarize%20this%20page%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-build-an-mcp-server) [ Gemini ](https://www.google.com/search?udm=50&aep=11&q=Summarize%20this%20page%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-build-an-mcp-server) [ Grok ](https://x.com/i/grok?text=Summarize%20this%20page%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-build-an-mcp-server) [ Perplexity ](https://www.perplexity.ai/search/new?q=Summarize%20this%20page%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-build-an-mcp-server) [ Claude ](https://claude.ai/new?q=Summarize%20this%20page%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-build-an-mcp-server) 



 ## Related Articles

 [  

 ai 

### What Is MCP? Understanding the Model Context Protocol

What is MCP? Learn how the Model Context Protocol powers tools like Copilot Studio by giving AI models access to real-ti...

 

 ](https://scrapfly.io/blog/posts/what-is-mcp-understanding-the-model-context-protocol) [     

 python api 

### How to Build a Web Scraping Agent with Gemini

Build a Gemini web scraping agent that works on real sites. Covers Gemini CLI skills, URL Context limits, Python pipelin...

 

 ](https://scrapfly.io/blog/posts/gemini-for-webscraping) [     

 python api 

### Best AI Web Scraping Tools for LLM and RAG Pipelines in 2026

A by-job ranking of the best AI web scraping tools for 2026, from prompt-based extraction to MCP servers and open-source...

 

 ](https://scrapfly.io/blog/posts/best-tools-for-ai-webscraping) 

  ## Related Questions

- [ Q How to block resources in Selenium and Python? ](https://scrapfly.io/blog/answers/how-to-block-resources-in-selenium)
- [ Q How to block resources in Playwright and Python? ](https://scrapfly.io/blog/answers/how-to-block-resources-in-playwright)
- [ Q How to block resources in Puppeteer? ](https://scrapfly.io/blog/answers/how-to-block-resources-in-puppeteer)
- [ Q What are SOCKS5 proxies and how they compare to HTTP proxies? ](https://scrapfly.io/blog/answers/what-are-socks5-proxies-in-web-scraping)
 
  



   



 Scale your web scraping effortlessly, **1,000 free credits** [Start Free](https://scrapfly.io/register)