Strona główna / Artykuły

Dwa sposoby awarii serwerów MCP oraz warstwa ochronna zapobiegająca im

Serwery MCP zawodzą z powodu zbyt szerokich tokenów oraz narzędzi nieuwzględniających specyfiki zadań. Tokeny z ograniczonym zakresem, weryfikacja odbiorców, usunięte błędy oraz limity dla narzędzi stanowią warstwę ochronną.

Wydawca: REACTAPP.TOP8 min czytania1896 słów

Źródła: MCP Servers Fail in Two Ways, and Both Are Preventable. Here’s the Guardrail Layer.

Większość serwerów MCP nie zawodzi z powodu awarii. Poddają się awarii, przyznając większe zaufanie, niż wymaga to zadanie, i to w dwóch obszarach: uprawnieniach, które przyznają, oraz interfejsie, który udostępniają. Mądrzejszy model tego nie naprawi. Warstwa ochronna umieszczona pomiędzy agentem a narzędziem taką sytuację naprawi, a poniższe fragmenty pokazują, co zawiera takowa warstwa, wraz z kodem.

Oczyszczenie, o którym nikt nie prosił

Użytkownik każe agentowi skopiować bazę danych produkcyjną do środowiska przedprodukcyjnego. Przetwarzając tę prośbę, agent uznaje, że krok oczyszczenia byłby pomocny, i najpierw usuwa stare dane testowe. Nikt o to nie prosił. Jeśli serwer MCP przekazuje własne dane uwierzytelniające użytkownika, a ten użytkownik ma uprawnienia administratora, usunięcie odbywa się i baza produkcyjna znika.

AWS przytacza dokładnie ten scenariusz we swoich wytycznych dotyczących zarządzania MCP, wyjaśniając, dlaczego nie można pominąć tokenów ograniczających zakres dostępu.

Serwer stanowi granicę zaufania

Ludzie zazwyczaj opisują serwer MCP w taki sam sposób, w jaki opisują każdy narzędzie integracyjne: coś, co łączy agenta z systemem, oferując kilka narzędzi i realizując zadanie. W rzeczywistości to właśnie tutaj decyzja modelu o działaniu staje się prawdziwym skutkiem ubocznym, wykorzystującym rzeczywistą infrastrukturę oraz prawdziwe dane uwierzytelniające. Wszystkie kroki poprzedzające to są jedynie procesem rozumowania. Serwer to punkt, w którym rozumowanie ma konsekwencje.

Zatem serwer stanowi granicę zaufania, niezależnie od tego, czy został tak zaprojektowany, czy nie. Patrząc na serwery produkcyjne przez tę lornetkę, ich błędy dzielą się na dwie grupy.

Klasa błędów jeden: zbyt szerokie uprawnienia

Opis sytuacji z bazą danych stanowi przypadek ogólny. Wytyczne AWS dotyczące zarządzania wskazują na rozwiązanie: każde wezwanie następcze powinno zawierać token specjalnie skonfigurowany i wygenerowany dla danego wezwania, a nigdy dane uwierzytelniające, które uruchomiły agenta. Dodano również, że należy weryfikować informację o grupie docelowej tokena, co zapobiega ponownemu użyciu tokena wytworzonego dla jednego serwera MCP w innym.

Niewiele zespołów stosuje to rozwiązanie na wczesnym etapie, ponieważ wymaga ono większych przygotowań niż po prostu przekierowanie tokena nadawcy. Jednak to właśnie to stanowi różnicę pomiędzy nieudanym wezwaniem narzędzia spowodowanym błędem a takim, które faktycznie trafia do środowiska produkcyjnego.

Klasa błędów nr dwa: zła interfejs

Druga grupa problemów jest subtelniejsza. Nie dochodzi do żadnych dramatycznych incydentów. Niezawodność po prostu stopniowo spada, a jej ustalenie jest trudne. Wytyczne Microsoftu dotyczące rozwoju Azure MCP wskazują na podstawowy błąd: serwery są projektowane jak REST API, przy czym każda operacja jest dostępna jako oddzielne narzędzie, podczas gdy projekt powinien opierać się na tym, czego chce osiągnąć agent. Ilustracja dobrze ilustruje ten problem. Zamiast czterech narzędzi o nazwach create_ticket, add_comment, assign_ticket i notify_user, pojedyncze narzędzie file_support_request wykonywa wewnętrznie wszystkie cztery kroki.

