Python

Building a Model Context Protocol (MCP) Server with Python

The Model Context Protocol (MCP) provides a structured way for AI models, tools, and external systems to communicate. Instead of tightly coupling applications to specific model APIs, MCP introduces a standardized interface for tools, resources, and prompts. In this article, we’ll build an MCP server using FastMCP, implement tools, resources and prompts, and test everything using the FastMCP client.

1. How MCP Works

The Model Context Protocol (MCP) is designed to standardize how AI systems interact with external capabilities. Instead of tightly coupling an AI model to specific APIs or services, MCP defines a communication layer that makes tools and structured data dynamically discoverable.

At a high level, MCP introduces three architectural roles:

  • Host: The host is the AI-powered application — for example, an IDE assistant, chat interface, or automation system. The host wants to extend its intelligence by using external tools and contextual data.
  • Client: The client acts as the communication bridge. It understands the MCP specification and connects the host to one or more MCP servers. The client handles capability discovery, invocation, and response handling.
  • Server: The server exposes capabilities to the host via the client. These functionalities are organized around three core components.

The Core Components

MCP standardizes three types of capabilities:

  • Tools: Tools are executable functions that enable AI systems to perform actions such as calling external APIs, running calculations, querying databases, and triggering workflows. They can accept input parameters and return structured responses, allowing models to interact reliably and consistently with external systems.
  • Resources: Resources provide structured, read-only data such as configuration files, knowledge base entries, system metadata, and documentation. They enable AI systems to retrieve relevant contextual information without executing any business logic, ensuring safe and consistent access to supporting data.
  • Prompts: Prompts are reusable prompt templates. They help standardize how models are instructed to behave. Instead of constructing prompts dynamically in every interaction, they can be registered and retrieved consistently.

Communication Flow

The interaction typically follows this pattern:

Host → Client → MCP Server → (Tools | Resources | Prompts)
MCP Interaction Flowchart Illustration - mcp server with python example
MCP Interaction Flowchart Illustration
  1. The host requests available capabilities.
  2. The client queries the MCP server.
  3. The server responds with metadata describing tools, resources, and prompts.
  4. The host invokes a capability through the client.
  5. The server executes logic and returns structured output.

This architecture improves modularity and scalability, allowing AI applications to evolve without tightly coupled integrations.

2. Implementing the Server with FastMCP

In this section, we will configure logging, create a FastMCP server instance, and implement tools, resources, and prompts. First, we’ll install FastMCP using pip:

pip install fastmcp

After installing FastMCP, another module required for this project is requests. The requests module is a third-party HTTP client library that allows the server to communicate with external APIs. In this project, it is used inside a tool to fetch data from a public API, demonstrating how MCP tools can integrate with external services and return structured responses.

pip install requests

Logging Configuration

We begin by configuring structured logging to ensure observability.

import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)],
)

logger = logging.getLogger("mcp-server")

This configuration ensures all logs include timestamps, severity levels, and the logger name. Logging is critical in production MCP servers because it helps trace tool invocations, API calls, and potential errors.

Initialize FastMCP Server

Next, we create the MCP server instance.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo MCP Server")

FastMCP initializes the MCP server and registers metadata such as the server name. All tools, resources, and prompts will be attached to this instance using decorators.

Tool Example 1: Random Number Generator

import random

@mcp.tool()
def generate_random_number(min_value: int, max_value: int) -> int:
    """
    Generates a random integer between min_value and max_value.
    """
    logger.info(f"Generating random number between {min_value} and {max_value}")
    return random.randint(min_value, max_value)

This tool demonstrates a simple executable capability. It accepts parameters, logs the request, performs computation, and returns a structured response. The @mcp.tool() decorator automatically exposes it to MCP clients.

Tool Example 2: Fetch Public API Data

import requests

@mcp.tool()
def fetch_joke() -> str:
    """
    Fetches a random joke from a public API.
    """
    logger.info("Fetching random joke from external API")
    response = requests.get(
        "https://official-joke-api.appspot.com/random_joke",
        timeout=5
    )
    data = response.json()
    return f"{data['setup']} - {data['punchline']}"

This tool demonstrates integration with an external system. It performs an HTTP request using requests, logs the action, parses JSON, and returns formatted output. This pattern can be extended to integrate databases, internal services, or third-party APIs.

Resource Example

import os

@mcp.resource("config://app-info")
def app_info() -> dict:
    """
    Returns application configuration details.
    """
    logger.info("Providing application configuration resource")
    return {
        "app_name": "Demo MCP Server",
        "environment": os.getenv("ENVIRONMENT", "development"),
        "version": "1.0.0"
    }

