← Wszystkie notatki

Klucz w konfiguracji, którego nie ma. Serwer MCP dla Zakupomatu w jeden wieczór

· Nie jednym promptem · odcinek 4

Mówisz Claude'owi: "weź ten przepis na ciasto i dopisz mi do listy wszystko poza jajkami, a jeśli coś już tam jest, to zwiększ ilość". Chwilę później Marta widzi mąkę, cukier i masło w Zakupomacie na swoim telefonie. Bez przepisywania, bez kopiuj-wklej.

Tak to dziś działa. Ale droga do tego była ciekawsza niż sam efekt. Ten odcinek jest dłuższy i bardziej techniczny niż poprzednie - tu jest całe mięso ;-)

Dlaczego MCP, a nie AI w środku Zakupomatu

Zanim o technologii - najważniejsza decyzja tej historii nie była techniczna.

Zakupomat ma na zawsze pozostać darmowy. Bez reklam, bez sprzedawania danych, bez abonamentu. A funkcje AI są super, ale kosztują - najczęściej za każde użycie. Każde "ułóż mi listę z przepisu" to wywołanie modelu, za które ktoś płaci. Im więcej sprytnych funkcji, tym wyższy rachunek za utrzymanie aplikacji, która nie ma z czego go pokryć. Prędzej czy później kończy się to reklamami, abonamentem albo "darmową" wersją, która nic nie umie. Nie, dziękuję!

A przecież większość z nas ma już swojego asystenta AI - Claude'a, ChatGPT, Gemini. Często nawet w darmowej wersji wystarczy, żeby zrobić to, czego potrzebujemy. I tu jest druga, ważniejsza rzecz: ten asystent wie o nas dużo więcej niż Zakupomat kiedykolwiek będzie wiedział. Ma dostęp do maila i kalendarza. Wie, że w sobotę robimy imprezę na 10 osób. I że po niedawnych wakacjach w Meksyku będą przysmaki kuchni meksykańskiej ;-) AI wbudowane w aplikację do listy zakupów nigdy tego nie zobaczy.

Więc zamiast wkładać AI do Zakupomatu, wpuszczam Zakupomat do AI, którego już używasz. Model "myśli" na Twoim koncie i za Twoje (często zerowe) pieniądze, a Zakupomat robi to, w czym jest dobry - trzyma wspólną listę rodziny i synchronizuje ją na żywo na wszystkich telefonach.

Koszt po mojej stronie? Kilka dodatkowych zapytań do serwera, który i tak działa. Zero złotych za tokeny.

Pomysł: "w konfiguracji podaję klucz i działa"

27 września wieczorem, tuż po tym, jak wersja 1.4.0 weszła do obu sklepów, napisałem do Claude'a:

"Na razie chciałbym mieć serwer MCP, który da się dodać do Claude'a i ChataGPT, gdzie w konfiguracji podaję klucz i już można działać z listą. Docelowo pluginy chciałbym mieć od razu na listach dodatków dostępnych w "sklepach" obu tych agentów."

Dla porządku: MCP (Model Context Protocol) to otwarty protokół (opracowany przez Anthropic), przez który asystent AI może korzystać z narzędzi zewnętrznej usługi. Serwer MCP wystawia swoje menu - listę narzędzi ("pokaż listę", "dodaj produkt", "odhacz pozycję"), a asystent sam decyduje, kiedy któreś wywołać.

Zakupomat nie ma kont. Rodzina ma jeden losowy klucz dostępu i tyle. Więc "podaję klucz w konfiguracji" brzmiało naturalnie.

Tylko że takiej konfiguracji nie ma. Agent najpierw przeczytał aktualną dokumentację Anthropic i OpenAI i wyszło, że zdalny serwer MCP w claude.ai i w ChatGPT działa albo bez żadnego uwierzytelnienia, albo przez OAuth. A katalogi obu asystentów wymagają OAuth. Pola "wklej swój klucz API" po prostu nie ma.

OAuth bez kont