Wskazówki wskazują na dwa dodatkowe objawy tego nawyku. Opisy mogą obiecywać rzeczy, których narzędzie nie dostarcza, więc agent ufa tekstowi, którego kod nigdy nie realizuje. A komunikaty o błędach mogą być bezużyteczne („Błąd”) lub zbyt poufne: ślady wywołań, wewnętrzne ścieżki plików lub potwierdzenie istnienia określonego użytkownika w bazie danych, co samo w sobie umożliwia ataki enumeracyjne.

Asymetria w kierunku, w którym kieruje się podejrzenie

Rozważmy, ile pracy inżynieryjnej wkłada się w ostateczną odpowiedź agenta: walidację struktury wyjściowej, zasady bezpieczeństwa, próby ponowne oraz kroki weryfikacyjne. Wyjście modelu jest traktowane jako podejrzane, i to słusznie.

Rozważmy teraz warstwę wywoływania narzędzi znajdującą się pod tą odpowiedzią. Token wywołującego przechodzi prosto dalej. Lista narzędzi się rozszerza, a nikt nie sprawdza, czy model nadal potrafi z nimi pracować. Opis narzędzia jest przyjmowany tak, jak jest napisany, i żaden test nie pokazuje, aby agent poprawnie go używał. Ogromny wysiłek wkłada się w kwestionowanie tego, co mówi model, natomiast prawie żaden w kwestionowanie tego, co robi.

To nierównoważone podejrzenie jest prawdziwą wadą obu grup. Ani inteligentniejsza logika uprawnień, ani lepsze nazwy narzędzi same w sobie jej nie naprawią. Potrzebna jest warstwa, która sprawdza każde wywołanie narzędzia, a nie tylko każdą odpowiedź modelu, zanim udzieli mu zaufania.

Budowa warstwy

Jednostką tutaj jest wywołanie narzędzia. Nie pozwól, aby surowy żądanie dotarło do backendu. Najpierw je otocz.

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional

@dataclass
class ScopedToken:
 audience: str # which MCP server this token is valid for
 permissions: list[str] # e.g. ["read", "create"] - never assume "all"
 issued_at: datetime
 expires_at: datetime
 source_user: str # who originally triggered this, for audit

Każde pole w tym otoczeniu istnieje po to, aby kolejna warstwa mogła ocenić, w jakim stopniu można zaufać tej żądaniu. Teraz czas na elementy sterujące.

Ogranicz zakres tokena przed uruchomieniem narzędzia

Nie używaj ponownie danych uwierzytelniających, którymi dysponował nadawca żądania, do dalszych działań. Wydaj węższy token, który obejmuje wyłącznie to, co narzędzie zamierza wykonać.

def issue_scoped_token(user_token: ScopedToken, tool_name: str,
 required_permissions: list[str]) -> ScopedToken:
 # Never grant more than the tool declares it needs
 granted = [p for p in required_permissions if p in user_token.permissions]
 if set(required_permissions) - set(granted):
 raise PermissionError(
 f"{tool_name} requires {required_permissions}, "
 f"caller only has {user_token.permissions}"
 )
 return ScopedToken(
 audience=tool_name,
 permissions=granted,
 issued_at=datetime.utcnow(),
 expires_at=datetime.utcnow() + timedelta(minutes=5),
 source_user=user_token.source_user,
 )

To właśnie ten element sprawia, że scenariusz z bazą danych kończy się bezpiecznie. Prośba skierowana wyłącznie do odczytu i tworzenia po prostu nie może wykonać operacji usunięcia, niezależnie od decyzji modelu.

Sprawdzaj grupę odbiorców przy każdym wywołaniu

Tok wydany dla jednego narzędzia powinien być odrzucony przez wszystkie inne narzędzia.

def enforce_audience(token: ScopedToken, expected_tool: str) -> None:
 if token.audience != expected_tool:
 raise PermissionError(
 f"Token issued for '{token.audience}' cannot be used on '{expected_tool}'"
 )

