Budowanie pierwszego serwera MCP
Użyj Python MCP SDK, aby utworzyć serwer udostępniający zasoby, narzędzia i prompty, a następnie połącz go z Claude Desktop i sprawdź jego działanie od początku do końca.
Budowanie pierwszego serwera MCP to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Konfiguracja projektu serwera MCP
Tworzenie serwera MCP w języku Python wymaga SDK mcp i środowiska Python. Utworzą Państwo serwer, z którym Claude Desktop lub dowolny klient MCP może łączyć się za pośrednictwem stdio. Na początek należy zainstalować pakiet i utworzyć plik serwera.
# 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.mdTworzenie obiektu serwera
Należy zaimportować elementy z pakietu mcp i utworzyć instancję Server z nazwą serwera. Nazwa jest wyświetlana klientom na ich liście serwerów MCP — należy wybrać nazwę opisową. Obiekt serwera jest punktem wejścia do rejestrowania wszystkich narzędzi, zasobów i promptów.
# 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))Rejestrowanie narzędzi za pomocą @app.list_tools()
Dekorator @app.list_tools() rejestruje procedurę obsługi, która zwraca listę dostępnych narzędzi, gdy klient o nią poprosi. Każde narzędzie definiuje się za pomocą nazwy, opisu i elementu inputSchema — schematu JSON opisującego jego parametry. Klient wysyła tę listę do LLM, aby model wiedział, które narzędzia może wywoływać.
@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': []}
)
]Implementowanie wykonywania narzędzi
Dekorator @app.call_tool() obsługuje wykonywanie narzędzi. Gdy klient wywołuje narzędzie, ten moduł obsługi otrzymuje jego nazwę i argumenty. Wykonaj odpowiednią logikę i zwróć listę obiektów TextContent zawierających ciąg znaków z wynikiem.
@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}')Udostępnianie zasobów
Zasoby to statyczne lub dynamiczne dane, które model może odczytywać — można myśleć o nich jak o plikach. Zdefiniuj identyfikatory URI zasobów i zaimplementuj czytnik zasobów. Zasoby pojawiają się u klienta jako elementy, do których AI może się odwoływać; są przydatne w przypadku konfiguracji, dokumentacji lub często używanych migawek danych.
@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}')Rejestrowanie szablonów promptów
Prompty to wielokrotnego użytku szablony wiadomości, które klienci udostępniają użytkownikom jako polecenia slash lub szybkie akcje. Przyjmują parametry i zwracają listę wiadomości stających się początkowym kontekstem rozmowy. Prompty świetnie nadają się do kodowania złożonych instrukcji, które użytkownik uruchamia jednym poleceniem.
@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}')Łączenie z Claude Desktop
Aby używać serwera MCP z Claude Desktop, dodaj go do pliku konfiguracyjnego Claude Desktop. W systemie macOS znajduje się on pod adresem ~/Library/Application Support/Claude/claude_desktop_config.json. Określ polecenie uruchamiające serwer oraz wszystkie wymagane przez niego zmienne środowiskowe.
# ~/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.Testowanie serwera za pomocą MCP CLI
Przed połączeniem z Claude Desktop przetestuj serwer za pomocą inspektora MCP lub narzędzi CLI. Polecenie mcp dev uruchamia serwer i otwiera inspektora w przeglądarce, w którym można ręcznie wywoływać narzędzia oraz wyświetlać surowe komunikaty protokołu, co ułatwia debugowanie problemów.
# 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 exchangeObsługa błędów w serwerach MCP
Serwery MCP nigdy nie powinny ulegać awarii z powodu nieprawidłowych danych wejściowych. Umieść wykonywanie wszystkich narzędzi w bloku try/except i zwracaj komunikaty o błędach jako TextContent, zamiast zgłaszać wyjątki. W przypadku błędów krytycznych (problemów z konfiguracją, braku kluczy API) zapisz je w dzienniku podczas uruchamiania i zgłoś wyjątek, zanim serwer wejdzie do głównej pętli.
@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)}')]Logowanie w serwerach MCP
Ponieważ serwery MCP komunikują się z klientem za pośrednictwem stdio, instrukcje print zakłócą działanie protokołu. Do logowania zawsze używaj stderr — dane trafiają wtedy do osobnego strumienia, który nie zakłóca wymiany komunikatów MCP. Skonfiguruj moduł logowania Pythona tak, aby zapisywał dane do 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.logPakowanie serwera MCP
Udostępnij serwer MCP, pakując go z plikiem pyproject.toml i publikując w PyPI, albo rozprowadzając go wśród zespołów jako kontener Docker. Wszystkie dane uwierzytelniające — klucze API, adresy URL baz danych — przechowuj w zmiennych środowiskowych, aby konfiguracja serwera była oddzielona od kodu. W przejrzystym pliku README opisz wymagane zmienne środowiskowe i przykładową konfigurację.
Szybki test
Sprawdź, czy rozumiesz, jak budować serwer MCP w Pythonie.
Podsumowanie lekcji
W tej lekcji nauczyłeś się, że: serwery MCP udostępniają narzędzia za pomocą dekoratorów @app.list_tools() i @app.call_tool(), zasoby i prompty rozszerzają serwer o dane możliwe do odczytu oraz wielokrotnego użytku szablony, a także że logowanie musi używać stderr, aby nie uszkodzić kanału protokołu MCP korzystającego ze stdio. Następnie połączymy serwer MCP z bazą danych i udostępnimy dynamiczne zasoby z paginacją.
Często zadawane pytania
Czy lekcja „Budowanie pierwszego serwera MCP” jest bezpłatna?
Tak — pełny tekst „Budowanie pierwszego serwera MCP” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Budowanie pierwszego serwera MCP”?
Użyj Python MCP SDK, aby utworzyć serwer udostępniający zasoby, narzędzia i prompty, a następnie połącz go z Claude Desktop i sprawdź jego działanie od początku do końca. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?
Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Budowanie pierwszego serwera MCP”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?
Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Czym jest MCP i dlaczego ma znaczenie
- Budowanie pierwszego serwera MCP
- Udostępnianie zasobów bazy danych przez MCP
- Bezpieczeństwo i uwierzytelnianie MCP