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:
co-authored by
Claude Opus 5
parent
3e6b365eec
commit
435e254c90
@@ -11,6 +11,7 @@ import {
|
||||
Star,
|
||||
Trash2,
|
||||
Wifi,
|
||||
Wrench,
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -36,6 +37,7 @@ import type {
|
||||
ServiceOverview,
|
||||
ServiceWithUsage,
|
||||
StorageStatus,
|
||||
ToolsLoadResult,
|
||||
} from '@/types/dashboard';
|
||||
|
||||
/**
|
||||
@@ -325,6 +327,41 @@ function ConnectorCard({
|
||||
const [test, setTest] = useState<ConnectorTestResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [logsOpen, setLogsOpen] = useState(false);
|
||||
const [toolsOpen, setToolsOpen] = useState(false);
|
||||
|
||||
/**
|
||||
* MCP se neoveruje jako ostatni sluzby.
|
||||
*
|
||||
* Jinde je overeni cteci volani navic, tady je to ta hlavni vec, kterou
|
||||
* konektor umi: teprve tim se zjisti, jake nastroje server nabizi, a
|
||||
* teprve pak jde postavit krok. Proto to neni "Overit", ale "Nacist nastroje".
|
||||
*/
|
||||
const isMcp = service?.transport === 'mcp';
|
||||
|
||||
async function handleLoadTools() {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await apiFetch<ToolsLoadResult>(
|
||||
`/api/dashboard/connectors/${connector.id}/mcp/tools`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
// Stejny tvar jako vysledek overeni, takze se ukaze stejnou cestou.
|
||||
setTest({
|
||||
ok: result.ok,
|
||||
checked: result.checked,
|
||||
message: result.message,
|
||||
status: result.status,
|
||||
detail: result.detail,
|
||||
});
|
||||
if (result.ok) setToolsOpen(true);
|
||||
onChanged();
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : 'Načtení nástrojů se nepodařilo odeslat.');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTest() {
|
||||
setBusy(true);
|
||||
@@ -379,8 +416,20 @@ function ConnectorCard({
|
||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||
<StatusBadge connector={connector} />
|
||||
{!connector.enabled && <Badge tone="warn">vypnutý</Badge>}
|
||||
{connector.mcp && (
|
||||
<Badge tone={connector.mcp.tools.length > 0 ? 'ok' : 'warn'}>
|
||||
nástrojů: {connector.mcp.tools.length}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isMcp && !connector.mcp && connector.ready && (
|
||||
<p className="mt-2 text-xs text-white/45">
|
||||
Nástroje ještě nejsou načtené. Dokud se nenačtou, nejde ze serveru postavit žádný
|
||||
krok automatizace.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{connector.missing.length > 0 && service && (
|
||||
<p className="mt-2 text-xs text-warn-400">
|
||||
Chybí:{' '}
|
||||
@@ -428,10 +477,26 @@ function ConnectorCard({
|
||||
<Pencil className="size-3.5" />
|
||||
Upravit
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={handleTest} disabled={busy}>
|
||||
<Wifi className="size-3.5" />
|
||||
{busy ? 'Ověřuji...' : 'Ověřit'}
|
||||
</Button>
|
||||
{isMcp ? (
|
||||
<>
|
||||
<Button size="sm" variant="ghost" onClick={handleLoadTools} disabled={busy}>
|
||||
<Wifi className="size-3.5" />
|
||||
{busy ? 'Načítám...' : 'Načíst nástroje'}
|
||||
</Button>
|
||||
{connector.mcp && connector.mcp.tools.length > 0 && (
|
||||
<Button size="sm" variant="ghost" onClick={() => setToolsOpen(true)}>
|
||||
<Wrench className="size-3.5" />
|
||||
Nástroje
|
||||
<span className="text-white/40">{connector.mcp.tools.length}</span>
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button size="sm" variant="ghost" onClick={handleTest} disabled={busy}>
|
||||
<Wifi className="size-3.5" />
|
||||
{busy ? 'Ověřuji...' : 'Ověřit'}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" variant="ghost" onClick={() => setLogsOpen(true)}>
|
||||
<ScrollText className="size-3.5" />
|
||||
Logy
|
||||
@@ -451,10 +516,99 @@ function ConnectorCard({
|
||||
open={logsOpen}
|
||||
onClose={() => setLogsOpen(false)}
|
||||
/>
|
||||
|
||||
<ConnectorTools
|
||||
connector={connector}
|
||||
open={toolsOpen}
|
||||
onClose={() => setToolsOpen(false)}
|
||||
/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nastroje, ktere MCP server nabizi.
|
||||
*
|
||||
* Ukazuje se tu **to same**, z ceho se stavi kroky v automatizaci - jmena poli
|
||||
* i vystupu jsou prevedena tou samou funkci na serveru. Kdyby to byly dva
|
||||
* prevody, portal by u konektoru ukazoval jina pole, nez jakymi se nastroj
|
||||
* opravdu vola.
|
||||
*
|
||||
* Nenacita se: seznam uz je soucasti konektoru, protoze se uklada pri stisku
|
||||
* Nacist nastroje. Tim je videt i to, jak stary ten seznam je.
|
||||
*/
|
||||
function ConnectorTools({
|
||||
connector,
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
connector: Connector;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const toolset = connector.mcp;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
title={`Nástroje: ${connector.name}`}
|
||||
description="Co server nabízí. Každý nástroj je v automatizaci samostatný krok."
|
||||
className="max-w-4xl"
|
||||
>
|
||||
{toolset && (
|
||||
<p className="border-b border-ink-600/60 px-5 py-3 text-xs text-white/40">
|
||||
{toolset.server}
|
||||
<span className="text-white/25"> / protokol {toolset.protocolVersion}</span>
|
||||
<span className="block">Načteno {formatDateTime(toolset.at)}</span>
|
||||
</p>
|
||||
)}
|
||||
<div className="max-h-[70vh] space-y-3 overflow-y-auto p-5">
|
||||
{!toolset && (
|
||||
<p className="text-sm text-white/40">
|
||||
Nástroje ještě nejsou načtené. Stiskněte Načíst nástroje.
|
||||
</p>
|
||||
)}
|
||||
{toolset?.tools.length === 0 && (
|
||||
<p className="text-sm text-white/40">Server odpověděl, ale žádný nástroj nenabízí.</p>
|
||||
)}
|
||||
|
||||
{toolset?.tools.map((tool) => (
|
||||
<div key={tool.name} className="rounded-lg border border-ink-600/60 p-3">
|
||||
<p className="font-semibold text-white">
|
||||
{tool.label}
|
||||
{tool.label !== tool.name && (
|
||||
<span className="ml-2 font-mono text-xs font-normal text-white/35">{tool.name}</span>
|
||||
)}
|
||||
</p>
|
||||
{tool.description && (
|
||||
<p className="mt-1 text-xs text-white/50">{tool.description}</p>
|
||||
)}
|
||||
<dl className="mt-2 grid gap-2 text-xs sm:grid-cols-2">
|
||||
<div>
|
||||
<dt className="text-white/35">Přijímá</dt>
|
||||
<dd className="text-white/70">
|
||||
{tool.inputs.length > 0 ? tool.inputs.join(', ') : 'nic'}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-white/35">Vrací</dt>
|
||||
<dd className="text-white/70">{tool.outputs.join(', ')}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{toolset && toolset.tools.length > 0 && (
|
||||
<p className="border-t border-ink-600/60 px-5 py-3 text-xs text-white/35">
|
||||
Hvězdička u parametru znamená povinný. Nepovinný parametr, který necháte prázdný, se
|
||||
serveru vůbec nepošle.
|
||||
</p>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poslednich pet overeni konektoru.
|
||||
*
|
||||
@@ -773,10 +927,10 @@ function ConnectorEditor({
|
||||
)}
|
||||
|
||||
{/*
|
||||
U SMTP nemá vlastní adresa co dělat: server, port i šifrování jsou
|
||||
mezi údaji výše. Prázdné pole navíc by svádělo sem psát adresu znovu.
|
||||
U SMTP i u MCP nese adresu konektor mezi udaji, ne tohle pole.
|
||||
Prazdne pole navic by svadelo psat adresu podruhe.
|
||||
*/}
|
||||
{service?.transport !== 'smtp' && (
|
||||
{(service?.transport ?? 'http') === 'http' && (
|
||||
<Field
|
||||
label="Vlastní adresa služby"
|
||||
hint="Nechte prázdné, pokud nemáte vlastní instanci."
|
||||
|
||||
@@ -319,8 +319,11 @@ export interface Service {
|
||||
/** true = funguje bez konektoru (webhook, pauza, transformace dat). */
|
||||
general: boolean;
|
||||
appId: string | null;
|
||||
/** `smtp` = sluzba se nevola pres HTTP, adresu nese konektor v udajich. */
|
||||
transport?: 'http' | 'smtp';
|
||||
/**
|
||||
* `smtp` = sluzba se nevola pres HTTP, adresu nese konektor v udajich.
|
||||
* `mcp` = totez, a navic zadne operace v katalogu nema - rekne je az server.
|
||||
*/
|
||||
transport?: 'http' | 'smtp' | 'mcp';
|
||||
visibility: ServiceVisibility;
|
||||
credentials: ServiceCredentialField[];
|
||||
verifyPath?: string;
|
||||
@@ -369,6 +372,39 @@ export interface Connector {
|
||||
/** Necitliva nastaveni. Tajna pole tu nejsou vubec. */
|
||||
config: Record<string, string>;
|
||||
ready: boolean;
|
||||
/** Nastroje MCP serveru. `null` u vsech ostatnich sluzeb. */
|
||||
mcp: Toolset | null;
|
||||
}
|
||||
|
||||
/** Co MCP server nabizi. Vznika stiskem Nacist nastroje. */
|
||||
export interface Toolset {
|
||||
/** ISO cas nacteni. Rika, jak stary seznam clovek vidi. */
|
||||
at: string;
|
||||
server: string;
|
||||
protocolVersion: string;
|
||||
tools: ToolSummary[];
|
||||
}
|
||||
|
||||
export interface ToolSummary {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
/** Nazvy parametru. Povinne maji na konci hvezdicku. */
|
||||
inputs: string[];
|
||||
/** Nazvy hodnot, ktere nastroj vraci. */
|
||||
outputs: string[];
|
||||
}
|
||||
|
||||
/** Odpoved na nacteni nastroju. Neuspech je vysledek, ne chyba API. */
|
||||
export interface ToolsLoadResult {
|
||||
ok: boolean;
|
||||
checked: string;
|
||||
message: string;
|
||||
status?: number;
|
||||
detail?: string;
|
||||
server?: string;
|
||||
protocolVersion?: string;
|
||||
tools: ToolSummary[];
|
||||
}
|
||||
|
||||
/** Kam se uklada a co to prezije. */
|
||||
|
||||
Reference in New Issue
Block a user