Kształtuj narzędzie pod zadanie

To odnosi się do wzorca file_support_request z wytycznych Azure:

def file_support_request(customer_email: str, issue_type: str, description: str) -> dict:
 ticket = create_ticket(issue_type, description)
 add_comment(ticket.id, f"Filed by {customer_email}")
 assign_ticket(ticket.id, team=route_by_type(issue_type))
 notify_user(customer_email, ticket.id)
 return {
 "ticket_id": ticket.id,
 "status": "open",
 "assigned_team": ticket.team,
 }

Jedno połączenie przekazuje jeden, jasno określony cel, więc agent nie musi samodzielnie ustalać kolejności czterech operacji.

Oczyszczaj błędy przed tym, jak zobaczy je agent

Zapisuj pełne szczegóły na serwerze i dawaj agentowi tylko to, co jest mu potrzebne do odzyskania funkcjonalności.

def safe_error(internal_message: str, request_id: str) -> dict:
 # internal_message goes to your logs, never to the model
 log.error(internal_message, extra={"request_id": request_id})
 return {
 "content": [{
 "type": "text",
 "text": f"Unable to complete the request. Request ID: {request_id}. "
 f"Try again or contact support."
 }],
 "isError": True,
 }

Ograniczenie liczby narzędzi na serwer

Dowiedz się o limicie przed rozpoczęciem produkcji. Wytyczne Microsoftu podają konkretną liczbę: maksymalnie 10 do 15 narzędzi na jednym serwerze, rozdzielonych według domen, przy czym nie należy dalej zwiększać liczby narzędzi na jednym serwerze.

MAX_TOOLS_PER_SERVER = 15

def register_tool(server, tool):
 if len(server.tools) >= MAX_TOOLS_PER_SERVER:
 raise ValueError(
 f"{server.name} already has {len(server.tools)} tools. "
 f"Split into a domain-specific server instead of adding more."
 )
 server.tools.append(tool)

Trzy szybkie sprawdzenia istniejącego serwera

Żadne z nich nie wymaga przebudowy.

Sprawdź zakres tokenów, a nie tylko ich obecność. Pobierz dziesięć ostatnich wywołań narzędzia i porównaj uprawnienia, które posiadał każdy token, z tymi, których wymagało narzędzie. Jeśli te wartości zazwyczaj się różnią, oznacza to, że przekazywane są tokeny o szerszym zakresie niż to konieczne, i to prawdopodobnie jest obecny stan rzeczy.

Oblicz liczbę tokenów dla swojego narzędzia. Zalecenia AWS szacują, że na same opisy potrzeba od 5 000 do 10 000 tokenów na każde wywołanie, gdy serwer ma ponad 20 narzędzi. Zlicz narzędzia, które posiadasz. Serwer przekraczający tę liczbę, bez żadnego filtrowania czy dzielenia danych, cierpi na nadmiar kontekstu, którego nie naprawi żadna zmiana modelu.

Wypróbuj test na poziomie agenta, który prawdopodobnie jeszcze nie został przeprowadzony. Weź trzy najczęściej używane narzędzia i sprawdź, przy użyciu rzeczywistego agenta, a nie testu jednostkowego, czy potrafi on wybrać odpowiednie rozwiązanie z codziennej prośby w języku naturalnym. Wskazówki Microsoftu opisują to jako warstwę, której prawie wszyscy pomijają. Przeprowadzenie testów jednostkowych i protokołowych bez tego sprawdzenia nie pozwala stwierdzić, czy narzędzia faktycznie działają.

Jak to pasuje do reszty

Nic z tego nie zastępuje istniejących działań związanych z niezawodnością, lecz działa obok nich. Tokeny o określonym zakresie działania kierują się tą samą zasadą co walidacja danych na każdej granicy zaufania. Ograniczenie liczby narzędzi oraz projekt skoncentrowany na zadaniach odzwierciedlają zasady wersjonowania API i projektowania interfejsów, tylko w przypadku powierzchni, którą odczytuje model, a nie programista. Każdy serwer MCP to kolejne miejsce, w którym system decyduje, w jakim stopniu może zaufać żądającemu, dlatego wymaga takiej samej uwagi jak reszta.

