Jak napisać własny serwer MCP do zbierania danych ze stron: przewodnik krok po kroku dla początkujących
Spis treści
- Wprowadzenie: co otrzymasz po ukończeniu tego przewodnika
- Przygotowanie wstępne: narzędzia, dostępy i wymagania systemowe
- Podstawowe pojęcia: jak zbudowany jest serwer mcp i po co jest agentowi ai
- Krok 1: tworzymy projekt i instalujemy zależności
- Krok 2: piszemy minimalny serwer mcp z pierwszym narzędziem
- Krok 3: podłączamy serwer mcp do klienta ai
- Krok 4: dodajemy narzędzia wyodrębniania danych
- Krok 5: podłączamy mobilne proxy i rotację ip
- Krok 6: czynimy serwer niezawodnym: powtórzenia, opóźnienia, cache i limity
- Weryfikacja wyniku: lista kontrolna gotowego serwera mcp
- Typowe błędy przy tworzeniu serwera mcp i ich rozwiązania
- Dodatkowe możliwości: blok dla zaawansowanych
- Faq: częste pytania o tworzenie serwera mcp
- Podsumowanie: co zrobiłeś i dokąd iść dalej
Wprowadzenie: co otrzymasz po ukończeniu tego przewodnika
Wyobraź sobie, że otwierasz czat z asystentem AI i piszesz: „Wejdź na stronę konkurenta, zbierz nazwy i ceny wszystkich produktów z katalogu i zestaw je w tabeli”. Asystent nie odpowiada „nie mam dostępu do internetu”, tylko naprawdę pobiera stronę, wyciąga dane i zwraca Ci gotowy wynik. Właśnie to zbudujesz, przechodząc przez ten przewodnik do końca. Łącznikiem między modelem językowym a siecią będzie Twój własny serwer MCP, napisany w Pythonie.
Ważne zastrzeżenie: nie będziemy omawiać gotowego Playwright MCP ani innych rozwiązań „z pudełka”. Na ich temat są osobne materiały na blogu. Tutaj zadanie jest inne: napisać serwer od zera, żebyś rozumiał każdą linijkę, mógł dodawać własne narzędzia, podłączać mobilne proxy i dopasowywać logikę do konkretnych zadań. Własne rozwiązanie jest zawsze bardziej elastyczne niż cudze.
Dla kogo jest ten przewodnik
- Marketerzy i właściciele firm, którzy muszą szybko zbierać ceny, opinie, opisy produktów i treści konkurencji, nie zamawiając scrapera u programisty.
- Specjaliści od arbitrażu, którzy monitorują oferty, landing page’e i kreacje i chcą delegować rutynę agentowi AI.
- Programiści, którzy słyszeli o protokole MCP, ale jeszcze nie zbudowali własnego serwera i chcą gotowy szablon.
- Użytkownicy mobilnych proxy, którym zależy, żeby zapytania agenta wychodziły przez ich proxy, a nie bezpośrednio z domowego IP.
Co trzeba wiedzieć wcześniej
Przewodnik jest przeznaczony dla początkujących. Doświadczenie w programowaniu nie jest wymagane, ale przyda się rozumienie, co to jest wiersz poleceń i jak otworzyć plik w edytorze tekstu. Cały kod można kopiować w całości, a każda jego część jest wyjaśniona prostym językiem. Jeśli już piszesz w Pythonie, dla Ciebie jest osobny blok z zaawansowanymi możliwościami bliżej końca artykułu.
Ile czasu to zajmie
Zaplanuj 2-3 godziny na pierwsze przejście. Instalacja narzędzi zajmie około 30 minut, minimalny działający serwer MCP pojawi się po godzinie, a pozostały czas pójdzie na dodanie narzędzi wyodrębniania danych, podłączenie proxy i testowanie. Powtórzenie wszystkiego od zera na innym komputerze zajmie Ci już 20-30 minut.
Przygotowanie wstępne: narzędzia, dostępy i wymagania systemowe
Zanim zaczniesz pisać kod, upewnij się, że masz wszystko, co potrzebne. Ten rozdział można przejść w pół godziny i pozwoli Ci uniknąć połowy typowych problemów na kolejnych etapach.
Wymagania systemowe
- Komputer z Windows 10/11, macOS 12 lub nowszym albo Linux (Ubuntu 22.04 i nowszy). Wszystko, co opisano, działa na każdym z tych systemów, różnice są tylko w ścieżkach do plików.
- Minimum 4 GB pamięci RAM i 1 GB wolnego miejsca na dysku.
- Stabilny dostęp do internetu.
Co zainstalować
- Python 3.11 lub nowszy. W 2026 roku aktualne są wersje 3.12 i 3.13. Pobierz instalator z oficjalnej strony projektu Python. Na Windows w pierwszym oknie instalatora koniecznie zaznacz pole Add python.exe to PATH, inaczej polecenie python nie będzie znajdowane w terminalu. Na macOS wygodniej zainstalować Python przez Homebrew poleceniem brew install python. Na Ubuntu wykonaj sudo apt install python3 python3-venv python3-pip.
- Edytor tekstu do kodu. Polecamy Visual Studio Code. Jest darmowy, podświetla składnię i pokazuje błędy. Nadaje się też każdy inny edytor, nawet Notatnik, ale z VS Code będzie Ci wygodniej.
- Klient MCP, czyli aplikacja z agentem AI, do której podłączysz serwer. Najprostszy wariant dla początkujących: Claude Desktop. MCP obsługują też edytor Cursor, VS Code z rozszerzeniem GitHub Copilot i szereg innych narzędzi. Zainstaluj przynajmniej jedno z nich przed rozpoczęciem pracy.
- Node.js 20 lub nowszy. Jest potrzebny nie do samego serwera, a do narzędzia MCP Inspector, za pomocą którego będziemy debugować narzędzia. Pobierz instalator wersji LTS z oficjalnej strony Node.js i zainstaluj z domyślnymi ustawieniami.
Dostępy
Do rozdziału o proxy potrzebne będą dane Twojego mobilnego proxy: host, port, login i hasło, a także link do zmiany adresu IP, jeśli Twój plan go obsługuje. Wszystko to znajdziesz w panelu klienta u dostawcy. Jeśli nie masz jeszcze proxy, przewodnik można przejść również bez niego: serwer będzie działał bezpośrednio, a proxy dodasz później jedną linijką.
Kopie zapasowe
Będziemy edytować plik konfiguracyjny klienta MCP. Przed tym skopiuj go w bezpieczne miejsce, na przykład na pulpit z dopiskiem „backup”. Jeśli coś pójdzie nie tak, po prostu przywrócisz kopię na miejsce. Sam kod serwera trzymaj w osobnym folderze i po każdym działającym kroku zapisuj kopię pliku lub rób commit w Git, jeśli umiesz go używać.
Wskazówka: Utwórz na dysku osobny folder z krótką ścieżką bez spacji i znaków diakrytycznych, na przykład C:/mcp-collector na Windows albo ~/mcp-collector na macOS i Linux. Spacje i polskie znaki w ścieżkach regularnie psują uruchamianie serwerów z konfigów, a Ty spędzisz godzinę na szukaniu przyczyny.
Podstawowe pojęcia: jak zbudowany jest serwer MCP i po co jest agentowi AI
Zanim napiszesz pierwszą linijkę kodu, zapoznajmy się z terminami. Bez tego instrukcja będzie wyglądać jak zestaw magicznych zaklęć, a z nimi każde działanie stanie się logiczne.
Czym jest MCP
MCP (Model Context Protocol) — otwarty protokół, który opisuje, jak model językowy komunikuje się z zewnętrznymi narzędziami. Przed jego pojawieniem się każda usługa wymyślała własny sposób, żeby „dać AI ręce”. MCP to ustandaryzował: jeśli napisałeś serwer zgodnie z protokołem, zrozumie go każdy kompatybilny klient, czy to Claude Desktop, Cursor, czy Twój własny agent. Można porównać MCP do złącza USB: nie ma znaczenia, co podłączasz, pendrive’a czy myszkę, złącze jest to samo.
Klient i serwer
W architekturze MCP są dwaj uczestnicy. Klient — to aplikacja z AI, która zadaje pytania i wywołuje narzędzia. Serwer MCP — to program, który te narzędzia udostępnia. W naszym przypadku serwer będzie umiejętnością „wejścia do internetu i wyciągnięcia danych”, a klientem będzie Twój asystent AI. Serwer uruchamia się lokalnie na Twoim komputerze, a klient komunikuje się z nim bezpośrednio.
Narzędzia, zasoby i prompty
Serwer MCP może przekazywać klientowi trzy typy bytów:
- Narzędzia (tools) — funkcje, które model może wywołać: „pobierz stronę”, „wyciągnij wszystkie linki”, „zmień IP proxy”. To podstawa naszego przewodnika.
- Zasoby (resources) — dane, które serwer udostępnia do czytania, na przykład zawartość pliku z ustawieniami albo wynik ostatniego zbierania.
- Prompty (prompts) — gotowe szablony zapytań, które użytkownik może wywołać jednym poleceniem.
Do zbierania danych wystarczą narzędzia. Zasoby i prompty omówimy w bloku zaawansowanym.
Jak model rozumie, co wywołać
Jest tu ważny niuans. Gdy klient łączy się z serwerem, pobiera listę narzędzi wraz z ich nazwami, opisami i parametrami. Te opisy trafiają do kontekstu modelu. Dalej model sam decyduje, które narzędzie wywołać i z jakimi argumentami, opierając się właśnie na tekście opisu. Dlatego opisy funkcji w naszym kodzie to nie formalność, a instrukcja dla AI. Im jaśniej napiszesz, co robi narzędzie i kiedy go użyć, tym precyzyjniej będzie działał agent.
Transport: stdio i HTTP
Serwer i klient muszą jakoś wymieniać komunikaty. Protokół przewiduje dwa główne sposoby. stdio — klient sam uruchamia Twój skrypt jako proces podrzędny i komunikuje się z nim przez standardowe wejście i wyjście. To najprostszy wariant do pracy lokalnej i od niego zaczniemy. Streamable HTTP — serwer działa jako usługa internetowa, do której klient łączy się pod adresem. Ten wariant jest potrzebny, jeśli serwer żyje na zdalnej maszynie albo łączy się z nim kilku klientów. Omówimy go w bloku zaawansowanym.
⚠️ Uwaga: Przy transporcie stdio całe standardowe wyjście procesu jest zajęte przez komunikaty protokołu. Jeśli w kodzie napiszesz zwykły print do debugowania, klient otrzyma śmieci zamiast poprawnej odpowiedzi i zerwie połączenie. Komunikaty debugowania można wypisywać tylko do strumienia błędów stderr. Zapamiętaj tę zasadę, oszczędzi Ci mnóstwo czasu.
Dlaczego zbieranie danych przez MCP jest wygodne
Klasyczny scraper jest sztywno zapisany: umie zbierać konkretne pola z konkretnej strony. Gdy tylko zmieni się layout, scraper się psuje. Połączenie „agent AI plus serwer MCP” działa inaczej: serwer daje uniwersalne narzędzia (pobierz, wyodrębnij tekst, znajdź elementy po selektorze), a model sam rozgryza strukturę strony i formułuje wynik. Zyskujesz elastyczność bez przepisywania kodu pod każde nowe źródło.
Krok 1: Tworzymy projekt i instalujemy zależności
Cel etapu: przygotować izolowane środowisko Pythona i zainstalować biblioteki, które są potrzebne do serwera MCP. Pod koniec kroku będziesz mieć folder projektu z działającym środowiskiem wirtualnym.
Po co środowisko wirtualne
Środowisko wirtualne to osobna kopia Pythona z własnymi bibliotekami w folderze projektu. Jest potrzebne, żeby nasz serwer nie kolidował z innymi programami w Pythonie na komputerze, a klient MCP dokładnie wiedział, który interpreter uruchomić. Bez tego połowa problemów „u mnie w terminalu działa, a w kliencie nie” jest gwarantowana.
Instrukcja krok po kroku
- Otwórz terminal. Na Windows naciśnij Win+R, wpisz powershell i naciśnij Enter. Na macOS otwórz aplikację Terminal przez Spotlight (Cmd+Spacja, następnie wpisz Terminal). Na Linux naciśnij Ctrl+Alt+T.
- Utwórz folder projektu i przejdź do niego. Na Windows wykonaj dwa polecenia: mkdir C:/mcp-collector, następnie cd C:/mcp-collector. Na macOS i Linux: mkdir ~/mcp-collector, następnie cd ~/mcp-collector.
- Sprawdź wersję Pythona poleceniem python --version (na macOS i Linux może być potrzebne python3 --version). Powinieneś zobaczyć wiersz w postaci Python 3.12.x. Jeśli wersja jest niższa niż 3.11 albo polecenie nie zostało znalezione, wróć do rozdziału przygotowania i zainstaluj ponownie Pythona.
- Utwórz środowisko wirtualne poleceniem python -m venv .venv. W folderze projektu pojawi się ukryty folder .venv. Zajmie to 10-20 sekund.
- Aktywuj środowisko. Na Windows w PowerShell: .venv/Scripts/Activate.ps1. Jeśli PowerShell pisze, że wykonywanie skryptów jest zabronione, wykonaj polecenie Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, potwierdź literą Y i powtórz aktywację. Na macOS i Linux: source .venv/bin/activate. Po aktywacji na początku wiersza terminala pojawi się znacznik (.venv).
- Zaktualizuj menedżer pakietów: python -m pip install --upgrade pip.
- Zainstaluj biblioteki jednym poleceniem: pip install "mcp[cli]" httpx beautifulsoup4. Tutaj mcp to oficjalne SDK protokołu dla Pythona (w 2026 roku aktualna jest gałąź 1.x), httpx to nowoczesna biblioteka do zapytań HTTP z obsługą proxy, beautifulsoup4 to narzędzie do parsowania HTML. Instalacja zajmie 1-2 minuty.
- Utwórz pusty plik server.py w folderze projektu. W VS Code: otwórz folder przez File, Open Folder, następnie naciśnij ikonę nowego pliku na panelu po lewej i wpisz nazwę.
Co oznaczają biblioteki
- mcp bierze na siebie cały protokół: rejestrację narzędzi, wymianę komunikatów, opis parametrów. Moduł FastMCP w nim pozwala zadeklarować narzędzie jako zwykłą funkcję z dekoratorem.
- httpx pobiera strony. W odróżnieniu od przestarzałego requests obsługuje HTTP/2, asynchroniczność i wygodne ustawianie proxy.
- beautifulsoup4 zamienia HTML w drzewo, po którym łatwo szukać elementów po tagach i selektorach CSS.
Wskazówka: Od razu zapamiętaj pełną ścieżkę do interpretera w środowisku wirtualnym. Na Windows to C:/mcp-collector/.venv/Scripts/python.exe, na macOS i Linux — /Users/imię/mcp-collector/.venv/bin/python (albo /home/imię/... na Linux). Przyda się przy podłączaniu do klienta. Dokładną ścieżkę można poznać poleceniem where python na Windows albo which python na macOS i Linux przy aktywowanym środowisku.
✅ Weryfikacja: Wykonaj polecenie pip list. Na liście powinny być pakiety mcp, httpx i beautifulsoup4. Wykonaj też python -c "import mcp, httpx, bs4; print('ok')" — w odpowiedzi powinno pojawić się słowo ok bez błędów.
Możliwe problemy
- Polecenie python nie zostało znalezione. Na Windows zainstaluj ponownie Pythona z zaznaczonym polem Add to PATH. Na macOS użyj python3 zamiast python.
- pip zgłasza brak uprawnień. Najprawdopodobniej środowisko nie jest aktywowane i instalujesz pakiety do systemowego Pythona. Sprawdź znacznik (.venv) na początku wiersza.
- Błąd kompilacji przy instalacji. Zaktualizuj pip i spróbuj ponownie. Jeśli nie pomaga, sprawdź, czy wersja Pythona nie jest niższa niż 3.11.
Krok 2: Piszemy minimalny serwer MCP z pierwszym narzędziem
Cel etapu: napisać działający serwer MCP z jednym narzędziem, które pobiera stronę pod adresem i zwraca jej HTML. To fundament, na którym będziemy rozbudowywać funkcje.
Kod serwera
Otwórz plik server.py i wklej do niego następujący kod w całości:
import sys
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP('web-collector')
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8',
}
def log(message: str) -> None:
print(message, file=sys.stderr)
@mcp.tool()
def fetch_page(url: str, max_chars: int = 20000) -> str:
'''Скачивает страницу по указанному URL и возвращает её HTML-код.
Используй, когда нужно посмотреть исходную разметку страницы.
Параметр max_chars ограничивает длину ответа, чтобы не переполнять контекст.'''
log(f'fetch_page: {url}')
with httpx.Client(headers=HEADERS, timeout=20.0, follow_redirects=True) as client:
response = client.get(url)
response.raise_for_status()
return response.text[:max_chars]
if __name__ == '__main__':
mcp.run()Omówienie kodu linia po linii
- FastMCP('web-collector') tworzy obiekt serwera o nazwie web-collector. Tę nazwę klient pokaże na liście podłączonych serwerów.
- HEADERS — nagłówki, które wysyłamy do stron. Wiele stron zwraca niepełną treść albo błąd, jeśli zapytanie przychodzi bez zwykłego przeglądarkowego User-Agent. Nagłówek Accept-Language podpowiada, że potrzebujemy wersji strony w danym języku.
- Funkcja log pisze komunikaty do stderr. Właśnie tak, a nie przez zwykły print, bo stdout jest zajęty przez protokół. Te komunikaty zobaczysz w logach klienta i w MCP Inspector.
- @mcp.tool() — dekorator, który zamienia zwykłą funkcję w narzędzie MCP. SDK automatycznie czyta nazwę funkcji, typy parametrów i docstring i tworzy opis dla modelu. Wartość domyślna max_chars = 20000 oznacza, że parametr jest opcjonalny.
- Docstring w potrójnych cudzysłowach — to przeczyta AI. Tutaj wyjaśniamy, co robi narzędzie i kiedy go użyć. Pisz takie opisy szczegółowo i w tym języku, w którym komunikujesz się z agentem.
- httpx.Client z parametrem follow_redirects=True automatycznie przechodzi przez przekierowania, a timeout=20.0 nie pozwala zapytaniu wisieć w nieskończoność.
- raise_for_status() wyrzuca błąd, jeśli strona zwróciła kod 4xx lub 5xx. SDK przechwyci go i zwróci klientowi zrozumiały komunikat o błędzie zamiast ciszy.
- mcp.run() uruchamia serwer z transportem stdio domyślnie. Będzie czekał na polecenia od klienta.
Pierwsze sprawdzenie przez MCP Inspector
Uruchamianie server.py bezpośrednio jest bezcelowe: będzie czekał na komunikaty od klienta i nic nie pokaże. Do sprawdzenia użyjemy MCP Inspector — interfejsu internetowego, który imituje klienta i pozwala wywoływać narzędzia ręcznie.
- Upewnij się, że środowisko wirtualne jest aktywowane i jesteś w folderze projektu.
- Wykonaj polecenie mcp dev server.py. To polecenie wchodzi w skład zainstalowanego pakietu mcp z rozszerzeniem cli. Przy pierwszym uruchomieniu pobierze Inspector przez npx, zajmie to około minuty.
- W terminalu pojawi się adres w postaci http://localhost:6274 i, w nowszych wersjach, token dostępu. Otwórz ten adres w przeglądarce (często otwiera się sam).
- W lewym panelu Inspector sprawdź, że wybrany jest transport STDIO, komenda — python, argumenty — server.py. Naciśnij przycisk Connect.
- Wskaźnik stanu zmieni się na zielony z podpisem Connected. Przejdź do zakładki Tools w górnym menu i naciśnij List Tools.
- Na liście pojawi się narzędzie fetch_page z opisem z docstringa i dwoma parametrami. Kliknij na nie.
- W polu url wpisz https://example.com, pole max_chars pozostaw puste lub wpisz 5000. Naciśnij Run Tool.
- Po prawej pojawi się wynik: kod HTML strony, zaczynający się od tagu doctype. Na dole, w zakładce z logami serwera, zobaczysz wiersz fetch_page: https://example.com.
✅ Weryfikacja: Inspector pokazuje status Connected, na liście Tools jest fetch_page, wywołanie z adresem example.com zwraca HTML bez błędów. Jeśli tak jest, Twój pierwszy serwer MCP działa.
Możliwe problemy
- mcp dev pisze, że npx nie został znaleziony. Node.js nie jest zainstalowany. Zainstaluj go i zrestartuj terminal.
- Inspector się otworzył, ale Connect zwraca błąd. Sprawdź, czy w polu komendy jest podany python z aktywowanego środowiska. Można wpisać pełną ścieżkę do python.exe w .venv.
- Błąd SyntaxError przy łączeniu. Kod został skopiowany z utratą wcięć. W Pythonie wcięcia są obowiązkowe: treść funkcji przesuwa się o cztery spacje. Sprawdź plik w edytorze.
- Narzędzie zwraca błąd 403. Strona nie przyjęła zapytania. Dla example.com tak nie będzie, a do prawdziwych stron wrócimy w kroku o proxy.
Krok 3: Podłączamy serwer MCP do klienta AI
Cel etapu: zarejestrować serwer w ustawieniach klienta AI, żeby agent widział Twoje narzędzie i mógł wywoływać je ze zwykłego czatu. Omówimy podłączenie do Claude Desktop jako najpopularniejszy wariant i krótko pokażemy alternatywy.
Podłączenie do Claude Desktop
- Otwórz Claude Desktop. Wejdź w ustawienia: na Windows przez menu w lewym górnym rogu, pozycja Settings; na macOS przez menu Claude, pozycja Settings.
- Przejdź do zakładki Developer i naciśnij przycisk Edit Config. Otworzy się folder z plikiem claude_desktop_config.json. Jeśli pliku nie ma, klient go utworzy.
- Zrób kopię zapasową tego pliku, kopiując go na pulpit.
- Otwórz plik w VS Code lub innym edytorze. Jeśli plik jest pusty, wklej zawartość w całości. Jeśli są w nim już inne serwery, dodaj swój blok do obiektu mcpServers po przecinku.
{
"mcpServers": {
"web-collector": {
"command": "C:/mcp-collector/.venv/Scripts/python.exe",
"args": ["C:/mcp-collector/server.py"]
}
}
}Na macOS i Linux zastąp ścieżki swoimi, na przykład /Users/ivan/mcp-collector/.venv/bin/python i /Users/ivan/mcp-collector/server.py. Zwróć uwagę: nawet na Windows ścieżki są zapisane z ukośnikami prostymi. Tak jest prościej, bo ukośniki odwrotne w JSON trzeba podwajać, a ukośniki proste Windows rozumie bez problemu.
- Zapisz plik. Upewnij się, że nie ma w nim zbędnych przecinków po ostatnim elemencie i że wszystkie nawiasy są zamknięte. Jeden zbędny przecinek czyni JSON nieprawidłowym, a klient po cichu zignoruje konfig.
- Całkowicie zamknij Claude Desktop i uruchom ponownie. Na Windows nie wystarczy zamknąć okna: kliknij prawym przyciskiem myszy ikonę w zasobniku systemowym i wybierz Quit. Klient czyta konfig tylko przy starcie.
- Po uruchomieniu otwórz nowy czat. Pod polem wpisywania znajdź ikonę narzędzi (znaczek suwaków lub złącza). Kliknij na nią: na liście powinien być serwer web-collector z jednym narzędziem fetch_page.
- Napisz na czacie: „Pobierz stronę https://example.com za pomocą fetch_page i powiedz, jaki jest tytuł tej strony”. Klient poprosi o zgodę na wywołanie narzędzia. Naciśnij Allow lub Allow for this chat.
- Po kilku sekundach agent odpowie, że tytuł strony to Example Domain. Wykonał prawdziwe zapytanie przez Twój serwer.
Podłączenie do Cursor i VS Code
W Cursor otwórz Settings, sekcja MCP, naciśnij Add new global MCP server. Otworzy się plik mcp.json o dokładnie takiej samej strukturze jak u Claude Desktop. Wklej ten sam blok i zapisz. W VS Code z Copilot utwórz w katalogu głównym folderu roboczego plik .vscode/mcp.json, gdzie zamiast klucza mcpServers jest klucz servers, a wewnątrz — te same command i args. Po zapisaniu nad blokiem serwera pojawi się przycisk Start. We wszystkich klientach zasada jest taka sama: podać komendę uruchomienia interpretera i ścieżkę do skryptu.
Wskazówka: W polu command podawaj python ze środowiska wirtualnego, a nie samo słowo python. Klient uruchamia proces ze swoim zestawem zmiennych środowiskowych i systemowe polecenie python może okazać się inną wersją bez zainstalowanych bibliotek. Pełna ścieżka rozwiązuje ten problem raz na zawsze.
✅ Weryfikacja: W interfejsie klienta widoczny jest serwer web-collector, agent na żądanie wywołuje fetch_page i poprawnie streszcza zawartość strony example.com. W logach klienta (w Claude Desktop to folder logs obok konfigu, plik mcp-server-web-collector.log) widoczny jest wiersz fetch_page: https://example.com.
Możliwe problemy
- Serwer nie pojawił się na liście. Sprawdź JSON pod kątem poprawności: wklej zawartość do dowolnego walidatora JSON online albo otwórz w VS Code, podkreśli błędy. Upewnij się, że klient został całkowicie zrestartowany.
- Obok serwera czerwony wskaźnik błędu. Otwórz plik logów. Najczęściej jest tam ModuleNotFoundError: podano nie ten python. Sprawdź ścieżkę w command.
- Agent mówi, że nie może uzyskać dostępu do internetu. Nie zobaczył narzędzia. Upewnij się, że narzędzia są włączone w panelu suwaków, i poproś wprost: „użyj narzędzia fetch_page”.
- Błąd spawn ENOENT. Ścieżka do python lub server.py jest błędna. Skopiuj ścieżkę z eksploratora i zastąp ukośniki odwrotne prostymi.
Krok 4: Dodajemy narzędzia wyodrębniania danych
Cel etapu: nauczyć serwer zwracać nie surowy HTML, a przydatne dane: czysty tekst, listę linków i elementy po selektorze CSS. Po tym agent będzie mógł zbierać ustrukturyzowane informacje, nie zużywając kontekstu na znaczniki.
Dlaczego sam fetch_page to za mało
HTML prawdziwej strony waży setki kilobajtów, a większość to skrypty, style i znaczniki pomocnicze. Jeśli za każdym razem podamy modelowi wszystko pod rząd, szybko trafi w limit kontekstu, a Ty zapłacisz za zbędne tokeny. Właściwa strategia: serwer robi wstępne czyszczenie i strukturyzację, a model pracuje już na kompaktowych danych. Dlatego dodamy trzy wyspecjalizowane narzędzia.
Zaktualizowany kod
Zastąp zawartość server.py rozszerzoną wersją. Funkcja fetch_page została, ale wspólna logika pobierania jest wyniesiona do osobnej funkcji _get_html, której używają wszystkie narzędzia.
import sys
from urllib.parse import urljoin
import httpx
from bs4 import BeautifulSoup
from mcp.server.fastmcp import FastMCP
mcp = FastMCP('web-collector')
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8',
}
def log(message: str) -> None:
print(message, file=sys.stderr)
def _get_html(url: str) -> str:
log(f'GET {url}')
with httpx.Client(headers=HEADERS, timeout=20.0, follow_redirects=True) as client:
response = client.get(url)
response.raise_for_status()
return response.text
def _clean(text: str) -> str:
return ' '.join(text.split())
@mcp.tool()
def fetch_page(url: str, max_chars: int = 20000) -> str:
'''Возвращает сырой HTML страницы. Используй только когда нужна именно разметка,
например чтобы подобрать CSS-селектор. Для чтения содержимого используй extract_text.'''
return _get_html(url)[:max_chars]
@mcp.tool()
def extract_text(url: str, max_chars: int = 15000) -> str:
'''Возвращает чистый текст страницы без скриптов, стилей и разметки.
Лучший выбор, когда нужно прочитать статью, описание товара или отзывы.'''
soup = BeautifulSoup(_get_html(url), 'html.parser')
for tag in soup(['script', 'style', 'noscript', 'svg', 'header', 'footer', 'nav']):
tag.decompose()
title = _clean(soup.title.get_text()) if soup.title else ''
body = _clean(soup.get_text(' '))
return f'Заголовок: {title}. Текст: {body}'[:max_chars]
@mcp.tool()
def extract_links(url: str, limit: int = 100, contains: str = '') -> list[dict]:
'''Возвращает список ссылок со страницы: текст ссылки и полный адр��с.
Параметр contains фильтрует ссылки, в адресе которых есть указанная подстрока,
например /product/ или /catalog/.'''
soup = BeautifulSoup(_get_html(url), 'html.parser')
result = []
seen = set()
for a in soup.find_all('a', href=True):
full = urljoin(url, a['href'])
if full in seen or (contains and contains not in full):
continue
seen.add(full)
result.append({'text': _clean(a.get_text())[:120], 'url': full})
if len(result) >= limit:
break
return result
@mcp.tool()
def select_elements(url: str, css_selector: str, limit: int = 50) -> list[str]:
'''Находит на странице элементы по CSS-селектору и возвращает их текст.
Примеры селекторов: h2, .price, div.product-card, table tr.
Используй, когда нужны конкретные повторяющиеся блоки: цены, названия, строки таблицы.'''
soup = BeautifulSoup(_get_html(url), 'html.parser')
elements = soup.select(css_selector)[:limit]
return [_clean(el.get_text(' ')) for el in elements]
if __name__ == '__main__':
mcp.run()Co robi każde narzędzie
- extract_text usuwa z dokumentu skrypty, style, nagłówek, stopkę i menu, a pozostały tekst skleja w jeden wiersz z pojedynczymi spacjami. Funkcja _clean przez split i join usuwa zbędne przejścia i tabulatory. Na początek odpowiedzi dodawany jest tytuł strony, żeby agent od razu rozumiał, co ma przed sobą.
- extract_links zbiera wszystkie tagi a, zamienia adresy względne na bezwzględne za pomocą urljoin, usuwa duplikaty przez zbiór seen i pozwala filtrować linki po podłańcuchu. Tak agent w jednym wywołaniu otrzymuje na przykład wszystkie karty produktów z katalogu.
- select_elements — najpotężniejsze narzędzie. Przyjmuje selektor CSS i zwraca tekst znalezionych elementów. Agent może najpierw obejrzeć kawałek HTML przez fetch_page, zrozumieć, że ceny leżą w klasie price, a następnie wywołać select_elements z selektorem .price.
Zwróć uwagę na docstringi: wyraźnie podpowiadamy modelowi, które narzędzie wybrać w jakiej sytuacji. To zauważalnie poprawia jakość pracy agenta.
Jak sprawdzić
- Uruchom mcp dev server.py i połącz się w Inspector. Na liście Tools są teraz cztery narzędzia.
- Wywołaj extract_links z url dowolnego serwisu informacyjnego lub katalogu i parametrem contains równym części adresu sekcji. Wynik — lista obiektów z polami text i url.
- Wywołaj select_elements z tym samym adresem i selektorem h2. Otrzymasz listę nagłówków.
- Zrestartuj Claude Desktop (konfig nie wymaga zmian, zmienił się tylko kod) i poproś: „Zbierz ze strony głównej takiej a takiej strony wszystkie nagłówki h2 i linki prowadzące do sekcji aktualności i sformatuj je w tabelę”.
Wskazówka: Jeśli nie wiesz, jaki selektor jest potrzebny, otwórz stronę w przeglądarce, naciśnij F12, wybierz narzędzie wyboru elementu (ikona ze strzałką w lewym górnym rogu panelu) i kliknij na wybrany blok. W kodzie zobaczysz jego klasę. Selektor z kropką i nazwą klasy, na przykład .product-title, zwykle działa. Co więcej, można po prostu poprosić agenta: „wczytaj HTML i sam dobierz selektor dla cen”.
✅ Weryfikacja: Wszystkie cztery narzędzia są widoczne w Inspector i w kliencie, extract_text zwraca czytelny tekst bez tagów, extract_links zwraca listę z bezwzględnymi adresami, select_elements po selektorze h2 zwraca nagłówki.
Możliwe problemy
- select_elements zwraca pustą listę. Albo selektor jest błędny, albo treść jest doczytywana przez JavaScript już po załadowaniu strony. Sprawdź przez fetch_page: jeśli w HTML nie ma potrzebnych danych, strona renderuje je po stronie klienta. Dla takich stron potrzebny jest silnik przeglądarki, to już temat osobnego artykułu.
- extract_text zwraca krzaki. Strona zwraca niestandardowe kodowanie. Dodaj po response.raise_for_status() wiersz response.encoding = response.charset_encoding or 'utf-8'.
- Odpowiedź się ucina. Zwiększ max_chars w wywołaniu albo poproś agenta, żeby pobierał stronę częściami przez kilka selektorów.
Krok 5: Podłączamy mobilne proxy i rotację IP
Cel etapu: skierować wszystkie zapytania serwera MCP przez mobilne proxy, dodać narzędzie zmiany IP i sprawdzania bieżącego adresu. Po tym agent będzie działał w imieniu operatora mobilnego, a nie z Twojego domowego czy firmowego IP.
Po co zbieraczowi danych mobilne proxy
Gdy zbierasz dane z jednego adresu IP, strony widzą dziesiątki identycznych zapytań pod rząd i zaczynają zwracać captchę, okrojoną treść albo błąd 429 „zbyt wiele zapytań”. Mobilne proxy rozwiązuje kilka zadań jednocześnie. Po pierwsze, adres należy do prawdziwego operatora mobilnego, a takie adresy dzielą między sobą tysiące abonentów, dlatego strony podchodzą do nich łagodniej. Po drugie, możesz zmieniać IP przez link lub timer, rozkładając obciążenie. Po trzecie, oddzielasz aktywność roboczą agenta od własnych sesji prywatnych. Dla marketera to też sposób, żeby zobaczyć stronę tak, jak widzi ją użytkownik mobilny z konkretnego regionu.
⚠️ Uwaga: Proxy to narzędzie do stabilnej i poprawnej pracy zbieracza, a nie do łamania zasad. Zbieraj tylko publicznie dostępne dane, przestrzegaj regulaminów stron i pliku robots.txt, nie twórz nadmiernego obciążenia i nie zbieraj danych osobowych bez podstaw prawnych. Odpowiedzialność za korzystanie z narzędzia leży po Twojej stronie.
Instrukcja krok po kroku
- Otwórz panel klienta swojego dostawcy mobilnych proxy i znajdź dane połączenia: host, port, login, hasło. Zwykle są zebrane w jeden wiersz w postaci login:password@host:port. Tam też skopiuj link do zmiany IP, jeśli jest dostępny.
- W pliku server.py dodaj na początku, po pozostałych importach, wiersz import os. Następnie poniżej bloku HEADERS dodaj ustawienia:
PROXY_URL = os.environ.get('MOBILE_PROXY_URL', '')
ROTATE_URL = os.environ.get('PROXY_ROTATE_URL', '')
def _client() -> httpx.Client:
kwargs = {'headers': HEADERS, 'timeout': 30.0, 'follow_redirects': True}
if PROXY_URL:
kwargs['proxy'] = PROXY_URL
return httpx.Client(**kwargs)- Zamień w funkcji _get_html wiersz z httpx.Client na wywołanie _client(). Teraz wygląda tak: with _client() as client. Wszystkie narzędzia automatycznie pójdą przez proxy.
- Dodaj dwa nowe narzędzia przed wierszem if __name__:
@mcp.tool()
def current_ip() -> str:
'''Показывает IP-адрес, с которого сервер сейчас выходит в интернет.
Используй, чтобы убедиться, что прокси подключён, или после смены IP.'''
with _client() as client:
return client.get('https://api.ipify.org').text.strip()
@mcp.tool()
def rotate_ip() -> str:
'''Запрашивает смену IP-адреса мобильного прокси через ссылку из личного кабинета.
Вызывай, если сайт начал отдавать ошибки 429 или капчу. После вызова подожди 5-10 секунд.'''
if not ROTATE_URL:
return 'Ссылка смены IP не настроена в переменной PROXY_ROTATE_URL'
response = httpx.get(ROTATE_URL, timeout=15.0)
log(f'rotate_ip: status {response.status_code}')
return f'Запрос смены IP отправлен, ответ прокси-сервиса: {response.status_code}'- Przekaż dane proxy przez zmienne środowiskowe w konfigu klienta. Celowo nie wpisujemy loginu i hasła w kod, żeby przypadkiem nie wysłać ich gdzieś razem z plikiem. Otwórz claude_desktop_config.json i uzupełnij blok serwera sekcją env:
{
"mcpServers": {
"web-collector": {
"command": "C:/mcp-collector/.venv/Scripts/python.exe",
"args": ["C:/mcp-collector/server.py"],
"env": {
"MOBILE_PROXY_URL": "http://login:password@proxy-host:port",
"PROXY_ROTATE_URL": "https://ссылка-смены-ip-из-кабинета"
}
}
}
}- Podstaw prawdziwe wartości w miejsce login, password, proxy-host i port. Jeśli dostawca wydaje proxy po protokole SOCKS5, zastąp http:// przez socks5:// i zainstaluj dodatkowy pakiet poleceniem pip install httpx[socks].
- Zapisz konfig, całkowicie zrestartuj klienta.
- Poproś agenta: „Wywołaj current_ip i powiedz, jaki mamy adres. Następnie wywołaj rotate_ip, odczekaj dziesięć sekund i ponownie sprawdź IP”. Adresy powinny się różnić.
Weryfikacja przez Inspector z proxy
Inspector też umie przekazywać zmienne środowiskowe. W lewym panelu rozwiń sekcję Environment Variables, dodaj MOBILE_PROXY_URL i PROXY_ROTATE_URL ze swoimi wartościami, połącz się i wywołaj current_ip. Odpowiedź powinna zgadzać się z IP, które pokazuje panel klienta dostawcy.
Wskazówka: Nie wywołuj rotate_ip przed każdym zapytaniem. U większości dostawców zmiana IP zajmuje kilka sekund, a zbyt częste zapytania mogą trafić w limit na zmianę. Rozsądna strategia: zmieniać adres co 30-100 zapytań albo tylko przy błędach 429 i 403. Można zaszyć tę logikę bezpośrednio w _get_html, co zrobimy w następnym kroku.
✅ Weryfikacja: Narzędzie current_ip zwraca adres proxy, a nie Twój domowy. Po rotate_ip i pauzie adres się zmienia. Narzędzia extract_text i extract_links dalej działają, a w logach widać wiersze GET z adresami stron.
Możliwe problemy
- Błąd 407 Proxy Authentication Required. Błędny login lub hasło albo są w nich znaki specjalne. Znaki typu @ lub : w haśle trzeba zakodować: @ zastąpić przez %40, : przez %3A.
- Błąd ConnectTimeout. Błędny host lub port albo Twój IP nie jest dodany do listy dozwolonych w panelu dostawcy, jeśli plan ma takie przypisanie.
- current_ip pokazuje Twój własny adres. Zmienna środowiskowa nie dotarła do serwera. Sprawdź pisownię MOBILE_PROXY_URL w konfigu i upewnij się, że klient został zrestartowany.
- rotate_ip zwraca status 429 lub komunikat o limicie. Zmieniasz IP częściej, niż pozwala plan. Zwiększ interwał.
Krok 6: Czynimy serwer niezawodnym: powtórzenia, opóźnienia, cache i limity
Cel etapu: zamienić przykład szkoleniowy w narzędzie, które nie pada od pierwszego błędu sieci, nie bombarduje stron zapytaniami i nie przepełnia kontekstu modelu. To ostatni obowiązkowy krok przed pełnym użytkowaniem.
Co dodajemy i po co
- Automatyczne powtórzenia. Błędy sieciowe się zdarzają. Zamiast od razu zwracać agentowi błąd, spróbujemy zapytania jeszcze dwa razy z pauzą.
- Automatyczna zmiana IP przy blokadzie. Jeśli strona odpowiedziała 429 lub 403, a link rotacji jest ustawiony, serwer sam zmieni adres i powtórzy zapytanie.
- Opóźnienie między zapytaniami. Uprzejmy zbieracz nie wysyła dziesiątek zapytań na sekundę. Pauza jednej-dwóch sekund zmniejsza obciążenie strony i ryzyko blokady.
- Cache. Agent często pobiera jedną stronę kilka razy różnymi narzędziami. Cache w pamięci na kilka minut oszczędzi ponownych pobrań.
- Limit rozmiaru. Nie będziemy pobierać stron cięższych niż kilka megabajtów.
Kod
Dodaj na początku pliku import time, a funkcję _get_html zastąp tą:
CACHE: dict[str, tuple[float, str]] = {}
CACHE_TTL = 300
REQUEST_DELAY = 1.5
MAX_BYTES = 3_000_000
_last_request = 0.0
def _get_html(url: str) -> str:
global _last_request
now = time.time()
cached = CACHE.get(url)
if cached and now - cached[0] < CACHE_TTL:
log(f'cache hit: {url}')
return cached[1]
last_error = None
for attempt in range(3):
wait = REQUEST_DELAY - (time.time() - _last_request)
if wait > 0:
time.sleep(wait)
try:
with _client() as client:
_last_request = time.time()
response = client.get(url)
if response.status_code in (403, 429) and ROTATE_URL:
log(f'status {response.status_code}, rotating ip')
httpx.get(ROTATE_URL, timeout=15.0)
time.sleep(8)
continue
response.raise_for_status()
if len(response.content) > MAX_BYTES:
raise ValueError(f'Страница слишком большая: {len(response.content)} байт')
html = response.text
CACHE[url] = (time.time(), html)
return html
except httpx.HTTPError as error:
last_error = error
log(f'attempt {attempt + 1} failed: {error}')
time.sleep(2 * (attempt + 1))
raise RuntimeError(f'Не удалось загрузить {url} после 3 попыток: {last_error}')Jak to działa
- Słownik CACHE przechowuje dla każdego adresu czas pobrania i HTML. Jeśli strona była zapytana mniej niż pięć minut temu, zwracamy zapisaną kopię, nie wykonując zapytania.
- Przed każdym zapytaniem liczymy, ile minęło od poprzedniego, i w razie potrzeby dosypiamy pauzę do REQUEST_DELAY sekund.
- Pętla z trzech prób. Przy odpowiedzi 403 lub 429 z ustawioną rotacją serwer zmienia IP, czeka osiem sekund i próbuje ponownie. Przy błędach sieciowych czeka dwie, cztery, sześć sekund między próbami.
- Jeśli strona jest większa niż trzy megabajty, uznajemy to za błąd: takie dokumenty i tak nie zmieszczą się w kontekście.
- Po trzech nieudanych próbach wyrzucamy zrozumiały błąd z adresem i przyczyną. Agent otrzyma go tekstem i będzie mógł Ci o nim powiedzieć albo spróbować innej drogi.
Polecamy też dodać narzędzie do czyszczenia cache, żeby agent mógł wymusić ponowne pobranie strony:
@mcp.tool()
def clear_cache() -> str:
'''Очищает кэш загруженных страниц. Вызывай, если нужно получить свежую версию страницы.'''
count = len(CACHE)
CACHE.clear()
return f'Кэш очищен, удалено записей: {count}'Wskazówka: Wartości REQUEST_DELAY i CACHE_TTL warto wynieść do zmiennych środowiskowych analogicznie do proxy, żeby zmieniać je bez edycji kodu. Do monitorowania cen sprawdzi się opóźnienie dwóch-trzech sekund i cache na minutę, do zbierania artykułów — opóźnienie sekundę i cache na godzinę.
✅ Weryfikacja: Wywołaj extract_text dla jednej strony dwa razy pod rząd. Za drugim razem w logach pojawi się wiersz cache hit, a odpowiedź przyjdzie natychmiast. Wpisz nieistniejącą domenę — po kilku sekundach agent otrzyma komunikat „Не удалось загрузить... после 3 попыток”, a nie zawiesi się.
Możliwe problemy
- NameError: ROTATE_URL nie jest zdefiniowany. Funkcja _get_html jest zadeklarowana powyżej bloku z ustawieniami proxy. Przenieś ustawienia PROXY_URL i ROTATE_URL wyżej w pliku.
- Agent narzeka na wolne działanie. To normalne: opóźnienia i rotacja IP zajmują czas. Jeśli się spieszysz, zmniejsz REQUEST_DELAY do 0.5, ale pamiętaj o ryzyku blokad.
- Pamięć rośnie. Cache przechowuje wszystkie strony z sesji. Dla długich sesji dodaj czyszczenie wpisów starszych niż TTL przy każdym wywołaniu albo ogranicz rozmiar słownika.
Weryfikacja wyniku: lista kontrolna gotowego serwera MCP
Przejdź przez listę kontrolną i odhacz każdy punkt. Jeśli wszystkie się spełniają, Twój serwer MCP do zbierania danych jest gotowy do realnej pracy.
Co powinno działać
- Polecenie mcp dev server.py uruchamia się bez błędów, Inspector łączy się i pokazuje status Connected.
- Na liście narzędzi są fetch_page, extract_text, extract_links, select_elements, current_ip, rotate_ip i clear_cache.
- Serwer web-collector wyświetla się w panelu narzędzi klienta AI bez wskaźnika błędu.
- Agent na żądanie w dowolnej formie sam wybiera odpowiednie narzędzie i wywołuje je.
- current_ip pokazuje adres mobilnego proxy, a po rotate_ip adres się zmienia.
- Ponowne zapytanie tej samej strony jest zwracane z cache.
- Błędny adres prowadzi do zrozumiałego komunikatu o błędzie, a nie do zawieszenia.
Test kompleksowy
- Wybierz publiczną stronę z katalogiem lub listą artykułów, której dane można legalnie wykorzystać.
- Poproś agenta: „Otwórz stronę główną strony, znajdź linki do sekcji katalogu, wejdź w pierwszych pięć kart, zbierz nazwę i cenę i sformatuj w tabeli z kolumnami Nazwa, Cena, Link”.
- Obserwuj łańcuch wywołań: agent powinien wywołać extract_links z filtrem, następnie kilka razy select_elements albo extract_text, a na końcu utworzyć tabelę.
- Sprawdź kilka wierszy ręcznie, otwierając karty w przeglądarce. Dane powinny się zgadzać.
Wskaźniki sukcesu
Zebranie pięciu kart zajmuje nie więcej niż 30-40 sekund z uwzględnieniem opóźnień. W logach klienta nie ma błędów poziomu traceback. Agent nie dopytuje, którym narzędziem się posłużyć, a działa sam. Jeśli tak jest, gratulacje: zbudowałeś własny serwer MCP i podłączyłeś agenta AI do sieci.
Typowe błędy przy tworzeniu serwera MCP i ich rozwiązania
Zebrałem tu problemy, z którymi zderza się niemal każdy na pierwszym przejściu. Format: problem, przyczyna, rozwiązanie.
1. Serwer łączy się w Inspector, ale nie działa w kliencie
Przyczyna: w konfigu klienta podano systemowy python bez zainstalowanych bibliotek albo błędną ścieżkę do pliku. Rozwiązanie: wpisz pełną ścieżkę do python w .venv i pełną ścieżkę do server.py, używaj ukośników prostych, zrestartuj klienta całkowicie.
2. Klient zrywa połączenie zaraz po starcie
Przyczyna: w kodzie został zwykły print bez file=sys.stderr i pomocniczy strumień stdout się zaśmiecił. Rozwiązanie: zastąp wszystkie print funkcją log. Sprawdź też, czy biblioteki nie piszą do stdout: na przykład niektóre paski postępu robią to domyślnie.
3. Agent nie wywołuje narzędzi i odpowiada z własnej wiedzy
Przyczyna: opisy narzędzi są zbyt krótkie lub ogólnikowe i model nie rozumie, kiedy ich użyć. Rozwiązanie: rozszerz docstringi, dodaj frazy „użyj, gdy...” i przykłady. W pierwszych zapytaniach wyraźnie nazywaj narzędzie.
4. Błąd 403 przy pobieraniu prawdziwych stron
Przyczyna: strona nie przyjmuje zapytań bez nagłówków przeglądarkowych albo z podejrzanego IP. Rozwiązanie: sprawdź, czy HEADERS są przekazywane, zaktualizuj User-Agent do aktualnej wersji przeglądarki, podłącz mobilne proxy i upewnij się, że rotacja działa.
5. Pusty wynik select_elements przy poprawnym selektorze
Przyczyna: dane są doczytywane przez JavaScript po załadowaniu strony i w źródłowym HTML ich nie ma. Rozwiązanie: sprawdź przez fetch_page. Jeśli danych nie ma, spróbuj znaleźć wewnętrzne API strony w zakładce Network przeglądarki: często karty przychodzą jako JSON pod osobnym adresem, który można odpytywać bezpośrednio tym samym extract_text.
6. Błąd 407 lub ConnectTimeout przy pracy przez proxy
Przyczyna: błędne dane uwierzytelniające, niezakodowane znaki specjalne w haśle albo błędny port. Rozwiązanie: skopiuj wiersz połączenia z panelu od nowa, zakoduj znaki specjalne, sprawdź protokół http lub socks5.
7. Konfig JSON nie jest stosowany
Przyczyna: zbędny przecinek, brakujący cudzysłów albo ukośniki odwrotne w ścieżkach. Rozwiązanie: sprawdź plik w walidatorze, zastąp ukośniki odwrotne prostymi, upewnij się, że po ostatnim elemencie nie ma przecinka.
8. Serwer działa, ale dane przychodzą w złym kodowaniu
Przyczyna: strona nie wskazuje kodowania w nagłówkach. Rozwiązanie: ustaw response.encoding wyraźnie albo użyj atrybutu response.content z ręcznym dekodowaniem przez decode('utf-8', errors='ignore').
Dodatkowe możliwości: blok dla zaawansowanych
Podstawowy serwer jest gotowy. Jeśli pewnie piszesz w Pythonie i chcesz więcej, oto kierunki rozwoju, z których każdy można zrealizować w jeden wieczór.
Zdalny serwer przez Streamable HTTP
Żeby serwer działał na osobnej maszynie albo łączyło się z nim kilku klientów, zastąp ostatni wiersz przez mcp.run(transport='streamable-http'). Domyślnie serwer podniesie się na porcie 8000, a adres połączenia będzie http://adres-maszyny:8000/mcp. W konfigu klienta zamiast command i args podaj klucz url z tym adresem. W tym trybie można pisać do stdout, ale lepiej zachować nawyk logowania do stderr. Koniecznie zamknij port przed światem zewnętrznym i dodaj sprawdzanie tokena w nagłówku, jeśli serwer jest dostępny nie tylko z sieci lokalnej.
Zasoby i prompty
Zasób z bieżącymi ustawieniami pomoże agentowi rozumieć kontekst pracy:
@mcp.resource('collector://settings')
def settings() -> str:
'''Текущие настройки сборщика.'''
return f'proxy: {"on" if PROXY_URL else "off"}, delay: {REQUEST_DELAY}, cache ttl: {CACHE_TTL}'Prompt zadaje gotowy scenariusz, który użytkownik wywołuje jednym poleceniem:
@mcp.prompt()
def price_monitor(url: str) -> str:
'''Сценарий мониторинга цен в каталоге.'''
return f'Открой {url}, собери ссылки на карточки товаров, зайди в каждую, вытащи название и цену и составь таблицу. Если увидишь ошибку 429, вызови rotate_ip и продолжи.'Zapisywanie wyników do pliku
Dodaj narzędzie save_csv, które przyjmuje listę słowników i ścieżkę do pliku i zapisuje dane przez moduł csv. Agent będzie mógł nie tylko zbierać, ale też składać wyniki w tabelę, którą otworzysz w Excelu. Ogranicz ścieżkę zapisu do jednego folderu, żeby agent nie mógł pisać byle gdzie na dysku.
Asynchroniczność i równoległe zbieranie
FastMCP obsługuje funkcje asynchroniczne: zadeklaruj narzędzie przez async def i użyj httpx.AsyncClient. Wtedy narzędzie fetch_many będzie mogło pobierać dziesięć stron jednocześnie przez asyncio.gather. Nie zapomnij o semaforze ograniczającym liczbę równoległych zapytań i o tym, że opóźnienie między zapytaniami przy równoległości trzeba liczyć inaczej.
Kilka proxy i inteligentna rotacja
Jeśli masz kilka mobilnych proxy na różne regiony, trzymaj je w zmiennej środowiskowej jako listę rozdzieloną przecinkami i dodaj narzędziu parametr region. Serwer będzie wybierał proxy po regionie, a agent będzie mógł porównywać ceny, które strona pokazuje użytkownikom z różnych miast. To jedno z najbardziej pożądanych zadań u marketerów i specjalistów od arbitrażu.
Pakowanie w Docker
Do uruchomienia na serwerze zbuduj obraz na bazie python:3.12-slim, skopiuj server.py i plik zależności, zainstaluj pakiety i wskaż punkt wejścia z transportem HTTP. Zmienne proxy przekazuj przy uruchomieniu kontenera, a nie zaszywaj w obrazie.
⚠️ Uwaga: Nigdy nie publikuj kodu z loginami, hasłami i linkami rotacji w otwartych repozytoriach. Trzymaj je tylko w zmiennych środowiskowych albo w pliku .env dodanym do .gitignore. Wyciek linku zmiany IP pozwoli obcym zarządzać Twoim proxy.
FAQ: częste pytania o tworzenie serwera MCP
Czy serwer MCP można napisać nie w Pythonie?
Tak. Oficjalne SDK są dla TypeScript, Java, Kotlin, C# i innych języków. Zasady są takie same: zadeklarować narzędzia z opisami i uruchomić transport. Python został wybrany w przewodniku za prostotę i bogaty zestaw bibliotek do pracy z HTML.
Czy do pracy z MCP potrzebny jest płatny plan klienta AI?
Claude Desktop obsługuje lokalne serwery MCP również na planie darmowym, ale z limitami na liczbę wiadomości. Cursor i VS Code też pozwalają podłączać serwery. Sprawdzaj aktualne warunki u konkretnego klienta.
Czy trzeba używać proxy?
Nie, serwer działa też bezpośrednio. Proxy jest potrzebne, gdy liczba zapytań jest zauważalna, strony są wrażliwe na częstotliwość odwiedzin albo zależy Ci na widzeniu treści z konkretnego regionu i z mobilnego IP.
Jak sprawdzić, że zapytania naprawdę idą przez proxy?
Wywołaj narzędzie current_ip i porównaj adres z tym, co pokazuje panel klienta dostawcy. Dodatkowo możesz poprosić agenta, żeby wczytał stronę serwisu do sprawdzania IP przez extract_text.
Ile narzędzi można dodać do jednego serwera?
Technicznie ograniczeń prawie nie ma, ale każdy opis zajmuje miejsce w kontekście modelu. Praktyka pokazuje, że 5-15 dobrze opisanych narzędzi działa lepiej niż 50 drobnych. Grupuj bliskie funkcje parametrami.
Jak aktualizować serwer bez restartu klienta?
Przy transporcie stdio klient uruchamia proces przy starcie, więc zmiany w kodzie zostaną podchwycone dopiero po restarcie klienta. W trybie deweloperskim wygodniej sprawdzać poprawki w Inspector, a klienta restartować po zakończeniu.
Co robić, jeśli strona zwraca dane dopiero po wykonaniu JavaScript?
Nasz serwer pracuje ze źródłowym HTML i takich danych nie zobaczy. Opcje: znaleźć wewnętrzne API strony w zakładce Network przeglądarki albo podłączyć silnik przeglądarki. Druga droga jest opisana w osobnych materiałach na blogu, tutaj celowo jej nie poruszamy.
Jak ograniczyć agenta, żeby nie chodził na niepożądane strony?
Dodaj w _get_html sprawdzanie domeny po białej lub czarnej liście ze zmiennej środowiskowej i zwracaj zrozumiały błąd dla zabronionych adresów. To pewniejsze niż poleganie na instrukcjach w czacie.
Czy można używać jednego serwera MCP z kilku klientów jednocześnie?
Przy stdio każdy klient uruchamia własną kopię procesu i to jest normalne: nie przeszkadzają sobie, ale i cache mają osobny. Dla wspólnego cache i jednego proxy przejdź na transport HTTP z bloku zaawansowanego.
Podsumowanie: co zrobiłeś i dokąd iść dalej
Podsumujmy. Przygotowałeś środowisko Pythona i zainstalowałeś oficjalne SDK protokołu. Napisałeś serwer MCP od zera i zrozumiałeś, jak model rozumie narzędzia przez ich opisy. Podłączyłeś serwer do klienta AI i zobaczyłeś, jak agent sam pobiera strony. Dodałeś narzędzia wyodrębniania tekstu, linków i elementów po selektorach. Skierowałeś ruch przez mobilne proxy z rotacją IP. Na koniec uczyniłeś serwer odpornym: powtórzenia, opóźnienia, cache i limity. To już nie przykład szkoleniowy, a działające narzędzie do codziennych zadań.
Co robić dalej? Zacznij używać serwera w realnych scenariuszach: monitorowanie cen konkurencji, zbieranie opinii, sprawdzanie landing page’y, analiza treści w niszy. Po drodze zrozumiesz, jakich narzędzi brakuje właśnie Tobie, i dodasz je według wzoru istniejących. Każde nowe narzędzie to funkcja z jasnym opisem, nic trudniejszego.
Następny poziom to blok zaawansowany: zdalny serwer po HTTP, równoległe zbieranie, praca z kilkoma proxy według regionów i zapisywanie wyników w tabelach. A gdy natrafisz na strony z dynamiczną treścią, zajrzyj do pokrewnych artykułów na blogu o automatyzacji przeglądarki. Najważniejsze już zrobiłeś: Twój agent AI wyszedł do sieci przez własny serwer MCP i w pełni kontrolujesz, jak to robi.