Przejdź do treści

Tłumaczenie maszynowe. Wersja angielska jest wiążąca; weryfikacja przez native speakera jeszcze nie nastąpiła.

MCP

Zarządzanie serwerami MCP za pomocą RFC 9728 i RFC 8707 — co naprawdę działa

Autor Olivares AI 9 min do przeczytania

Łączysz Claude Code z serwerem MCP. Serwer wymaga uwierzytelnienia. Twój klient wysyła żądanie; serwer odpowiada nagłówkiem 401 Unauthorized i WWW-Authenticate: Bearer, który zawiera URL resource_metadata. Co dzieje się następnie, to przepływ OAuth 2.1 zdefiniowany przez trzy RFC i zestaw rozszerzeń specyficznych dla MCP, które razem rozwiązują jeden z trudniejszych problemów w bezpieczeństwie agentów: upewnienie się, że token uzyskany do komunikacji z tym serwerem MCP nie może być ponownie użyty wobec tamtego.

Ten wpis śledzi cały przepływ — co mówi specyfikacja, co RFC faktycznie zapewniają i gdzie w praktyce kryją się pułapki bezpieczeństwa.

Przepływ: od 401 do tokena powiązanego z zasobem

Model autoryzacji MCP (wersja 2025-11-25, przeniesiona bez zmian do wersji kandydującej — zamrożona 2026-05-21 — dla ostatecznej specyfikacji zaplanowanej na 2026-07-28) jest procesem dwufazowym. Faza 1 to wykrywanie: 401 przenoszący WWW-Authenticate: Bearer resource_metadata="..." informuje klienta, że ten serwer jest chroniony przez OAuth i gdzie znaleźć jego metadane. Faza 2 to dostęp autoryzowany: wykorzystać metadane, odkryć serwer autoryzacyjny, uzyskać token powiązany z tym konkretnym serwerem i go użyć.

Konkretne kroki:

  1. 401 + WWW-Authenticate: Serwer MCP odrzuca nieautoryzowane żądanie. Parametr resource_metadata w wyzwaniu wskazuje dokument Metadanych Zasobu Chronionego serwera.

  2. PRM pobieranie (RFC 9728): Klient wykonuje żądanie GET na URL /.well-known/oauth-protected-resource. Odpowiedź to dokument JSON deklarujący kanoniczny URI zasobu serwera, serwery autoryzacyjne, które go chronią, oraz zakresy obsługiwane przez zasób. Klient weryfikuje, że pole resource w dokumencie odpowiada serwerowi, do którego zamierzał się połączyć — niezgodność jest sygnałem podszywania się i musi zostać odrzucona.

  3. AS odkrywanie (RFC 8414): Korzystając z URL wystawcy z tablicy authorization_servers z PRM, klient przechodzi przez dobrze znane kandydatury (najpierw /.well-known/oauth-authorization-server, potem /.well-known/openid-configuration), aby pobrać metadane serwera autoryzacji. Wystawca w zwróconym dokumencie musi być identyczny Bajtowo (byte-identyczny) z wystawcą, dla którego został pobrany — nie „równoważny po normalizacji”, nie „wystarczająco bliski”. Identyczny bajtowo.

  4. Pozyskiwanie tokenu (RFC 8707): Klient wysyła żądanie tokenu do punktu końcowego tokenu AS, uwzględniając resource=<canonical server URI> zarówno w żądaniach autoryzacyjnych, jak i w żądaniach tokenu. To wiąże odbiorcę tokenu z tym konkretnym serwerem MCP. AS wydaje token, którego odbiorcą jest ten zasób, a klient przedstawia go serwerowi.

  5. Autoryzowany dostęp: Klient używa tokenu do wywoływania metod introspekcji tylko do odczytu serwera MCP (tools/list, resources/list itp.). Token potwierdza, że klient jest autoryzowany; wskaźnik zasobu potwierdza, że token został wygenerowany dla tego serwera.

Co faktycznie zapewnia RFC 9728

RFC 9728 (Metadane chronionego zasobu) jest mechanizmem odkrywania. Odpowiada na pytanie: „który serwer autoryzacyjny chroni ten zasób i czego oczekuje?” Nie uwierzytelnia serwera, nie weryfikuje tokenu ani nie egzekwuje kontroli dostępu. Są to odrębne kwestie.

Dokument PRM ma jedno wymagane pole (resource) oraz zestaw opcjonalnych, z których najważniejsze jest authorization_servers. Specyfikacja MCP zaostrza opcjonalność RFC: wymagany jest co najmniej jeden serwer autoryzacyjny. Dokument PRM, który nie wymienia żadnego, traktowany jest jako błąd.

Pole resource jest kanonicznym URI chronionego zasobu. Klient musi porównać je z serwerem, z którym zamierzał się skontaktować, i odrzucić niezgodność:

