MCP konektory: nacte nastroje ze serveru a udela z nich kroky

Firma si zalozi napojeni na svuj MCP server, stiskne Nacist nastroje a jeho
nastroje se objevi v builderu jako kroky automatizace vcetne toho, jake
promenne prijimaji a jake vraceji.

Pribylo:
- sluzba `mcp` - jedina v katalogu bez pevnych operaci, rekne je az server.
  Udaje: adresa serveru, token nebo klic v X-API-Key
- POST /connectors/:id/mcp/tools - zepta se serveru na tools/list a ulozi
  vysledek. Je to zaroven overeni konektoru, proto u MCP neni tlacitko Overit
- src/mcp/client.ts - handshake, sezeni z hlavicky odpovedi, odpoved jako JSON
  i jako SSE stream, strankovani nastroju, nic z toho nevyhazuje vyjimku
- src/mcp/schema.ts - ze schematu vzniknou pole kroku a zpatky se z vyplnenych
  retezcu udelaji argumenty ve spravnych typech. Ten druhy smer je ten
  podstatny: server ceka {"limit": 10}, ne {"limit": "10"}
- src/data/mcpTools.ts - nastroje v katalogu, kes nad tim, co je u konektoru
- sloupec `mcp` u konektoru (migrace 004). Bez ulozeni by po restartu zmizely
  z katalogu kroky, ktere uzivatel uz ma ve stromech
- vnitrni krok runMcpTool - jedna obsluha pro vsechny nastroje vsech serveru

Rozhodnuti:
- nastroj patri firme, ne katalogu. serviceCatalog(tenantId) bez firmy nevrati
  zadny, takze zapomenuty argument znamena "nic", ne "vsechno"
- ID operace nese ID konektoru (tool:<konektor>:<nastroj>), protoze firma muze
  mit dva servery a na obou nastroj `search`
- krok se neopakuje, MCP nema idempotencni klic
- chyba nemaze nastroje, vypadek serveru nesmi vymazat kroky z automatizaci
- servery se pri startu neobvolavaji, jeden nedostupny by shodil katalog vsem

Dokumentace: prepsany 24-mcp-konektory.md na skutecny stav, novy
00-pro-programatory.md (rozcestnik, model ctyr pojmu, pravidla, ktera plati
vsude, co je krehke), doplnene 01, 12 a 99.

