MIKAEELS>_

Building Your First MCP Server – A Practical Python Walkthrough

Advertisement

Building Your First MCP Server – A Practical Python Walkthrough

In the previous articles, we explored why MCP exists and how its core concepts work. This article focuses on implementation. By the end, you will have a working mental and technical model of an MCP server and understand how it exposes context and actions in a controlled way.

The goal here is not complexity, but correctness. A small, well designed MCP server is far more valuable than a large, fragile one.

What an MCP Server Is Responsible For

An MCP server is not a model host and not an AI agent. Its responsibilities are narrow and explicit:

  • Expose resources (read only context)
  • Expose tools (controlled actions)
  • Expose prompts (instructions)
  • Enforce contracts and boundaries
  • Act as the system side of the protocol

The server does not reason. It provides facts and capabilities.

Basic MCP Server Lifecycle

At a high level, an MCP server follows this lifecycle:

  1. Initialize server with identity and metadata
  2. Register resources
  3. Register tools
  4. Register prompts
  5. Start serving requests
  6. Respond deterministically

Each step is explicit, which is one of MCP’s strengths.

Project Setup

For this example, assume:

  • Python 3.10 or newer
  • An MCP compatible server library
  • A simple local execution environment

The structure will look like this:

mcp_server/
├── server.py
├── resources.py
├── tools.py
└── prompts.py

Separating concerns early keeps the system maintainable.

Creating the MCP Server

We start by creating the server instance.

from mcp.server import Server server = Server( name="example-mcp-server", description="A simple MCP server for demonstration" )

This identity matters in multi server environments where models may connect to several MCP servers.

Registering a Resource

Resources expose read only contextual data.

@server.resource( name="application_config", description="Application configuration settings" ) def get_application_config(): return { "environment": "production", "region": "eu-west-1", "feature_flags": { "beta_dashboard": False } }

Key points:

  • The model cannot modify this data
  • The structure is explicit
  • Ownership remains with the system

If configuration changes, the server changes. The model does not guess.

Registering a Tool

Tools expose actions that the model may request.

@server.tool( name="create_support_ticket", description="Create a customer support ticket", input_schema={ "type": "object", "properties": { "priority": {"type": "string"}, "summary": {"type": "string"} }, "required": ["priority", "summary"] } ) def create_support_ticket(priority: str, summary: str): ticket_id = "TCK-12345" return { "status": "created", "ticket_id": ticket_id }

Important design rules:

  • Inputs are validated
  • Execution happens outside the model
  • Results are structured
  • Side effects are controlled

This is how MCP enables safe action execution.

Registering a Prompt

Prompts define how the model should behave.

@server.prompt( name="support_agent_prompt", description="Guidelines for customer support behavior" ) def support_agent_prompt(): return ( "You are a professional customer support agent. " "Respond clearly, factually, and politely. " "Do not speculate. Ask for clarification when information is missing." )

This avoids duplicating instruction text across multiple clients or models.

Starting the Server

Finally, start the server:

if __name__ == "__main__": server.run()

At this point:

  • Resources are discoverable
  • Tools are callable
  • Prompts are reusable
  • The model interface is clean

How a Model Interacts with This Server

From the model’s perspective:

  1. Discover available resources
  2. Read necessary context
  3. Follow provided prompts
  4. Invoke tools when action is required

The model never:

  • Writes configuration
  • Invents identifiers
  • Executes logic directly

This keeps reasoning and execution clearly separated.

Testing and Validation

Before connecting a real model, validate:

  • Resource outputs are correct
  • Tool inputs are strictly validated
  • Tool side effects are safe
  • Prompts are concise and unambiguous

Treat MCP servers like APIs. They deserve the same rigor.

Common Implementation Mistakes

Early MCP servers often fail due to:

  • Returning unstructured text instead of data
  • Mixing business logic into prompts
  • Over exposing tools
  • Allowing tools to perform multiple responsibilities
  • Treating MCP as an agent framework

MCP works best when it stays boring and predictable.

Conclusion

Building an MCP server is intentionally straightforward. The value comes from discipline, not complexity.

By clearly separating resources, tools, and prompts, MCP servers become reliable integration points between models and real systems.

In the next article, we will focus on the MCP client side, explaining how models discover context, invoke tools safely, and handle failures without breaking production systems.

Once both sides are understood, MCP becomes a powerful architectural primitive rather than an experimental feature.

Advertisement

Comments

Share your thoughts and questions below