Rozwiązanie zachowało intencję: użytkownik dalej tylko wkleja klucz rodziny. Tyle że nie w ustawieniach asystenta, a na stronie Zakupomatu, do której asystent go przekierowuje w ramach standardowego przepływu OAuth. "Logowaniem" jest klucz rodziny, a asystent dostaje token, który wskazuje tę jedną rodzinę.

Odrzucone opcje:

  • Klucz w adresie serwera (/mcp?key=…). Ląduje w logach serwera, a specyfikacja MCP wprost zabrania przekazywania tokenów w query stringu.
  • Serwer bez uwierzytelnienia. Nie wymaga komentarza ;-)

Co jest w środku:

  • serwer autoryzacji OAuth 2.1 z PKCE (S256) i rotacją refresh tokenów,
  • rejestracja klienta na dwa sposoby: DCR (Dynamic Client Registration - klient rejestruje się sam) i CIMD (Client ID Metadata Document - klient podaje adres dokumentu, który go opisuje, a serwer ten dokument pobiera),
  • strona "Połącz z Zakupomatem" w trzech językach,
  • endpoint /mcp i narzędzia na liście zakupów.

I dwa zabezpieczenia, które łatwo przeoczyć:

  1. Adresy powrotu tylko dla Claude'a, ChatGPT i loopbacka. Bez tego dowolna strona mogłaby zarejestrować własnego "klienta" i zbierać klucze rodzin przez prawdziwą stronę Zakupomatu. Phishing z oficjalnym certyfikatem.
  2. Dokumenty CIMD pobierane tylko z hostów Anthropic i OpenAI. Inaczej ktoś mógłby zmusić mój serwer do odpytywania dowolnych adresów (klasyczny SSRF).

Do tego zgadywanie klucza na stronie łączenia liczy się do tego samego limitu co logowanie w aplikacji - 10 błędów na 15 minut z jednego IP.

Python 3.11 był. Tylko cudzy

Oficjalne SDK MCP dla Pythona wymaga wersji 3.10 lub nowszej. Agent sprawdził serwer przez SSH: systemowy Python to 3.9.2. Odpisałem, że przecież niedawno coś budowałem i na serwerze jest 3.11. I miałem rację! Tylko że ten 3.11 był zainstalowany przez uv w katalogu zupełnie innego projektu ;-)

Do wyboru było:

  • osobna usługa na Pythonie 3.11 z oficjalnym SDK - musiałaby trzymać klucze rodzin albo dostać drugi dostęp do bazy,
  • pożyczyć Pythona z cudzego projektu - nie, dziękuję,
  • przenieść cały backend na 3.11 - konflikty zależności i ryzyko na produkcji dla jednej funkcji,
  • wbudować MCP w istniejący backend i napisać protokół ręcznie.

Wygrała ostatnia opcja i z perspektywy czasu to była najlepsza decyzja tego wieczoru. Dlaczego:

  • token wskazuje household_id, więc klucza rodziny nie trzeba nigdzie przechowywać,
  • narzędzia MCP wołają te same funkcje co aplikacje, więc zmiana zrobiona przez asystenta od razu leci do otwartych telefonów przez SSE (połączenie na żywo, które żyje w pamięci tego samego procesu),
  • zakupy zakończone przez asystenta trafiają do tej samej historii zakupów,
  • usunięcie rodziny kasuje też jej połączenia z asystentami.

"Ręcznie" brzmi groźnie, ale potrzebny był mały podzbiór protokołu: initialize, ping, tools/list i tools/call. Bezstanowo, odpowiedzi zwykłym JSON-em, bez sesji. Zgodność sprawdzona oficjalnym klientem z SDK MCP 2.2.0 - lokalnie i na produkcji.

Wszystko to siedzi w pamięci projektu jako decyzja D-026, razem z odrzuconymi alternatywami.

Narzędzia, które asystent rozumie

