MCP-integráció
Az AbstractMcpTool olyan tool alaposztálya, amely a válaszhoz szükséges
adatot egy vagy több MCP (Model Context Protocol) szervertől szerzi be – egyedi
REST-integráció, saját HTTP-kliens és saját hitelesítés-kezelés nélkül. Ez a fejezet
a szerverek deklarálását, a hívásokat, a felhasználóhoz fordulás eszközeit és a
platform által garantált biztonsági viselkedést mutatja be.
14.1 Mikor válaszd az AbstractMcpTool-t?
Ha egy külső vagy belső rendszer (hibajegy-kezelő, monitorozó rendszer,
dokumentumtár, nyilvántartás) MCP szervert kínál, az AbstractMcpTool a
legrövidebb út az adataihoz: a plugin csak megnevezi a szervert, és a tool logikájában
a szerver tool-jait hívja. A kapcsolat felépítése, a protokoll-verzió egyeztetése, a
hitelesítés, az időkorlátok, az elérhetőség figyelése, a hívások auditja és a külső
tartalom biztonságos kontextusba helyezése a platform dolga.
Az MCP tool-ok kiválasztása és sorrendje a plugin kódjában marad: a platform LLM-je
nem választ a szerver tool-jai közül. A tool maga pontosan úgy aktiválódik, mint
bármely más tool – a getExecutableInstruction() és a kérés domain-je,
system-szűrője alapján –, és a futása során a plugin kódja dönti el, melyik MCP tool-t,
milyen argumentumokkal, milyen sorrendben hívja.
AbstractMcpTool tisztázó kérdezésre képes tool
(IClarifiableTool), ezért az orchestrátor aktiválja – a domain-hez
orchestrátornak kell tartoznia (→ 4. fejezet,
→ 5. fejezet). A tisztázó kört a platform kezeli: a
leszármazottnak nem kell a 7. fejezetben leírt
életciklust megvalósítania, csak a saját kérdéseit kell feltennie.
A platform a 2025-03-26 és 2025-11-25 közötti, munkamenet-alapú, és a 2026-07-28-as, állapotmentes MCP protokollverziót is beszéli Streamable HTTP-n; hogy egy szerver melyiket használja, azt a platform maga állapítja meg. A plugin kódja mindkét esetben ugyanaz.
14.2 Az alaposztály – AbstractMcpTool<Request>
A Request típusparaméter a felhasználói kérésből kinyert paraméterek
beanje, pontosan úgy, mint bármely más tool-nál (→ 3. fejezet).
A szokásos tool-metódusokon (getName(), getDomain(),
getEligibleSystem(), getPriority(),
getInstruction(), getExecutableInstruction(),
getRequestClass(), getIdentityDescription()) túl az alábbiak
tartoznak az alaposztályhoz.
| Metódus | Kötelező? | Leírás |
|---|---|---|
getMcpServers() |
igen | A tool által használt MCP szerverek leírói (→ 14.3).
A tool regisztrálásakor egyszer hívódik, amikor az @Autowired mezők már
be vannak töltve. Üres lista esetén a tool nem aktiválható. |
executeMcp(mcp, request, context) |
igen | A tool logikája egy futásra. Az IMcpSession paraméteren keresztül
hívja a szervereket és kérdez a felhasználótól; kontextust, kész választ, saját
tisztázó kérdést, vagy null-t ad vissza, ha nincs mit adnia. |
onClarificationAnswer(mcp, request, pluginState, questionId, answer, context) |
nem | A tool saját, ToolExecutionResult.needsClarification(...)-nel
visszaadott tisztázó kérdésére érkezett válasz feldolgozása. A kérdéshez adott
részleges állapot (pluginState) változatlanul visszaérkezik.
Alapértelmezés: null. Az egyszerű esetekre a 14.5 pont eszközei
kényelmesebbek. |
onCallDeclined(serverId, toolName, context) |
nem | A felhasználó elutasított egy megerősítést igénylő vagy kiszivárgási aggály miatt
visszatartott hívást; a hívás nem ment el. Alapértelmezés: null
(a tool nem ad kontextust). |
extractRequest(userPrompt, context) |
nem | A kérés-paraméterek kinyerése. Alapértelmezés: LLM-es kinyerés a request bean
sémájával és a getInstruction() szövegével. |
getLeakCheckInstruction(context) |
nem | A külső szervernek szánt adatok kiszivárgás-ellenőrzésének szempontjai
(→ 14.7). null esetén az adott hívásra
nincs ellenőrzés. |
getAdditionalSystemPromptSupplement() |
nem | Tool-specifikus rendszerprompt-kiegészítés. A getSystemPromptSupplement()
final: a külső tartalom kezelésére vonatkozó platform-szöveget és ezt
a kiegészítést fűzi össze. |
getMaxContextChars() |
nem | Eredményenként a kontextusba kerülő külső tartalom legnagyobb hossza karakterben. Alapértelmezés: 20000; a legkisebb érvényes érték 500. |
formatForContext(result) |
nem | Egy MCP-eredmény kontextusba kerülő szövege (→ 14.10). |
formatProgress(progress) |
nem | A szerver hívás közbeni előrehaladási jelzésének megjelenő szövege (→ 14.11). |
useRemoteInputSchema(serverId, toolName) |
— | Dinamikus paraméter-mód bekapcsolása (→ 14.9); konstruktorból vagy inicializáló metódusból hívandó. |
toContext(results) |
— | Segédmetódus: MCP-eredményekből kontextus eredményt épít (→ 14.10). Listát és változó számú paramétert is fogad. |
Az alaposztály isEligible() metódusa akkor ad true-t, ha a tool
egyik deklarált szervere sem ismerten elérhetetlen. Ha a leszármazott saját jogosultsági
feltételt ad hozzá, az ősosztály döntését is figyelembe kell vennie
(super.isEligible(userData) && ...).
A futás kontextusát az McpExecutionContext rekord hordozza: a kérés
domain-je, system-szűrője, szövege, nyelve és kulcsszavai, a kérés azonosítója, és ha
ismert, a chat és a bejelentkezett felhasználó azonosítója.
14.3 Szerverek deklarálása – McpServerDescriptor
Egy szervert egy McpServerDescriptor ír le: hol érhető el, hogyan kell
hitelesíteni, milyen időkorlátokkal hívható, és a platform infrastruktúráján belül vagy
azon kívül fut-e. A leíró azonosítója egy tool-on belüli alias – a hívások erre
hivatkoznak. A leíró megváltoztathatatlan; a módosító metódusok új példányt adnak.
Az ajánlott forma a konfigurációból való beolvasás
(McpServerDescriptor.fromEnvironment(id, environment)): a plugin saját
plugin.properties fájljában adja meg a kulcsokat, a titkokat környezeti
változóból feloldva, így a szerver címe és tokenje telepítésenként, újrafordítás nélkül
állítható (→ 11. fejezet).
| Kulcs | Alapértelmezés | Jelentés |
|---|---|---|
mcp.{id}.url | kötelező | A szerver MCP végpontja. |
mcp.{id}.external | true |
A szerver a platform infrastruktúráján kívül fut; a kimenő adatokon küldés előtt kiszivárgás-ellenőrzés fut (→ 14.7). |
mcp.{id}.headers.FEJLÉCNÉV | — | Statikus fejléc minden kérésen, tipikusan Authorization. |
mcp.{id}.timeout.connect-ms | 5000 | Kapcsolódási időkorlát. |
mcp.{id}.timeout.call-ms | 60000 | Egy hívás teljes időkorlátja. |
mcp.{id}.tls.insecure | false |
true esetén a tanúsítvány és a hosztnév nincs ellenőrizve – csak
fejlesztői környezetben használandó. |
mcp.{id}.oauth.token-url | — | OAuth 2.0 client credentials token végpont (→ 14.12). |
mcp.{id}.oauth.client-id, .client-secret | — | Kötelezők, ha a token végpont meg van adva. |
mcp.{id}.oauth.scope, .audience | — | Opcionálisak. |
mcp.{id}.elicitation | true |
false esetén a szerver hívás közben nem kérhet adatot a
felhasználótól (→ 14.8). |
# plugin.properties
mcp.kk-grafana.url=${GRAFANA_MCP_URL:}
mcp.kk-grafana.external=false
mcp.kk-grafana.headers.Authorization=Bearer ${GRAFANA_MCP_TOKEN:}
mcp.kk-grafana.timeout.connect-ms=5000
mcp.kk-grafana.timeout.call-ms=60000
Bevett minta, hogy üres URL esetén a tool nem deklarál szervert – így egy nem konfigurált telepítésben a tool egyszerűen nem aktiválható, és nem okoz hibát:
@Autowired
private Environment environment;
@Override
public List<McpServerDescriptor> getMcpServers() {
String url = environment.getProperty("mcp." + SERVER + ".url");
if (url == null || url.isBlank()) {
return List.of();
}
return List.of(McpServerDescriptor.fromEnvironment(SERVER, environment)
.expectTools("search_dashboards", "list_datasources", "query_prometheus"));
}
Az expectTools(...) a szerveren elvárt tool-neveket adja meg: ha közülük
bármelyik hiányzik a szerver tool-listájából, a szerver állapota leromlottnak minősül,
és figyelmeztetés kerül a naplóba – így egy szerver-frissítés okozta eltérés még az első
sikertelen hívás előtt kiderül. Az expectTool(name, argumentsBean) ezen felül
a hívás argumentum-beanjét is összeveti a tool távoli bemeneti sémájával: ha a séma olyan
kötelező mezőt ír elő, ami a beanből hiányzik, vagy a bean olyan mezőt küldene, amit a
séma nem ismer, a szerver szintén leromlottnak minősül.
Konfiguráció helyett a leíró kódból is felépíthető:
McpServerDescriptor.builder(id, url), majd external(...),
header(...) / headers(...), connectTimeoutMs(...),
callTimeoutMs(...), insecureTls(...), oauth(...),
elicitation(...), expectTool(...) és build().
external alapértelmezése szándékosan true: egy
elfelejtett beállítás így a szigorúbb viselkedést adja. Belső, a platform
infrastruktúráján futó szervernél állítsd false-ra.
14.4 Hívások – IMcpSession
Az executeMcp() egy IMcpSession példányt kap, amely egyetlen
tool-futás élettartamára szól. Ezen keresztül a tool tetszőleges számú, akár egymásra
épülő hívást indíthat – egy hívás eredménye a következő hívás argumentuma lehet.
A példány nem szálbiztos: egy futáson belül, egy szálról használandó.
| Metódus | Leírás |
|---|---|
callTool(serverId, toolName, arguments) |
MCP tool hívása alapértelmezett beállításokkal. Az argumentum Map,
bean (a JSON-mezőneveivel alakítva) vagy null lehet. |
callTool(serverId, toolName, arguments, options) |
Ugyanez McpCallOptions beállításokkal. |
getTool(serverId, toolName) |
A tool leírása (McpToolDefinition: név, cím, leírás, bemeneti és
kimeneti séma, viselkedési jelzések), vagy null, ha a szerver nem
hirdet ilyen tool-t – így egy opcionális tool megléte hívás előtt ellenőrizhető. |
listTools(serverId) |
A szerver összes meghirdetett tool-ja. |
choose, ask, missingRequired,
completeArguments |
Kérdezés a felhasználótól a hívások előtt (→ 14.5). |
McpCallOptions – a hívás beállításai
| Beállítás | Jelentés |
|---|---|
McpCallOptions.defaults() |
Nincs megerősítés, nem ismételhető, a szerver-leíró időkorlátja érvényes. |
McpCallOptions.retryable() |
Megszakadt kapcsolat, időtúllépés vagy ismeretlen kimenet esetén a hívás egyszer megismételhető. Csak mellékhatás nélküli, olvasó hívásnál szabad megadni. |
McpCallOptions.requireConfirmation(question) |
A hívás előtt a felhasználónak jóvá kell hagynia a végrehajtást (→ 14.6). |
withTimeoutMs(ms), withRetryable(),
withConfirmation(question) |
A beállítások kombinálása; mindegyik új példányt ad. |
A platform magától csak akkor ismétel egy hívást, ha a kérés bizonyíthatóan el sem
jutott a szerverhez. A szerver readOnlyHint jelzésére ez a döntés nem
épül: hogy egy hívás ismételhető-e, azt a plugin mondja meg.
McpToolResult – az eredmény
| Metódus | Leírás |
|---|---|
isError() |
A tool a végrehajtás során hibát jelzett (pl. nem létező rekord). Ez nem protokollhiba: az eredmény megérkezett, a hibaüzenet a szöveges tartalomban olvasható. |
structured() |
Az eredmény JSON alakja: a szerver strukturált tartalma, ha adott ilyet; különben a
szöveges tartalom JSON-ként értelmezve, ha az érvényes JSON; különben
null. |
as(Class) |
A strukturált alak típusos beanként; az ismeretlen mezők figyelmen kívül maradnak.
null, ha nincs strukturált alak. |
text() |
A szöveges tartalmi elemek sortöréssel összefűzve, vagy null. |
contents() |
A nyers tartalmi elemek (McpContent: típus, szöveg, MIME-típus,
erőforrás-azonosító, név, Base64 adat, eredeti JSON). |
isSuspicious(), getSuspiciousMatch() |
A tartalomban utasításszerű, gyanús kifejezés szerepel; a tartalom ettől még elérhető marad (→ 14.10). |
GrafanaDatasourceList list = mcp.callTool(SERVER, "list_datasources",
Map.of("type", "prometheus"), McpCallOptions.retryable())
.as(GrafanaDatasourceList.class);
McpToolResult value = mcp.callTool(SERVER, "query_prometheus",
Map.of("datasourceUid", uid, "expr", request.getPromQl(),
"endTime", "now", "queryType", "instant"),
McpCallOptions.retryable());
toContext(...)
útján, elhatárolva kerüljön (→ 14.10).
Hibák – McpException
A protokoll-, átviteli és megszakítási hibák McpException-ként érkeznek;
a getKind() megmondja, mi történt, és ebből az is következik, hogy a hívás
elérte-e a szervert. Ha a futás hibára fut, a tool eredmény nélkül zárul.
| Fajta | Jelentés |
|---|---|
NOT_SENT | A kérés bizonyíthatóan nem jutott el a szerverhez;
az isSafeToRetry() csak ennél true. |
OUTCOME_UNKNOWN | Átviteli hiba; nem eldönthető, hogy a szerver feldolgozta-e a kérést. |
TIMEOUT | A hívás túllépte az időkorlátját. |
CANCELLED | A felhasználó megszakította a kérést. |
PROTOCOL_ERROR | A szerver JSON-RPC hibát adott; a
getJsonRpcCode() és a getData() elérhető. |
SERVER_UNAVAILABLE | A szerver nem elérhető, vagy a hitelesítés el lett utasítva. |
UNSUPPORTED_RESULT | A szerver a kliens által nem támogatott eredményt adott, vagy egy adatkérés a megengedett körszám után sem zárult le. |
AUDIT_UNAVAILABLE | Az audit-bejegyzés nem írható, ezért a hívás nem ment el. |
LEAK_CHECK_FAILED | A kiszivárgás-ellenőrzés nem futtatható, ezért a hívás nem ment el. |
INTERRUPTED | A futás a felhasználó válaszára vár; a függő hívás nem ment el (→ lent). |
INTERRUPTED
fajtájú kivétellel megszakad, és a tool-futás a felhasználónak feltett kérdéssel zárul –
akkor is, ha a kód a kivételt elkapja. A megszakítás után a futás
további hívásai sem mennek el. Az INTERRUPTED kivételt ezért nem kell
kezelni, és nem szabad lenyelve tovább dolgozni.
14.5 Kérdezés a hívás előtt
Gyakori helyzet, hogy egy MCP tool kötelező paramétere a kérésből nem derül ki, de a
plugin fejlesztője tudja, honnan szerezhető be: egy másik MCP-hívás adja meg a
lehetséges értékeket. Ha csak egy lehetséges érték van, az a paraméter; ha több, a
felhasználónak kell választania – még mielőtt a cél-tool hívása elmenne. Erre az
IMcpSession négy metódust ad.
| Metódus | Leírás |
|---|---|
choose(key, question, choices) |
Választás McpChoice(value, label) lehetőségek közül. Üres listánál nincs
kérdés, az eredmény null; egyetlen lehetőségnél nincs kérdés, az
eredmény annak értéke; több lehetőségnél a felhasználó gombok közül választ. A
visszatérési érték a választott lehetőség value-ja, vagy
null, ha a felhasználó kihagyta a választást. |
ask(key, question) |
Szabad szöveges kérdés; a válasz szövege, vagy null, ha a felhasználó
kihagyta. |
missingRequired(serverId, toolName, arguments) |
Az MCP tool bemeneti sémája szerint kötelező, de az argumentumokból hiányzó (vagy
null, illetve üres szöveges értékű) paraméterek nevei. Nem kérdez. |
completeArguments(serverId, toolName, arguments, question) |
A hiányzó kötelező paramétereket a felhasználótól kéri be, a tool bemeneti
sémájában megadott típus, leírás, értéklista és korlátok szerint, és a kiegészített
argumentumokat adja vissza (az átadott map nem módosul). Ha semmi nem hiányzik,
nincs kérdés. null, ha a felhasználó kihagyta a kérdést.
A question a kérdés bevezető szövege; null esetén
alapértelmezett szöveg jelenik meg. |
Hogyan jelenik meg a kérdés?
A kérdés a tisztázó körben, a tool nevében jelenik meg. Egyetlen választós vagy igen–nem
mezőnél gombok, egyetlen szöveges vagy szám mezőnél szabad szöveges bevitel jelenik meg,
és a beírt érték változatlanul kerül a paraméterbe. Több hiányzó mezőnél a felhasználó
szabad szövegben válaszol, az értékeket a platform helyi modellje tölti ki, és a kitöltött
adatok még egyszer, jóváhagyásra vagy javításra megjelennek. A típusnak, az értéklistának,
a hosszúsági és értékkorlátoknak vagy a formátumnak (e-mail, webcím, dátum, időpont) nem
megfelelő érték nem kerül be: a kérdés az ok megjelölésével megismétlődik. Minden
kérdésnél van „Kihagyom” lehetőség – ekkor a metódus null-t ad.
A válasz után: a futás elölről indul
Kérdésnél a futás megszakad (→ 14.4), és a tool-futás a kérdéssel zárul. A válasz után a tool-futás elölről indul, ugyanazzal a kinyert kérés-paraméterrel, és amikor a kód ugyanazzal a kulccsal újra eljut ugyanahhoz a kérdéshez, a metódus már a választ adja vissza. Ebből három szabály következik:
- A kulcs legyen stabil – egy adott kérdéshez a futáson belül mindig
ugyanaz a
keytartozzon. AcompleteArgumentskulcsa maga a szerver, a tool és az átadott argumentumok; neki nincs kulcs-paramétere. - A kód a kérdésig legyen determinisztikus – az újrafutásban ugyanazokkal a döntésekkel kell ugyanoda eljutnia.
- A kérdés előtti hívások újra lefutnak – a kérdést megelőző MCP-hívások (pl. a lehetséges értékek listája) az újrafutásban ismét elmennek. Kérdés előtt ezért lehetőleg csak olvasó hívás legyen.
Ha a choose újrafutásában a korábban választott érték már nincs a
lehetőségek között (pl. időközben törölték), a kérdés újra megjelenik. Egy kérdésre
legfeljebb öt kör lehet; utána a futás UNSUPPORTED_RESULT hibával zárul.
Példa – választás több adatforrás közül
A metrika-lekérdezéshez adatforrás-azonosító kell. Ha a felhasználó megnevezte az adatforrást, a névre illeszkedők, egyébként az összes Prometheus adatforrás a jelölt; egyetlen jelöltnél nincs kérdés:
private String prometheusUid(IMcpSession mcp, String requested) {
GrafanaDatasourceList list = mcp.callTool(SERVER, "list_datasources",
Map.of("type", "prometheus"), McpCallOptions.retryable())
.as(GrafanaDatasourceList.class);
if (list == null || list.getDatasources() == null || list.getDatasources().isEmpty()) {
return null;
}
List<GrafanaDatasourceList.Datasource> candidates = list.getDatasources();
if (requested != null && !requested.isBlank()) {
String wanted = requested.trim().toLowerCase(Locale.ROOT);
List<GrafanaDatasourceList.Datasource> matching = candidates.stream()
.filter(d -> wanted.equals(d.getUid())
|| (d.getName() != null && d.getName().toLowerCase(Locale.ROOT).contains(wanted)))
.toList();
if (!matching.isEmpty()) {
candidates = matching;
}
}
List<McpChoice> choices = candidates.stream()
.map(d -> McpChoice.of(d.getUid(), d.getName()
+ (Boolean.TRUE.equals(d.getIsDefault()) ? " (alapértelmezett)" : "")))
.toList();
return mcp.choose("prometheus-datasource",
"Melyik Prometheus adatforrásból kérdezzem le a metrikát?", choices);
}
Példa – hiányzó kötelező paraméterek bekérése
Map<String, Object> args = new LinkedHashMap<>();
args.put("project", request.getProject()); // lehet null
args.put("summary", request.getSummary());
args = mcp.completeArguments(SERVER, "create_issue", args,
"A hibajegy létrehozásához még szükség van néhány adatra.");
if (args == null) {
return null; // a felhasználó kihagyta
}
McpToolResult created = mcp.callTool(SERVER, "create_issue", args,
McpCallOptions.requireConfirmation("Létrehozzam a hibajegyet?"));
completeArguments csak egyszerű típusú (szöveg, szám, logikai, választós
vagy több-választós) paramétert tud bekérni. Az objektum vagy objektum-lista típusú
kötelező paraméter hiányzó marad – ezt a plugin kódjának kell előállítania.
14.6 Megerősítés mellékhatásos hívás előtt
Mellékhatással járó hívásnál (jegy létrehozása, rekord módosítása, annotáció felvétele)
a McpCallOptions.requireConfirmation(question) gondoskodik róla, hogy a hívás
csak a felhasználó jóváhagyása után menjen el. A kérdés mellé a kimenő argumentumok
olvasható összefoglalója is kikerül, a felhasználó „Igen, mehet” / „Nem” válasszal dönt.
Jóváhagyás után a futás elölről indul, és a jóváhagyott hívás már nem kérdez;
elutasításnál a hívás nem megy el, és az onCallDeclined(...) adja a futás
eredményét.
IllegalStateException-t
dob, és a futás eredmény nélkül zárul. Több mellékhatásos lépést egyetlen, összefoglaló
kérdéssel megerősített hívásba, vagy külön tool-futásokba kell szervezni.
if (request.getAnnotationText() != null && dashboards.size() == 1
&& mcp.getTool(SERVER, "create_annotation") != null) {
Dashboard target = dashboards.get(0);
results.add(mcp.callTool(SERVER, "create_annotation",
Map.of("dashboardUid", target.getUid(), "text", request.getAnnotationText(),
"time", System.currentTimeMillis(), "tags", List.of("private-ai")),
McpCallOptions.requireConfirmation(
"Felvegyem az annotációt a(z) \"" + target.getTitle() + "\" dashboardra?")));
}
14.7 Külső szerver: kiszivárgás-ellenőrzés
Külső szervernél (external=true) minden hívás előtt ellenőrzés fut: a
platform helyi modellje eldönti, tartalmaznak-e a kimenő argumentumok olyan adatot,
amelynek kiküldése aggályos. Az ellenőrzés az adat a platformról való kilépése előtt
történik, és eredménye nem kerül gyorsítótárba.
- Ha nincs aggályos adat, a hívás elmegy.
- Ha van, a hívás nem megy el: a felhasználó látja, milyen adatok mennének ki, melyik
rendszerbe, és miért aggályos – és ő dönt. Jóváhagyás után a hívás pontosan ezekkel
az adatokkal elmegy; elutasításnál nem, és az
onCallDeclined(...)adja az eredményt. - Ha az ellenőrzés nem futtatható, a hívás nem megy el
(
LEAK_CHECK_FAILED).
Az ellenőrzés szempontjait a getLeakCheckInstruction(context) adja. Az
alapértelmezés aggályosnak tekinti a természetes személyt azonosító vagy azonosíthatóvá
tevő adatot (név, születési dátum, lakcím, telefonszám, e-mail-cím, TAJ-szám, okmányszám,
adóazonosító, bankszámlaszám) és a konkrét személyhez köthető egészségügyi adatot; nem
aggályos az általános szakmai kérdés, a nyilvános szervezeti információ és a
munkafolyamathoz tartozó azonosító (jegyszám, projektkód). A felülírás szigoríthatja vagy
a tool szakterületéhez igazíthatja a szempontokat, illetve null
visszaadásával egy adott hívásra kihagyhatja az ellenőrzést. A metódus paramétere
(McpLeakCheckContext) a felhasználói kérést, a cél szerver leíróját, a
hívandó MCP tool nevét és a kimenő argumentumokat hordozza.
@Override
protected String getLeakCheckInstruction(McpLeakCheckContext context) {
if ("search_public_docs".equals(context.toolName())) {
return null; // nyilvános dokumentáció keresése, a kérdés maga sem személyes
}
return """
Aggályos minden természetes személyt azonosító adat és minden, konkrét személyhez
köthető egészségügyi adat. Aggályos továbbá a belső szerződésszám és az ajánlati ár.
""";
}
Ha a szerver a hívás közben a felhasználótól adatot kér (→ 14.8), a válaszokat tartalmazó ismételt hívásnál az ellenőrzés a válaszokra is kiterjed.
14.8 Ha a szerver kérdez
Egy MCP szerver a hívás feldolgozása helyett azt is válaszolhatja, hogy még adatra van
szüksége a felhasználótól – például egy hibajegy-kezelő jelzi, hogy az adott projektben
egy mező kötelező, vagy egy szolgáltatás egy külső oldalon kér jóváhagyást. A tool
ilyenkor a szerver kérdéseit egyenként, a tisztázó körben teszi fel, a kérő szerver
megnevezésével (külső szervernél ezt jelezve); a válaszok után a futás elölről indul, és a
hívás a válaszokkal együtt megy el. A leszármazottnak ehhez semmit nem kell
tennie – a callTool(...) a szokásos eredményt adja vissza.
- Az adatot kérő (form) kérdések ugyanúgy jelennek meg, mint a 14.5 pontban leírt kérdések: gombok, szabad szöveg, több mezőnél kitöltés és jóváhagyás, érvényesítés a szerver sémája szerint.
- A külső oldal megnyitását kérő (url) kérdésnél a felhasználó a címet és a cél hosztot látja, a címen – a beszélgetés megkerülésével – adja meg az adatot, és a „Kész” választással folytatja. A nem http(s) címet a platform kérdés nélkül elutasítja.
- A „Kihagyom” / „Mégsem” válasz a szerver felé elutasításként megy vissza; hogy ekkor mit ad a hívás, azt a szerver dönti el.
- Egy hívásra legfeljebb öt kérdéskör lehet; utána a hívás
UNSUPPORTED_RESULThibával zárul.
Ez a képesség csak a 2026-07-28-as protokollverziót beszélő szervereknél érhető el; a
régebbi, munkamenet-alapú szerverek felé a platform nem jelzi, hogy kérdezhetnek.
Szerverenként kikapcsolható a mcp.{id}.elicitation=false kulccsal; ilyenkor a
szerver hívás közben nem kérhet adatot a felhasználótól.
14.9 Paraméter-módok: bean és dinamikus
Bean mód (alapértelmezés): a kérés-paramétereket a
getRequestClass() beanje írja le, a kinyerés a bean sémájával és a
getInstruction() szövegével történik, pontosan úgy, mint más tool-oknál. A
tool logikája a beanből állítja össze az MCP-hívások argumentumait – ez a forma akkor
jó, ha a tool több hívást láncol, vagy a kérés és a hívás argumentumai eltérnek.
Dinamikus mód: ha a tool lényegében egyetlen MCP tool-t hív, és annak
bemeneti sémája közvetlenül kinyerhető a kérésből, a
useRemoteInputSchema(serverId, toolName) a távoli tool sémáját teszi a
kinyerés sémájává. Ilyenkor a Request típus Map, a
getRequestClass() Map.class-t ad, és a kinyert map egy az
egyben mehet a callTool(...)-ba.
public class TicketSearchTool extends AbstractMcpTool<Map> {
public TicketSearchTool() {
useRemoteInputSchema("jira", "search_issues");
}
@Override
protected ToolExecutionResult executeMcp(IMcpSession mcp, Map request, McpExecutionContext context) {
return toContext(mcp.callTool("jira", "search_issues", request, McpCallOptions.retryable()));
}
@Override
public Class<? extends Map> getRequestClass() {
return Map.class;
}
// ...
}
A távoli sémát a platform a strukturált LLM-kimenethez használható részhalmazra alakítja
(McpSchemaNormalizer): a kimenet csak a type,
properties, items, required, enum és
description kulcsszavakat tartalmazza; a helyi hivatkozások feloldódnak, az
allOf ágak összefésülődnek, az anyOf/oneOf közül az
első nem-null ág marad meg, az elhagyott megkötések (formátum, minta, határértékek)
szövegesen a mező leírásába kerülnek, a 6. szint alatti részek szerkezet nélküli mezővé
egyszerűsödnek. A távoli leírás megbízhatatlan adat – ha a kinyerés minősége számít,
a bean mód a biztosabb.
14.10 Kontextus a válaszhoz
A toContext(results) MCP-eredményekből CONTEXT módú eredményt
épít (→ 9. fejezet): eredményenként egy dokumentumot,
amelynek elején és végén a forrást (szerver és tool) megnevező jelölés áll, azzal, hogy a
tartalom adat, nem utasítás. A tartalom a getMaxContextChars() hosszán
csonkul, a tool-hibát jelző és a gyanús kifejezést tartalmazó eredmény külön
figyelmeztetést kap. A tool rendszerprompt-kiegészítése pedig kimondja, hogy a jelölt
részekben szereplő utasítás, kérés vagy szerepmódosítás adatnak minősül.
A dokumentum szövegét a formatForContext(result) adja. Alapértelmezésként a
strukturált (JSON) eredmény olvasható, behúzásos kulcs–érték szövegként jelenik meg,
JSON-szintaxis nélkül (McpContextFormatter.toReadableText(JsonNode)); a nem JSON
szöveges eredmény változatlanul. A leszármazott tool-specifikus formára cserélheti –
a forrásjelölés, a méretkorlát és a figyelmeztetések a visszaadott szövegre is
érvényesek.
@Override
protected String formatForContext(McpToolResult result) {
if ("search_dashboards".equals(result.getToolName())) {
GrafanaDashboardSearchResult found = result.as(GrafanaDashboardSearchResult.class);
if (found != null && found.getDashboards() != null) {
return found.getDashboards().stream()
.map(d -> "- [" + d.getTitle() + "](" + publicUrl(d.getUrl()) + ")")
.collect(Collectors.joining("\n"));
}
}
return super.formatForContext(result);
}
Egy eredmény a toContext(...) előtt át is alakítható: a
McpToolResult publikus konstruktorával a plugin saját tartalmú eredményt
állíthat elő ugyanazzal a szerver- és tool-névvel és gyanús-jelöléssel, így az
elhatárolás és a figyelmeztetések megmaradnak.
14.11 Előrehaladás hosszú hívásnál
Ha a szerver egy hosszan futó hívás közben előrehaladást jelez, a jelzések a tool
progress-eseményeiként jutnak el a felhasználóhoz, legfeljebb félmásodpercenként
frissítve. A megjelenő szöveget a formatProgress(McpProgress) adja:
alapértelmezésként a szerver üzenete, és ha a teljes mennyiség ismert, a százalékos
állás; ha egyik sincs, nincs megjelenítés. Az McpProgress a szerver-aliast,
a tool nevét, az eddigi előrehaladást, a teljes mennyiséget (ha ismert), a szerver
üzenetét és a percent() segédmetódust adja. null visszaadásával
az adott jelzés nem jelenik meg. Az előrehaladás a hívás időkorlátját nem hosszabbítja meg.
14.12 Hitelesítés
A hívások a plugin által konfigurált, szolgáltatás-szintű hitelesítéssel mennek – a
felhasználó saját jogosultsága a szerver felé nem jelenik meg. A felhasználói szintű
jogosultságot a tool isEligible() metódusa és a plugin logikája
érvényesítheti.
- Statikus token: a
mcp.{id}.headers.Authorizationkulcs (pl.Bearer ${TOKEN:}), a token környezeti változóból. - OAuth 2.0 client credentials (pl. Keycloak): a
mcp.{id}.oauth.*kulcsok, kódbólMcpOAuthClientCredentials(tokenUrl, clientId, clientSecret, scope, audience). A platform tokent kér, a lejárata előtt újat kér, és ha a szerver a tokent elutasítja, egyszer új tokennel megismétli a kérést. A kapott token felülírja a statikusAuthorizationfejlécet.
A tokenek és a kliens-titok nem kerülnek naplóba, és a leíró szöveges alakjában sem jelennek meg.
14.13 Garanciák és korlátok
| Téma | Viselkedés |
|---|---|
| Elérhetőség | A platform időközönként ellenőrzi a deklarált szerverek elérhetőségét; ismerten elérhetetlen szerver mellett a tool nem aktiválható. |
| Audit | Minden hívás – a jóváhagyási kérdésekkel és döntésekkel együtt – visszakereshető
auditba kerül, amelynek utólagos módosítása kimutatható. Ha a hívás előtti bejegyzés nem írható, a
hívás nem megy el (AUDIT_UNAVAILABLE). A tool saját,
14.5 pont szerinti kérdései nem MCP-forgalom, ezek nem. |
| Megszakítás | Ha a felhasználó megszakítja a kérést, a folyamatban lévő hívás leáll
(CANCELLED). |
| Platform API | Tisztázó kör nélkül (→ 13. fejezet) a kérdést, megerősítést vagy jóváhagyást igénylő futás nem fejeződhet be. |
| Megerősítés | Futásonként legfeljebb egy megerősítést igénylő hívás (→ 14.6). |
| Újrafutás | Minden felhasználói válasz után a futás elölről indul; a kérdés előtti hívások újra lefutnak (→ 14.5). |
| Transport | Streamable HTTP; a helyi folyamatként indított (stdio) szerverek nem támogatottak. |
14.14 Teljes példa – Grafana MCP tool
A showcase plugin ShowcaseMcpTool osztálya a Grafana MCP szerverén
(grafana/mcp-grafana) keresztül dashboardokat keres, egy metrika aktuális
értékét kérdezi le, és kérésre annotációt vesz fel. Rövidített váz:
public class ShowcaseMcpTool extends AbstractMcpTool<ShowcaseMcpRequest> {
private static final String SERVER = "grafana";
@Autowired
private Environment environment;
@Override
public List<McpServerDescriptor> getMcpServers() {
String url = environment.getProperty("mcp." + SERVER + ".url");
if (url == null || url.isBlank()) {
return List.of();
}
return List.of(McpServerDescriptor.fromEnvironment(SERVER, environment)
.expectTools("search_dashboards", "list_datasources", "query_prometheus"));
}
@Override
protected ToolExecutionResult executeMcp(IMcpSession mcp, ShowcaseMcpRequest request,
McpExecutionContext context) {
List<McpToolResult> results = new ArrayList<>();
if (request.getDashboardQuery() != null && !request.getDashboardQuery().isBlank()) {
results.add(mcp.callTool(SERVER, "search_dashboards",
Map.of("query", request.getDashboardQuery(), "limit", 10),
McpCallOptions.retryable()));
}
if (request.getPromQl() != null && !request.getPromQl().isBlank()) {
String uid = prometheusUid(mcp, request.getDatasource()); // → 14.5 példa
if (uid != null) {
results.add(mcp.callTool(SERVER, "query_prometheus",
Map.of("datasourceUid", uid, "expr", request.getPromQl(),
"endTime", "now", "queryType", "instant"),
McpCallOptions.retryable()));
}
}
return results.isEmpty() ? null : toContext(results);
}
@Override
public String getName() { return "showcase_mcp_grafana"; }
@Override
public String getDomain() { return "SHOWCASE"; }
@Override
public String getExecutableInstruction() {
return """
showcase_mcp_grafana: true, ha a felhasználó Grafana dashboardot keres, vagy egy
monitorozott metrika aktuális értékére kérdez.
""";
}
@Override
public String getInstruction() {
return """
Töltsd ki a következő mezőket a felhasználói kérés alapján:
- dashboardQuery (type: string): ...
- promQl (type: string): ...
- datasource (type: string): a Prometheus adatforrás neve, ha a felhasználó
kifejezetten megnevezi; egyébként null
""";
}
@Override
public Class<? extends ShowcaseMcpRequest> getRequestClass() {
return ShowcaseMcpRequest.class;
}
}
A regisztráció megegyezik a többi chat tool-éval: az osztály a plugin.jar
META-INF/services/hu.medical.rag.common.tool.intf.IChatbotTool fájljába
kerül, publikus, paraméter nélküli konstruktorral
(→ 2. fejezet).