SDK
14. fejezet

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.

ℹ️
Az 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ódusKö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).

KulcsAlapértelmezésJelentés
mcp.{id}.urlkötelezőA szerver MCP végpontja.
mcp.{id}.externaltrue 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-ms5000Kapcsolódási időkorlát.
mcp.{id}.timeout.call-ms60000Egy hívás teljes időkorlátja.
mcp.{id}.tls.insecurefalse 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}.elicitationtrue 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().

⚠️
Az 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ódusLeí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ásJelenté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ódusLeí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());
⚠️
Az eredmény tartalma – a tool-leírásokkal és sémákkal együtt – harmadik féltől származó, megbízhatatlan adat. LLM promptba csak a 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.

FajtaJelentés
NOT_SENTA 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.
TIMEOUTA hívás túllépte az időkorlátját.
CANCELLEDA felhasználó megszakította a kérést.
PROTOCOL_ERRORA szerver JSON-RPC hibát adott; a getJsonRpcCode() és a getData() elérhető.
SERVER_UNAVAILABLEA szerver nem elérhető, vagy a hitelesítés el lett utasítva.
UNSUPPORTED_RESULTA 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_UNAVAILABLEAz audit-bejegyzés nem írható, ezért a hívás nem ment el.
LEAK_CHECK_FAILEDA kiszivárgás-ellenőrzés nem futtatható, ezért a hívás nem ment el.
INTERRUPTEDA futás a felhasználó válaszára vár; a függő hívás nem ment el (→ lent).
⚠️
Ha egy hívás vagy kérdés a felhasználó válaszára vár, a futás 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ódusLeí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:

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?"));
ℹ️
A 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.

⚠️
Egy tool-futásban legfeljebb egy megerősítést igénylő hívás mehet el. Ha egy megerősített hívás már elment, és a futás egy újabb, még jóvá nem hagyott megerősítést igénylő hívást indítana, a hívás 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.

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.

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.

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émaViselkedé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).