0Pricing
AI Engineering Academy · Leçon

Créer votre premier serveur MCP

Utilisez le SDK MCP Python pour créer un serveur qui expose des ressources, des outils et des invites, puis connectez-le à Claude Desktop pour vérifier son fonctionnement de bout en bout.

Créer votre premier serveur MCP est une leçon AI Engineering Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Engineering Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Engineering Academy comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

Project Setup for an MCP Server

Building an MCP server in Python requires the mcp SDK and a Python environment. You'll create a server that Claude Desktop or any MCP client can connect to via stdio. Start by installing the package and creating your server file.

# Create a project directory
# mkdir my_mcp_server && cd my_mcp_server

# Create a virtual environment
# python -m venv venv && source venv/bin/activate

# Install the MCP SDK
# pip install mcp httpx

# Project structure:
# my_mcp_server/
#   server.py          <- Your MCP server
#   requirements.txt
#   README.md

Creating the Server Object

Import from the mcp package and create a Server instance with your server's name. The name is shown to clients in their MCP server list — pick something descriptive. The server object is the entry point for registering all your tools, resources, and prompts.

# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import asyncio
import httpx

# Create the server — name is shown in Claude Desktop
app = Server('weather-server')

# --- Tool registrations go here ---

# Entry point
if __name__ == '__main__':
    asyncio.run(stdio_server(app))

Registering Tools with @app.list_tools()

The @app.list_tools() decorator registers a handler that returns the list of available tools when the client asks. Each tool is defined with a name, a description, and an inputSchema — the JSON Schema describing its parameters. The client sends this list to the LLM so it knows which tools it can call.

@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name='get_weather',
            description='Get current weather for a city. Use when the user asks about weather in a specific location.',
            inputSchema={
                'type': 'object',
                'properties': {
                    'city': {
                        'type': 'string',
                        'description': 'City name, e.g. London or New York'
                    },
                    'units': {
                        'type': 'string',
                        'enum': ['metric', 'imperial'],
                        'description': 'Temperature units. Default is metric.'
                    }
                },
                'required': ['city']
            }
        ),
        types.Tool(
            name='list_cities',
            description='Return a list of major cities the user can query weather for.',
            inputSchema={'type': 'object', 'properties': {}, 'required': []}
        )
    ]

Implementing Tool Execution

The @app.call_tool() decorator handles tool execution. When the client calls a tool, this handler receives the tool name and arguments. Execute the appropriate logic and return a list of TextContent objects containing the result string.

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == 'list_cities':
        cities = ['London', 'New York', 'Tokyo', 'Paris', 'Sydney']
        return [types.TextContent(type='text', text=', '.join(cities))]

    if name == 'get_weather':
        city = arguments['city']
        units = arguments.get('units', 'metric')
        unit_symbol = 'C' if units == 'metric' else 'F'

        # Call real weather API (stub here)
        async with httpx.AsyncClient() as client:
            # Replace with actual API call
            result = f'{city}: 18{chr(176)}{unit_symbol}, partly cloudy, humidity 65%'

        return [types.TextContent(type='text', text=result)]

    raise ValueError(f'Unknown tool: {name}')

Exposing Resources

Resources are static or dynamic data the model can read — think of them like files. Define resource URIs and implement a resource reader. Resources appear in the client as items the AI can reference, useful for configuration, documentation, or frequently-accessed data snapshots.

@app.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri='weather://supported-cities',
            name='Supported Cities List',
            description='Complete list of cities available in this weather server.',
            mimeType='text/plain'
        )
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    if uri == 'weather://supported-cities':
        cities = ['London', 'New York', 'Tokyo', 'Paris', 'Sydney', 'Dubai', 'Singapore']
        return '\n'.join(cities)
    raise ValueError(f'Unknown resource URI: {uri}')

Registering Prompt Templates

Prompts are reusable message templates that clients surface to users as slash commands or quick actions. They accept parameters and return a list of messages that become the initial context for a conversation. Prompts are great for encoding complex instructions users trigger with a single command.

@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
    return [
        types.Prompt(
            name='weather-report',
            description='Generate a formatted weather report for a city.',
            arguments=[
                types.PromptArgument(name='city', description='City name', required=True)
            ]
        )
    ]

@app.get_prompt()
async def get_prompt(name: str, arguments: dict) -> types.GetPromptResult:
    if name == 'weather-report':
        city = arguments.get('city', 'London')
        return types.GetPromptResult(
            description=f'Weather report for {city}',
            messages=[
                types.PromptMessage(
                    role='user',
                    content=types.TextContent(
                        type='text',
                        text=f'Use the get_weather tool to look up {city} and give me a detailed weather report including what clothing I should wear.'
                    )
                )
            ]
        )
    raise ValueError(f'Unknown prompt: {name}')

