Součást série o stavbě cz-agents → pod kapotou

Krátký kontext

Model Context Protocol je otevřený protokol pro připojení externích nástrojů a datových zdrojů k LLM aplikacím. Místo aby agent dostal dlouhý prompt s ručně vloženými daty, může volat server, který vystaví konkrétní nástroje, schémata a zdroje. V mém případě to znamenalo dát modelu přístup k českým compliance zdrojům: ARES pro identitu subjektu, ISIR pro insolvence, sankční seznamy a agregovaný due-diligence pohled. Cíl nebyl postavit chatbota. Cíl byl udělat malé, auditovatelné datové rozhraní, které agent může použít bez toho, aby si vymýšlel odpovědi o firmě typu Acme Imports s.r.o. nebo o testovacím IČO 00000000.

Problém zdrojových dat

Česká veřejná data vypadají na první pohled jednoduše: existuje registr, existuje endpoint, existuje dokumentace. Po šesti měsících práce je moje shrnutí méně optimistické. Tyto systémy byly navržené primárně pro lidské vyhledávání, ne pro programatické dotazy s vyšší frekvencí, které mají konzistentní latenci, predikovatelné chyby a stabilní kontrakty.

ARES má XML i JSON API, ale praktický provoz vyžaduje respektovat rate limity, backoff a crawl etiquette. Neexistuje pohodlný bulk export, který by pokryl běžnou potřebu: stáhni všechno relevantní, validuj lokálně a udržuj vlastní index. Pokud produkt potřebuje zkontrolovat konkrétní subjekt, je to v pořádku. Pokud agent začne při jedné analýze skládat desítky dotazů, server musí být konzervativní, cachovat a umět říct ne.

ISIR je jiný typ problému. Insolvenční rejstřík kombinuje strukturované údaje a dokumenty, ale důležité informace často končí v PDF. Parser pak řeší kvalitu textové vrstvy, naskenované přílohy, právní formulace a rozdíly mezi soudními obvody. Některé pole je přítomné u jednoho typu události, u jiné chybí. Někde existuje očekávaná kombinace identifikátorů, jinde zůstane jen jméno, datum nebo odkaz na spis. To není chyba jednoho API, to je vlastnost systému, který dlouho sloužil lidem v prohlížeči.

Důležité je, že nejde jen o technickou špínu ve formátu. V compliance doméně má každé chybějící nebo nejednoznačné pole provozní dopad. Pokud ARES vrátí subjekt bez očekávaného detailu, agent nesmí vyplnit mezeru odhadem. Pokud ISIR dokument nejde spolehlivě přečíst, výsledek musí nést informaci o nejistotě. Pokud sankční shoda vznikla jen podobností jména, uživatel potřebuje vidět, jaké atributy se shodly a jaké chybí. Tohle je rozdíl mezi datovým wrapperem a produkčním compliance nástrojem.

Sankce přidávají další vrstvu. EU Financial Sanctions Files jsou dostupné jako token-authenticated JSON a pro české subjekty dávají evropský základ. Pokud ale existuje USD transakční expozice, musel jsem doplnit i OFAC SDN. Ani tady nejde jen o stažení seznamu. Reálná práce je normalizace jmen, aliasů, zemí, dat narození, právnických osob a výsledné vysvětlení, proč systém označil možnou shodu. Compliance výstup bez vysvětlení je skoro horší než žádný výstup.

Dvě technické pasti

1. AsyncLocalStorage a Streamable HTTP transport

Nejkonkrétnější bug, který mě překvapil, byl ztracený request context. V Node.js jsem používal AsyncLocalStorage pro uložení IP adresy požadavku, aby rate limiting a základní analytika nemusely posílat metadata přes každou funkci. Fungovalo to v běžných Express handlerech. Pak jsem přidal StreamableHTTPServerTransport.handleRequest() z MCP SDK a najednou část metrik vracela undefined.

Příčina byla v tom, že transport si uvnitř vytvořil nový async chain. Kód běžel, odpovědi chodily, žádná výjimka nepadala. Jen kontext, na který spoléhal rate limiter a analytics vrstva, nebyl dostupný v místě, kde se nástroj skutečně vykonával. To je přesně typ chyby, která vypadá jako drobná observability anomálie, dokud nezjistíte, že limiter nevidí klienta.

Workaround byl pragmatický: vedle AsyncLocalStorage jsem přidal module-level fallback proměnnou a malé funkce setRequestIp() / clearRequestIp(). Wrapper nastaví IP těsně před voláním handleru a vyčistí ji ve finally bloku. Není to vzor pro obecnou business logiku, ale pro úzký kus request metadat kolem MCP transportu to bylo lepší než tahat IP adresu do každého tool callu.

2. SQLite místo Postgresu

