Im bardziej wpychamy AI do aplikacji, tym częściej wraca ten sam problem: jak dać agentowi dostęp do potrzebnych rzeczy, ale nie otworzyć mu całej aplikacji. W teorii brzmi to prosto, w praktyce łatwo zrobić bałagan. Szczególnie w aplikacjach wielodzierżawczych jeden błąd potrafi skończyć się wyciekiem danych między organizacjami. MCP ułatwia wystawianie funkcji aplikacji jako narzędzi dla agenta, ale samo w sobie nie rozwiązuje bezpieczeństwa. To nadal trzeba ogarnąć samemu i ogarnąć porządnie.
W zwykłej aplikacji Laravel często działa to „samo”, bo jest sesja, middleware i globalne scope’y. Użytkownik jest zalogowany, system zna jego organizację i automatycznie dokleja filtr typu `where organization_id = ?`. Problem zaczyna się wtedy, gdy agent działa na tokenie, a nie na klasycznej sesji. Nie ma wtedy pełnego kontekstu requestu i nie ma gwarancji, że jakiś globalny scope zadziała tak, jak myślisz. W najgorszym wariancie zapytanie, które miało być ograniczone do jednej organizacji, zacznie widzieć dane wszystkich. I to jest właśnie ten typ błędu, który niczego nie wywala, tylko po cichu robi syf.
Dlatego najbezpieczniej jest nie liczyć na „magię otoczenia”, tylko filtrować dane jawnie. Jeśli narzędzie MCP działa na zdarzeniach, to powinno mieć jedno miejsce, które zawsze rozwiązuje rekord w kontekście organizacji użytkownika. Na przykład metoda w stylu `resolveOrgEvent($user, $uuid)`, która bierze event tylko z organizacji przypisanej do użytkownika. Jeśli użytkownik nie ma organizacji albo event nie należy do niego, dostaje `null`. I tyle. Bez zgadywania, bez wyjątków zdradzających za dużo, bez bocznych kanałów informacyjnych.
Druga sprawa to identyfikatory. Publicznie powinny wychodzić UUID, nie rosnące ID z bazy. Jeśli agent dostanie endpoint oparty o `event/1`, `event/2`, `event/3`, to prędzej czy później ktoś zacznie to sprawdzać hurtowo. UUID nie rozwiązuje wszystkiego, ale przynajmniej utrudnia enumerację zasobów. To jest zwykła higiena bezpieczeństwa, nie żadna egzotyka.
Samo ograniczenie danych to jeszcze nie wszystko, bo dochodzi pytanie: co agent w ogóle może zrobić. Tu najlepiej nie wymyślać osobnego systemu uprawnień dla AI, tylko używać tego samego RBAC, które już działa w aplikacji. Jeśli człowiek potrzebuje `events.view.details`, to agent też powinien tego potrzebować. Dzięki temu nie budujesz drugiej matrycy uprawnień, która po miesiącu i tak się rozjedzie. Dobrze też jasno rozdzielić narzędzia tylko do odczytu od tych, które coś zmieniają. `list_events` i `event_readiness_check` to jedno, `publish_event` to już inna liga.
Jak takich narzędzi zaczyna przybywać, to bardzo szybko widać, czy masz wzorzec, czy każdy robi po swojemu. Dobre podejście jest proste: narzędzia listujące pokazują tylko zasoby z organizacji użytkownika, narzędzia działające na konkretnym rekordzie zawsze przechodzą przez wspólne rozwiązywanie zasobu, autoryzacja leci przez istniejące uprawnienia aplikacji, a zapis i odczyt są jasno rozdzielone. Jak wszystkie narzędzia trzymają się tych samych zasad, to łatwiej to testować, audytować i utrzymać.
No i właśnie testy są tu obowiązkowe. Nie tylko „czy działa”, ale przede wszystkim „czy nie da się wyjść poza organizację”. Trzeba sprawdzić, że user z orgA nie zobaczy eventu z orgB nawet wtedy, gdy poda poprawny UUID. Trzeba też sprawdzić przypadek użytkownika bez organizacji, bo brak organizacji ma oznaczać brak dostępu, a nie przypadkowy full access. Takie testy łapią błędy, które w normalnym klikaniu potrafią przejść niezauważone.
Cały temat sprowadza się do jednej rzeczy: agent AI to też klient systemu, tylko trochę bardziej nieprzewidywalny. Nie można zakładać, że „będzie grzeczny” albo „sam się ograniczy”. Jeśli wystawiasz aplikację przez MCP, to musisz jawnie ograniczać tenant, używać normalnych uprawnień, nie wypuszczać publicznie prostych ID, rozdzielać odczyt od zapisu i pilnować tego testami. To nie jest jakaś specjalna zasada dla AI. To po prostu porządnie zrobione bezpieczeństwo w aplikacji wielodzierżawczej.
25.06.2026 · 🤖 AI