// RFC 9728 §3.3: wartość zasobu PRM MUSI identyfikować chronione
// zasób, do którego odnosi się klient — proste porównanie ciągów znaków z
// wskaźnik zasobów, do którego ten klient przypisuje swoje tokeny.
if prm.Resource != c.resource {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: protected resource metadata declares resource %q, "+
        "expected %q (RFC 9728 §3.3 reject)", prm.Resource, c.resource)
}

Ta kontrola zapobiega klasie ataków, w których złośliwy lub błędnie skonfigurowany dokument PRM twierdzi, że reprezentuje inny zasób. Porównanie nie jest normalizowane — jest to bezpośrednie dopasowanie ciągu znaków do kanonicznego URI zasobu, który klient obliczył dla podanego mu adresu URL serwera.

Wskaźniki zasobów RFC 8707 — dlaczego powiązanie tokena ma znaczenie

Bez wskaźników zasobów, token dostępu uzyskany z AS może potencjalnie być użyty na dowolnym serwerze zasobów chronionym przez AS. Jeśli masz dwa serwery MCP — na przykład serwer dokumentacji tylko do odczytu i piaskownicę do wykonywania kodu — oba za tym samym dostawcą tożsamości, token uzyskany dla jednego mógłby zostać przedstawiony drugiemu. To jest problem z tzw. „confused deputy”.

RFC 8707 rozwiązuje to, dodając parametr resource do żądań autoryzacji i tokenów. AS wydaje token, którego odbiorcą jest wyraźnie ten URI zasobu. Poprawnie walidujący serwer zasobów odrzuca token, którego odbiorca nie odpowiada jego własnej tożsamości.

W praktyce żądanie tokena zawiera wskaźnik zasobu wraz z przyznaniem:

form := url.Values{
    "grant_type": {"client_credentials"},
    "resource":   {c.resource}, // RFC 8707 — powiąż token z odbiorcą
}

To pojawia się w każdym typie przyznawania, którego używa konektor: poświadczenia klienta, realizacja kodu autoryzacyjnego i rotacja tokena odświeżającego. Wskaźnik zasobu nie jest opcjonalny — jest obecny w każdym żądaniu tokena, więc każdy token jest z definicji powiązany z audytorium.

Sprawdzenie identyczności bajtowej wydawcy

Najbardziej krytyczna dla bezpieczeństwa weryfikacja w całym przepływie jest jednocześnie najprostsza do sformułowania i najłatwiejsza do popełnienia błędu: wartość wydawcy w dokumencie metadanych AS musi być identyczna bajt po bajcie z wydawcą, którego klient użył do skonstruowania dobrze znanego URL.

Nie równa się bez uwzględnienia wielkości liter. Nie jest równoważna po normalizacji schematu. Nie jest taka sama po usunięciu końcowego ukośnika. Identyczna bajt po bajcie. Sekcja 3.3 RFC 8414 jasno to określa, a specyfikacja MCP przejmuje to wymaganie.

// discoverASMetadata: ODRZUCA dokument, którego issuer nie jest
// BAJTOWO IDENTYCZNY z issuer, dla którego go pobrano (RFC 8414 §3.3).
// Niezgodny dokument jest sygnałem podszywania się.
if as.Issuer != issuer {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: AS metadata at %s declares issuer %q, "+
        "expected %q (RFC 8414 §3.3 reject)", cand, as.Issuer, issuer)
}

Dlaczego tak surowo? Ponieważ atakujący, który kontroluje DNS lub znajduje się na ścieżce sieciowej, może dostarczyć dokument metadanych wskazujący własny punkt końcowy tokenu, twierdząc, że jest prawowitym wystawcą. Jeśli klient znormalizowałby wystawcę przed porównaniem, https://auth.example.com i https://AUTH.example.com by się zgadzały — a dokument atakującego zostałby zaakceptowany. Porównanie identyczne bajtowo eliminuje ten problem.

Ta sama dyscyplina ma zastosowanie w RFC 9207 (walidacja wystawcy odpowiedzi autoryzacyjnej). Gdy klient rozpoczyna przepływ kodu autoryzacyjnego, zapisuje wystawcę z zweryfikowanych metadanych AS. Przy przekierowaniu z powrotem parametr iss w odpowiedzi musi odpowiadać tej zapisanej wartości — ponownie, bajt po bajcie, bez normalizacji — przed zrealizowaniem kodu autoryzacyjnego. To jest obrona przed pomyłką: bez tego, złośliwy AS mógłby przechwycić kod i sprawić, że klient zrealizuje go w punkcie końcowym tokenów atakującego.

Identyfikacja klienta: CIMD zastępuje DCR

