Większość poradników o Model Context Protocol zakłada, że budujesz serwer przed sklepem lub bazą danych, czymś z zamówieniami i stanem magazynowym. My zrobiliśmy rzecz mniej oczywistą: zamieniliśmy statyczną stronę marketingową w działający serwer MCP tylko do odczytu. Każdy klient MCP może teraz wysłać POST na pojedynczy punkt końcowy i zapytać, jakie usługi istnieją albo jak rozpocząć zapytanie, w typowanym JSON-RPC, bez parsowania ani jednej strony HTML.
To relacja praktyka z tego wdrożenia. Jest celowo wąska: kilka narzędzi tylko do odczytu, żadnej bazy danych, żadnych zapisów. Ta wąskość jest tu istotą, i większość ciekawych decyzji z niej wynika. Poniżej wyjaśniamy, dlaczego witryna treściowa w ogóle zyskuje na punkcie końcowym MCP, dlaczego ten punkt końcowy jest funkcją Cloudflare Pages, a nie trasą frameworka, dlaczego napisaliśmy JSON-RPC ręcznie zamiast sięgać po SDK, oraz jeden błąd z ukośnikiem końcowym, który po cichu psuje każdy POST, jeśli go przeoczysz.
Po co serwer MCP na statycznej stronie marketingowej
Sklep wystawia MCP, bo agent musi wykonać akcję: sprawdzić stan magazynu, zbudować koszyk, złożyć zamówienie. Strona marketingowa nie ma koszyka. Po co więc powierzchnia narzędziowa?
Uczciwa odpowiedź to odkrywanie przez agentów. Nasza strona już wcześniej miała warstwę odkrywania dla agentów: plik llms.txt, zestaw dokumentów .well-known oraz kartę serwera MCP pod /.well-known/mcp/server-card.json. Ta karta opisywała serwer, jego transport i możliwości. Deklarowała nawet możliwość tools. Był jeden problem: za nią nic nie stało. Karta rozgłaszała serwer, który nie istniał. Agent, który poszedł za kartą i próbował się połączyć, nie dostawał nic.
To częsty stan tych plików. Zostają wygenerowane, zostają rozgłoszone w nagłówku Link, a potem wskazują na zasób, który nigdy nie powstał. Karta jest obietnicą bez implementacji.
Celem nie było więc wymyślenie nowej powierzchni. Było nim uczynienie istniejącej, rozgłaszanej powierzchni realną. Narzędzia wynikają wprost z tego, do czego służy strona marketingowa:
check_serviceszwraca katalog usług: id, nazwę, opis, kategorię i kanoniczny adres URL.request_quotezwraca zlokalizowany adres URL kontaktu oraz instrukcje przesłania zapytania.
Pierwsza wersja działała z tymi dwoma. Później doszły trzy kolejne narzędzia tylko do odczytu, każde jako nowy wpis w tablicy TOOLS i nowa gałąź w callTool, bez zmian w transporcie:
check_tech_stackzwraca stos technologiczny, z którym pracujemy, pogrupowany według kategorii, z opcjonalnym filtrem kategorii.get_case_studieszwraca studia przypadków wraz z ich stosem, filtrowane opcjonalną frazą.search_blog_postsprzeszukuje bloga po tytule, opisie, kategoriach, tagach lub slugu, z opcjonalnym filtrem języka i kategorii oraz limitem wyników.
Pięć narzędzi, wszystkie tylko do odczytu. Agent może teraz wyliczyć, co robimy, i podać użytkownikowi właściwy kolejny krok, deterministycznie, zamiast zgadywać z prozy.
Jak uruchomić serwer MCP jako funkcję Cloudflare Pages
Strona jest zbudowana w Astro w trybie wyjścia statycznego. Nie ma adaptera SSR. Każda podstrona jest prerenderowana do HTML w czasie builda, a pliki w stylu API, które serwuje (JSON katalogu usług, profil agenta), to pliki statyczne w public/, a nie trasy serwerowe.
Ten jeden fakt napędza cały projekt. Dynamiczny punkt końcowy, który czyta ciało żądania i zwraca wyliczoną odpowiedź, nie może tu być trasą Astro, bo nie ma serwera, który by ją uruchomił. Dodanie go oznaczałoby przełączenie trybu wyjściowego na hybrydowy lub serwerowy i podpięcie adaptera, co jest dużą, ryzykowną zmianą w buildzie, który już produkuje tysiące podstron.
Strona jednak już uruchamia ręcznie napisane funkcje Cloudflare Pages: middleware obsługujące przekierowania oraz kilka małych handlerów API. To właśnie ten szew. Dynamiczny punkt końcowy MCP to po prostu jeszcze jedna funkcja Pages, functions/mcp.ts, stojąca obok pozostałych. Wdraża się w ramach zwykłego builda, nie wymaga adaptera i nic nie zmienia w sposobie renderowania podstron.
Wniosek się uogólnia. Gdy chcesz dodać jeden dynamiczny punkt końcowy do skądinąd statycznej strony, nie sięgaj po tryb serwerowy frameworka. Sięgnij po prymityw funkcji brzegowej, który twój host już oferuje. W Cloudflare Pages jest to funkcja. Kosztem jest jeden plik, a nie przebudowa modelu renderowania.
Jak zbudować serwer MCP bez oficjalnego SDK
Odruch, gdy czytasz “serwer MCP”, to zainstalować @modelcontextprotocol/sdk. Dla serwera stdio z wieloma narzędziami, zasobami i promptami ten odruch jest słuszny. Dla kilku narzędzi tylko do odczytu na funkcji brzegowej już nie.
Protokół transmisji MCP to JSON-RPC 2.0. Serwer, który odpowiada klientowi MCP, musi obsłużyć niewielki zestaw metod:
initialize, gdzie serwer zwraca wersję protokołu, możliwości i tożsamość.tools/list, gdzie zwraca definicje narzędzi wraz z ich JSON Schema.tools/call, gdzie uruchamia nazwane narzędzie i zwraca wynik opakowany w treść MCP.- powiadomienia takie jak
notifications/initialized, które nie niosą id i nie oczekują odpowiedzi.
To cała powierzchnia dla serwera tylko do odczytu. Ręczna jej implementacja to mniej więcej 120 linii. Dyspozytor to zwykły switch:
switch (method) {
case "initialize":
return ok({ protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo });
case "tools/list":
return ok({ tools: TOOLS });
case "tools/call":
return ok({ content: [{ type: "text", text: callTool(name, args, services).text }] });
default:
return err(-32601, `Method not found: ${method}`);
}Zważ to na wadze przeciw SDK. SDK jest zaprojektowane pod stdio i pod pełny protokół. Na środowisku brzegowym dziedziczysz jego założenia dotyczące bundlowania oraz jego maszynerię transportową dla funkcji, których nie używasz. Dla kilku narzędzi odczytu zależność, o której musisz myśleć, jest gorszym kompromisem niż 120 linii, które w pełni kontrolujesz. To ta sama powściągliwość najpierw-tylko-do-odczytu, zastosowana do zależności: wystawiaj minimum, posiadaj minimum.
Jedną rzeczą, którą warto zrobić ręcznie starannie, jest kontrakt błędów. JSON-RPC ma zdefiniowane kody błędów, a klienci MCP ich oczekują: -32700 dla błędu parsowania, -32601 dla nieznanej metody, -32602 dla błędnych parametrów. Trafienie w nie sprawia, że ręcznie napisany serwer zachowuje się wobec rygorystycznego klienta jak prawdziwy.
Dlaczego publiczny serwer MCP powinien być tylko do odczytu
Kuszące kolejne narzędzie to takie, które przechwytuje kontakt: bierze imię, e-mail i wiadomość, i przesyła je do nas mailem. Nie buduj tego jako otwartego narzędzia MCP.
Publiczny punkt końcowy z narzędziem zapisującym to cel dla spamu. Każdy w internecie może wywołać tools/call z submit_quote i sfałszowanym ładunkiem, i teraz twoja skrzynka, twój CRM czy baza danych są celem bez żadnego tarcia i bez człowieka w pętli. W chwili, gdy narzędzie zapisuje, punkt końcowy potrzebuje uwierzytelniania, ograniczania częstości i obsługi nadużyć, co jest dużą powierzchnią do obrony dla strony marketingowej.
Dlatego request_quote nic nie zapisuje. Zwraca zlokalizowany adres URL kontaktu oraz ustrukturyzowane wskazówki:
if (name === "request_quote") {
const lang = LOCALES.includes(String(args?.lang)) ? String(args.lang) : "en";
return { text: JSON.stringify({
contact_url: CONTACT_URLS[lang],
method: "web-form",
note: "Read-only endpoint. Submit the inquiry through the contact form; this tool does not send it for you.",
reply_time: "within one working day",
}, null, 2) };
}Agent dostaje wszystko, czego potrzebuje, by poprowadzić użytkownika dalej: właściwą stronę kontaktu dla języka użytkownika oraz jasne stwierdzenie, że przesłanie odbywa się przez formularz. Człowiek pozostaje w pętli. Punkt końcowy nie ma czego nadużyć.
To nie jest słabszy projekt wymuszony ostrożnością. To ta sama postawa, którą polecamy klientom budującym MCP dla własnych systemów: najpierw tylko do odczytu, dodawaj zapisy dopiero za uwierzytelnianiem, gdy jest ku temu konkretny powód. Serwer tylko do odczytu jest szczery co do tego, czym jest, i można go bezpiecznie zostawić otwartym.
| Wybór projektowy | Otwarte narzędzie zapisu i odczytu | Przekazanie tylko do odczytu |
|---|---|---|
| Wymagane uwierzytelnianie | Tak | Nie |
| Powierzchnia dla spamu | Wysoka | Brak |
| Człowiek w pętli | Opcjonalnie | Zawsze |
| Kod do obrony | Ograniczanie częstości, walidacja, obsługa nadużyć | Brak |
| Dopasowanie do publicznej strony marketingowej | Słabe | Dobre |
Dlaczego przekierowanie na ukośnik końcowy gubi ciało POST
Ta kosztowała realny czas debugowania, więc warto ją wyłożyć wprost.
Nasza strona kanonizuje adresy URL z ukośnikiem końcowym. Middleware przekierowuje z kodem 301 każdą ścieżkę bez niego na wersję z ukośnikiem. Dla podstron to w porządku. Dla punktu końcowego JSON-RPC to cicha katastrofa.
POST /mcp trafia na przekierowanie i zwraca 301 na /mcp/. Wielu klientów HTTP, podążając za przekierowaniem, nie wysyła ponownie ciała POST-a albo obniża metodę. Klient MCP widzi pustą lub błędną odpowiedź i wnioskuje, że serwer jest zepsuty. Serwer jest sprawny. Żądanie po prostu nigdy nie dotarło ze swoim ciałem.
Naprawą nie jest walka z middleware. Jest nią rozgłaszanie adresu URL, który strona faktycznie serwuje. Karta serwera MCP oraz każde odwołanie w warstwie odkrywania wskazują na /mcp/, z ukośnikiem końcowym, tak że dobrze zachowujący się klient wysyła POST bezpośrednio na kanoniczny adres URL i nigdy nie dotyka przekierowania. Jeśli twój framework lub host normalizuje ukośniki, zdecyduj, która forma jest kanoniczna, i rozgłaszaj dokładnie tę formę wszędzie.
Działający punkt końcowy możesz zweryfikować tak samo jak my:
curl -s -X POST https://wppoland.com/mcp/ \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'POST bez ukośnika na ten sam host pokaże ci za to 301.
Jak serwer MCP pomaga w GEO i w kontakcie z agentami AI
Generative engine optimization to głównie kwestia bycia czytelnym i weryfikowalnym dla modeli. Dane strukturalne, jasne encje, spójne twierdzenia na twoich własnych powierzchniach i tych zewnętrznych. Działający punkt końcowy MCP to mocna wersja tej czytelności: to nie wskazówka o twojej treści, to wywoływalny interfejs do niej.
Dwie rzeczy sprawiają, że jest wart niewielkiego wysiłku, nawet gdy adopcja jest wciąż wczesna. Po pierwsze, domyka lukę między tym, co rozgłasza warstwa odkrywania, a tym, co istnieje. Karta wskazująca na działający serwer to spójny sygnał; karta wskazująca na nic to sygnał zepsuty, a zepsute sygnały są gorsze niż ich brak. Po drugie, dla każdego, kto sprzedaje tę usługę, punkt końcowy jest dowodem. Budujemy serwery MCP dla klientów, a najbardziej wiarygodną demonstracją tego jest publiczny, tylko do odczytu serwer działający na naszej własnej domenie, obok otwartoźródłowego serwera MCP dla WooCommerce, który publikujemy.
Żaden duży dostawca AI nie zobowiązuje się dziś formalnie do czytania kart serwera MCP ani llms.txt. To uczciwe zastrzeżenie i mówimy je wprost. Punkt końcowy jest tani w utrzymaniu, czyni rozgłaszaną obietnicę prawdziwą i jest działającym artefaktem, a nie deklaracją. Te trzy razem przekraczają próg opłacalności.
Telemetria MCP z innej strony, październik 2026: Amir Pournasserian zliczał ruch na serwerze MCP swojej strony przez trzy i pół dnia (od 14 do 18 października 2026). Klienci otworzyli około 300 połączeń (308 wywołań initialize, 281 tools/list) i wykonali 29 wywołań narzędzi, 15 wyszukiwań i 14 pobrań. Wielu z nich przedstawiało się jako katalogi i rankingi serwerów MCP lub agentów. Pytali też o około dwunastu adresów do wykrywania serwera, których jego serwer nie ma, w tym o /.well-known/mcp/server-card.json, który autor wywodzi z wcześniejszych wersji roboczych Server Cards. Pod tym adresem stoi nasza karta, podlinkowana z naszego /.well-known/ai-catalog.json. Propozycja MCP Server Cards, SEP-2127, dziś ze statusem Final, dopuszcza kartę pod dowolnym niezarezerwowanym adresem wskazanym w katalogu AI i zaleca <streamable-http-url>/server-card. Autor nie znalazł popularnego klienta, który sam szedłby za katalogiem. Claude Code, Claude.ai, VS Code i Cursor biorą adres serwera od użytkownika albo z własnej galerii. Jedna strona prywatna i trzy i pół dnia danych. Większość klientów pobrała listę narzędzi, a niewielu ich użyło.
Które funkcje MCP może pominąć mały serwer
Krótka lista, bo wiedza o tym, co build pomija, jest równie użyteczna jak wiedza o tym, co zawiera.
- Żadnych narzędzi na zapas.
get_case_studiesnie było w pierwszej wersji i agenci czytali studia przypadków zllms.txtoraz podlinkowanych stron, dopóki narzędzie nie powstało. Każde narzędzie z listy odpowiada dziś na pytanie, które agenci faktycznie zadają. - Brak możliwości
resourcesczyprompts. Serwer deklaruje jedynietools, bo tylko to implementuje. Deklarowanie możliwości, której nie serwujesz, jest gorsze niż jej pominięcie: klient wywołującyresources/listdostaje błąd zamiast czystego “niewspierane”. - Brak SDK, jak opisano wyżej.
- Brak zapisów, jak opisano wyżej.
Każde pominięcie to decyzja, a nie przeoczenie. Punkt końcowy robi dokładnie to, czego wymagają jego narzędzia tylko do odczytu, i nic więcej, dlatego jest na tyle mały, by ogarnąć go w jednym posiedzeniu, i na tyle bezpieczny, by zostawić go otwartym na internet.
Czego specyfikacja MCP wymaga od punktu końcowego Streamable HTTP
Transport za POST /mcp/ to Streamable HTTP. Specyfikacja transportów MCP (wersja 2025-06-18) definiuje go jako następcę transportu HTTP+SSE z wersji 2024-11-05. Większość jej reguł dotyczy strumieniowania, a serwer tylko do odczytu spełnia je, niczego nie strumieniując.
- Każda wiadomość JSON-RPC od klienta to nowe żądanie HTTP POST na jedną ścieżkę punktu końcowego. Klient musi wysłać nagłówek
Acceptz obiema wartościami:application/jsonitext/event-stream. - Gdy ciało POST-a to powiadomienie albo odpowiedź, serwer musi odpowiedzieć
202 Acceptedbez ciała. Nasza funkcja robi tak dlanotifications/initialized. - Gdy ciało to żądanie, serwer wybiera: otwiera strumień SSE albo zwraca jeden obiekt JSON z
Content-Type: application/json. Klient musi obsłużyć oba warianty, więc serwer, który nie ma czego strumieniować, zawsze może odpowiedzieć zwykłym JSON-em. - Sesje są opcjonalne. Serwer może nadać nagłówek
Mcp-Session-Idw odpowiedzi nainitialize, ale nic go do tego nie zmusza. Bez identyfikatora sesji nie ma magazynu sesji do utrzymania, co pasuje do funkcji brzegowej, która nie trzyma stanu między żądaniami. - Po inicjalizacji klient musi wysyłać nagłówek
MCP-Protocol-Versionw każdym żądaniu. Jeśli nagłówka brak, a serwer nie ma innego sposobu na ustalenie wersji, powinien przyjąć2025-03-26. Na nieprawidłową lub niewspieraną wartość serwer musi odpowiedzieć400 Bad Request.
Ta sama strona zawiera ostrzeżenie bezpieczeństwa dla tego transportu. Serwery muszą walidować nagłówek Origin przy każdym połączeniu, serwery uruchomione lokalnie powinny nasłuchiwać tylko na 127.0.0.1, a nie na wszystkich interfejsach sieciowych, a serwery powinny uwierzytelniać połączenia. Atak, który specyfikacja wymienia z nazwy, to DNS rebinding: zdalna strona używa przeglądarki odwiedzającego, by dosięgnąć serwera MCP na jego własnym komputerze.
Jak działa negocjacja wersji protokołu MCP
Negocjacja wersji odbywa się w initialize, a specyfikacja cyklu życia opisuje ją krótko. Klient musi wysłać wersję protokołu, którą wspiera, i powinna to być najnowsza wspierana przez niego wersja. Jeśli serwer wspiera tę wersję, musi odpowiedzieć tą samą. Jeśli nie, musi odpowiedzieć inną wersją, którą wspiera, również najlepiej najnowszą. Klient, który nie wspiera wersji z odpowiedzi, powinien się rozłączyć.
Wersje protokołu to ciągi dat, na przykład 2024-11-05, 2025-03-26 i 2025-06-18. Nasza funkcja używa 2025-06-18 jako wartości domyślnej. Dla ręcznie napisanego serwera reguła sprowadza się do jednej decyzji: listy wersji, na które odpowiadasz. Strona cyklu życia pokazuje kształt błędu przy niezgodności: kod -32602, komunikat “Unsupported protocol version” i obiekt data z wersjami wspieranymi oraz żądaną.
Po negocjacji obie strony muszą respektować wynegocjowaną wersję i używać wyłącznie wynegocjowanych możliwości. To formalny powód, dla którego opisany wyżej serwer deklaruje tylko tools: możliwość w odpowiedzi na initialize to obietnica, na której klient ma prawo polegać.
Jak oznaczyć narzędzie MCP jako tylko do odczytu
Schemat MCP daje każdemu narzędziu opcjonalny obiekt annotations z czterema wskazówkami o zachowaniu:
| Wskazówka | Znaczenie | Domyślnie |
|---|---|---|
readOnlyHint | narzędzie nie modyfikuje swojego otoczenia | false |
destructiveHint | narzędzie może wykonywać destrukcyjne zmiany; ma znaczenie tylko, gdy readOnlyHint to false | true |
idempotentHint | powtórne wywołanie z tymi samymi argumentami nie daje dodatkowego efektu; ma znaczenie tylko, gdy readOnlyHint to false | false |
openWorldHint | narzędzie może wchodzić w interakcję z otwartym światem zewnętrznych bytów | true |
Wartości domyślne są pesymistyczne. Narzędzie bez adnotacji jest opisane jako takie, które może zapisywać, może niszczyć dane i może sięgać poza własną dziedzinę. Narzędzia w tym buildzie nie mają adnotacji, więc same wskazówki opisywałyby je właśnie tak. Dodanie readOnlyHint: true do definicji każdego narzędzia to zmiana jednej linii.
Schemat zaznacza też, że te właściwości są wskazówkami, a nie wiernym opisem zachowania, a specyfikacja narzędzi mówi, że klienci muszą traktować adnotacje jako niezaufane, chyba że pochodzą od zaufanych serwerów. Wskazówka nigdy nie zastępuje kontroli. Własność tylko do odczytu tego punktu końcowego mieszka w kodzie, w którym żadne narzędzie nie ma ścieżki zapisu. Adnotacja jedynie opisuje tę własność klientowi, który zdecyduje się jej zaufać.
Specyfikacja narzędzi nakłada na każdy serwer trzy obowiązki: walidować wszystkie dane wejściowe narzędzi, wdrożyć właściwą kontrolę dostępu i ograniczać częstość wywołań narzędzi. Nasza funkcja realizuje pierwszy z nich, odrzucając błędne argumenty kodem -32602. Specyfikacja rozdziela też dwa rodzaje porażki: błędy protokołu, takie jak nieznane narzędzie, wracają jako błędy JSON-RPC, a narzędzie, które się uruchomiło i zawiodło, zwraca zwykły wynik z isError: true, dzięki czemu model widzi treść błędu i może na nią zareagować.
Więcej poradników o budowie serwerów MCP
Praktyczny podręcznik konfiguracji, testowania schematów JSON-RPC i podłączania Claude Desktop do naszego produkcyjnego endpointu znajdziesz w przewodniku po publicznym serwerze MCP WPPoland. Jeśli chcesz pełniejszej wersji tych decyzji, klaster wokół tego wpisu pokrywa sąsiedni grunt: budowa serwera MCP dla WooCommerce dla przypadku ze stanem i obsługą sklepu, MCP kontra REST: kiedy co wygrywa dla pytania, czy w ogóle potrzebujesz MCP, oraz wzorce uwierzytelniania MCP na moment, w którym faktycznie dodajesz narzędzie zapisujące i potrzebujesz uwierzytelniania. Komercyjna wersja tej pracy mieszka na stronie usługi tworzenie serwerów MCP.
Krótka wersja mieści się w jednym zdaniu: strona marketingowa może być serwerem MCP, punkt końcowy to jedna funkcja brzegowa, każde narzędzie utrzymuj jako tylko do odczytu, a rozgłaszaj adres URL, który twój host faktycznie serwuje.