Druhé rozhodnutí bylo méně dramatické, ale důležitější pro provoz: použil jsem SQLite přes better-sqlite3. U compliance produktu zní Postgres jako reflex. Já jsem ale stavěl read-heavy servery, které mají lokální cache registrů, sankčních seznamů a normalizovaných výsledků. Primární požadavek nebyl multi-writer concurrency, ale jednoduchý single-container deploy, rychlé lokální dotazy a minimum provozních závislostí.

Topologie proto používá sdílený read-only volume mount mezi službami. Jeden proces data aktualizuje a ostatní kontejnery je čtou bez inter-service DB spojení. Tím se z deploymentu ztratila celá vrstva connection stringů, credentials, network policy a migrací přes síť. Trade-off je explicitní: není to vhodné pro mnoho zapisujících služeb ani pro transakční workload. Pro compliance dotazy, které jsou z většiny čtení a periodické obnovy datasetů, to vyšlo lépe než menší Postgres cluster.

Architektura po šesti měsících

Výsledek je sedm open-source npm balíčků a jedna uzavřená premium vrstva ddplus. Open-source část pokrývá jednotlivé MCP servery a sdílené utility: ARES, ISIR, sankce, due-diligence agregátor, normalizaci vstupů, transportní helpery a společné validační schéma. Každý server běží jako Docker kontejner, protože tím zůstává lokální vývoj i nasazení čitelné. Apache reverse proxy sedí před nimi a směruje veřejné endpointy podle hostu a cesty.

Multi-server topologie se ukázala jako lepší než jeden velký proces. ARES má jiné cache chování než sankce. ISIR má jiné retry a dokumentové edge cases. Due-diligence agregátor je orchestrátor, který kombinuje výsledky z ostatních serverů a vrací modelu menší, vysvětlený výstup. Premium tier je oddělený záměrně; nechci do open-source balíčků míchat detaily placených funkcí a nechci, aby self-hosted varianta měla skryté runtime závislosti.

Nejvíc se mi osvědčilo držet hranici mezi servery nudnou. Každý tool vrací malé JSON objekty s verzí zdroje, časem poslední obnovy a explicitním statusem. Agregátor z nich skládá odpověď, ale nemění význam původních polí. Když se ARES dočasně nedotáže, výsledek není tichý úspěch s prázdnou firmou. Je to částečný výsledek s důvodem. To je pro LLM důležité, protože model má tendenci prázdno převyprávět jako jistotu, pokud mu nedáte silný signál, že jde o neúplná data.

Proč SaaS nad open-source

Původní předpoklad byl, že open-source MCP servery budou stačit. Technicky stačí. U compliance uživatelů jsem ale narazil na jinou potřebu: nechtějí převzít odpovědnost za self-hosting, monitoring a obnovu dat. Chtějí vědět, že sankční seznam je aktuální, že ARES fallback funguje, že se ISIR parser nerozbil na nové kombinaci dokumentů a že někdo řeší incident, když veřejný zdroj změní chování.

Proto jsem přidal hosted vrstvu s trialem a následným subscription modelem. Tiers jsou 29, 99 a 199 EUR podle objemu a provozních limitů. Není to snaha zavřít open-source za paywall. Je to oddělení dvou typů hodnoty: kód, který lze číst, spustit a upravit, a služba, která nese provozní odpovědnost. GDPR přístup je konzervativní: minimalizovat ukládaná vstupní data, logovat hlavně technická metadata a u citlivých dotazů držet co nejkratší retenční okno.

Trial jsem nechal jednoduchý, protože v téhle doméně lidé nepotřebují produktový příběh na deset obrazovek. Potřebují ověřit, jestli jejich workflow dostane použitelnou odpověď, jak vypadá rate limit a jestli se dá výstup napojit na existující interní proces. U placené vrstvy je pak podstatná předvídatelnost: kolik dotazů projde, co se stane při vyčerpání limitu a jak rychle se obnovují podkladové datasety. To jsou nudné otázky, ale přesně ty rozhodují, jestli někdo službu pustí k reálným compliance kontrolám.

Co z toho plyne

Po šesti měsících je pro mě hlavní lekce jednoduchá: MCP je užitečný formát pro hranici mezi agentem a systémem, ale většina práce je pořád v datech. Musíte znát rate limity, výpadkové chování, právní význam polí, falešné pozitivní shody a provozní cenu každého externího endpointu. Pro ukázku stačí jeden tool call. Pro compliance produkt je potřeba vysvětlit, proč se výsledek objevil a kdy mu nevěřit.

Open-source balíčky jsou na github.com/martinhavel/cz-agents-mcp. Hosted trial běží na cz-agents.dev. Obě varianty používají stejný základ: malé MCP servery, konzervativní caching, explicitní limity a výstupy, které mají být čitelné pro stroj i pro člověka.