Specyfikacja MCP definiuje kolejność priorytetową, według której klient identyfikuje się wobec serwera autoryzacyjnego:

  1. Wstępnie zarejestrowane poświadczenia — operator przygotowuje client_id i client_secret z wyprzedzeniem
  2. CIMD (Dokumenty metadanych identyfikatora klienta) — klient hostuje dokument JSON pod adresem URL HTTPS; ten adres URL jest client_id
  3. Dynamiczna rejestracja klienta (RFC 7591) — klient rejestruje się na punkcie końcowym rejestracji AS
  4. Wywołanie użytkownika — nie dotyczy agentów bez interfejsu użytkownika

DCR jest przestarzałe w wersji kandydującej do finalnej specyfikacji zaplanowanej na 2026-07-28, na korzyść CIMD. Powód jest operacyjny: DCR tworzy trwały stan klienta na serwerze autoryzacji. Każdy agent, który się rejestruje, pozostawia parę client_id/client_secret, którą musi przechowywać AS, a nikt ich nie śledzi ani nie cofa. Dla floty agentów prowadzi to do niekontrolowanego rozprzestrzeniania poświadczeń.

CIMD odwraca model. Klient przechowuje dokument pod adresem URL, którym zarządza. AS pobiera dokument, gdy potrzebuje zweryfikować klienta, buforuje go zgodnie z nagłówkami buforowania HTTP i niczego nie przechowuje na stałe. Rotacja kluczy to aktualizacja dokumentu. Wycofanie klienta to usunięcie dokumentu. Żadne osierocone rejestracje nie kumulują się na AS.

Kompromis: CIMD wymaga, aby klient uruchamiał punkt końcowy HTTPS. Dla samodzielnie hostowanej płaszczyzny zarządzania jest to naturalne — płaszczyzna już uruchamia usługi HTTPS. Dla narzędzia CLI na laptopie dewelopera jest to mniej naturalne, dlatego wstępnie zarejestrowane poświadczenia pozostają pierwszą opcją w kolejności priorytetów.

Tożsamość CIMD nie może przenosić wspólnego sekretu (adres URL dokumentu jest publiczny — sekret w nim byłby wyciekiem poświadczeń). Uwierzytelnianie klienta używa private_key_jwt (RFC 7523): klient podpisuje krótkotrwały JWT prywatnym kluczem, którego publiczny odpowiednik jest opublikowany w polu jwks dokumentu CIMD.

Reguła „nigdy nie przekazuj dalej”

Strukturalna obrona, którą łatwo przeoczyć: łącznik używa tylko tokena, który sam uzyskał, dla konkretnego serwera, poprzez opisywany powyżej proces odkrywania. Nigdy nie akceptuje tokena od osoby trzeciej ani nie przekazuje go do serwera MCP. Nigdy nie odczytuje tokena z przychodzącego żądania i nie przesyła go dalej.

To jest obrona przed zdezorientowanym pełnomocnikiem na poziomie protokołu. Gdyby klient przekazywał tokeny, które otrzymał, napastnik mógłby przedstawić token przypisany do zasobu o niskich uprawnieniach i sprawić, że klient przekaże go do zasobu o wysokich uprawnieniach (lub odwrotnie — uzyskać token o wysokich uprawnieniach, oszukując klienta, aby przedstawił go do serwera kontrolowanego przez napastnika). Poprzez uzyskiwanie własnych tokenów i nigdy nie używanie cudzych, łącznik nie może zostać zmuszony do działania jako przekaźnik tokenów.

Przekazywanie tokenów nie jest tylko odradzane — jest strukturalnie niemożliwe. Klient HTTP łącznika dla przepływów OAuth jest oddzielny od jakiegokolwiek obsługiwacza żądań przychodzących. Nie ma ścieżki kodu, która odczytuje token dostępowy (bearer token) z przychodzącego żądania i zapisuje go w wychodzącym.

SSRF: ochrona przed ponownym wiązaniem DNS

Każdy URL punktu końcowego metadanych i tokenów, który łącznik pobiera, jest chroniony przed SSRF na dwóch warstwach. Pierwsza to kontrola wstępna: URL musi być HTTPS (poza loopbackiem dla lokalnego rozwoju) i nie może być dosłownym zarezerwowanym adresem IP.

Drugim jest kontrola w czasie wybierania, która eliminuje lukę TOCTOU w przypadku DNS-rebinding. Nazwa hosta, która rozwiązuje się do publicznego adresu IP podczas wstępnej kontroli, może przy ponownym wywołaniu gniazda zostać powiązana z prywatnym adresem IP. Klient HTTP konektora instaluje funkcję net.Dialer.Control, która sprawdza konkretny rozpoznany adres IP w momencie łączenia i odrzuca wszelki adres zastrzeżony. To jest weryfikacja autorytatywna — kontrola wstępna jest szybkim odrzutem w oczywistych przypadkach, ale kontrola w czasie wybierania jest tą, która ma znaczenie.

