ScreenshotNeo

BlogAI agents

How to Use FastMCP to Build an MCP Server in Python

Build a working FastMCP server in Python, run it over stdio or HTTP, inspect tools, and connect it to AI clients.

By the ScreenshotNeo team1 October 20267 min read

FastMCP lets you turn ordinary Python functions into MCP tools. You add the fastmcp package, create a FastMCP instance, decorate functions with @mcp.tool, and start the server. FastMCP derives tool schemas, validation, and documentation from your function signatures and docstrings.

This guide uses the standalone FastMCP project and its from fastmcp import FastMCP import. The MCP Python SDK also has a FastMCP class at mcp.server.fastmcp; that is a separate package context. The SDK page consulted is for its v1 maintenance line and says v2 is current stable, so verify the version-specific SDK documentation before mixing examples (FastMCP repository; MCP Python SDK v1 documentation).

1. Create a minimal FastMCP server

Install FastMCP in a project managed by uv:

uv init fastmcp-demo
cd fastmcp-demo
uv add fastmcp

Create server.py:

from fastmcp import FastMCP

mcp = FastMCP('Demo')

@mcp.tool
def add(a: int, b: int) -> int:
    '''Add two numbers.'''
    return a + b

if __name__ == '__main__':
    mcp.run()

Run it directly:

uv run python server.py

The process waits for an MCP client on standard input/output. The if __name__ == '__main__' block is useful when launching Python directly. FastMCP functions should have meaningful names, type annotations, and docstrings because those declarations become the tool interface.

2. Run the server with the FastMCP CLI

The CLI defaults to stdio, which is the usual choice for local desktop clients and command-line integrations:

fastmcp run server.py

Start a Streamable HTTP server explicitly:

fastmcp run server.py --transport http
fastmcp run server.py --transport http --host 0.0.0.0 --port 9000
Mode Command Typical use
stdio fastmcp run server.py Local MCP clients that launch your process
HTTP fastmcp run server.py --transport http A separately running service or remote client
HTTP on a chosen address --host 0.0.0.0 --port 9000 Container or network deployment

The CLI documentation lists 127.0.0.1 as the HTTP host default, 8000 as the port default, and /mcp as the default HTTP path. It also documents SSE as a selectable transport. Check the current CLI guide before relying on transport details in a production integration (Running Servers CLI documentation).

Explicit instances and factories

FastMCP can infer common variable names such as mcp, server, or app. You can select an instance explicitly:

fastmcp run server.py:my_server

A factory is useful when setup must happen at startup:

from fastmcp import FastMCP

def create_server() -> FastMCP:
    server = FastMCP('Factory demo')

    @server.tool
    def health() -> str:
        '''Return a health message.'''
        return 'ok'

    return server
fastmcp run server.py:create_server

When using fastmcp run, the CLI ignores the Python if __name__ == '__main__' block. Put required initialization in the selected instance or factory.

3. Add more useful tools

Parameters and return annotations make the generated schema clearer and allow validation before your function runs:

from fastmcp import FastMCP

mcp = FastMCP('Utilities')

@mcp.tool
def word_count(text: str) -> int:
    '''Count whitespace-separated words in text.'''
    return len(text.split())

@mcp.tool
def repeat(text: str, times: int = 2) -> str:
    '''Repeat text a bounded number of times.'''
    if times < 1 or times > 10:
        raise ValueError('times must be between 1 and 10')
    return text * times

if __name__ == '__main__':
    mcp.run()

Keep tools small and deterministic where possible. Validate limits inside the function for business rules, even when the type annotation already validates the basic input type. Raise a useful error that tells the client what to change.

Tools, resources, and prompts

FastMCP servers can expose three kinds of MCP capability:

  • Tools perform actions or calculations and are the best starting point for most servers.
  • Resources expose data that a client can read.
  • Prompts provide reusable prompt patterns.

A server does not need all three. Start with tools, then add resources or prompts when the client workflow benefits from them.

4. Inspect your server during development

Launch the browser-based Inspector workflow with:

fastmcp dev inspector server.py

The CLI documentation says auto-reload is enabled by default and that the Inspector connects over stdio. For HTTP, start the server separately:

fastmcp run server.py --transport http --port 9000

Then configure the Inspector to use the server URL exposed by your current FastMCP version. This separation matters: an Inspector process launched for stdio does not automatically test an HTTP listener.

5. Configure a repeatable project

A single file and uv add fastmcp are enough to begin. For deployment, FastMCP documents fastmcp.json and fastmcp project prepare. The prepare flow creates a prepared uv project with dependencies and a lock file, which helps make prebuilt environments reproducible. Use it when your server has several dependencies or must be built consistently in CI.

