blogs@sid.

Building your first MCP using FastMCP

By: Sid

Pre-requisites

• MCP Documentation
(https://modelcontextprotocol.io/introduction)

• FastMCP
(https://gofastmcp.com/getting-started/welcome)

• A couple of concepts on MCP’s that would help :
   ◦ Different types of transport : stdIO/SSE
   ◦ Some core functionality and what we get with the python
     package

What is FastMCP?

FastMCP is a MCP protocol which is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools.

It’s designed to be high-level and Pythonic; in most cases, decorating a function is all you need \(^ O ^)/

If you look at the official MCP documentation, you’ll see they used FastMCP as well; they contributed to the MCP documentation!

In this example which I built, I used FastMCP v2.

Different Transport Techniques

Transports in the Model Context Protocol (MCP) provide the foundation for communication between clients and servers. A transport handles the underlying mechanics of how messages are sent and received.

Standard Input/Output (stdio): The stdio transport enables communication through standard input and output streams. This is particularly useful for local integrations and command-line tools.

Server-Sent Events (SSE): SSE transport enables server-to-client streaming with HTTP POST requests for client-to-server communication.

Use SSE when:
• Only server-to-client streaming is needed
• Working with restricted networks
• Implementing simple updates

This is preferred!

MCP makes it easy to implement custom transports for specific needs. Any transport implementation just needs to conform to the Transport interface :)

What are Tools ? (think of them like POST endpoints)

Tools in FastMCP transform regular Python functions into capabilities that LLMs can invoke during conversations. When an LLM decides to use a tool:

• It sends a request with parameters based on the tool’s schema.
• FastMCP validates these parameters against your function’s signature.
• Your function executes with the validated inputs.
• The result is returned to the LLM, which can use it in its response.

FastMCP provides a mcp@tool decorator

• Uses the func name as the tool name
• The docstring of the func is the description
• Generates an input schema based on the function’s parameters and type annotations.
• Handles parameter validation and error reporting.

Return types are automatically converted for the LLM:

str → text
dict, list, Pydantic model → JSON
bytes → base64-encoded blob
None → no content

You can use async def for tools that do I/O (like network requests), so your server doesn’t get blocked.

Using Annotations in tools :

We can add in annotations : Annotations are extra metadata that help client applications (like UIs or LLMs) understand how to use or display the tool.
They do not affect the tool’s logic

How to add annotations:

Annotations

Using context in tools :

Context is a special helper you add to your tool function.

It lets your tool log messages, show progress, read files, ask the LLM for help, and know who called it.

Used by adding ctx: Context as a parameter and calling its methods.

Context

What are Resources ? (sort of like GET endpoints)

A resource is like a read-only file or data endpoint that your LLM or client can ask for by name (using a URI, like a web address).

It can be a static file, a piece of text, a chunk of JSON, or even something generated on the fly by a Python function.

Resources are not tools (which do things); resources are data (which you read).

Static resources: Predefined files or text (like a README file or a notice).
Dynamic resources: Data generated by a function when requested (like the current weather, or a database lookup).
Resource templates: Resources with parameters in the URI (like "weather://{city}/current"), so you can get different data for different requests.

How do resources work ?

• You define a resource in your Python code, using the @mcp.resource decorator.
• You give it a unique URI (like "resource://greeting" or "data://config").
• When a client (like an LLM) asks for that URI, FastMCP:

1.Finds your function or file for that resource.
2.Runs the function (if it’s dynamic) or reads the file (if it’s static).
3.Returns the data to the client.

Resources

What are Prompts?

• Prompts are like reusable message templates that you define in Python.
• They help LLMs generate consistent, structured, and purposeful responses.
• Think of a prompt as a fill-in-the-blank message that you can use over and over, just by changing the blanks (parameters).

How do prompts work ?

• You define a prompt using the @mcp.prompt() decorator on a Python function.
• You give it parameters (like topic, language, etc.) in the function signature.
•When a client (or LLM) requests that prompt and provides values for the parameters, FastMCP:
•Validates the parameters.
•Runs your function with those values.
•Returns the generated message(s) to the LLM.

Prompt

Example code that uses the following concepts :

First, try to ask your copilot what the weather is like in your city, It should say it doesn't have access to weather information or, it will suggest building an weather API application to fetch the data.

Copilot Before

Write this code and dump it in a python file

from fastmcp import FastMCP


mcp = FastMCP(
    "weather-mcp",
    description="MCP server for accessing weather documentation",
    host="0.0.0.0",
    port=8052,
    transport="sse",
)

@mcp.tool()
def get_weather(city):
    """
    This is an MCP server tool that can help fetch the weather data
    """
    if city == "San Diego":
        return "Sunny"
    elif city == "Seattle":
        return "Rainy"
    elif city == "New York":
        return "Pleasant and Cloudy"
    else:
        return "Unknown city"

@mcp.resource("resource://greeting")
def get_greeting() -> str:
    """Provides a simple greeting message."""
    return "Hello from FastMCP Resources!"

def main():
    mcp.run(transport="sse")


if __name__== "__main__":
    main()

install fastmcp using pip3 install fastmcp

run the file on terminal

python3 <file_name>.py

You should see it now using the tool and giving responses like this Copilot After Also, notice how we just returned "Rainy", but now, the MCP Client uses this returned data viz Rainy and formulates full sentences and even supports it if it matches with the existing pre-trained data.

This was how I built an MCP at work, I obviously cant open source that code :(
but y'all should try building one, super easy; super fun!