Zespoły przyjmują MCP, ponieważ daje on agentom większy zasięg: jeden standardowy sposób dostępu do rzeczywistych systemów zamiast setek dostosowanych integracji. Problem polega na tym, że model wykonywa żądanie o słabo określonym zakresie równie wiernie jak to dobrze określone, ponieważ nie rozróżnia tych różnic. Tylko warstwa zabezpieczeń to potrafi.

Błędy uprawnień i błędy interfejsu wydają się niespowiązane – jeden dotyczy dostępu, a drugi projektu. Oba wynikają z jednego nieuwzględnionego założenia, mianowicie że wywołanie narzędzia zasłużyło na zaufanie, którego nikt tak naprawdę nie przyznał. Większość systemów AI kieruje swoje podejrzenia na stronę wyjściową. Strona wywołań narzędzi również na to zasługuje.

To pozostawia jedno pytanie do każdego używanego serwera MCP: co obecnie powstrzymuje agenta przed działaniem na wywołanie niosące nadmierne zaufanie, i czy wyświetliłoby się ostrzeżenie, gdyby już tak było?

Szybkie odpowiedzi

Czym jest zakres tokena MCP? Oznacza to przydzielenie każdemu wywołaniu narzędzia własnego, wąsko określonego tokena do tego celu, bez przekazywania oryginalnych uprawnień osoby wywołującej. Wytyczne dotyczące zarządzania firmy AWS zalecają to rozwiązanie, aby działanie wygenerowane błędem lub niezamierzone nie mogło się powieść tylko dlatego, że podstawowe uprawnienia na to pozwalały.

Ile narzędzi powinien udostępniać jeden serwer MCP? Zalecenia Azure wskazują na wartość od 10 do 15 jako orientacyjną granicę, po której serwer powinien być podzielony według domen. W takim przypadku agenci znacznie rzadziej wybierają odpowiednie narzędzie, a według zaleceń AWS koszt długiej listy narzędzi wynosi od 5 000 do 10 000 tokenów za każdą próbę połączenia, tylko już za same opisy, przy ponad 20 narzędziach.

W czym różni się projektowanie narzędzi skierowanych na zadania od projektowania systemowych API? Projektowanie systemowych API przekształca każdą operację backendu w odrębne narzędzie, na wzór REST API. Projektowanie skierowane na zadania oferuje to, czego użytkownik chce osiągnąć, jako jedno narzędzie, które wewnętrznie obsługuje wszystkie kroki. Przykładem od Microsoftu jest pojedyncze narzędzie file_support_request zamiast czterech operacji związanych z obsługą zgłoszeń.

Dlaczego należy sanitizować komunikaty błędów MCP? Ślady wywołań, wewnętrzne ścieżki lub wskazówki dotyczące istnienia określonej rekordu dostarczają atakującym informacji, których nie powinni otrzymać, co stanowi co najmniej słabość typu enumeracji. Bezpieczniejszym podejściem jest zapisywanie pełnych szczegółów do logów po stronie serwera oraz zwracanie agencie tylko tych informacji, których potrzebuje do odzyskania funkcjonalności, wraz z identyfikatorem korelacji.

Czy testy jednostkowe wykrywają te problemy? Nie. Testy jednostkowe obejmują wyłącznie logikę narzędzia, a testy protokołu sprawdzają zgodność z MCP, ale żadne z nich nie dowodzi, że agent potrafi znaleźć, wybrać i prawidłowo użyć narzędzia w rzeczywistej sytuacji. Do tego potrzebna jest trzecia warstwa testowania realizowana przez rzeczywisty agent, której większość zespołów pomija.

Literatura pokrewna

  • Ponowne zmierzenie podatku od tokenów MCP: wzrost spowodowany opisami dwóch narzędzi — Ponowne uruchomienie dziewięciu serwerów MCP przez 13 dni pokazało, że liczba tokenów w definicjach narzędzi wzrosła z 36 320 do 38 738, przy czym prawie wszystko to wynikało z edycji opisów już istniejących na jednym z serwerów.