6. Add a screenshot tool with ScreenshotNeo

One practical MCP tool is a website screenshot function. ScreenshotNeo is a website screenshot API and MCP server. Its API accepts one GET request and returns a PNG, JPEG, WebP, or PDF. The example below keeps the browser work outside your MCP process and returns the captured bytes as a file path.

from pathlib import Path
import requests
from fastmcp import FastMCP

mcp = FastMCP('Web capture')

@mcp.tool
def capture_website(url: str, output: str = 'shot.webp') -> str:
    '''Capture a website with ScreenshotNeo and save the image locally.'''
    response = requests.get(
        'https://api.screenshotneo.com/v1/shot',
        params={'access_key': 'YOUR_API_KEY', 'url': url},
        timeout=90,
    )
    response.raise_for_status()
    Path(output).write_bytes(response.content)
    return f'Saved screenshot to {output}'

if __name__ == '__main__':
    mcp.run()

See the ScreenshotNeo API documentation for authentication and options. ScreenshotNeo supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets, custom viewports, retina scale, PDF output, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, custom headers and cookies, timezone and geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous jobs, bulk capture, usage reporting, and an OpenAPI specification.

7. Or skip the browser setup

Use ScreenshotNeo directly from your MCP tool or any backend. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing result. ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

cURL

curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
open('shot.webp', 'wb').write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo has 1,000 free shots each month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account.

8. Troubleshooting

Symptom Cause Fix
ModuleNotFoundError: fastmcp The package is not installed in the active environment. Run uv add fastmcp and launch with uv run, or activate the environment that contains the dependency.
CLI cannot find a server The file does not expose an inferred instance. Use fastmcp run server.py:my_server or provide a factory such as server.py:create_server.
Startup code never runs fastmcp run ignores the main guard. Move setup into module-level initialization or a factory function.
Inspector connects but HTTP client fails The Inspector was started for stdio while the server is HTTP. Run the HTTP server separately and configure the Inspector with its URL.
Tool input is rejected Input does not match the annotated type or your own validation rules. Send the expected type and bounds; improve the annotation and error message.
Screenshot response is not an image The target returned a bot check, blank page, timeout, or another failed verdict. Inspect the response status and X-Page-Verdict/X-Billed headers, then adjust waits, headers, cookies, or blocking options.

9. Performance, reliability, and cost

  • Choose the transport for the deployment. stdio avoids a network listener and suits local clients. HTTP is appropriate when a separately managed service must accept connections.
  • Keep tool calls bounded. Set timeouts on outbound requests, validate input sizes, and return concise results rather than large payloads.
  • Make side effects explicit. Tool names and docstrings should say whether a function reads data, writes data, or triggers an external action.
  • Use caching where freshness permits. ScreenshotNeo lets you choose a cache TTL. Cache hits are not billed.
  • Track screenshot verdicts. The X-Page-Verdict and X-Billed headers let an application distinguish a clean billed capture from a failed or cached response.
  • Plan concurrency around your dependencies. A FastMCP tool that calls a slow website or API will remain slow even if the MCP transport is fast; apply request timeouts and avoid unbounded parallel work.
  • Control costs by design. ScreenshotNeo bills only clean shots. The free tier includes 1,000 shots per month; paid tiers are $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, and $249 for 1,000,000.

10. FastMCP versus the SDK-bundled class

Choice Install/import context Use when
Standalone FastMCP uv add fastmcp; from fastmcp import FastMCP You want the standalone project and its CLI workflow.
SDK-bundled FastMCP from mcp.server.fastmcp import FastMCP Your project follows a specific MCP Python SDK version.

Do not combine installation commands and import paths from these contexts without checking their matching documentation and versions. The consulted SDK page is explicitly a v1 maintenance document and states that v2 is current stable.

FAQ

Do I need to define a JSON schema manually?

No. FastMCP uses the decorated Python function, annotations, and docstring to generate the tool schema, validation, and documentation.

Should I use stdio or HTTP?

Use stdio when a local MCP client launches your server. Use HTTP when the server runs independently and clients connect over the network.

Can one server expose several tools?

Yes. Decorate each function with @mcp.tool and keep each function’s inputs and behavior clearly documented.

Can FastMCP serve screenshots to an AI agent?

Yes. You can register a Python tool that calls ScreenshotNeo, or use ScreenshotNeo’s MCP server and its screenshot, page-info, and PDF tools directly.

Where should I verify CLI flags?

Use the current FastMCP running guide because transport and CLI behavior can change between releases.