Mimochodem opraveno: setStatus v connectors/postgres.ts melo v RETURNING
doslovny retezec ${COLUMNS} misto dosazeni, a dva odstavce v dokumentu 12 byly
dvakrat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-28 09:43:03 +02:00
co-authored by Claude Opus 5
parent 3e6b365eec
commit 435e254c90
22 changed files with 2101 additions and 199 deletions
+159
View File
@@ -0,0 +1,159 @@
# 00 - Pro programatory
Rozcestnik a duvody. Ostatni dokumenty popisuji, **co** aplikace dela, tenhle
popisuje, **jak je postavena a proc tak**.
## Nez zacnete
| Poradi | Precist | Proc |
| ------ | ---------------------------------------------------------------- | -------------------------------------------- |
| 1 | `AGENTS.md` v korenu | pravidla AppFactory, plati nad vsim |
| 2 | tenhle soubor | model a pravidla, ktera plati vsude |
| 3 | [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md) | kde co lezi |
| 4 | [15-rejstrik-funkci.md](15-rejstrik-funkci.md) | ktera funkce je na co, at se nepisou dvakrat |
| 5 | to, co se tyka ukolu | viz tabulka nize |
| Delam | Ctu |
| -------------------- | -------------------------------------------------------------------------------------------------------- |
| sluzbu nebo konektor | [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md), [21-realne-sluzby.md](21-realne-sluzby.md) |
| skript sluzby | [11-skripty-konektoru.md](11-skripty-konektoru.md) |
| krok automatizace | [05-dashboard-a-builder.md](05-dashboard-a-builder.md), [20-fronta-a-runtime.md](20-fronta-a-runtime.md) |
| tickety a helpdesk | [06-tickety.md](06-tickety.md), [18-ticketovaci-system.md](18-ticketovaci-system.md) |
| prava a firmy | [07-firmy-a-prava.md](07-firmy-a-prava.md), [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md) |
| uloziste | [14-databaze.md](14-databaze.md) |
| texty a jazyky | [23-jazyky.md](23-jazyky.md) |
| vzhled | [22-znacka-a-design.md](22-znacka-a-design.md) |
## Model, ktery je potreba mit v hlave
Ctyri pojmy. Plete se to a z te zamneny vznikaji nejhorsi chyby.
| Pojem | Co to je | Ciji je |
| ------------ | ----------------------------------------------- | ------------ |
| **Sluzba** | co umime: iDoklad, e-mail, MCP server, webhook | nase |
| **Skript** | vykonna cast operace, obycejny JS ve `scripts/` | nase |
| **Konektor** | pristupove udaje jedne firmy k jedne sluzbe | firmy |
| **Krok** | jedno pouziti operace v automatizaci | automatizace |
Sluzba rika "iDoklad potrebuje `X-ClientId` a `X-ClientSecret`". Konektor rika
"a tohle jsou nase". Skript rika `GET /issued-invoices/12` a **k udajum se
nedostane** - runtime je doplni az do requestu.
Diky tomu volaji dve firmy tutez sluzbu kazda pod svym uctem a v kodu neni ani
jeden pristupovy udaj.
## Pravidla, ktera plati vsude
**Filtr na firmu je povinny argument.** `listTickets(tenantIds)`,
`getConnector(id, tenantIds)`, `listConnectors(tenantIds)`. Zapomenuty filtr tak
neznamena "vse", ale nezkompiluje se. Cizi zaznam se chova jako neexistujici
(`undefined`), ne jako chyba prava - z odpovedi nema byt poznat, ze existuje.
**Tajna hodnota se nikdy nevraci z API.** `toPublicConnector` je jedine misto,
kde se konektor prevadi pro klienta, prave proto, aby se na to neslo zapomenout
na druhem miste. Do logu jde vsechno pres `createRedactor`, a to **v obou
tvarech** - `Bearer abc` i holé `abc`, protoze cizi sluzby vraceji v chybach
jednou to a jednou ono.
**Zadna ticha selhani.** Kazdy `catch` loguje a uzivatel se o chybe dozvi.
Chyba cizi sluzby neni chyba API: vraci se 200 s `ok: false` a **celym telem
odpovedi**, protoze prave tam je napsane, co jí vadilo. "HTTP 403" samo o sobe
nikoho nikam nedovede.
**Katalog je zdroj pravdy.** Co neni v `src/data/services.ts`, to nejde ulozit
do stromu. Validace pri ukladani se pta katalogu, ne klienta.
**Ve strome je vsechno retezec.** Hodnota kroku je sablona (`{{subject}}`),
takze i cislo je text. Prevod na skutecny typ se dela az pred volanim, u toho,
kdo vi, jaky typ to ma byt.
**Prava se nedovozuji na klientovi.** Server vraci `GET /api/dashboard/access`
s tim, co uzivatel smi. Dvoji vypocet se jednou rozejde.
**Prava plati za firmu, ne za cloveka.** `permissionsOf(user, tenantId)` ma
firmu jako povinny argument. Clovek muze byt spravce v jedne firme a bezny
uzivatel v druhe.
## Jak vznika operace, kterou clovek vybere v builderu
Tri cesty, kazda ma svuj duvod:
| Cesta | Kdy | Kde |
| -------------------- | --------------------------------------- | ----------------------------- |
| **Zapis v katalogu** | popis toho, co umime nebo budeme umet | `src/data/services.ts` |
| **Skript** | volani cizi sluzby pres HTTP | `scripts/*.js` + manifest |
| **Vnitrni krok** | sahá do naseho uloziste, nebo neni HTTP | `src/runtime/builtinSteps.ts` |
Runtime zkousi **nejdriv vnitrni krok, pak skript**. Kdyz operace nema ani
jedno, je to zatim jen zapis v katalogu a krok to rekne narovinu misto toho, aby
tise neudelal nic.
Vnitrni krok je pro zalozeni ticketu (nase uloziste), pro e-mail (SMTP neni HTTP)
a pro nastroje MCP (JSON-RPC se sezenim). Skript ma jen `ctx.http` a to je
zamer: skript, ktery umi sahnout do uloziste, uz neni skript, ale druha
aplikace.
## Katalog, ktery se meni za behu
Sluzby jsou staticky zapis v modulu. Tri veci se do nej doplnuji az za behu
a kazda jinak:
| Co | Odkud | Klic |
| ------------------ | --------------------- | -------------------- |
| operace ze skriptu | manifest skriptu | `setScriptActions` |
| nabidky v polich | uloziste (lide, typy) | `withRuntimeOptions` |
| nastroje MCP | cizi server | `setMcpOperations` |
Prvni dve jsou nase a spolecne vsem firmam. Treti je jina: **nastroj MCP patri
jedne firme**, protoze ho vystavuje jeji server. Proto s sebou nese `tenantId`
a `serviceCatalog(tenantId)` bez firmy nevrati zadny. Zapomenuty argument tak
znamena "nic", ne "vsechno" - stejne pravidlo jako u filtru na firmu.
U MCP navic **ID operace nese ID konektoru** (`tool:con_a1b2:create_order`).
Firma muze mit dva servery a na obou nastroj `search`; kdyby ID bylo jen
`search`, krok by nemel jak rict, ktery z nich. U ostatnich sluzeb to nehrozi,
tam je operace vlastnost sluzby, ne napojeni.
## Uloziste ma tri rezimy
| Rezim | Kdy | Prezije |
| ---------- | ----------------------------------- | -------------------- |
| `postgres` | je `DATABASE_URL` i `SECRETS_KEY` | vse |
| `file` | neni databaze, ale je datova slozka | restart, ne redeploy |
| `memory` | ani jedno | nic |
**Rozhodnuti je na jednom miste** (`connectorStore.ts`). Kdyby se rozlezlo po
kodu, jedno misto by se zapomnelo a chovalo by se pak jinak nez zbytek.
Nasazeni bezi v rezimu `file`. Ma to dusledek, ktery je poznat az pri zatezi:
**kazda zmena prepisuje celou kolekci** jako formatovany JSON. U ticketu, ktere
nesou i log prubehu, to s poctem zaznamu roste. Az bude portal pomaly, tohle je
prvni misto, kam se divat.
## Co je krehke
| Misto | Co hrozi |
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| ID operaci a poli v katalogu | odkazuji se na ne ulozene stromy, prejmenovani je rozbije |
| poradi v `bootstrapData` | tickety az po resitelich, nastroje MCP az po firmach |
| overeni konektoru bez `verifyPath` | proxy vraci 200 s prazdnym telem i pro neexistujici aplikaci, takze test projde a nic to nerika |
| migrace | nikdy se neupravuji zpetne, oprava je vzdy novy soubor |
| BOM v JSONu | rozbije Node i Vite, zapisovat UTF-8 bez BOM |
## Kdyz neco pridavate
**Novou sluzbu** popiste v katalogu vcetne `credentials` a `verifyPath`. Bez
`verifyPath` overeni konektoru nerika nic o udajich, jen ze neco odpovida.
**Novy endpoint** patri do `src/openapi.ts`. Neni to volitelne, vyzaduje to
`AGENTS.md`, a nezdokumentovany endpoint neexistuje pro nikoho krome toho, kdo
ho napsal.
**Novy text v portalu** patri do `web/src/i18n/cs.ts` a klic do `en.ts`.
Anglictina je `Partial`, takze nemusi byt uplna - co chybi, spadne na cestinu.
**Novy sloupec** znamena novou migraci **a** doplneni obou uloziste,
souborového i databazoveho. Rozhrani je jedno, implementace dve.
Na konci prace se aktualizuje [01-prehled-a-stav.md](01-prehled-a-stav.md)
a [99-zmeny.md](99-zmeny.md).
+6
View File
@@ -39,6 +39,7 @@ React aplikaci ze slozky `dist/public`.
| Odesilani souboru ze skriptu | hotovo | `ctx.http.postForm`, obsah jako Base64 |
| OpenAI pod vlastnim klicem | hotovo | dotaz, soubor, prepis zvuku, seznam modelu |
| Konektory za firmu | hotovo | pristupove udaje v konektoru, overeni napojeni |
| MCP servery firmy | hotovo | nacte nastroje ze serveru, kazdy je krok automatizace |
| Transformace dat | hotovo | pravidla i sablona JSON, kroky si predavaji struktury |
| Prace nad celym modelem | hotovo | ukazka tela, cesty v sablonach, smycka nad seznamem |
| Vlastni skripty firmy | hotovo | prevod dat v JS, v logu vstup i vystup |
@@ -122,6 +123,7 @@ pro frontu, beh kroku a rozpocet na 150 klientu, a
| Soubor | O cem |
| ---------------------------------------------------------------- | ----------------------------------------------- |
| [00-pro-programatory.md](00-pro-programatory.md) | rozcestnik a duvody rozhodnuti |
| [02-appfactory-proxy.md](02-appfactory-proxy.md) | beh za reverse proxy, ROOT_PATH, health |
| [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md) | kde co je |
| [04-api.md](04-api.md) | endpointy a to, co ze Swaggeru neni videt |
@@ -141,4 +143,8 @@ pro frontu, beh kroku a rozpocet na 150 klientu, a
| [18-ticketovaci-system.md](18-ticketovaci-system.md) | udalosti, externi ID, statistiky, pohledy |
| [19-kapacita-200-firem.md](19-kapacita-200-firem.md) | zmereno, co zvladne soucasny stav |
| [20-fronta-a-runtime.md](20-fronta-a-runtime.md) | fronta, worker, spoustece, ochrana proti smycce |
| [21-realne-sluzby.md](21-realne-sluzby.md) | napojeni na bezici aplikace a e-mail |
| [22-znacka-a-design.md](22-znacka-a-design.md) | znacka WorkNuke, tokeny, prvky |
| [23-jazyky.md](23-jazyky.md) | prepinani jazyku a slovniky |
| [24-mcp-konektory.md](24-mcp-konektory.md) | MCP servery firmy jako kroky |
| [99-zmeny.md](99-zmeny.md) | zaznam zmen, nejnovejsi nahore |
+29 -22
View File
@@ -54,13 +54,19 @@ Adresu lze prepsat na dvou urovnich:
Nazev promenne vznikne z ID sluzby velkymi pismeny, pomlcka je podtrzitko:
`openai` je `OPENAI_BASE_URL`, `sap-bo` je `SAP_BO_BASE_URL`.
Treti pripad je sluzba, ktera **nejde pres HTTP**: e-mail. Ma
`transport: 'smtp'`, adresu serveru nese konektor mezi udaji a operaci nevykona
skript, ale vnitrni krok. Podrobnosti v [21-realne-sluzby.md](21-realne-sluzby.md).
Treti pripad jsou sluzby, ktere **nejdou pres HTTP tak jako zbytek**. Rika to
pole `transport`:
Treti pripad je sluzba, ktera **nejde pres HTTP**: e-mail. Ma
`transport: 'smtp'`, adresu serveru nese konektor mezi udaji a operaci nevykona
skript, ale vnitrni krok. Podrobnosti v [21-realne-sluzby.md](21-realne-sluzby.md).
| `transport` | Sluzba | Cim se lisi |
| ----------- | ---------- | -------------------------------------------------------------- |
| `http` | vetsina | vychozi, skript rekne cestu a runtime doplni adresu a hlavicky |
| `smtp` | E-mail | neni to HTTP, operaci vykona vnitrni krok |
| `mcp` | MCP server | JSON-RPC se sezenim, a hlavne **zadne operace v katalogu** |
U obou nevychozich nese adresu serveru **konektor mezi udaji**, ne pole "vlastni
adresa sluzby": sluzba zadnou vlastni adresu nema, protoze kazda firma ma svuj
server. Podrobnosti v [21-realne-sluzby.md](21-realne-sluzby.md)
a [24-mcp-konektory.md](24-mcp-konektory.md).
## Pristupove udaje patri konektoru, ne prostredi
@@ -193,11 +199,11 @@ Tak je na tom Prepis hovoru: jeho jedine volani je prepis, ktery se uctuje.
`verifyPath` smi nest i query (`/company?limit=1`), aby overeni nestahovalo
cely seznam. Do hlasky se query nedava, odrizne se - muze v ni byt tajemstvi.
U sluzby s `transport: 'smtp'` se `verifyPath` nepouziva vubec: overeni se
**prihlasi na posmovni server** a nic neodesle.
U sluzeb, ktere nejdou pres HTTP, se `verifyPath` nepouziva vubec:
U sluzby s `transport: 'smtp'` se `verifyPath` nepouziva vubec: overeni se
**prihlasi na posmovni server** a nic neodesle.
- `smtp` se **prihlasi na posmovni server** a nic neodesle,
- `mcp` si **rekne o seznam nastroju**, coz je jedina cteci operace, kterou
protokol ma.
Neuspesne overeni **neni chyba API**. Vraci se 200 s `ok: false` a popisem,
protoze vysledek "nefunguje to" je platna odpoved na otazku "funguje to?".
@@ -293,18 +299,19 @@ Cte se zvlast pres `/connectors/:id/checks`.
## API
| Metoda | Cesta | Popis |
| ------ | -------------------------------------- | ------------------------- |
| GET | `/api/dashboard/services` | katalog pro builder |
| GET | `/api/dashboard/connectors/services` | katalog ocima firmy |
| GET | `/api/dashboard/connectors` | konektory firmy |
| POST | `/api/dashboard/connectors` | zalozit |
| GET | `/api/dashboard/connectors/:id` | detail |
| PATCH | `/api/dashboard/connectors/:id` | upravit |
| DELETE | `/api/dashboard/connectors/:id` | smazat |
| POST | `/api/dashboard/connectors/:id/test` | overit napojeni |
| GET | `/api/dashboard/connectors/:id/checks` | poslednich pet overeni |
| GET | `/api/dashboard/connectors/egress-ip` | odchozi IP adresa portalu |
| Metoda | Cesta | Popis |
| ------ | ----------------------------------------- | --------------------------- |
| GET | `/api/dashboard/services` | katalog pro builder |
| GET | `/api/dashboard/connectors/services` | katalog ocima firmy |
| GET | `/api/dashboard/connectors` | konektory firmy |
| POST | `/api/dashboard/connectors` | zalozit |
| GET | `/api/dashboard/connectors/:id` | detail |
| PATCH | `/api/dashboard/connectors/:id` | upravit |
| DELETE | `/api/dashboard/connectors/:id` | smazat |
| POST | `/api/dashboard/connectors/:id/test` | overit napojeni |
| GET | `/api/dashboard/connectors/:id/checks` | poslednich pet overeni |
| POST | `/api/dashboard/connectors/:id/mcp/tools` | nacist nastroje MCP serveru |
| GET | `/api/dashboard/connectors/egress-ip` | odchozi IP adresa portalu |
## Stranky portalu
+146 -138
View File
@@ -1,161 +1,169 @@
# 24 - MCP konektory
Navrh, neni naprogramovane.
Hotovo. Firma si zalozi napojeni na svuj MCP server, stiskne **Nacist nastroje**
a jeho nastroje se objevi v builderu jako kroky automatizace.
Cil: firma vyplni udaje sveho MCP serveru a jeho nastroje muze pouzit model,
ktery za ni neco udela.
## Co MCP je
## Co MCP je a co neni
Protokol, kterym se zjisti, **co protistrana umi**, misto aby se to muselo
predem naprogramovat. Server vystavi nastroje, u kazdeho jmeno, popis a schema
toho, co prijima a co vraci. Klient si o ne rekne (`tools/list`) a pak je vola
(`tools/call`).
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.
Bezne je klientem model - dostane nastroje jako sve schopnosti a sam si vybira,
ktery zavolat. **My jsme klientem taky, jen si nastroj vybira clovek v
builderu.** Prinos je tentyz: napojeni na cizi system vznikne bez radky kodu
u nas.
**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.
## Jak to vypada
Z toho plyne jedina vec, na ktere cely navrh stoji:
1. Konektory, Novy konektor, sluzba **MCP server**.
2. Vyplni se adresa serveru a token.
3. Tlacitko **Nacist nastroje**. Portal se serveru zepta, co nabizi.
4. V builderu jsou nastroje jako kroky, s vlastnimi poli a vystupy.
> **MCP konektor neni zdroj kroku. Je to schopnost, kterou dostane krok
> s modelem.**
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
```
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
```
Model dostane nastroje zaskrtnutych serveru, sam se rozhodne, ktere zavolat,
a krok vrati vysledek plus **seznam toho, co model opravdu udelal**.
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.
## Dve cesty, jak to postavit
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"
}
```
Malo prace: konektor drzi adresu a token, krok je slozi do pozadavku.
Cena za to:
- **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.
### B) MCP klientem jsme my
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.
Vic prace, ale:
- 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.
**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.
Tlacitko **Nastroje** u konektoru pak ukaze, co server rekl: u kazdeho nastroje
popis, jake parametry prijima (povinne s hvezdickou) a jake hodnoty vraci.
## Co si firma vyplni
| 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 |
| Pole | K cemu |
| ------------------- | -------------------------------------------------- |
| Adresa MCP serveru | cely endpoint, napr. `https://mcp.firma.cz/mcp` |
| Token | posila se jako `Authorization: Bearer` |
| API klic v hlavicce | pro servery, ktere misto tokenu chteji `X-API-Key` |
**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.
Adresa je **mezi udaji**, ne v poli "vlastni adresa sluzby". U ostatnich sluzeb
je adresa vlastnost sluzby a konektor ji smi jen prepsat, tady je to naopak:
sluzba zadnou adresu nema, protoze kazda firma ma svuj server. Stejne to ma
SMTP.
## Schvalovani
Vlastni jmeno hlavicky se zadat neda. Bud `Authorization: Bearer`, nebo
`X-API-Key` - to jsou dva zpusoby, kterymi se autorizuje drtiva vetsina
serveru. Zadat libovolnou hlavicku by znamenalo zmenu modelu pristupovych
udaju, kde jmeno hlavicky urcuje sluzba, ne konektor.
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:
## Nacteni nastroju
| Rezim | Kdy |
| ------------------------- | ----------------------------------------------- |
| Bez schvalovani | vychozi. Pojistkou je vycet povolenych nastroju |
| Se schvalenim pres ticket | beh se zastavi, zalozi ticket a ceka na cloveka |
`POST /api/dashboard/connectors/{id}/mcp/tools`
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.
Je to zaroven **overeni konektoru**, proto se zapisuje do historie: kdyz server
odpovi seznamem, adresa i token sedi. Nic to nemeni, da se to spustit kdykoliv.
Tlacitko "Overit" u MCP konektoru neni - delalo by presne tohle.
## Bezpecnost
Dve pravidla, ktera nejsou zrejma:
- **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.
- **Prazdny vysledek se ulozi.** Server, ktery uz nastroj nenabizi, ma zmizet
i z katalogu.
- **Chyba nemeni nic.** Vypadek serveru nesmi vymazat kroky z automatizaci,
ktere uz nekdo postavil.
## Postup
## Jak se z nastroje stane krok
| 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 |
Prevod dela `src/mcp/schema.ts`, prochazi se **prvni uroven** vstupniho
schematu:
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.
| Ve schematu | V builderu |
| ----------------- | -------------------------------- |
| `string` | radek textu |
| `enum` | vyber z hodnot |
| `boolean` | vyber Ano / Ne |
| `number` | radek textu, prevede se na cislo |
| `object`, `array` | pole na JSON |
## Co bych nedelal
Zanoreny objekt zustava jedno pole s JSONem. Rozpadat ho na `adresa.ulice` by
znamenalo vymyslet si jmena, ktera server nezna, a u seznamu by to neslo vubec.
- **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.
Zpatky je to ten podstatny smer: ve strome je **vsechno retezec**, protoze je to
sablona s `{{promennymi}}`, ale server ceka `{"limit": 10}`, ne `{"limit": "10"}`.
Prevod na typy ze schematu se deje az pred volanim. Rada serveru na tom jinak
spadne az uvnitr nastroje, kde uz neni poznat, co se stalo.
Nepovinne pole, ktere zustane prazdne, **se serveru vubec neposle**. Prazdny
retezec neni totez jako nevyplneno - pretisknul by vychozi hodnotu serveru.
## Co krok vraci
Vzdy tri hodnoty, at uz nastroj deklaruje cokoliv:
| Vystup | Co je to |
| ------------ | ----------------------------------------------------- |
| `text` | textova cast odpovedi |
| `isError` | nastroj rekl, ze se nepovedlo (neni to chyba spojeni) |
| `structured` | strukturovana cast, kdyz ji nastroj ma |
Kdyz nastroj deklaruje `outputSchema`, jsou k tomu jeho vlastni pole rozbalena
do vystupu, takze na ne jde postavit podminka bez psani cesty. Pri kolizi jmen
vyhravaji ty tri spolecne: `text` znamena text odpovedi vzdycky, at uz si
nastroj rika co chce. Vlastni pole toho jmena je porad v `structured`.
`outputSchema` je v MCP nepovinne a vetsina serveru ho nema. Pak je znamy jen
text odpovedi. Neni to nedodelek u nas.
## Bezpecnost a limity
- **Adresa nesmi mirit do vnitrni site.** Tataz kontrola jako u HTTP a SMTP,
vyplnuje ji firma.
- **Token neopousti server.** Do prohlizece se nevraci a v logu je zredigovany
vcetne tvaru bez slova `Bearer`.
- **Cizi napojeni se chova jako neexistujici.** Krok si konektor nacita pres
filtr na firmu, takze strom s cizim ID konektoru selze.
- **Krok se neopakuje.** MCP nema idempotencni klic, takze druhy pokus po
timeoutu by nastroj provedl podruhe - a jestli to znamena druhou objednavku,
vi jen server, ktery neni nas.
- Plati stejny timeout a strop na velikost odpovedi jako u skriptu
(`SCRIPT_TIMEOUT_MS`, `SCRIPT_MAX_RESPONSE_BYTES`).
- `tools/list` se strankuje nejvys dvacetkrat. Server, ktery vraci porad tentyz
kurzor, by jinak cyklil donekonecna.
## Co se **nedela**
- **Nastroje se pri startu neobvolavaji.** Cte se to, co je ulozene
u konektoru. Obvolavat servery vsech firem kvuli startu aplikace by
znamenalo, ze jeden nedostupny server shodi katalog vsem.
- **Zmena udaju nastroje nemaze.** Jina adresa muze vratit jiny seznam, ale
dokud ho nekdo nenacte, jsou ty stare porad to jedine, co v ulozenych
automatizacich drzi kroky nazivu.
- **`resources` a `prompts` se nectou.** Server je umi vedle nastroju, ale krok
stromu ma neco udelat, ne cist dokumenty.
- **Nejsme MCP server.** Je to opacny smer a jine rozhodnuti: pustit cizi
modely na nase tickety.
## Kde to je
| Cast | Soubor |
| ------------------- | ------------------------------------------- |
| Protokol | `src/mcp/client.ts` |
| Prevod schemat | `src/mcp/schema.ts` |
| Nastroje v katalogu | `src/data/mcpTools.ts` |
| Sluzba `mcp` | `src/data/services.ts` |
| Nacteni nastroju | `src/routes/connectors.ts` |
| Vykonna cast kroku | `src/runtime/builtinSteps.ts`, `runMcpTool` |
| Ulozeni u konektoru | `src/data/connectors/*`, migrace `004` |
| Portal | `web/src/pages/dashboard/Connectors.tsx` |
## Co jeste chybi
| Chybi | Poznamka |
| ---------------------------- | -------------------------------------------------------------------- |
| Nastroje pro model | dnes vybira nastroj clovek. Predat je modelu je dalsi krok, viz nize |
| stdio a starsi SSE transport | umi se jen Streamable HTTP, tedy to, co delaji verejne servery |
| Schvalovani volani | server ho umi vyzadovat, my na to zatim neumime cekat |
| Vlastni jmeno hlavicky | jen `Authorization` a `X-API-Key` |
### Nastroje pro model
Az bude krok "nechat model splnit ukol", muze dostat nastroje MCP serveru jako
sve schopnosti a vybirat si sam. Dve cesty, lisi se tim, kudy tece token:
- **Server preda modelu OpenAI.** Malo prace (`{"type": "mcp", ...}` v Responses
API), ale token jde do OpenAI, ta vola server firmy primo, server musi byt
dostupny z jeji site a volani nejdou pres nas log.
- **Klientem zustaneme my.** Nastroje se modelu predaji jako obycejne funkce
a volame je my. Token zustava u nas, plati nase stropy a redakce, funguje to
s jakymkoliv modelem - a hlavne uz je to postavene, tenhle dokument je presne
o tom.
Druha cesta je uz z devadesati procent hotova. Prvni by znamenala poslat
zakaznikuv token treti strane, coz je proti tomu, jak je postaveny zbytek
systemu.
+56 -14
View File
@@ -2,24 +2,66 @@
Nejnovejsi nahore.
## 2026-08-28 - navrh MCP konektoru
## 2026-08-28 - MCP konektory hotove
Novy dokument [24-mcp-konektory.md](24-mcp-konektory.md). Neni to
naprogramovane, je to navrh.
Firma si zalozi napojeni na svuj MCP server, stiskne **Nacist nastroje** a jeho
nastroje se objevi v builderu jako kroky. Popis
v [24-mcp-konektory.md](24-mcp-konektory.md).
**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 byl predtim dvakrat prepsany a stoji za to rict proc. Prvni verze davala
nastroje do vyberu kroku, ale popisovala MCP jako obycejny katalog vzdalenych
procedur - tak nedava nic navic proti HTTP konektoru. Druha verze to prehnala
opacnym smerem a chtela nastroje predavat jen modelu. Vysledne zadani je
uprostred: **klientem jsme my**, nastroj vybira clovek v builderu, a prinos je
v tom, ze napojeni na cizi system vznikne bez radky kodu u nas.
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.
### Pribylo
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.
- **Sluzba `mcp`.** Jedina v katalogu, ktera nema zadne pevne operace - rekne je
az server. Udaje jsou adresa serveru, token nebo klic v `X-API-Key`.
- **`POST /connectors/:id/mcp/tools`.** Zepta se serveru na `tools/list` a ulozi
vysledek. Je to zaroven overeni konektoru, proto tlacitko "Overit" u MCP neni.
- **Klient protokolu** (`src/mcp/client.ts`): handshake, sezeni z hlavicky
odpovedi, odpoved jako JSON i jako SSE stream, strankovani nastroju.
- **Prevod schemat** (`src/mcp/schema.ts`). Ze schematu vzniknou pole kroku
a zpatky se z vyplnenych retezcu udelaji argumenty ve spravnych typech.
Ten druhy smer je ten podstatny: server ceka `{"limit": 10}`, ne
`{"limit": "10"}`, a rada serveru na tom spadne az uvnitr nastroje.
- **Nastroje u konektoru** (sloupec `mcp`, migrace `004`). Bez ulozeni by po
restartu zmizely z katalogu kroky, ktere uzivatel uz ma ve stromech.
- **Vnitrni krok `runMcpTool`.** Jedna obsluha pro vsechny nastroje vsech
serveru - ktery to je, rika az ID operace `tool:<konektor>:<nastroj>`.
### Rozhodnuti, ktera stoji za zminku
- **Nastroj patri firme, ne katalogu.** `serviceCatalog(tenantId)` bez firmy
nevrati zadny nastroj. Zapomenuty argument tak znamena "nic", ne "vsechno" -
stejne pravidlo jako u filtru na firmu.
- **ID operace nese ID konektoru.** Firma muze mit dva servery a na obou nastroj
`search`.
- **Krok se neopakuje.** MCP nema idempotencni klic, druhy pokus po timeoutu by
nastroj provedl podruhe.
- **Chyba nemaze nastroje.** Vypadek serveru nesmi vymazat kroky z hotovych
automatizaci. Prazdny seznam se naopak ulozi.
- **Servery se pri startu neobvolavaji.** Jeden nedostupny by shodil katalog
vsem.
### Opraveno mimochodem
- `setStatus` v `connectors/postgres.ts` melo v `RETURNING` doslovny retezec
`${COLUMNS}` misto dosazeni. V rezimu `file`, ve kterem bezi nasazeni, se to
neprojevilo.
- Dva odstavce v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md) byly
dvakrat.
## 2026-08-28 - dokument pro programatory
Novy [00-pro-programatory.md](00-pro-programatory.md): rozcestnik, model ctyr
pojmu (sluzba, skript, konektor, krok), pravidla, ktera plati vsude, tri cesty
vzniku operace, tri rezimy uloziste a seznam mist, ktera jsou krehka.
Neni to kopie [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md).
Ta rika, kde co lezi, tenhle rika proc to tak je.
## 2026-08-28 - transformace maji svou kategorii, pribylo XML