feat(mcp)!: port na MCP SDK 2.0 (FastMCP → MCPServer) + wydanie 0.3.0 - #19
Merged
Conversation
Spec powstał po rozpoznaniu API 2.0 w źródłach i zweryfikowaniu każdego twierdzenia na próbnym porcie wykonanym na kopii repo w izolowanym środowisku — nie na podstawie samej lektury dokumentacji. Najważniejsze ustalenie: port to w większości rename (`.tool()`, `.prompt()`, `.custom_route()`, lifespan, `ctx.request_context` i cała warstwa OAuth RS przechodzą bez zmian), ale kryje jeden realny błąd. SDK 2.0 przestało doklejać ukośnik do URL bez ścieżki, przez co PRM ogłaszałby inny issuer niż nasz dokument AS — dokładnie ta niezgodność, którą 863b7f4 celowo eliminował. Powód jest strukturalny: normalizacja issuera była zduplikowana po obu stronach i musiała dawać wynik bajt w bajt zgodny. Spec zamyka to jednym źródłem prawdy zamiast dopasowania stałej. Migracja na httpx2 świadomie poza zakresem: respx nie przechwytuje httpx2, więc oznaczałaby przepisanie 138 wywołań w 15 plikach testów jednocześnie z portem, który te testy mają pilnować. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`mcp.server.fastmcp` zniknęło w 2.0 bez warstwy zgodności, a gałąź 1.x jest w trybie maintenance. Zależność: `mcp>=2,<3`, bez extra `[cli]` (mamy własny argparse, extra ciągnęło typer + python-dotenv bez powodu). Doszedł jawny `starlette` — importujemy `JSONResponse` wprost, a tranzytywność nie jest kontraktem; dokładnie tak straciliśmy `httpx`, gdy SDK przeszło na `httpx2`. Sufit `<3` zostaje świadomie. Zdjęcie go odtwarzałoby mechanizm awarii 0.1.1: `test-newest-deps` jest informacyjny (`continue-on-error`), więc nie zatrzyma wydania, a canary chodzi raz w tygodniu i JUŻ PO publikacji — żadne z nich nie jest bramką. Sufit podnosimy ręcznie, po porcie. `uv.lock` przegenerowany. Bez tego blokujący job `test` szedłby dalej po zamrożonym 1.28.1 i cała matryca 3.10–3.13 nie dotknęłaby portu. Jedyna zmiana strukturalna: SDK 2.0 wyjęło host/port z konstruktora, więc `_auth_kwargs` rozcięte na nie (argumenty konstruktora) i `_http_kwargs` (argumenty `run()`). `host` musi zostać w tym drugim, bo `run()` przekazuje go do `streamable_http_app(host=...)`, skąd bierze się ochrona przed DNS-rebinding opisana w docs/uwierzytelnianie.md. NAPRAWA BŁĘDU, nie tylko rename: SDK 2.0 przestało doklejać ukośnik do URL bez ścieżki (`url_preserve_empty_path`). Normalizacja issuera była zduplikowana — `AuthSettings` po stronie PRM i własny `AnyHttpUrl` w `authorization_server_metadata` — i musiała dawać wynik bajt w bajt zgodny (RFC 8414 §3.3). Po zmianie w SDK obie ciche rozjechałyby się, wywracając „authorize" dokładnie w trybie PROXY, dla którego ten kod powstał. Zamiast dopasowywać stałą: issuer płynie z JEDNEGO obiektu AuthSettings, a `authorization_server_metadata` wypisuje go dosłownie. Rozjazd staje się niemożliwy z konstrukcji, nie dzięki czyjejś pamięci. Przy okazji `version=` w konstruktorze: 2.0 domyśla w serverInfo pusty string (1.x podstawiało wersję SDK, co i tak wprowadzało w błąd). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trzy luki, które port na SDK 2.0 albo otworzył, albo obnażył. 1. Gwarancja offline przestała mieć pokrycie. `docs/rozwoj.md` obiecuje, że pakiet testów nie robi żywych wywołań, i dotąd pilnował tego respx. Ale SDK 2.0 chodzi po `httpx2`, którego respx NIE przechwytuje (sprawdzone: żądanie wychodzi do sieci). Test idący przez stos SDK odpytałby prawdziwy serwer i przeszedł. Blokada gniazd w conftest zostawia loopback, bo testy logowania OAuth stawiają serwer pętli zwrotnej (RFC 8252). Dwa testy pilnują samej blokady — inaczej mogłaby przestać działać niezauważona. 2. W repo nie było ANI JEDNEGO testu na poziomie protokołu: zero `ClientSession`, zero handshake'u. Całe ryzyko zmiany SDK jest tymczasem protokolarne. Nowe testy gadają z serwerem w podprocesie po stdio i już przy pisaniu złapały snake_case z 2.0 (`serverInfo` → `server_info`). Sieci nie ruszają — initialize i listowanie obsługuje sam serwer — więc chodzą w zwykłym CI, nie pod markerem „live". 3. Izolacja tokenu między użytkownikami stoi teraz na węższej podstawie. W 1.x lifespan wchodził per sesja; w 2.0 wchodzi RAZ, a jego wynik dzielą wszystkie sesje i żądania. Jedyne, co trzyma bearery osobno, to ContextVar. Test zweryfikowany mutacją: po podmianie ContextVara na stan globalny pada, czyli faktycznie broni niezmiennika, a nie tylko go opisuje. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port na MCP SDK 2.0. Dla użytkownika funkcjonalnie bez zmian, ale podmiana głównej wersji SDK pod spodem to za dużo na patcha. Spec uzupełniony o bramki przed tagiem (świeże `uvx`, ręczny `workflow_dispatch` na mcpb.yml, test dymny na żywym BPP) i o ścieżkę wycofania. Ta druga ma dwa kroki, nie jeden: `yank` na PyPI cofa resolvery do sprawnego 0.2.0, ale NIE rusza linku /releases/latest/download/bpp-mcp.mcpb, do którego prowadzi przycisk „Zainstaluj w Claude Desktop" w README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Zdejmuje pin
mcp<2z 0.2.0 — port na natywneMCPServerz SDK 2.0.Co się okazało
Port to w większości rename:
.tool(),.prompt(),.custom_route(), lifespan,ctx.request_contexti cała warstwa OAuth RS (AuthSettings,TokenVerifier,AccessToken) przechodzą bez zmian. Jedyna zmiana strukturalna: SDK 2.0 wyjęłohost/portz konstruktora, więc_auth_kwargsrozcięte na nie i_http_kwargs(argumentyrun()).Zweryfikowane najpierw na próbnym porcie wykonanym na kopii repo w izolowanym środowisku, przed tknięciem prawdziwych plików.
Naprawa błędu, nie tylko rename
SDK 2.0 przestało doklejać ukośnik do URL bez ścieżki (
url_preserve_empty_path). Normalizacja issuera była zduplikowana —AuthSettingspo stronie PRM i własnyAnyHttpUrlwauthorization_server_metadata— i musiała dawać wynik bajt w bajt zgodny, bo tego wymaga RFC 8414 §3.3. Zmierzone przed poprawką:To wywracałoby „authorize" dokładnie w trybie PROXY, dla którego ten kod powstał — czyli kasowało niezmiennik, który
863b7f4celowo ustawiał. Zamiast dopasować stałą, issuer płynie teraz z jednego obiektuAuthSettings, aauthorization_server_metadatawypisuje go dosłownie. Rozjazd jest niemożliwy z konstrukcji, a nie dzięki czyjejś pamięci.Trzy luki, które port otworzył albo obnażył
Gwarancja offline straciła pokrycie.
docs/rozwoj.mdobiecuje testy bez żywych wywołań i dotąd pilnował tego respx — ale SDK 2.0 chodzi pohttpx2, którego respx nie przechwytuje (sprawdzone: żądanie wychodzi do sieci). Doszła blokada gniazd wconftest.py, z przepustką dla loopbacku (logowanie OAuth stawia serwer pętli zwrotnej).Zero testów na poziomie protokołu. W repo nie było ani jednego
ClientSessionani handshake'u, choć całe ryzyko zmiany SDK jest protokolarne. Nowe testy gadają z serwerem w podprocesie po stdio i już przy pisaniu złapały snake_case z 2.0 (serverInfo→server_info). Sieci nie ruszają, więc chodzą w zwykłym CI.Izolacja tokenu na węższej podstawie. W 1.x lifespan wchodził per sesja; w 2.0 wchodzi RAZ i jego wynik dzielą wszystkie sesje. Jedyne, co trzyma bearery osobno, to ContextVar. Test zweryfikowany mutacją — po podmianie ContextVara na stan globalny pada.
Uwaga o zależnościach
mcp>=2,<3— sufit zostaje. Zdjęcie go odtwarzałoby mechanizm awarii 0.1.1:test-newest-depsjestcontinue-on-error, więc nie zatrzyma wydania, a canary chodzi raz w tygodniu i już po publikacji. Żadne z nich nie jest bramką.uv.lockprzegenerowany — bez tego blokujący jobtestszedłby dalej po zamrożonym 1.28.1 i cała matryca nie dotknęłaby portu. Odpadło extra[cli](mamy własny argparse), doszedł jawnystarlette.Weryfikacja
211 testów zielonych (204 + 7 nowych), ruff czysty,
uv sync --lockedspójny,twine check --strictprzechodzi.uvx --from .na świeżym rozwiązaniu startuje na 3.10 i 3.13. Test dymny po stdio na żywymbpp.umlub.plzwraca prawdziwe dane; tryb HTTP wykrywapassthroughi oddaje 401 zWWW-Authenticate.Żaden test nie został usunięty ani osłabiony. Zmienione asercje dotyczą wyłącznie kanonicznej postaci issuera — czyli świadomej zmiany zachowania SDK — i są wymienione w commitach.
Przed tagiem
Spec zawiera bramki (ręczny
workflow_dispatchnamcpb.yml, bo.mcpbjest dziś budowana dopiero na tagu, czyli po kroku nieodwracalnym) oraz dwustopniową ścieżkę wycofania:yankna PyPI cofa do sprawnego 0.2.0, ale nie rusza linku.mcpbz README.🤖 Generated with Claude Code