Pierwsza wersja miała 7 narzędzi do listy zakupów. Połączyłem Claude'a i od razu chciałem sprawdzić dwie rzeczy, które od dawna mnie uwierały: czy poprawi mi literówki w nazwach produktów i czy ustawi nowe produkty we właściwym miejscu na liście, tak jak chodzimy po sklepie. I... dupa! Nie mógł. Agent zrobił świetną listę zakupów, ale o bazie produktów zapomniał.

"Czemu Claude nie może edytować listy produktów - dodawanie, edycja nazw, usuwanie, zmiana kolejności to powinny być opcje dostępne w MCP..." - napisałem. Kwadrans później doszły 4 kolejne narzędzia do bazy produktów. Razem 11: 2 tylko do odczytu, 9 zapisujących. I nie trzeba było nawet łączyć się od nowa - wystarczyła nowa rozmowa.

Dwie decyzje projektowe, które robią różnicę:

  • Produkty wskazuje się nazwą, nie identyfikatorem (bez względu na wielkość liter). Asystent mówi "mleko", a nie id: 4711.
  • Przestawianie jest względne: "za produktem X", "przed Y", "na początek", "na koniec". Asystent nie musi wysyłać całej listy identyfikatorów w nowej kolejności.

Projektujesz API dla programisty - dajesz mu identyfikatory. Projektujesz dla agenta - dajesz mu język, którym on myśli.

Jeden wieczór, pięć PR-ów

GodzinaPRCoZmiany
23:27#110Serwer MCP z logowaniem kluczem rodziny+1863 / -25
23:37#111CIMD z własnym User-Agentem+29 / -2
23:48#112Edycja bazy produktów przez MCP+309 / -16
00:09#113Strona pomocy + MCP zgodny z decyzją o notatkach+803 / -22
00:24#114Aktualne kroki dla ChatGPT+12 / -12

Około 1460 linii serwera (OAuth, protokół, strona łączenia), testy backendu z 71 do 111 po pierwszym PR. Od pierwszego do ostatniego scalenia - niecała godzina, z przejściem przez północ ;-)

I najlepsze: całość robiłem na telefonie, przez sesję /remote-control w Claude Code. Siedząc w wannie! Agent pracował na moim komputerze, a ja czytałem, decydowałem i pisałem "wdrażaj" między jedną a drugą dolewką ciepłej wody ;-)

Ciekawsze od tabelki jest to, że trzy z tych pięciu PR-ów to poprawki po zderzeniu z rzeczywistością.

Test przeszedł. Claude nie

Agent przetestował cały przepływ oficjalnym klientem z SDK. Zielono. Wdrożone.

Dodaję łącznik w Claude na telefonie i... "Can't connect: this access request isn't valid or comes from an unknown app". Wysłałem Claude'owi zrzut ekranu z jednym pytaniem: "O co chodzi?" (z tym, że użyłem innych słów :P).

Odpowiedź była w logach Apache. Oficjalny klient z SDK rejestruje się przez DCR. Prawdziwy Claude łączy się przez CIMD - podaje adres swojego dokumentu na claude.ai, a mój serwer ten dokument pobiera. I tu niespodzianka: claude.ai odpowiada 403 na domyślny User-Agent Pythona (Python-urllib). Z własnym User-Agentem - 200.