Connecting to Claude Desktop

To use your MCP server with Claude Desktop, add it to the Claude Desktop configuration file. On macOS, this is at ~/Library/Application Support/Claude/claude_desktop_config.json. Specify the command to start your server and any environment variables it needs.

# ~/Library/Application Support/Claude/claude_desktop_config.json
# Add this JSON configuration:

# {
#   "mcpServers": {
#     "weather-server": {
#       "command": "/path/to/venv/bin/python",
#       "args": ["/path/to/my_mcp_server/server.py"],
#       "env": {
#         "WEATHER_API_KEY": "your_api_key_here"
#       }
#     }
#   }
# }

# After saving, restart Claude Desktop.
# Your server's tools will appear in Claude's tool list.

Testing Your Server with the MCP CLI

Before connecting to Claude Desktop, test your server using the MCP inspector or CLI tools. The mcp dev command starts your server and opens a browser-based inspector where you can call tools manually and see raw protocol messages, making it easy to debug issues.

# Install the MCP development tools
# pip install 'mcp[cli]'

# Start the inspector with your server
# mcp dev server.py

# The inspector opens at http://localhost:5173
# You can:
# - See all registered tools and their schemas
# - Call tools with custom arguments
# - Browse available resources
# - Test prompt templates
# - View the full JSON-RPC message exchange

Error Handling in MCP Servers

MCP servers should never crash on bad input. Wrap all tool execution in try/except and return error messages as TextContent rather than raising exceptions. For fatal errors (configuration issues, missing API keys), log them on startup and raise before the server enters its main loop.

@app.call_tool()
async def call_tool_safe(name: str, arguments: dict) -> list[types.TextContent]:
    try:
        if name == 'get_weather':
            city = arguments.get('city')
            if not city:
                return [types.TextContent(type='text', text='Error: city argument is required.')]
            result = await fetch_weather(city, arguments.get('units', 'metric'))
            return [types.TextContent(type='text', text=result)]
        raise ValueError(f'Unknown tool: {name}')
    except httpx.TimeoutException:
        return [types.TextContent(type='text', text='Error: Weather API timed out. Try again.')]
    except Exception as e:
        return [types.TextContent(type='text', text=f'Error: {str(e)}')]

Logging for MCP Servers

Since MCP servers communicate over stdio with the client, print statements will break the protocol. Always use stderr for logging — it goes to a separate stream that doesn't interfere with the MCP message exchange. Configure Python's logging module to write to stderr.

import logging
import sys

# Configure logging to stderr (NOT stdout — that's the MCP channel)
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    stream=sys.stderr
)
logger = logging.getLogger('weather-server')

# In your tool handler:
# logger.info(f'Getting weather for {city}')
# logger.error(f'API call failed: {e}')

# Claude Desktop captures stderr to a log file:
# ~/Library/Logs/Claude/mcp-server-weather-server.log

Packaging Your MCP Server

Share your MCP server by packaging it with a pyproject.toml and publishing to PyPI, or distribute it as a Docker container for teams. Use environment variables for all secrets — API keys, database URLs — so the server configuration stays separate from the code. Document the required env vars and example config in a clear README.

Quick Check

Test your understanding of building an MCP server in Python.

Lesson Recap

In this lesson you learned: MCP servers expose tools via @app.list_tools() and @app.call_tool() decorators, resources and prompts extend the server with readable data and reusable templates, and logging must use stderr to avoid corrupting the stdio MCP protocol channel. Next up we connect an MCP server to a database and expose dynamic resources with pagination.

Questions Fréquemment Posées

La leçon « Créer votre premier serveur MCP » est-elle gratuite ?

Oui — le texte complet de « Créer votre premier serveur MCP » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Engineering Academy, passe à CoddyKit PRO. Le cours AI Engineering Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Créer votre premier serveur MCP » ?

Utilisez le SDK MCP Python pour créer un serveur qui expose des ressources, des outils et des invites, puis connectez-le à Claude Desktop pour vérifier son fonctionnement de bout en bout. Tu pratiques AI Engineering Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Engineering Academy ?

Aucune expérience préalable n'est requise. AI Engineering Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Créer votre premier serveur MCP » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Engineering Academy ?

Oui. Chaque leçon AI Engineering Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Qu’est-ce que MCP et pourquoi est-ce important
  2. Créer votre premier serveur MCP
  3. Exposer des ressources de base de données via MCP
  4. Sécurité et authentification de MCP
← Retour à AI Engineering Academy