PaiCLI 11. izdanje: MCP napredne mogućnosti — resources, @-mention ekspanzija, pasivne notifikacije
Prošli izdanje smo PaiCLI povezali sa osnovnim mogućnostima MCP protokola, mogli smo pozivati eksterne alate. Ali nakon nekoliko dana korišćenja sam uvideo, samo moći pozivati alate nije dovoljno.
MCP Server pored exposing alata može exposing podatke. Npr. fajl sistem Server, ne samo može pomoći da čitaš i pišeš fajlove (alat), već može čit celu strukturu direktorijuma kao listu resursa (resources). Ili baza podataka Server, alat je izvršavanje SQL, resources su šema tabela i opis polja.
Ako Agent može direktno čitati ove podatke, mnogi zadaci uopšte ne moraju prvo "pozvati alat da proveri", direktno se hrane podaci modelu.
Iskreno, dok sam pisao ovaj deo sam bio malo zbunjen, jer resources i notifications u MCP protokolu se retko pominju u svakodnevnim člancima i tutorialima, većina govori samo o tools/call. Ali ako želiš kompletno implementirati MCP protokol, ili pogledaš Claude Code-ov MCP integracioni kod, videćeš da su resources i notifications neizbežni.
Ovaj izdanje ćemo nadoknaditi napredne mogućnosti MCP: resources čitanje, @-mention sintaksa, pasivne notifikacije i otkazivanje tokom izvršenja. Pokušaću da sve kažem jednostavnim jezikom, svaki koncept razlomimo na najmanje delove.
[Ubaci ovaj PaiCLI v11 screenshot: cilj: prikazati banner nadograđen na v11 i MCP-Native slogan; ključne reči: v11.0.0, MCP-Native Agent CLI; predlog pozicije: komandna linija]
01, Prvo pogledajmo rezultat
Prvo ne pričamo o principima, pogledajmo šta PaiCLI može raditi nakon ovog izdanja.
Prva mogućnost, tools sloj resources-a. Ako MCP Server deklariše mogućnost resources, PaiCLI automatski registruje dva virtuelna alata: list_resources i read_resource. Agent pri izvršenju zadatka može aktivno pozivati ova dva alata da pregleda i čita resurse koje Server izlaže.
Druga mogućnost, @-mention. U unosu možemo direktno referisati resurse:
pomogni mi da pogledam @filesystem:file://README.md ovaj dokumentPaiCLI će pre slanja Agent-u automatski da proširi @filesystem:file://README.md u stvarni sadržaj dokumenta, umota u <resource> tag. Agent dobija pravi sadržaj fajla, ne mora dodatno pozivati alat za čitanje.
Treća mogućnost, pasivne notifikacije. Server ako ažurira listu alata ili sadržaj resursa, aktivno će poslati notifikaciju, PaiCLI nakon prijema automatski osveži keš, ne treba restarovati.
Četvrta mogućnost, otkazivanje tokom izvršenja. U toku izvršenja zadatka ukucaj /cancel možeš prekinuti trenutni zadatak, Agent neće nastaviti dalje. Ranije ako bi Agent skrenuo, morao si čekati da sam se završi ili Ctrl+C izaći iz celog programa i izgubiti sav kontekst. Sada se možeš elegantno prekinuti, nakon što se Agent zaustavi kontekst ostaje, možeš nastaviti razgovor.
Još dve male CLI komande: /mcp resources <server> prikazuje koje resurse Server izlaže, /mcp prompts <server> prikazuje koje Prompt templejte Server izlaže.
Ove četiri mogućnosti zajedno, PaiCLI-jeva implementacija MCP protokola od "može se koristiti" evoluirala u "dobro se koristi". Prošli izdanje samo Tools, ovaj izdanje dopunjava Resources i Notifications, tri glavna koncepta MCP protokola (Tools, Resources, Prompts) smo već prekrili dva i po (Prompts smo samo uradili prikaz, još uvek nismo radili injekciju).
[Ubaci @-mention ekspanzija screenshot: cilj: prikazati korisnikov unos @ sintakse nakon Agent-a dobija prošireni sadržaj; ključne reči: @filesystem, resource tag, sadržaj fajla; predlog pozicije: komandna linija]
02, Šta su Resources
MCP protokol ima tri glavna koncepta: Tools (alati), Resources (resursi), Prompts (prompt templejti). Prošli izdanje smo implementirali Tools, ovaj izdanje glavni fokus je Resources.
Razlika između Resources i Tools? Jednom rečenicom: Tools su radnje, Resources su podaci.
Poziv jednog Tool-a, Server izvršava neku operaciju (čita fajl, pokreće komandu, šalje zahtev), zatim vraća rezultat. Čitanje jednog Resource-a, Server samo predaje postojeće podatke, nema sporednog efekta.
Zašto podatke treba izdvojiti u Resources, umesto sve sa Tools? Zato što Agent-ov poziv Tool-a ima cenu. Svaki put poziva Tool-a treba jedan krug LLM zaključivanja (model odlučuje koji alat pozvati, slaže parametre, čeka rezultat, nastavlja zaključivati sledeći korak), potrošnja tokena i kašnjenje nisu mali.
Ako su neki podaci statički, predvidljivi, npr. šema tabele baze podataka, stablo projekta, sadržaj konfiguracionog fajla, unapred hrani Agent-u mnogo efikasnije nego da sam traži.
Analogija: zamoliš kolegu da ti ispraviš bug. Tool način je kao da kažeš "sam pogledaj kod", kolega otvara IDE, fajl po fajl listaje. Resource način je kao da mu neposredno nalepiš greša i relevantni kod, on pročita i može raditi. Koji je brži? Očigledno drugi.
MCP protokol Resources ima svoj URI sistem, format je sličan poznatom URL-u: file://README.md, postgres://users/schema, git://HEAD/src/main. Svaki Resource ima URI, naziv, MIME tip i opis, Klijent može kroz resources/list dobiti kompletnu listu, kroz resources/read dobiti specifičan sadržaj.
PaiCLI implementira Resources "dvostrukom" metodom.
[Ubaci MCP resurs koncept dijagram: cilj: porediti Tool i Resource razlike; ključne reči: Tool je radnja, Resource je podatak, URI sistem; predlog pozicije: tabla/crtanje]
Sloj alata: automatska registracija virtuelnih alata
PaiCLI pri pokretanju MCP Server-a proverava capabilities polje koje Server vrati prilikom initialize handshake-a. Ako deklariše resources mogućnost, PaiCLI radi dve stvari:
Prvo, poziva resources/list da dobije listu resursa, kešira.
Drugo, automatski registruje dva virtuelna alata u ToolRegistry:
mcp__{server}__list_resources: listuje sve resurse koje Server izlažemcp__{server}__read_resource: čita sadržaj određenog resursa prema URI
Ova dva virtuelna alata su ista kao obični MCP alati, podližu HITL odobrenju, takođe se beleže u AuditLog. Agent pri izvršenju zadatka, ako smatra da treba saznati koje resurse Server ima, automatski može pozvati list_resources; ako hoće da pročita specifičan resurs, poziva read_resource.
[Ubaci McpResourceTool izvorni kod screenshot: cilj: prikazati schema definiciju virtuelnog alata i implementaciju invoker-a; ključne reči: descriptors, LIST_RESOURCES, READ_RESOURCE; predlog pozicije: IDE]
Kod se nalazi u McpResourceTool.java, glavna logika ima manje od 80 linija. descriptors() metoda definiše schemu dva alata, invoker() vraća Function<String, String>, prima JSON parametre, interno prosljeđuje McpClient-ovoj listResources() i readResource(uri).
U read_resource schema postoji jedan obavezan parametar uri, tip string. Agent pri pozivu mora prvo preko list_resources dobiti listu dostupnih URI, zatim izabrati jedan i proslediti read_resource. Ovaj dizajn osigurava da Agent neće izmišljati URI, smanjuje halucinacije.
Postoji jedna stvar na treba obratiti pažnju: naziv virtuelnog alata je format mcp__{server}__{tool}, dve donje crte razdvajaju. Npr. resources alati filesystem Server-a se zovu mcp__filesystem__list_resources. Ovo je isto kao pravilo imenovanja običnih MCP alata iz prošlog izdanja, u ToolRegistry se jedinstveno upravlja, neće se sukobiti sa ugrađenim alatima.
Sloj keša: prljava oznaka umesto brisanja
McpResourceCache koristi ConcurrentHashMap da čuva listu resursa svakog Server-a, ali strategija osvežavanja nije jednostavno "obriši i ponovu učitaj", već koristi prljavu oznaku (stale flag).
Kada se primi resources/list_changed notifikacija, samo označi taj Server kao stale, odmah ne briše keš. Sledeći put kad neko pristupa listi resursa tog Server-a, uoči da je označen kao stale tek onda ponovo učitava.
Kada se primi resources/updated notifikacija, samo označi specifični URI kao stale, ne utiče na druge resurse istog Server-a.
Dobra strana ovog dizajna: notifikacije mogu dolazi često (Server masovno ažurira resurse), ali stvarno čitanje može biti kasnije, nema potrebe svaki put ponovno učitavati. Lazy loading, koristi se pa osvežava, štedi puno suvišnih mrežnih zahteva.
Keš koristi ConcurrentHashMap skladište, thread-safe. Stale oznaka takođe koristi ConcurrentHashMap.newKeySet(), u suštini to je thread-safe Set. Zato što notifikacije mogu iz bilo koje niti doći (NotificationRouter executor niti), a čitanje resursa može biti u Agent-ovoj izvršnoj niti, dve niti istovremeno rade sa kešom, mora garantovati thread safety.
[Ubaci McpResourceCache izvorni kod screenshot: cilj: prikazati stale oznaku i invalidate logiku; ključne reči: staleServers, staleUrisByServer, invalidateServer; predlog pozicije: IDE]
03, Kako se koristi @-mention
Drugi prag dvostrukog metoda je @-mention sintaksa u korisničkom unosu.
Format sintakse je @{server}:{protocol}://{path}, npr.:
pomogni mi da pogledam @filesystem:file://README.md ovaj dokument
@db:postgres://users/schema ovu šemu tabele ima problemPaiCLI pre slanja korisničkog unosa Agent-u prvo prolazi kroz AtMentionExpander. Šta radi? Pronalazi sve @-mention, redom poziva odgovarajući Server-ov readResource da dobije sadržaj, zatim u originalnom tekstu @-mention zamenjuje prošireni <resource> blok.
Nakon proširenja Agent vidi ovo:
<resource server="filesystem" uri="file://README.md" mimeType="text/markdown">
# PaiCLI
Terminalski AI Agent alat koda na osnovu Jave...
</resource>AtMentionParser je zadužen za parsiranje, koristi regex @([a-zA-Z][\w-]*):([a-z]+)://([^\s@]+) za podudaranje. Jedan detalj vredi pomenuti: preskače @-mention unutar navodnika. Ako korisnik napiše "@filesystem:file://test.txt", unutar navodnika neće biti proširen. Ovaj dizajn je da kompatibilan sa mogućnošću @ znak u kodu.
[Ubaci AtMentionParser izvorni kod screenshot: cilj: prikazati regex podudaranje i logiku preskakanja navodnika; ključne reči: RESOURCE_PATTERN, isInsideQuotes, MentionToken; predlog pozicije: IDE]
AtMentionExpander takođe ima zaštitu od odsecanja od 200.000 karaktera. Ako sadržaj nekog resursa premaši 200.000 karaktera, odseca se i na kraju dodaje [resource truncated by PaiCLI at 200000 chars]. Ovo je da bi se sprečilo da jedan veliki resurs prsne kontekst.
Zašto @-mention umesto automatske injekcije? Zato što automatska injekcija ima problem: ne znaš koji resursi trenutno trebaju Agent-u. Sve injektirati pretroši kontekst, po potrebi injektira dodatno zaključivanje, a to zahteva dodatnu procenu LLM-a. @-mention daje izbor korisniku, korisnik zna šta Agent ovaj put treba da vidi, direktno specificira.
Ovaj dizajn je referenca na Claude Code. Claude Code može koristiti @ da referiše na fajlove, Cursor takođe ima sličnu sintaksu. Ali PaiCLI-jev @-mention ne referiše samo na lokalne fajlove, već na bilo koje resurse koje MCP Server izlaže. Fajl sistem, baza podataka, Git repozitorijum, API dokumentacija, dokle Server podatke izlaže kao Resource, korisnik može koristiti @-mention.
Takođe, @-mention se prepoznaje samo u korisničkom unosu, ne u izlazu modela. Ovo ograničenje je namerno, sprečava model u odgovoru da konstruiše @-mention da tajno pročita resurse. Plan mod i Team mod interakcija jednim tasterom takođe ne prihvata automatsko dopunjavanje @-mention, da ne bi ometalo ESC i Ctrl+O prečice.
[Ubaci @-mention automatsko dopunjavanje screenshot: cilj: prikazati listu kandidata nakon unosa @; ključne reči: @filesystem, @db, lista dopunjavanja; predlog pozicije: komandna linija]
04, Pasivne notifikacije
MCP protokol je dvostran, ovo mnogi zanemaruju. Klijent može pozivati Server-ove metode (request), Server takođe može aktivno slati poruke Klijentu (notification).
Poruke koje Server aktivno šalje Klijentu se zovu Notification (notifikacija). PaiCLI trenutno obrađuje dve vrste notifikacija:
notifications/tools/list_changed: lista alata Server-a se promenilanotifications/resources/list_changedinotifications/resources/updated: lista resursa Server-a se promenila ili sadržaj nekog resursa je ažuriran
Nakon prijema notifikacije o promeni liste alata, PaiCLI ponovo poziva tools/list, dobija najnoviju listu alata, zatim kroz ToolRegistry.replaceMcpToolsForServer() radi atomičnu zamenu, ne utiče na alate drugih Server-a.
Nakon prijema notifikacije o promeni resursa, označava odgovarajući keš kao stale, sledeći pristup se ponovlano učitava.
NotificationRouter implementacija ima jednu vrlo važnu dizajnersku odluku: handler mora biti izvršen u nezavisnoj niti, ne sme sinhrono u reader niti transport-a.
Zašto? Zato što handler unutar može morati da šalje JSON-RPC zahtev. Npr. nakon prijema tools/list_changed, handler mora pozvati tools/list da ponovno učita listu alata. Ako handler sinhrono izvršava u reader niti, onda nakon slanja tools/list zahteva treba čekati da reader nita pročita odgovor. Ali reader niti se još uvek izvršava handler, blokirana je, ne može čitati odgovor. Čeka samog sebe, ovo je klasičan deadlock.
[Ubaci NotificationRouter izvorni kod screenshot: cilj: prikazati dizajn asinhronog dispatch-a i napomenu o deadlock u komentarima; ključne reči: dispatcher, daemon nit, izbjegavanje deadlock; predlog pozicije: IDE]
Rešenje je korišćenje jedne single-thread daemon executor-a za asinhroni dispatch handler-a. reader nit nakon prijema notifikacije radi samo jednu stvar: baca zadatak u executor red i odmah se vraća da čita sledeću poruku. Handler se izvršava u executor niti, ne blokira reader.
Ova jama je otkrivena pri integraciji server-everything (zvanični test Server MCP-a). Ovaj Server odmah po pokretanju šalje tools/list_changed, ako handler sinhrono izvršava, prvi tools/list će timeout.
Kroz dijagram pokaži deadlock proces:
reader nit: primio tools/list_changed → sinhrono izvršava handler
↓
handler: poziva tools/list šalje zahtev → čeka da reader niti pročita odgovor
↓
reader nit: još uvek izvršava handler, ne može pročitati odgovor → deadlockNakon asinhronog dispatch-a:
reader nit: primio tools/list_changed → baca u executor red → nastavlja čitati poruku
executor nit: izvlaci handler iz reda → poziva tools/list → reader niti čita odgovor → završenoNotificationRouter implementira Consumer<JsonNode> interfejs, istovremeno implementira AutoCloseable. Kada PaiCLI izađe, poziva close() da shutdownNow() executor, sprečava curenje daemon niti. Neuspeh handler-a ne utiče na tok poruka transport-a, ovo je best-effort dizajn.
05, Otkazivanje tokom izvršenja
Prethodni PaiCLI je imao problem: nakon što Agent počne izvršavati zadatak, nema načina da se prekine. Ako Agent skrene sa puta, ili izvršava dugo trajuću operaciju, možeš samo čekati da se završi ili direktno Ctrl+C izaći iz celog programa i izgubiti sav kontekst.
Ovaj izdanje dodao /cancel komandu. U toku izvršenja zadatka ukucaj /cancel i pritisni Enter, PaiCLI će pokušati da prekine trenutni zadatak.
Implementacija koristi dve klase: CancellationToken i CancellationContext.
CancellationToken je vrlo jednostavan, samo jedan AtomicBoolean. Poziv cancel() postavlja na true, isCancelled() proverava da li je otkazan. Takođe dodatno proverava Thread.currentThread().isInterrupted(), tako da čak i ako donji kod koristi Java mehanizam prekida umesto naš Token, može biti otkriven.
CancellationContext je globalni menadžer konteksta, koristi InheritableThreadLocal + AtomicReference dvostruko skladište trenutni Token. Zašto InheritableThreadLocal? Zato što Agent-ovo izvršenje može preći preko niti, npr. Multi-Agent podzadaci se izvršavaju u thread pool-u, podniti mora moći da oseti prekidni signal roditeljske niti.
[Ubaci CancellationContext izvorni kod screenshot: cilj: prikazati dvostruko skladištenje i dizajn nasleđivanja niti; ključne reči: InheritableThreadLocal, AtomicReference, startRun; predlog pozicije: IDE]
ReAct petlja, Plan-and-Execute raspoređivanje zadataka, Multi-Agent orkestracija, masovno izvršenje alata, svi ulazi u izvršenje dodaju CancellationContext.isCancelled() proveru na granici petlje. Jednom detektira signal otkaza, izlazi iz petlje, ne nastavlja dalje izvršavanje.
Treba napomenuti da je otkaz best-effort. Ako Agent čeka LLM striming odgovor, Java interrupt ne može nužno odmah prekinuti HTTP konekciju. OkHttp striming čitanje nakon interrupt-a će baciti InterruptedIOException, ali ovo zavisi od operativnog sistema mrežnog stoga, ne garantuje se da će svaki put odmah delovati.
Zato PaiCLI-jeva strategija otkaza ima "dve linije odbrane": prva je Thread.interrupt(), pokušava prekinuti donji IO; druga je provera CancellationToken flag-a, čak i ako interrupt nije delovao, sledeća granica petlje će otkriti flag pa izaći. Najmanje jedna linija će delovati, osigurava se da Agent neće nakon što korisnik jasno otkazuje nastaviti sa visokorizičnim operacijama, npr. upis fajlova ili izvršenje komande.
[Ubaci /cancel izvršeni efekat screenshot: cilj: prikazati nakon unosa /cancel Agent staje; ključne reči: /cancel, zadatak otkazan, prestati izvršavati; predlog pozicije: komandna linija]
06, /mcp prompts i /mcp resources
Pored toga, napominjem na dve nove CLI komande.
/mcp resources <server> listuje resurse koje određeni Server izlaže, uključuje URI, naziv, MIME tip i opis.
/mcp prompts <server> listuje Prompt templejte koje određeni Server izlaže. Prompt templejt je treći glavni koncept MCP protokola, Server može unapred definisati nekoliko Prompt templejata za Klijent. Npr. kod revizion Server može izložiti review-pr Prompt templejt, uključuje standarde revizije koda i format izlaza. PaiCLI trenutno samo urađuje prikaz, samo poziva prompts/list prikazuje naziv, naslov i opis, ne poziva prompts/get da uzme konkretan sadržaj, takođe ne ubacuje Prompt u tok razgovora. Ova mogućnost ostavlja za narednu verziju, tada može napraviti /mcp use-prompt <server> <prompt-name> ovakvu komandu.
Ove dve komande CLI parsiranje ponovo koristi CliCommandParser iz prošlog izdanja, dodato je tri nova tipa komandi: MCP_RESOURCES, MCP_PROMPTS i CANCEL. Logika parsiranja je ista kao ranije, po razmacima se reči podudara sa prefiksom komande.
Pregled strukture izvornog koda
Ovaj izdanje novi kod je podeljen u četiri paketa po funkciji:
mcp/resources/ ← Resource keš i virtuelni alati
mcp/mention/ ← @-mention parsiranje, ekspanzija, dopunjavanje
mcp/notifications/ ← notifikacija rutiranje i asinhroni dispatch
runtime/ ← otkaz kontekst i TokenUz promene McpClient, McpServerManager, ToolRegistry, Main, CliCommandParser, AuditLog, ukupno je dodato i izmenjeno oko 20 fajlova. McpClient je dodao četiri nova metoda listResources(), readResource(uri), subscribeResource(uri) i listPrompts(), kao i logiku ispitivanja capability. McpServerManager pri pokretanju Server-a na osnovu capabilities odlučuje da li registrujati resources virtuelne alate i notifikacije rutere.
Pokrivanje testova je takođe ažurirano, dodano je McpResourceCacheTest, AtMentionParserTest, AtMentionExpanderTest, NotificationRouterTest itd. test klase, trenutno ima 336 testova, svi prolaze.
Pakovanje životopisa
Ako radiš sličan MCP napredni rad, na životopisu možeš ovako napisati:
Naziv projekta: PaiCLI — MCP-Native Agent CLI
Opis projekta: AI Agent komandna linija na osnovu Jave, kompletna implementacija MCP protokola (alat + resurs + notifikacija + otkaz), podržava više modela i interaktivni razgovor.
Tehnološki stack: Java 21, MCP Protocol, JSON-RPC 2.0, ConcurrentHashMap, AtomicBoolean, InheritableThreadLocal, ExecutorService
Ključne odgovornosti:
- Implementirao dvostruki mehanizam čitanja MCP Resources, nakon što Server deklariše resources mogućnost automatski registruje list_resources/read_resource virtuelne alate, istovremeno podržava korisnika da kroz @-mention sintaksu u unosu direktno referiše resurse i proširi u XML inline blok
- Dizajnirao McpResourceCache strategiju keširanja prljavim oznakama, za resources/list_changed i resources/updated notifikacije označuje Server-level i URI-level stale, izbjegava duplo učitavanje u scenariju visokofrekventnih notifikacija
- Razvio NotificationRouter asinhroni notifikacioni dispečer, koristeći nezavisni daemon thread pool za obradu JSON-RPC notifikacija koje Server šalje, rešava deadlock gde reader nit sinhrono izvršava handler
- Implementirao CancellationToken + CancellationContext saradnji mehanizam otkaza, kroz InheritableThreadLocal prenosi signal otkaza preko niti, podržava best-effort prekid ReAct/Plan/Multi-Agent različitih izvršnih modova
- Dodao @-mention parser, regex podudara
@server:protocol://pathformat i automatski preskače tekst unutar navodnika, pri ekspanziji za resurse preko 200k karaktera radi odsecanja da bi se sprečilo prekoračenje konteksta
[Ubaci PaiCLI test prošao screenshot: cilj: prikazati 336 testova sve prošlo; ključne reči: 336 tests, 0 failures, BUILD SUCCESS; predlog pozicije: komandna linija]
ending
Nakon što su završene napredne mogućnosti MCP, PaiCLI više nije samo "Agent koji može pozivati alate".
Može čitati podatke, može primati notifikacije, može biti prekinut.
Ove tri mogućnosti zajedno, Agent postaje alat koji se može bezbedno koristiti, a ne "crna kutija koja jednom pokrenuta mora sačekati da se završi".
Sledeći izdanje planiram raditi inženjering dugog konteksta, resources automatski injektira u system prompt, Agent na početku razgovora već zna šta može pozvati, šta može čitati.
[Dobar Agent nije Agent koji može sve, Agent koji zna kada treba stati.]
Vidimo se sledeći put.