Poprawka to jedna linijka (PR #111, +29/-2 razem z testem i logowaniem). Ale znalezienie jej bez logów byłoby niemożliwe, bo nieudane pobranie dokumentu było po cichu połykane. Teraz trafia do logu.

Lekcja dla każdego, kto testuje integracje: test przeszedł inną ścieżką niż ta, na której był błąd. Oficjalny klient testowy to nie to samo co prawdziwy klient na produkcji. Agent ma swoje testy. Człowiek ma prawdziwy telefon.

Hamulec bezpieczeństwa

Przy pierwszym wdrożeniu agent wywołał backup bazy, a potem... nie mógł pójść dalej. Automatyczny klasyfikator uprawnień w Claude Code zatrzymał wdrożenie na produkcję, dopóki nie napisałem "wdrażaj".

Na początku trochę mnie to irytowało. Dziś uważam, że dokładnie tak ma być - agent może przygotować wszystko, ale ostateczna decyzja o wyjściu na proda powinna należeć do człowieka, który wcześniej sprawdzi wszystko, co uzna za stosowne.

Agent złapał, że łamie decyzję projektu

Przy pisaniu strony pomocy agent porównał ją z llms.txt (opis Zakupomatu dla modeli AI) i zauważył, że llms.txt mówi "No notes on items", a jego własne narzędzia MCP pozwalają dopisywać notatki do pozycji. Sprzeczność z decyzją D-017 z lipca, kiedy notatki świadomie usunąłem z aplikacji.

Agent zgłosił to z numerem decyzji i poprawił MCP, zamiast po cichu ją obejść. Szczegóły asystent zapisuje teraz w polu ilości, stare notatki widzi i ich nie kasuje.

To jest dokładnie to, po co powstał brain projektu. Bez zapisanej decyzji agent nie miałby z czym się porównać.

ChatGPT: zmiana pod nogami

W Claude łącznik po poprawce dodał się od ręki. W ChatGPT - już nie tak prosto.

Dokumentacja sprawdzona na początku wieczoru mówiła o "Apps". Godzinę później w ChatGPT były już "Plugins", a przełącznik trybu dewelopera przeniesiony gdzie indziej. Instrukcje na stronie trzeba było poprawiać tego samego wieczoru (PR #114).

A potem kolejna niespodzianka. W aplikacji mobilnej ChatGPT i w przeglądarce na telefonie opcji dodania własnego serwera po prostu nie było. Z telefonu wyglądało to tak, jakby ChatGPT nas nie wpuszczał. Dopiero w aplikacji desktopowej się udało - choć, jeśli dobrze pamiętam, samo wpisanie adresu serwera MCP nie wystarczyło i potrzebny był cały plugin. Zresztą właśnie tak używam Zakupomatu w ChatGPT do dziś.

Tylko że zwykły użytkownik nie będzie instalował pluginów z paczki na komputerze. Żeby Zakupomat w ChatGPT był dla każdego, trzeba trafić do katalogu. Więc następnego dnia zgłosiłem ANSLAN jako dewelopera w OpenAI.

Co było dalej z katalogami - jak Codex przygotował paczkę, Claude powtórzył jego błąd, a model próbował zapłacić za zakupy moją kartą - opowiem w następnym odcinku ;-)

Co to znaczy dla Twojego zespołu

Asystenty AI to nowy kanał wejścia do produktu - obok aplikacji, strony i API. I ten kanał ma swoje pułapki:

  • Testuj prawdziwymi klientami, nie tylko oficjalnym SDK. Test przeszedł inną ścieżką niż ta, na której był błąd. I miej logi, które nie połykają błędów.
  • Bezpieczeństwo OAuth to nie tylko PKCE. Lista dozwolonych adresów powrotu i pobieranie metadanych tylko z zaufanych hostów to różnica między integracją a narzędziem do phishingu.
  • Narzędzia dla agentów mówią językiem agentów: nazwy zamiast identyfikatorów, operacje względne zamiast pełnych stanów.
  • Najprostsza architektura bywa najlepsza. MCP wbudowany w istniejący backend dał za darmo izolację rodzin, zmiany na żywo i historię zakupów.

A przede wszystkim - agent zbuduje to w jeden wieczór, ale tylko jeśli ktoś wie, czego szukać, i ma zapisane decyzje, z którymi agent może się porównać.

W ANSLAN pomagamy przygotować istniejące systemy do pracy z agentami AI - także po tej stronie, gdzie to agenty AI są użytkownikami Twojego produktu.

A sam Zakupomat możesz po prostu wziąć i używać: zakupomat.app. Na zawsze za darmo, bez reklam, bez sprzedaży danych i bez abonamentu. Android, iPhone, przeglądarka - i Claude. Jak połączyć asystenta: zakupomat.app/ai.html.

Do zobaczenia za tydzień :-)