This resource exposes structured, read-only data. The URI-style identifier (config://app-info) uniquely identifies the resource. Resources are ideal for exposing configuration, metadata, or documentation.

Prompt Example

@mcp.prompt()
def greeting_prompt(name: str) -> str:
    """
    Returns a structured greeting prompt template.
    """
    logger.info(f"Generating greeting prompt for {name}")
    return f"You are a professional assistant. Greet {name} in a warm and concise manner."

Prompts allow us to register reusable instructions for AI systems. Instead of constructing prompts dynamically, they can be centrally defined and versioned.

Run Server

Finally, we start the MCP server.

if __name__ == "__main__":
    logger.info("Starting MCP server...")
    mcp.run()

mcp.run() starts the MCP server and registers all previously defined tools, resources, and prompts. When the server starts, it becomes discoverable by MCP clients.

3. Running and Testing the Server

First, start the server:

python server.py

You should see logs indicating the server has started.

[02/24/26 11:02:11] INFO     Starting MCP server...                 server.py:6

4. Testing the MCP Server with the FastMCP Client

After implementing your MCP server, the next step is validating that its tools and resources behave correctly. FastMCP provides an asynchronous client that connects to your server, discovers capabilities, and invokes them. Because the client is asynchronous, it must be used with async with inside an async function and executed using asyncio.run().

Below is a client_test.py file that demonstrates how to connect to the server and test each registered primitive.

import asyncio

from fastmcp import Client


async def test_server():
    async with Client("server.py") as client:

        # List Available Tools

        tools = await client.list_tools()
        print("Available tools:", [t.name for t in tools])

        # Call Tool: Random Number Generator

        random_result = await client.call_tool(
            "generate_random_number", {"min_value": 1, "max_value": 20}
        )

        print("\nRandom Number:", random_result.content[0].text)

        # Call Tool: Fetch Joke

        joke_result = await client.call_tool("fetch_joke", {})

        print("\nJoke:", joke_result.content[0].text)

        # List Available Resources

        resources = await client.list_resources()
        print("\nAvailable resources:", [r.uri for r in resources])

        # Read Resource

        config = await client.read_resource("config://app-info")

        print("\nApp Config:\n", config[0].text)

        # Retrieve Prompt

        prompt = await client.get_prompt("greeting_prompt", {"name": "Thomas"})

        print("\nGenerated Prompt:\n", prompt.messages[0].content.text)


asyncio.run(test_server())

The FastMCP Client connects directly to server.py, automatically starting the MCP server process if needed. Once connected, the client:

  • Discovers registered tools using list_tools()
  • Executes tools via call_tool()
  • Retrieves structured resources using read_resource()
  • Fetches prompt templates using get_prompt()

Each operation is logged, allowing us to observe the full interaction cycle. This approach ensures that our MCP server is correctly exposing and executing its capabilities before integrating it into a larger AI host application.

Running the Test

Run the test file:

python client_test.py

You should see log output confirming a successful connection to the server, a list of available tools, the results of tool executions, returned resource data, and the generated prompt.

Example Output

[02/24/26 11:44:30] INFO     Starting MCP server...                 server.py:63
[02/24/26 11:44:31] INFO     Processing request of type            server.py:720
                             ListToolsRequest                                   
Available tools: ['fetch_joke', 'generate_random_number']
                    INFO     Processing request of type            server.py:720
                             CallToolRequest                                    
                    INFO     Generating random number between 1 and server.py:38
                             20                                                 

Random Number: 1
                    INFO     Processing request of type            server.py:720
                             CallToolRequest                                    
                    INFO     Fetching random joke from external API server.py:25

Joke: What happens when you anger a brain surgeon? - They will give you a piece of your mind.
[02/24/26 11:44:32] INFO     Processing request of type            server.py:720
                             ListResourcesRequest                               

Available resources: [AnyUrl('config://app-info')]
                    INFO     Processing request of type            server.py:720
                             ReadResourceRequest                                
                    INFO     Providing application configuration    server.py:46
                             resource                                           

App Config:
 {
  "app_name": "Demo MCP Server",
  "environment": "development",
  "version": "1.0.0"
}
                    INFO     Processing request of type            server.py:720
                             GetPromptRequest                                   
                    INFO     Generating greeting prompt for Thomas  server.py:59

Generated Prompt:
 You are a professional assistant. Greet Thomas in a warm and concise manner.

This verifies that the MCP server is functioning correctly and working end-to-end.

5. Conclusion

In this article, we built a functional Model Context Protocol server using FastMCP. We explored how MCP works, including hosts, clients, servers, and the three core primitives: tools, resources, and prompts. We implemented multiple tools using decorators, exposed structured resources, registered reusable prompts, configured logging for observability, and tested everything using the FastMCP client.

With this foundation, we can now extend our MCP server with authentication, database-backed resources, streaming tools, or advanced AI integrations.

6. Download the Source Code

This article explored how to build your own MCP server using Python.

Download
You can download the full source code of this example here: how to build your own mcp server with python

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button