Buduj nowy MCP server MateMatic (lub retrofit istniejacego) z 5 elementami zwalidowanymi na dograh v1.31.0 - FastMCP(instructions=) z procedural orchestration, drift test, dwukanalowy auth X-API-Key...
Wzorzec kanoniczny dla MCP serverow MateMatic. Walidowany empirycznie na dograh-hq/dograh v1.31.0 (production system, 3-4 dni release cycle, 2.6k gwiazdek, drift testy w CI, BSD-2).
FastMCP(instructions=...) z procedural orchestrationInstrukcje wstrzykiwane do system promptu kazdego klienta MCP. LLM widzi je PRZED pierwszym tool call.
Tresc:
Anti-content:
tools/list)PropertySpec.llm_hint)Wzor (Python):
from fastmcp import FastMCP
from .instructions import MY_MCP_INSTRUCTIONS
from .tools.foo import foo_tool
from .tools.bar import bar_tool
mcp = FastMCP("matematic-saos", instructions=MY_MCP_INSTRUCTIONS)
for _tool in (foo_tool, bar_tool):
mcp.tool(_tool)
tests/test_mcp_instructions_drift.py)Fail jesli:
Wzor w examples/test_instructions_drift.py.
FastMCP domyslnie stripuje Authorization header. MUSISZ explicit get_http_headers(include={"authorization"}).
from fastmcp.server.dependencies import get_http_headers
async def authenticate_mcp_request() -> User:
headers = get_http_headers(include={"authorization"})
api_key = headers.get("x-api-key")
if not api_key:
auth = headers.get("authorization", "")
if auth.lower().startswith("bearer "):
api_key = auth.split(" ", 1)[1].strip()
if not api_key:
raise HTTPException(
status_code=401,
detail="Missing API key - send X-API-Key or Authorization: Bearer <key>",
)
return await _handle_api_key_auth(api_key)
<server>.org_id per-tenant routingPer-org routing do Langfuse/observability:
from opentelemetry import trace
span = trace.get_current_span()
if span.is_recording():
org_id = user.selected_organization_id
span.set_attribute("mcp.org_id", str(org_id))
span.set_attribute("mcp.user_id", str(user.id))
span.set_attribute("langfuse.user.id", str(user.id))
WAZNE rozroznienie z dograh-auth:
<server>.org_id (np. dograh.org_id) triggeruje per-org Langfuse project routing dla pipeline spansmcp.org_id zeby zostalo na default (developer-facing) projectPozwala klientowi MCP automatycznie zatwierdzac wywolania bez monitu (readOnlyHint=True + destructiveHint=False).
from mcp.types import ToolAnnotations
_READ_ONLY = ToolAnnotations(
readOnlyHint=True,
idempotentHint=True,
destructiveHint=False,
openWorldHint=False,
)
for _tool in (list_docs, search_docs, read_doc):
mcp.tool(_tool, annotations=_READ_ONLY)
code bledu + test, ktory go przypinaWzorzec zmierzony na firecrawl/anydoc (MIT, audyt 2026-08-08). Ich ConvertError
ma metode code() zwracajaca stabilny string maszynowy (unsupported,
encrypted, resourceLimit, ...), oddzielony od komunikatu dla czlowieka - plus
test, ktorego jedynym zadaniem jest pilnowanie, ze te stringi sie nie zmienily,
z komentarzem "bindingi publikuja to doslownie, wiec zmiana lamie kazdego konsumenta".
Nasz odpowiednik: agent branchuje na code, czlowiek czyta message. Bez tego
LLM parsuje polski tekst bledu regexem i lamie sie przy pierwszej korekcie stylu.
class ToolError(Exception):
"""Blad narzedzia MCP. `code` = kontrakt maszynowy, `message` = dla czlowieka."""
CODES = ("not_found", "invalid_input", "upstream_unavailable",
"rate_limited", "forbidden", "partial_result")
def __init__(self, code: str, message: str, **detail):
assert code in self.CODES, f"nieznany code: {code}"
self.code, self.message, self.detail = code, message, detail
def as_result(self) -> dict:
return {"status": "failed", "code": self.code,
"message": self.message, "detail": self.detail}
# tests/test_error_codes_stable.py
def test_kody_bledow_sa_stabilne():
"""Konsumenci (agent, klient MCP) branchuja na tych stringach.
Zmiana ktoregokolwiek = breaking change, wiec ma boles TUTAJ, nie u klienta."""
assert set(ToolError.CODES) == {
"not_found", "invalid_input", "upstream_unavailable",
"rate_limited", "forbidden", "partial_result",
}
Sprzezenie z partial_result (kardynalna regula cichej niekompletnosci): tool,
ktory zwrocil CZESC danych, NIE MOZE zwrocic status: ok. Trojstan
ok | degraded | failed plus pelny mianownik w tej samej odpowiedzi:
return {"status": "degraded", "code": "partial_result",
"coverage": {"unit": "pages", "total": 14, "usable": 9},
"message": "9 z 14 stron bez warstwy tekstowej", "data": blocks}
Anty-wzorzec, ktory to unaocznil: anydoc liczy poprawny mianownik
("{} of {} pages need OCR"), po czym wysyla go do fasady log, ktorej
nigdzie nie rejestruje - konsument dostaje Ok i niepelny dokument.
Zdarzenie niepelnosci ma isc TA SAMA droga co wynik, nie kanalem obok.
6b. Kod niesie tez „czy ponawiac" - i tabela mieszka przy zrodle (2026-10-06).
Sam stabilny code nie mowi agentowi, co zrobic. Pod jednym statusem (np. 429) siedza
sytuacje przeciwne: limit na minute warto ponowic za 60 s, limit dobowy - dopiero jutro,
limit zyciowy - nigdy. Klient ponawiajacy kazde 429 wali wtedy w limit dobowy w petli.
Dlatego kazdy kod dostaje w JEDNEJ tabeli: status HTTP, retryable i okno odnowienia,
a odpowiedz niesie retryable + retry_after_s (i naglowek Retry-After w HTTP).
KODY = { # jedna tabela, przy miejscu, gdzie kody sie rodza
"rate_limited": {"http": 429, "okno": "minuta"}, # retryable, 60 s
"quota_daily": {"http": 429, "okno": "doba"}, # nie dzis, Retry-After do polnocy
"quota_total": {"http": 429, "okno": None}, # nigdy
"upstream_unavailable": {"http": 503, "okno": "minuta"}, # timeout / 5xx zrodla
"invalid_input": {"http": 400, "okno": None},
}
Test ma porownac tabele ze ZRODLEM w obie strony (kod uzyty w kodzie, a nieobecny w tabeli
= czerwien; wpis w tabeli, ktorego kod nie uzywa = czerwien) i sprawdzic status, retryable
i Retry-After na prawdziwej sciezce odpowiedzi. Lustro „przepisane ze zrodla" bez takiego
testu gnije: reczna lista 4 powodow odmowy przy 13 w zrodle, a limit dobowy wracal przez REST
jako HTTP 200 (zmierzone 2026-10-06, z kontrola pozytywna parsera i mutantem). Wzorzec z
helixdb/helix-db (Apache-2.0, idea): QueryErrorCode + klasa bledu -> (HTTP, retryable) +
test tabelaryczny + diff tabeli w docs z enumem. commit_outcome_unknown = 503 i NIE
ponawiaj: gdy nie wiadomo, czy zapis sie utrwalil, ponowienie moze go zdublowac.
6c. Czyja wina, cicha awaria zrodla i wynik czesciowy, ktory mowi, jak go uzupelnic (2026-10-13). Szesc regul wyniesionych z ocen trzech cudzych konektorow PL (leniwyposel/sejm-mcp, PawelHaracz/polish-caselaw-mcp, soba-labs/krs-mcp), przepisanych od zera i sprawdzonych na pieciu naszych konektorach (ELI Sejmu, SAOS, CBOSA, ISAP, KRS) testem czerwonym najpierw oraz przebiegiem na zywym zrodle.
fault w tabeli kodow. Kazdy wpis 6b dostaje czyja wine: caller (przeformuluj
pytanie / popraw parametr), upstream (czekaj albo zglos czlowiekowi), network (ponow
pozniej), server (zglos blad konektora). Ten sam kod moze niesc inna wine w instancji:
timeout to upstream_unavailable z fault: network, 5xx - z fault: upstream. Wyjatek
spoza zrodla (bug w formatterze) to osobny kod internal_error z fault: server - nigdy
„blad komunikacji, sprobuj ponownie", bo agent ponawia wtedy wlasny bug.forbidden = przeglad czlowieka. 401/403 zrodla: retryable: false, zero ponowien w
petli, zero automatycznego obchodzenia nowych zabezpieczen (rotacja naglowkow, generowane
tokeny frontendu, nieudokumentowane endpointy). Jedno zdanie o tym w AGENTS.md konektora.content-type PRZED parsowaniem i
daj nazwany kod (upstream_blocked, upstream_maintenance, upstream_shape,
format_unavailable) zamiast bledu parsera. Na endpointach, ktore SLUSZNIE oddaja HTML,
typ nie wystarczy - patrz na sygnature tresci. Nieznany ksztalt JSON to upstream_shape,
nigdy []. Do cache trafia wylacznie odpowiedz po kontroli typu i ksztaltu.partial_result to trzy zdania - dlaczego, czego
brakuje (z mianownikiem), jak uzupelnic - plus coverage. Kilka powodow laczy sie w jedno
pole. Odpowiedz tekstowa ZACZYNA SIE od „Wynik niepelny: ...", bo slaby model czyta poczatek,
nie pola na koncu. Porazka pobrania czesci danych to „nie sprawdzono", nigdy „sprawdzono -
brak" (anty-wzorzec: catch { return null } w petli skanu, a wynik liczy tylko sukcesy).retryable: true, przez wspolny hamulec. Jeden hamulec na proces
(nie na klienta, bo narzedzie tworzy nowego klienta przy kazdym wywolaniu): przy 429/5xx/sieci
odstep miedzy zapytaniami rosnie x2 (0,25 -> 2 s), po sukcesie maleje o polowe; do 3 prob;
Retry-After honorowany z sufitem (30 s) i budzetem czekania na wywolanie - ponad budzet
oddaj blad z retry_after_s, nie wisz. Status ostatniej odpowiedzi zostaje w bledzie.tools/list == klucze (pusta lista = BLOKADA).
Same wywolania zywe poza CI. Pilot zaplacil od razu: narzedzie bez przypadku zywego okazalo
sie zepsute na zywym zrodle (globalny Accept: application/json dawal 406 na endpoincie HTML).KODY = { # 6b + os fault (6c)
"not_found": Kod(fault="caller", retryable=False, co_zrobic="..."),
"rate_limited": Kod(fault="upstream", retryable=True, co_zrobic="odczekaj retry_after_s"),
"upstream_unavailable": Kod(fault="upstream", retryable=True, co_zrobic="ponow raz pozniej"),
"forbidden": Kod(fault="upstream", retryable=False, co_zrobic="zglos czlowiekowi"),
"upstream_maintenance": Kod(fault="upstream", retryable=False, co_zrobic="powiedz o przerwie"),
"upstream_shape": Kod(fault="upstream", retryable=False, co_zrobic="to NIE jest brak wynikow"),
"internal_error": Kod(fault="server", retryable=False, co_zrobic="zglos blad konektora"),
}
# komunikat: "[kod] co sie stalo (fault=..., retryable=..., retry_after_s=..., status=...). Co zrobic: ..."
Bramka: test 6b w obie strony obejmuje WSZYSTKIE pliki, w ktorych kody sie rodza (klient HTTP
i narzedzia), nie tylko plik serwera - kod rzucany przez zmienna (kod = "a" if ... else "b")
jest dla regexu niewidoczny, wiec kody pisz literalami. Kontrola pozytywna: mutant literalu
kodu ma dac czerwien w obu kierunkach.
Element 4 kaze wpisywac do spanu org_id i user_id. Element 7 pilnuje, zeby to
byly JEDYNE rzeczy, ktore wyjda z procesu. Bez tego telemetria konektora
kancelaryjnego jest kanalem wycieku danych objetych tajemnica zawodowa, a wyciek
nastapi przez atrybut, ktorego nikt swiadomie nie dodawal.
Trzy reguly.
Jedno gardlo. Kazdy zestaw atrybutow przechodzi przez jedna funkcje przed eksportem. Klucz spoza listy jest odrzucany niezaleznie od tego, gdzie na spanie zostal ustawiony.
_DOZWOLONE_ATRYBUTY = frozenset({
"mcp.org_id", "mcp.user_id", "mcp.tool", "mcp.status",
"mcp.duration_ms", "mcp.code", "service.name", "service.version",
})
def _przesiej(atrybuty: dict) -> dict:
"""Jedyne miejsce, przez ktore atrybuty wychodza z procesu."""
return {k: v for k, v in atrybuty.items() if k in _DOZWOLONE_ATRYBUTY}
exception.message nigdy nie wychodzi. str(blad) rutynowo niesie sciezki
dyskowe, fragmenty promptu, sygnatury spraw i output providera. Eksportuj
exception.type, czyli sama nazwe klasy, i nasz stabilny code z elementu 6.
Trescia bledu zajmuje sie log lokalny, ktory zostaje na maszynie.
Providery prywatne, nie globalne. Nie rejestruj swojego providera przez
set_tracer_provider ani set_logger_provider. Globalna rejestracja sprawia, ze
cudza instrumentacja w tym samym procesie (SDK modelu, framework agenta) zaczyna
wychodzic Twoim eksporterem, razem z tym, co akurat wkłada do swoich spanow.
# tests/test_telemetry_allowlist.py
def test_atrybut_spoza_listy_nie_wychodzi():
assert _przesiej({"mcp.org_id": "7", "prompt": "tresc pisma"}) == {"mcp.org_id": "7"}
def test_exception_message_nie_wychodzi():
assert "exception.message" not in _przesiej({"exception.message": "C:/akta/…"})
Regula bez bramki nie trzyma, wiec allowlista bez tego testu jest komentarzem. Deklaracja w README nie jest kontrola - kontrola jest funkcja, przez ktora wszystko przechodzi, i test, ktory to przypina.
Wzorzec zaadaptowany z flintai/cli/telemetry.py w sandbox-quantum/flintai-cli
(Apache-2.0 z Commons Clause, wylacznie idea, zero kodu).
Dzis konektor floty ma trzy rzeczy zlepione w jednym module: co potrafi (interfejs wyszukiwania, pobierania, eksportu), jak to robi (klient HTTP do SAOS, parser CBOSA, zapytanie SPARQL) i kto z tego korzysta (tool MCP widoczny dla modelu). Dziala, dopoki jest jeden backend. Przy drugim (cache offline obok zrodla na zywo, SQLite obok Postgresa, sandbox zamiast lokalnego fs) zaczyna sie fork konektora.
Element 8 rozdziela to na trzy role i mowi: jedna rola to nie zdolnosc. Nowa zdolnosc projektuje sie w komplecie, nawet gdy dostawca jest na razie jeden.
ok|degraded|failed z pelnym mianownikiem. Zero I/O.SaosLiveProvider,
SaosCacheProvider, SaosFixtureProvider (do testow bez sieci). Kazdy przechodzi TEN SAM
zestaw testow kontraktu.# definicja - orzecznictwo/definition.py (zero I/O)
class OrzecznictwoProvider(Protocol):
async def szukaj(self, zapytanie: str, limit: int) -> WynikSzukania: ...
async def pobierz(self, sygnatura: str) -> Orzeczenie | ToolError: ...
# dostawcy - orzecznictwo/providers/{saos_live,saos_cache,fixture}.py
# konsument - orzecznictwo/tools.py: zna tylko OrzecznictwoProvider
# tests/test_orzecznictwo_kontrakt.py - jeden zestaw, kazdy dostawca go przechodzi
@pytest.mark.parametrize("provider", [SaosLiveProvider, SaosCacheProvider, FixtureProvider])
def test_pobierz_nieznana_sygnature_daje_not_found(provider): ...
Co to daje floty: podmiana saos_live na saos_cache w cordis.yml/konfigu przesuwa
za jednym ruchem wszystkie toole, ktore z tej zdolnosci korzystaja. „PATRON na SQLite" i
„PATRON na Postgres" staja sie podmiana dostawcy, nie forkiem. A dostawca-fixture daje testy
konektora bez sieci i bez bana na zrodle (asymetria ryzyka: przyspieszanie pobierania kosztuje ban na zrodle, zwolnienie kosztuje tylko czas).
Anty-wzorzec, ktory to unaocznia: dwa toole MCP z wlasnym klientem HTTP do tego samego zrodla, kazdy z innym retry, innym throttlem i innym formatem bledu. To sa dwa dostawcy bez definicji, wiec nie da sie ich zamienic ani przetestowac jednym zestawem.
Wzorzec zaadaptowany z „capability seam" w deepseek-ai/deepseek-harness (MIT, wylacznie
idea, zero kodu): tam Service Definition / Service Provider / Consumer, i ta sama regula -
„a package may combine roles, but one role alone is not a seam".
Kazdy konektor korpusowy dostaje jeden tool bezargumentowy, ktorego jedynym zadaniem jest
powiedziec: co ta baza obejmuje, ile ma rekordow, KIEDY kazda rodzina zostala pobrana
i czego w niej NIE MA. Nazwa: coverage (EN) / pokrycie_bazy (PL). Read-only,
ToolAnnotations jak w elemencie 5.
Problem, ktory to zamyka. Dzis wiedza o naszych lukach istnieje - ale jako proza w
instructions. Przyklad z at-eli-mcp: „Landesrecht not covered - relay the dataset_note".
To wiedza bierna: model musi ja przeczytac i zechciec przekazac. Agent nie ma jak
zapytac. Gdy tego nie zrobi, konektor odpowiada pewnie na pytanie o prawo krajowe
landu i konczy exit 0 - dokladnie to, przed czym ostrzega zasada cichej
niekompletnosci: najgrozniejsza awaria konczy sie sukcesem.
Element 9 zamienia wiedze bierna w wywolywalna.
Kontrakt odpowiedzi - trzy czesci, zadnej nie wolno pominac:
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False))
async def coverage() -> dict:
"""Declares what this corpus covers, when each family was captured, and what it does NOT cover."""
return {
"status": "ok", # trojstan z elementu 6: ok | degraded | failed
"as_of_note": "Daty mowia KIEDY dane pobrano, nie ze sa nadal aktualne.",
"families": [
{"name": "Bundesrecht", "tool": "at_search", "records": 23069,
"captured_at": "2026-08-21"},
],
"known_gaps": [ # NIGDY pusta lista - patrz bramka nizej
{"id": "AT-001", "family": "Landesrecht",
"missing": "Prawo krajowe landow nie jest wystawione.",
"fallback": "Siegnij do ris.bka.gv.at, sekcja Landesrecht."},
],
}
Bramka (obowiazkowa, inaczej element 9 jest deklaracja):
def test_coverage_gaps_never_silently_empty():
c = coverage()
assert c["families"], "pusty mianownik = bramka bez czego sprawdzac"
assert c["known_gaps"], (
"PUSTA lista luk = BLOCK. Zaden korpus prawny nie jest kompletny; "
"pusta lista znaczy 'nie sprawdzilismy', nie 'nie ma dziur'."
)
for f in c["families"]:
assert f.get("captured_at"), f"rodzina {f['name']} bez daty pobrania"
Pusta known_gaps przechodzilaby zawsze i wygladala na czysty wynik - klasyczna
bramka z pusta lista, ktora przechodzi zawsze. Dlatego pusta lista = czerwone,
nie zielone.
Dwie rzeczy, ktore ten element ma ODZIEDZICZYC po reszcie konektora (obie zlapane pomiarem po rolloucie 2026-08-24, nie przy czytaniu diffa):
Tool coverage audytuje jak kazdy inny tool. INSTRUCTIONS naszych konektorow
mowia wprost: „every tool call appends to the audit log". Wypuszczenie toola bez
wpisu do dziennika czyni to zdanie falszywym - i to na 38 konektorach naraz.
Nie jest to niespojnosc kosmetyczna, tylko obietnica bez pokrycia:
deklaracja o architekturze bywa prawdziwa, a o konkretnym przebiegu falszywa. Do tego bramka, ktora
sprawdza, ze wpis realnie laduje na dysku (przekieruj katalog audytu zmienna
srodowiskowa na tmp_path i policz linie), a nie ze w kodzie stoi wywolanie.
Drift test w MOCNYM kierunku. Slaby kierunek (INSTRUCTIONS nie moga wymieniac
nieistniejacego toola) lapie przeterminowana dokumentacje. Mocny kierunek - kazdy
ZAREJESTROWANY tool musi byc wymieniony w INSTRUCTIONS - lapie cos grozniejszego:
zdolnosc, ktora trafila na produkcje bez zmiany zachowania. Tool jest wykrywalny,
ale model nie ma powodu po niego siegnac. Dokladnie to stalo sie z elementem 9:
narzedzie wyszlo w 38 konektorach, a instrukcje milczaly. Wylapal to JEDEN konektor
(boutique-mcp), ktory jako jedyny mial ten kierunek. Dzis maja go wszystkie.
Bramka ma akceptowac obie formy wzmianki - tool i tool(arg=...) - inaczej
produkuje falszywe alarmy na instrukcjach z przykladem wywolania.
Dlaczego to jest nasza sprawa, a nie ciekawostka. Haslo MateMatic brzmi „AI, ktora wie, czego nie wie". Element 9 jest jedynym miejscem we flocie, gdzie to zdanie staje sie funkcja, a nie haslem na stronie. Konektor bez niego moze byc technicznie poprawny i jednoczesnie sprzedawac obietnice, ktorej nie realizuje.
Zrodlo wzorca: emidio-trancoso/advocacia-aberta (MIT), tool cobertura_da_base
w hostowanym MCP vade-mecum - zmierzony na zywo 2026-08-24 (initialize -> sesja ->
tools/call): 9 rodzin z datami pobrania i 7+ luk z identyfikatorami BASE-0xx, kazda
z instrukcja odwrotu. Idea i ksztalt kontraktu, zero ich kodu.
Zmierzone na flocie 2026-08-24: 1 z 38 workflow wydania uruchamial testy przed
publikacja na PyPI, a ZERO repo mialo CI na push/PR. Kazda bramka opisana w tym
kanonie - drift test, trojstan, kontrakt coverage, asercja audytu - chronila
wylacznie laptopa autora. Otagowany zepsuty commit szedl prosto do uzytkownikow,
a zepsuty commit bez tagu lezal na main, dopoki ktos nie wydal.
To jest zasada „regula bez bramki nie trzyma” o pietro wyzej: bramka bez CI jest regula bez bramki.
Kazde repo konektora ma miec dwa wyzwalacze:
# ci.yml - na push do main i na kazdy PR
on: { push: { branches: [main] }, pull_request: }
# release.yml - krok bramek PRZED budowaniem paczki
- name: Run gates before publishing
run: |
python -m pip install -e ".[dev]"
python -m pytest tests/ -q -k "not smoke"
-k "not smoke" jest swiadome i nalezy do kontraktu: smoke testy wolaja zywe zrodla
rzadowe, a awaria portalu ministerstwa nie jest powodem, zeby blokowac NASZE wydanie
ani czerwienic CI. Bramki deterministyczne sa. Mieszanie obu w jednym przebiegu daje
bramke, ktora ludzie naucza sie ignorowac - a wtedy przestaje chronic tak samo
skutecznie, jak gdyby jej nie bylo.
W examples/:
server.py - kanon setup FastMCP + registrationinstructions.py - szkielet z 4 sekcjami (Call order / Allowed shape / Iterating on errors / Style)auth.py - dwukanalowy + OTeltest_instructions_drift.py - drift test do CIclaude mcp add --transport http <name> https://<your-mcp>/api/v1/mcp/ \
--header "X-API-Key: <key>"
claude mcp list
# Powinno wyswietlic <name> + status connected + tools count
W Claude Code: "List my
| Anti-pattern | Konsekwencja |
|---|---|
| Restating tool signatures w instructions | Drift, signatury rosna z kodu |
| Re-enumerating error_codes w instructions | Drift, error_codes rosna z docstring |
| Hardcoded API key validation w kazdym tool | Reuse authenticate_mcp_request() |
Stdlib logging.info(request_body) |
Loguruj sekrety. Uzyj Loguru z masking PII |
| Brak ToolAnnotations na read-only | Klient pyta o approval przy kazdym wywolaniu |
| Single-channel auth (tylko X-API-Key lub tylko Bearer) | Niektorzy klienci wysylaja drugi - 401 |
| Brak OTel atrybutow org_id | Brak per-tenant observability w multi-tenant |
| Tool bez wpisu do dziennika audytu, gdy INSTRUCTIONS obiecuja audyt KAZDEGO wywolania | Zdanie w instrukcjach staje sie falszywe; bramka ma sprawdzac wpis NA DYSKU, nie wywolanie w kodzie |
| Drift test tylko w slabym kierunku | Tool zarejestrowany, ale niewymieniony w INSTRUCTIONS = zdolnosc bez routingu modelu |
Wiedza o lukach TYLKO jako proza w instructions |
Agent nie ma jak zapytac; konektor odpowiada pewnie poza swoim pokryciem (element 9) |
known_gaps: [] w toolu coverage |
Bramka przechodzi zawsze, wynik wyglada na czysty - pusta lista luk = BLOCK |
api/mcp_server/server.py, api/mcp_server/instructions.py, api/mcp_server/auth.py w upstream repoLIVE 2026-05-25 (6 z 6 TypeScript MCP serverow MateMatic):
| MCP | Wersja | Release |
|---|---|---|
| mcp-eu-compliance | v0.2.0 | pilot kanonu |
| mcp-saos | v1.1.0 | orzecznictwo SAOS |
| mcp-eu-sparql | v1.1.0 | EUR-Lex + CJEU |
| mcp-isap | v1.1.0 | Sejm ELI (DU + MP) |
| mcp-nsa | v1.1.0 | CBOSA (sady admin) |
| mcp-krs | v1.1.0 | KRS MS |
Pozostalo (Python stack + nowsze MCP): sejm-eli-mcp, kio-orzeczenia-mcp, ewentualne uodo-mcp / pomoc-prawna-mcp.
matematic-patron-pr-review-pl - komplementarny skill (review PR repo PATRON)Te reguły obowiązują w każdym skillu tej wtyczki, także gdy sam skill milczy. Są skopiowane do każdego skilla, więc działają zarówno po instalacji całej wtyczki, jak i pojedynczego skilla.
Plugin to narzędzia deweloperskie MateMatic: spec-driven development, kanon budowy MCP serverów, recenzent PR dla LegalTech, generator instalatora marketplace. Skierowany do osób budujących, nie do prawnika końcowego.
Plugin daje warsztat deweloperski (advanced). Nie jest narzędziem do pracy prawnej - do tego są pozostałe pluginy huba.