Podwyższenie zakresu

Serwer MCP może odpowiedzieć na żądanie kodem WWW-Authenticate: Bearer error="insufficient_scope" scope="mcp:tools:list mcp:resources:read". Specyfikacja (SEP-835/SEP-2350) definiuje, jak klient powinien to obsługiwać: należy obliczyć sumę zbioru zakresów, które wcześniej żądał klient, oraz zakresów, które serwer właśnie zakwestionował, a następnie ponownie uzyskać token z tym rozszerzonym zestawem. Suma zachowuje wcześniej przyznane uprawnienia i dodaje nowe. Podwyższenie poziomu odbywa się jednokrotnie — drugie zgłoszenie niedostatecznego zakresu w tym samym żądaniu nie jest ponawiane, aby zapobiec nieskończonym pętlom.

Serwer może być bezstanowy w swoim wyzwaniu: podaje tylko zakresy potrzebne do aktualnej operacji, a nie pełny zestaw, o który klient mógł wcześniej poprosić. Akumulacja po stronie klienta umożliwia to działanie bez konieczności śledzenia historii zakresów dla każdego klienta przez serwer.

Co to oznacza w praktyce

Przepływ OAuth dla serwerów MCP jest dobrze określony i, gdy jest ściśle wdrożony, rozwiązuje rzeczywiste powierzchnie ataków: powtórne użycie tokenów między serwerami, podszywanie się pod metadane, SSRF z ponownym wiązaniem DNS oraz niekontrolowane rozprzestrzenianie poświadczeń klienta. Sprawdzenie identyczności bajtów wydawcy, wskaźnik zasobów w każdym żądaniu tokena oraz strukturalna zasada braku przejścia są podstawową ochroną. Nie są to funkcje, które można wdrożyć częściowo — każda z nich jest twardym sprawdzeniem, które kończy się niepowodzeniem w przypadku błędu.

Trudniejszym problemem jest sprawa operacyjna. Zespoły wdrażające serwery MCP muszą:

  • Opublikować ważny dokument PRM w /.well-known/oauth-protected-resource z polem resource odpowiadającym kanonicznemu URI ich serwera
  • Użyj AS, który obsługuje wskaźniki zasobów — wielu dostawców tożsamości nadal tego nie robi lub traktuje parametr resource jako doradczy, a nie powiązany z odbiorcą
  • Przejdź z DCR na CIMD zanim wycofanie zostanie zamienione na usunięcie, lub zarejestruj poświadczenia wprost
  • Przypisz poświadczenia do wydawcy aby zapobiec cichym powtórnym użyciom między wydawcami, gdy zmieni się topologia AS

Dokumentacja MCP łącznika opisuje konfigurację operacyjną, a model bezpieczeństwa wyjaśnia, jak token powiązany z zasobem wpasowuje się w szerszą mapę dostępu.

Powiązane artykuły

Najczęstsze pytania

Czy RFC 9728 gwarantuje, że serwer MCP, z którym rozmawiam, jest legalny?

Nie. RFC 9728 pozwala klientowi odkryć, który serwer autoryzacji chroni zasób i jakie wymagania dotyczące zakresów są potrzebne, ale są to metadane dotyczące zasobu, a nie dowód jego tożsamości. Certyfikat TLS potwierdza nazwę hosta serwera; PRM informuje, jak się do niego uwierzytelnić. Te dwa elementy się uzupełniają: PRM bez weryfikacji TLS to odkrywanie wobec nieweryfikowanego hosta, a TLS bez PRM pozostawia klienta w zgadywaniu, jak uzyskać token.

Dlaczego dynamiczna rejestracja klientów jest przestarzała w specyfikacji MCP, skoro nadal działa?

DCR tworzy trwały stan klienta na serwerze autoryzacji — client_id i client_secret AS muszą go przechowywać i zarządzać nim. Dla floty agentów bez interfejsu staje się to problemem niekontrolowanego rozprzestrzeniania się poświadczeń: każdy agent się rejestruje, a nikt nie śledzi ani nie rotuje tych rejestracji. CIMD (Dokumenty Metadanych ID Klienta) zastępuje to hostowanym dokumentem kontrolowanym przez klienta, który AS pobiera na żądanie — brak trwałego stanu na AS, brak osieroconych rejestracji, a rotacja kluczy to aktualizacja dokumentu zamiast ponownej rejestracji.

Sprawdź dostęp Twoich agentów

Olivares AI to otwarta platforma w modelu hostowanym samodzielnie dla całego środowiska AI w organizacji. Wdrożenie na własnej infrastrukturze zapewnia mapę dostępu, o którą od dawna proszą zespoły bezpieczeństwa i platformowe.