Prepsany navrh MCP: patri k modelu, ne do vyberu kroku

Prvni verze byla postavena spatne. Davala MCP nastroje do vyberu kroku, tedy
nastroj vybiral clovek a vyplnil mu pevna pole. Tak MCP nedava nic navic proti
HTTP konektoru, ktery uz mame - je to protokol pro modely, kde si nastroj
vybira model podle toho, co je zrovna potreba. Byla to zamena kategorie.

Spravne: MCP konektor neni zdroj kroku, je to schopnost, kterou dostane krok
s modelem. V builderu se objevi krok "Nechat model splnit ukol" a v nem se
zaskrtne, ktera napojeni smi pouzit. Krok vraci vysledek plus seznam toho, co
model opravdu zavolal - bez nej je to cerna skrinka a do provozu to nepatri.

Dokument popisuje dve cesty, lisi se tim kudy tece token zakaznika:
- predat server modelu (OpenAI ho zavola sam) - malo prace, ale token jde do
  OpenAI, server musi byt dostupny z jeho site a volani nejdou pres nas log
- byt MCP klientem my - vic prace, ale token zustava u nas, plati nase stropy,
  redakce i seznamy povolenych IP, a funguje to s jakymkoliv modelem

Doporucena je druha. Tvar tool objektu overen proti dokumentaci OpenAI
(type, server_label, server_url, headers, authorization, allowed_tools,
require_approval).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-28 09:15:11 +02:00
co-authored by Claude Opus 5
parent 2d38ac1350
commit 3e6b365eec
2 changed files with 129 additions and 135 deletions
+117 -128
View File
@@ -2,171 +2,160 @@
Navrh, neni naprogramovane.
Cil: firma si vyplni udaje sveho MCP serveru a jeho nastroje se objevi
v builderu jako kterakoliv jina operace.
Cil: firma vyplni udaje sveho MCP serveru a jeho nastroje muze pouzit model,
ktery za ni neco udela.
## Co je MCP z pohledu tohohle systemu
## Co MCP je a co neni
MCP server je **katalog operaci, ktery se zepta az za behu**. Rekne "umim
`create_invoice`, `search_customer`, `send_report`", ke kazde da popis a schema
vstupu, a pak je umi vykonat.
MCP je protokol **pro modely**. Server vystavuje nastroje s popisem a schematem
vstupu a *model* si sam vybere, ktery zavolat a s jakymi argumenty. Smysl je
v tom, ze se nastroj nemusi predem naprogramovat do postupu - model ho najde
a pouzije podle toho, co je zrovna potreba.
To je presne to, co u nas dela `src/data/services.ts` plus slozka `scripts/`.
Rozdil je v jedine veci, a ta rozhoduje o celem navrhu:
**Neni to protokol pro vzdalene volani procedur.** Kdyz nastroj vybere clovek
v builderu a vyplni mu pevna pole, MCP nedava nic navic proti HTTP konektoru,
ktery uz mame. Jen pribude vrstva a s ni JSON-RPC obalka.
| Dnes | MCP |
| ----------------------------------- | ----------------------------------------- |
| Operace jsou zname pri prekladu | Operace se zjisti az od serveru |
| Katalog je stejny pro vsechny firmy | Kazda firma ma jiny server, jine nastroje |
| Skript je nas, prosel code review | Nastroj je zakaznikuv, nevidime do nej |
Z toho plyne jedina vec, na ktere cely navrh stoji:
## Kde to narazi
> **MCP konektor neni zdroj kroku. Je to schopnost, kterou dostane krok
> s modelem.**
Retezec od builderu k behu je dnes tenhle:
V builderu se tedy neobjevi polozka `create_invoice`. Objevi se krok
"Nechat model splnit ukol" a v nem se zaskrtne, ktera napojeni smi pouzit.
## Jak to vypada v kroku
```
StepPicker -> service.actions (co nabidnout)
ulozeni -> findOperation(...) (existuje ta operace?)
beh -> scriptIdFor(...) (kdo to vykona)
Krok: Nechat model splnit ukol
Zadani: "Zaloz objednavku podle teto poptavky a vrat jeji cislo."
Data: {{data}}
Napojeni: [x] Interni sklad [ ] Firemni wiki
Model: gpt-4o-mini
```
Vsechny tri se ptaji **statickeho katalogu**. U MCP zadny staticky katalog
neni: seznam operaci patri konektoru, ne sluzbe. Bez zmeny by slo ulozit jen
krok, ktery uz nekdo predem zapsal do kodu, coz je presny opak toho, o co jde.
Model dostane nastroje zaskrtnutych serveru, sam se rozhodne, ktere zavolat,
a krok vrati vysledek plus **seznam toho, co model opravdu udelal**.
## Navrh: jedna sluzba, operace z konektoru
Ten seznam neni pridavek. Cely tenhle system stoji na tom, ze je v logu videt,
co ktery krok provedl. Krok, ktery udela nekolik zapisu do ciziho systemu
a rekne jen "hotovo", je cerna skrinka a do provozu nepatri.
**Ne sluzba na kazdy MCP server.** Sluzby jsou kod a zakaznik si je nezalozi.
Misto toho jedna sluzba `mcp` a kazdy server je **konektor** pod ni:
## Dve cesty, jak to postavit
```
Sluzba "MCP server" definujeme my, jednou
| credentials: adresa, token, nazev
|
+-- Konektor "Interni sklad" zaklada si firma
| tools: create_order, get_stock, ... zjisteno od serveru
|
+-- Konektor "Firemni wiki" jiny server, jine nastroje
Rozhodnuti, ktere je potreba udelat vedome: lisi se tim, kudy tece token
zakaznika.
### A) Server predame modelu
OpenAI Responses API umi vzit MCP server jako nastroj:
```json
{
"type": "mcp",
"server_label": "interni-sklad",
"server_url": "https://mcp.firma.cz",
"headers": { "Authorization": "Bearer ..." },
"allowed_tools": ["create_order", "get_stock"],
"require_approval": "never"
}
```
Krok stromu pak nese `serviceId: 'mcp'`, `operationId: '<nazev nastroje>'`
a `connectorId`. Ten treti udaj je u MCP **povinny**, na rozdil od ostatnich
sluzeb: bez nej neni jasne, ktery server se pta, a dva konektory mohou mit
nastroj stejneho jmena.
Malo prace: konektor drzi adresu a token, krok je slozi do pozadavku.
### Kudy se nastroje dostanou do builderu
Cena za to:
Katalog uz umi doplnit nabidku az za behu. `withRuntimeOptions` presne tohle
dela pro resitele, skupiny a typy ticketu - hodnoty, ktere v dobe prekladu
neexistuji. Nastroje MCP jsou tentyz pripad, jen misto polozek vyberu doplni
cele operace.
- **Token zakaznika jde do OpenAI** a OpenAI vola jeho server primo. To musi
firma vedome odsouhlasit, ne se to dozvedet z dokumentace.
- Server musi byt dostupny **z internetu a ze site OpenAI**, ne jen z nasi.
Firemni server za VPN takhle nepouzijeme.
- Volani nejdou pres nas, takze **v nasem logu je jen to, co OpenAI vrati**,
a neplati na ne nase stropy ani redakce.
Schema vstupu prijde jako JSON Schema a prevede se na `OperationField`, tedy
tvar, ktery builder uz umi vykreslit. Prevod je primocary u toho, co se
v praxi pouziva:
### B) MCP klientem jsme my
| JSON Schema | `OperationField` |
| ----------------------- | -------------------- |
| `string` | `text` |
| `string` s `enum` | `choice` s `options` |
| `string` a dlouhy popis | `longtext` |
| `number`, `integer` | `text` s kontrolou |
| `boolean` | `choice` ano/ne |
| `object`, `array` | `json` |
Sami se serveru zeptame na `tools/list`, nastroje predame modelu jako obycejne
funkce (function calling), a kdyz si model o volani rekne, **zavolame ho my**
a vysledek mu vratime.
Co se neprevede, skonci jako `json`. Radsi pole, do ktereho clovek napise
strukturu rucne, nez pole, ktere tvari, ze rozumi necemu, cemu nerozumi.
Vic prace, ale:
### Kdo to vykona
- Token zustava u nas, stejne jako u kazdeho jineho konektoru.
- Server staci, kdyz je dostupny **z nasi adresy** - plati na nej seznamy
povolenych IP, ktere uz kvuli ostatnim sluzbam resime.
- Kazde volani jde pres `ctx.http`, tedy pres nase stropy, timeouty, redakci
a zapis do logu behu.
- Funguje s jakymkoliv modelem, ktery umi function calling, ne jen s OpenAI.
Jeden skript `mcp.call-tool` pro vsechny nastroje. Nazev nastroje je vstup,
ne soubor. Psat skript na kazdy nastroj nejde - nevznikaji u nas.
**Doporucuju B.** Ne proto, ze je hezci, ale protoze zbytek systemu stoji na
tom, ze pristupove udaje neopousti server a ze je v logu videt kazde volani.
Varianta A obe tahle pravidla porusuje, a to u konektoru, ktery si zaklada
zakaznik.
Varianta A ma smysl jako zkratka pro verejne MCP servery bez tajemstvi.
## Co si firma vyplni
| Pole | K cemu |
| ----------------- | -------------------------------------------------- |
| Nazev | co clovek uvidi v builderu, napr. "Interni sklad" |
| Adresa serveru | HTTPS adresa MCP endpointu |
| Token | tajny, posila se jako `Authorization: Bearer` |
| Vlastni hlavicka | pro servery, ktere chteji neco jineho nez Bearer |
| Povolene nastroje | prazdne = vsechny. Jinak vycet, ktery se smi volat |
| Pole | K cemu |
| ----------------- | ----------------------------------------------------- |
| Nazev | co clovek uvidi u kroku, napr. "Interni sklad" |
| Adresa serveru | HTTPS adresa MCP endpointu |
| Token | tajny, posila se jako `Authorization: Bearer` |
| Vlastni hlavicka | pro servery, ktere chteji neco jineho nez Bearer |
| Povolene nastroje | prazdne = vsechny. Jinak vycet, ktery model smi videt |
Posledni radek neni ozdoba. MCP server muze zpristupnit i nastroje, ktere do
automatizace nepatri (mazani, sprava uctu). Vycet je jednodussi nez vysvetlovat
lidem, ktere nastroje nepouzivat.
**Vycet povolenych nastroju je hlavni pojistka.** U bezneho kroku vybira
operaci clovek, tady vybira model. Server muze vystavovat i nastroje, ktere do
automatizace nepatri (mazani, sprava uctu), a model o nich nevi nic krome
popisu, ktery si napsal jejich autor. Co neni ve vyctu, model vubec neuvidi.
## Jen HTTP, ne stdio
## Schvalovani
MCP zna dva prenosy. `stdio` spousti server jako lokalni proces - to u nas
nepripada v uvahu: znamenalo by to pustit zakaznikuv program uvnitr naseho
containeru, a to je jina trida rizika nez zavolat cizi API.
MCP zna rezim, kdy volani ceka na schvaleni cloveka. V automatizaci, ktera bezi
sama v noci, neni koho se zeptat, takze jsou dve poctive moznosti:
Zustava **Streamable HTTP**, tedy JSON-RPC pres POST. To `ctx.http` umi uz ted,
takze skript nepotrebuje zadny novy pristup k siti.
| Rezim | Kdy |
| ------------------------- | ----------------------------------------------- |
| Bez schvalovani | vychozi. Pojistkou je vycet povolenych nastroju |
| Se schvalenim pres ticket | beh se zastavi, zalozi ticket a ceka na cloveka |
Adresa serveru projde stejnou kontrolou jako u HTTP a SMTP: **nesmi mirit do
vnitrni site** (`isPrivateHost`). Vyplnuje ji firma, takze je to jedina zabrana
proti tomu, aby si nechala zavolat na neco uvnitr.
## Overeni konektoru
`tools/list`. Je to cteci volani, vyzaduje autorizaci a jeho vysledek se stejne
potrebuje - takze overeni a zjisteni nastroju je jedno volani, ne dve.
Vysledek se ulozi ke konektoru a **necte se pri kazdem otevreni builderu**.
Seznam nastroju se meni radove pri nasazeni serveru, ne mezi dvema kliknutimi.
Obnovi se pri overeni konektoru a tlacitkem "Nacist nastroje znovu".
## Co se stane, kdyz nastroj zmizi
Ulozeny strom odkazuje na nastroj jmenem. Server se nasadi znovu, nastroj se
prejmenuje a krok ukazuje do prazdna.
Nesmi to byt chyba ulozeni. Strom uz ulozeny je a firma za to nemuze:
| Situace | Vysledek |
| ---------------------------------------- | ----------------------------------------- |
| Nastroj v ulozenem seznamu neni | **nedodelek**, jako chybejici konektor |
| Server je nedostupny pri ukladani stromu | ulozit, validovat proti ulozenemu seznamu |
| Nastroj chybi az pri behu | koncova chyba, neopakovat |
Rozdil proti dnesku: u naseho skriptu je chybejici operace nase chyba, tady je
to bezna zmena na cizi strane.
Druhy rezim je vic prace, ale zapada do produktu: **delegovani na cloveka uz
umime** a je to presne ono - model dojde k mistu, kde si netroufa, a preda to
dal. Do prvni faze bych to nedaval.
## Bezpecnost
- **Do logu jde odpoved nastroje.** Prochazi stejnou redakci jako vsechno
ostatni, ale token do ni patri jen ten nas - co si server pise do odpovedi,
neovlivnime. Stoji za to pri prvnim nasazeni kouknout, co vraci.
- **Strop na velikost odpovedi** uz existuje (`SCRIPT_MAX_RESPONSE_BYTES`).
U MCP je potreba: nastroj muze vratit cely dokument.
- **Idempotence nefunguje.** Nas `Idempotency-Key` je hlavicka, ktere MCP
nerozumi. Druhy pokus po timeoutu tedy nastroj zavola podruhe. U ctecich
nastroju to nevadi, u zapisovych ano - proto se u kroku s MCP **neopakuje
automaticky**, dokud nebude jak rict, ze je nastroj bezpecny opakovat.
- **Nastroj je zakaznikuv.** Nevidime do nej a neruceme za to, co udela. To je
rozdil proti nasim skriptum a musi to byt videt i v portalu.
- **Adresa serveru nesmi mirit do vnitrni site** (`isPrivateHost`), stejne jako
u HTTP a SMTP. Vyplnuje ji firma.
- **Odpoved nastroje jde do logu behu** a prochazi redakci. Co si server pise
do odpovedi, neovlivnime.
- **Strop na velikost odpovedi** uz existuje a je tu potreba: nastroj muze
vratit cely dokument.
- **Idempotence nefunguje.** MCP zadny takovy pojem nema, takze druhy pokus po
timeoutu zavola nastroj podruhe. Krok s MCP se proto **neopakuje sam**.
- **Nastroj je zakaznikuv.** Nevidime do nej a neruceme za to, co udela.
V portalu to musi byt videt.
- **Model muze zavolat vic nastroju za sebou.** Strop na pocet volani v jednom
kroku je potreba, jinak jde spotreba i cas nahoru bez omezeni.
## Postup
| Faze | Co |
| ---- | -------------------------------------------------------------------------- |
| 1 | Sluzba `mcp`, pole konektoru, overeni pres `tools/list`, ulozeni seznamu |
| 2 | Nastroje do katalogu pres `withRuntimeOptions`, prevod JSON Schema na pole |
| 3 | Skript `mcp.call-tool` a vykonani ve strome |
| 4 | Vycet povolenych nastroju, tlacitko na obnoveni seznamu |
| Faze | Co |
| ---- | ------------------------------------------------------------------------ |
| 1 | Sluzba `mcp`, pole konektoru, overeni pres `tools/list`, ulozeni seznamu |
| 2 | Krok "Nechat model splnit ukol" s vyberem napojeni, varianta B |
| 3 | Zaznam volanych nastroju do logu behu a strop na pocet volani |
| 4 | Schvalovani pres ticket |
Prvni faze je uzitecna sama o sobe: firma si napojeni zalozi a overi, i kdyz
se jeste neda pouzit v kroku.
Prvni faze je uzitecna sama o sobe: firma si napojeni zalozi, overi a vidi,
jake nastroje server nabizi, jeste nez se da pouzit v kroku.
## Co bych nedelal
- **Nezpristupnoval bych MCP zdroje a prompty.** Server umi vedle nastroju
i `resources` a `prompts`. Do stromu kroku patri akce, ne cteni dokumentu -
a bez toho je model o polovinu jednodussi.
- **Nedelal bych z nas MCP server.** Zajimava vec, ale je to opacny smer:
pustit cizi modely na nase tickety je jine rozhodnuti nez zavolat cizi
nastroj.
- **Nespoustel bych nastroje bez konektoru.** Zadny "rychly test adresou" -
napojeni je vlastnost firmy a ma jednu cestu.
- **Nedaval bych MCP nastroje do vyberu kroku.** To byla chyba prvni verze
tohohle navrhu. Kdyz nastroj vybira clovek, staci HTTP konektor - MCP je tam
jen vrstva navic.
- **Nezpristupnoval bych `resources` a `prompts`.** Server je umi vedle
nastroju, ale krok stromu ma neco udelat, ne cist dokumenty.
- **Nedelal bych z nas MCP server.** Je to opacny smer a jine rozhodnuti:
pustit cizi modely na nase tickety.
+12 -7
View File
@@ -7,14 +7,19 @@ Nejnovejsi nahore.
Novy dokument [24-mcp-konektory.md](24-mcp-konektory.md). Neni to
naprogramovane, je to navrh.
Podstata: MCP server je katalog operaci, ktery se zepta az za behu, kdezto nas
katalog je znamy pri prekladu. Retezec `service.actions` -> `findOperation`
-> `scriptIdFor` se dnes cely pta statickeho katalogu, takze by slo pouzit jen
nastroj, ktery uz nekdo predem zapsal do kodu - presny opak toho, o co jde.
**Prvni verze navrhu byla postavena spatne** a je prepsana. Davala MCP nastroje
do vyberu kroku, tedy nastroj vybiral clovek a vyplnil mu pevna pole. Tak MCP
nedava nic navic proti HTTP konektoru, ktery uz mame - je to protokol pro
**modely**, kde si nastroj vybira model podle toho, co je zrovna potreba.
Navrh je **jedna sluzba `mcp` a kazdy server jako konektor pod ni**, s nastroji
doplnenymi do katalogu pres `withRuntimeOptions`, tedy tim samym zpusobem, jakym
uz se doplnuji resitele a typy ticketu. Jen HTTP, ne stdio.
Spravne zadani: MCP konektor **neni zdroj kroku, je to schopnost, kterou dostane
krok s modelem**. V builderu se objevi krok "Nechat model splnit ukol" a v nem
se zaskrtne, ktera napojeni smi pouzit.
Dokument popisuje dve cesty a lisi se tim, kudy tece token zakaznika: predat
server modelu (OpenAI ho zavola sam), nebo byt MCP klientem my. Doporucena je
druha, protoze zbytek systemu stoji na tom, ze udaje neopousti server a ze je
v logu videt kazde volani.
## 2026-08-28 - transformace maji svou kategorii, pribylo XML