Men: “Agar buni bitta qilsak, bitta loyiha kamroq bo‘ladi”, deb o‘ylagan paytdan boshlab, asl muammoni hal qilish jarayoni allaqachon boshlangan edi.
Ichki platformamizga AI agent qo‘shishga qaror qildik, shunda xizmatlarni tabiiy tildan foydalanib boshqarish mumkin bo‘lardi. Ammo birinchi savol “Nima qurishimiz kerak?” emas, balki “Qancha narsa qurishimiz kerak?” edi. Suhbat UI’ini mavjud xizmat ekranlari ichiga joylashtirishni rejalashtirganimiz sababli, MCP serveri va agent hosti bitta loyihada birga mavjud bo‘la oladimi yoki yo‘qmi, aniqlashimiz kerak edi.
Ularni birlashtirish jozibador ko‘rindi. Bu deployment birliklari sonini bittaga kamaytiradi, jarayonlararo aloqani yo‘q qiladi va kod bazalari o‘rtasida oldinga-orqaga o‘tish zaruratini bartaraf etadi. Aslida, dastlab ko‘rib chiqqan yo‘nalishimiz shu edi.
Xulosa ularni ajratishdan iborat bo‘ldi. Keyinchalik bu shunchaki qancha loyiha bo‘lishi masalasi emasligi aniq bo‘ldi. Boshqaruvni qayerga joylashtirish va tasdiqlash ekranining ta’rifiga kim egalik qilishi kabi savollarning barchasi shu chegara bo‘ylab hal qilindi.
Ushbu maqolada tizimni ikkita loyihaga ajratish sabablari va shu chegara bo‘ylab boshqaruv qatlamini loyihalashdagi sinov va xatolar umumlashtiriladi. Unda boshqaruv ikki joyga joylashtirilgan va tashqi qatlam ichki qatlam bergan yaxshiroq javobni ushlab qolgan holat, shuningdek, tasdiqlash ekranini chizuvchi qatlam uning ta’rifiga egalik qilgani sababli jim xatolar qayta yuzaga kelgan tuzilma ko‘rib chiqiladi.
1. Texnologiyani tanlash asoslari — Nega ularni birlashtirmaslik kerak
Qaror qabul qila olmaganimning sababi MCP nimaga mo‘ljallanganini aniq tushunmaganim edi. Bu savolni bevosita tekshirganimda, javob ravshan bo‘ldi. MCP jarayon chegaralarini kesib o‘tish protokolidir. Agar u ayni bir jarayon ichida ishlatilsa, MCP o‘ziga JSON-RPC yuboradigan tuzilmaga aylanadi. Boshqacha aytganda, komponentlar integratsiya qilingach, MCP’dan foydalanish uchun hech qanday sabab qolmaydi.
Shu nuqtai nazardan, qolgan sabablar tabiiy ravishda kelib chiqdi.
-
Boshqaruv tamoyili amal qilmaydi. Host tasdiqlaganidan keyin ham MCP serveri siyosat asosida yakuniy qayta tekshiruvni amalga oshiradigan ikki tomonlama boshqaruv g‘oyasi faqat ikkala qatlam amalda ajratilgan bo‘lsa ma’noga ega.
-
MCP serverlaridan bir nechta host foydalanadi. Unga nafaqat ichki agentlar, balki tashqi MCP klientlari ham ulanadi. Server hostga bog‘lanib qolgach, uning qayta foydalanish imkoniyati tugaydi.
-
Ularning kechikish va xarajat xususiyatlari bir-biriga mutlaqo qarama-qarshi. LLM chaqiruvlari sekin va qimmat, tool qidiruvlari esa tez va arzon. Ularni birga deploy qilish bir-biriga xalaqit berishiga olib keladi.
Ko‘rib chiqish jarayonida bekor qilgan yana bir variant bor edi: loyihalar sonini kamaytirish uchun tayyor agent SDK’dan foydalanish. U ikki sababga ko‘ra mos kelmadi. Ichki stekimiz Java’da, SDK esa faqat Python va TypeScript’ni qo‘llab-quvvatlardi. Eng muhimi, u loyihalar sonini kamaytirmasdi. Turli tillarda yozilgan komponentlarni birlashtirib bo‘lmagani uchun, ajratish majburiy bo‘lib qolardi.
Bu muhokama davomida yana bir noto‘g‘ri tushuncha oydinlashdi. Agentni o‘zimiz qurish inference jarayonini ham o‘zimiz qurishimiz kerak degani emas. Umumiy maqsadli LLM har doim qarorlarni qabul qiladi; biz esa bajarish sikli, tool integratsiyasi, siyosatlar va auditni quramiz. Shuning uchun tamoyilni bir jumlada belgiladik: inference’ni umumiy maqsadli LLM’ga topshirish, bajarilishni boshqarishni esa bevosita platformaga berish.
2. Amalga oshirish jarayoni va tafsilotlar
2.1 Ikki jarayon va mas’uliyatlarni taqsimlash
Arxitektura ikkita servis sifatida yakunlandi. MCP serveri tool ro‘yxati va xaritasini, shuningdek siyosatlar, tasdiqlashlar va auditni boshqaradi. Agent hosti suhbatni qayta ishlash, LLM chaqiruvlari va bajarish siklini boshqaradi. Har ikki tomon Java 25 / Spring Boot 4.1’dan foydalanadi.
Bu yerda muhim bo‘lgan narsa jarayonlarni shunchaki ajratish emas, balki ajratilgandan keyin qaysi narsa qaysi tomonga tegishli bo‘lishini hal qilish edi. Agar bu qaror barqaror bo‘lmasa, ikkita jarayon bo‘lsa-da, mas’uliyatlar chalkashib ketadi. Aslida, bu darhol yuz berdi.
2.2 Boshqaruvga egalikni bir joyda jamlash — Ikki tomonlama boshqaruv tuzog‘i
Upload tool kabi haqiqatan ham biror narsani o‘zgartiradigan tool’larni qurayotganimizda, bajarishdan oldin inson tasdig‘ini olish uchun tasdiqlash kartasini qo‘shdik. Mezonlar quyidagicha edi: “Foydalanuvchilarni ID kiritishga majburlamang, qo‘shimcha savollar bermang va butun jarayonni ekrandan foydalanishdan ham oson qiling.”
Biroq birinchi amalga oshirishda tasdiqlashni talab qiladigan tool’ni chaqirish karta o‘rniga xato satrini qaytardi. Model satrni qabul qildi, uni tabiiy tilda tushuntirdi va foydalanuvchidan yana ichki ID’ni so‘radi. Karta hech qachon ko‘rinmadi.
Buning ikki sababi bor edi. Birinchidan, tizim tasdiqlashni kutish holatini exception sifatida tashlayotgan edi. Ikkinchidan, agentda ham tasdiqlashni bloklovchi mantiq mavjud bo‘lib, bu MCP serveriga taklif yaratish imkoniyatini ham bermasdi. Boshqaruv har ikki qatlamda takroran mavjud bo‘lgani uchun, tashqi qatlam ichki qatlam bera oladigan yaxshiroq javobni ushlab qoldi.
Shuning uchun tasdiqlash qaroriga egalikni faqat MCP serveriga berdik va agentni uni shunchaki uzatadigan qildik. Shuningdek, tasdiqlashni kutishni xato sifatida emas, strukturali bajarish taklifi sifatida odatiy tarzda qaytarishga o‘zgartirdik.
[Tasdiqlashni kutishni xato emas, taklif sifatida qaytarish]
// A pending approval is not an error. Returning it as a structure keeps the
// decision with the person, instead of letting the model narrate a failure.
static String of(String approvalId, MethodSignature signature, Object[] args,
McpTool mcpTool, NaviToolPolicy policy, ObjectNode form) {
ObjectNode proposal = OBJECT_MAPPER.createObjectNode();
proposal.put("proposalType", "approval.required");
proposal.put("approvalId", approvalId);
proposal.put("tool", mcpTool.name());
proposal.put("risk", policy.risk());
// 'arguments' keeps the original values for re-execution after approval.
// 'fields' carries only what a person needs to read on the card.
ObjectNode arguments = proposal.putObject("arguments");
ArrayNode fields = proposal.putArray("fields");
for (int i = 0; i < parameters.length && i < args.length; i++) {
arguments.putPOJO(names[i], args[i]);
if (isHidden(names[i])) continue; // internal ids stay out of sight
fields.addObject().put("label", label(parameters[i], names[i]))
.putPOJO("value", args[i]);
}
return proposal.toString();
}
Argumentlar va maydonlarni ajratish ushbu dizaynning kaliti bo‘ldi. Bajarish uchun zarur bo‘lgan dastlabki argumentlar arguments ichida o‘zgartirilmagan holda saqlanadi va tasdiqlashdan keyin qayta bajarishda ishlatiladi, karta esa faqat inson tasdiqlashi kerak bo‘lgan qiymatlarni ko‘rsatadi. Foydalanuvchilar bilishiga sabab bo‘lmagan internal ID kabi kalitlar yashiriladi.
Biroq argumentlarni yashirish yangi muammoni keltirib chiqardi. Upload tool’ning barcha argumentlari internal ID’lardan iborat bo‘lgani uchun, ularni yashirish natijasida karta bo‘sh bo‘lib qoldi. Shuning uchun tool’ga faqat ko‘rsatish uchun mo‘ljallangan parametrlarni qo‘shdik. Model ularni foydalanuvchiga tanish nomlar bilan to‘ldiradi va kartada faqat shu qiymatlar ko‘rinadi.
Xuddi shu tamoyilni ruxsatlarni boshqarishga ham qo‘lladik. Tool siyosati talab qiladigan rollar e’lon qilinadi va qaytarilayotgan tool ro‘yxati chaqiruvchining ruxsatlariga ko‘ra filtrlanadi. Kimdir ro‘yxatni chetlab o‘tib, MCP’ni bevosita chaqirsa ham, xuddi shu tekshiruv bajarishdan oldin yana bir bor amalga oshiriladi.
[Ro‘yxatga kiritilganda bir marta va bajarishdan oldin darhol yana bir marta]
// The list is filtered by the caller's grants, but the check is repeated here:
// a client that bypasses tools/list must not bypass the policy.
if (StringUtils.hasText(toolPolicy.requiredRole())) {
grant = grantResolver.resolveGrant(
toolPolicy.executionLocation(), toolPolicy.requiredRole());
if (grant == null) {
String reason = "required role not granted: "
+ toolPolicy.requiredRole() + "@" + toolPolicy.executionLocation();
audit(context, mcpTool.name(), riskLevel, false, reason);
return denied(reason);
}
}
2.3 Ta’riflar ularni biladigan tomonga tegishli bo‘lishi kerak
Tasdiqlash kartasi ishlay boshlagach, xuddi shu tamoyilni qo‘llamagan yana bir joy qoldi. Kartadagi qiymatlarni o‘zgartirish uchun ishlatiladigan tahrirlanadigan maydonlar ro‘yxati frontend’da hard-code qilingan edi.
Bu tuzilmaning muammosi xatolar jim sodir bo‘lishidir. Agar tool maydon qo‘shsa yoki maydon nomini o‘zgartirsa va frontend bundan xabardor bo‘lmasa, o‘sha maydon shunchaki tahrirlanmaydigan bo‘lib qoladi. Hech qanday xato yuz bermagani uchun, kimdir buni aniqlamaguncha holat o‘zgarmaydi.
Buning sababi forma ta’rifini formani chizuvchi tomon boshqarganida edi. Qaysi maydonlar mavjudligini aslida tool egasi biladi, ammo kontrakt teskari tuzilgan edi. Shuning uchun tizimni o‘zgartirdik: MCP serveri har bir tool-and-type kombinatsiyasi uchun formalar yaratadi va ularni tasdiqlash taklifi bilan birga yuboradi, frontend esa faqat ularning shaklini tekshiradi va chizadi. Agar ular mos kelmasa, maydonlar faqat o‘qish rejimiga o‘tadi.
Natijada frontend forma sxemasi fayli 190 qatordan 31 qatorgacha qisqardi va endi maydonlar o‘zgartirilganda faqat serverda o‘zgarish qilish talab etiladi.
3. Sinov va xatolar orqali o‘rganilgan saboqlar
Birinchidan, boshqaruv ikki qatlamda takroran joylashtirilsa, tashqi qatlam ichki qatlam bera oladigan yaxshiroq javobni ushlab qoladi. Tasdiqlash kartasi ko‘rinmay qolganda aynan shu holat yuz berdi. Avval boshqaruvga egalikni bitta joyga berib, qolganini delegatsiya qilishga qaror qilishimiz kerak edi. “Biror narsani ikki joyda bloklash xavfsizroq bo‘lishi kerak” degan sezgi bloklash uchun to‘g‘ri, ammo tizim shunchaki bloklash o‘rniga yaxshiroq narsani ko‘rsatishi kerak bo‘lganda noto‘g‘ri.
Ikkinchidan, ta’riflar ularni chizuvchi tomonga emas, balki ularni biladigan tomonga tegishli bo‘lishi kerak. Frontend forma sxemasini saqlayotganini ko‘rganimiz zahoti bu tuzilmani shubha ostiga olishimiz kerak edi. Bu tasdiqlash boshqaruvini serverda markazlashtirish qaroridagi tamoyilning o‘zi edi, ammo biz uni formaga qo‘llashni unutdik. Xuddi shu tamoyilni belgilaganimizdan keyin ham uni juda tor doirada qo‘llash ayni turdagi xatoning bo‘shliqda qayta yuzaga kelishiga imkon beradi.
Uchinchidan, nima xato sifatida, nima esa odatiy javob sifatida qaytarilishi kerakligini farqlashimiz zarur. Tasdiqlashni kutish xato emas; bu inson qarorini kutish holatidir. U exception sifatida tashlangan zahoti struktura yo‘qoladi va faqat satr qoladi, natijada model bu satrni talqin qilib, uni so‘zlar bilan tushuntiradi. Agar model inson tugma orqali hal qilishi kerak bo‘lgan narsani gaplar bilan tushuntirayotgan bo‘lsa, javob formati odatda noto‘g‘ri bo‘ladi.
To‘rtinchidan, “funksiya yoqilgan” va “funksiya ishlayapti” — ikki xil narsa. Bu davrda native tool calling yoqilgan edi, ammo amalda tizim har safar boshqa yo‘ldan ketardi. Konfiguratsiya faylidagi map key ichidagi ikki nuqta binding’ni ko‘zda tutilmagan kalitdan foydalanishga majbur qildi va qidiruv hech narsa qaytarmagani sababli tizim jimgina fallback yo‘liga o‘tdi. Xato chiqishining birorta satri ham yo‘q edi. Fallback’lar mavjud tizimlarda xatolar jim sodir bo‘ladi, shuning uchun yoqilgan deb hisoblagan yo‘lingiz haqiqatan ham ishlatilayotganini tekshirish uchun loglarni ko‘rish odatini shakllantirishingiz kerak.
Beshinchidan, deployment birliklarini qanday bo‘lishni muhokama qilganda, avval tranzaksiya va ma’lumotlar bazasi chegaralarini tekshirishimiz kerak. Bir paytlar alohida MCP jarayoni mavjud servisga bog‘lanib, uni bevosita chaqiradigan taklifni ko‘rib chiqqan edik. Biroq bu servisning query qatlami tranzaksiyalar va persistence’ga bog‘langan edi, ya’ni amalda servisni yana bir marta ishga tushirish kerak bo‘lardi. Diagrammada bu mantiqli ko‘rindi, ammo kodning bir qismini bir daqiqaga ochib ko‘rishning o‘zi taklifni bekor qilish uchun yetarli bo‘ldi.
4. Amalga oshirish natijalari
Komponentlarni ajratish va boshqaruv qatlamini loyihalash haqidagi qarorning natijalarini quyidagicha umumlashtirish mumkin.
|
Band |
Agar integratsiya qilinganida |
Ajratish natijasi |
|---|---|---|
|
MCP roli |
O‘ziga JSON-RPC — protokoldan foydalanish uchun sabab qolmaydi |
Jarayon chegaralari bo‘ylab haqiqiy aloqa |
|
Boshqaruv |
Host va server bir joyda — qayta tekshiruv ma’nosiz |
Host o‘tkazganidan keyin ham yakuniy qarorni server qabul qiladi |
|
Qayta foydalanish imkoniyati |
Muayyan hostga bag‘ishlangan |
Bir xil serverdan foydalanadigan bir nechta MCP mijozlari |
|
Tasdiqlash javobi |
Istisno → satr → model IDni yana so‘raydi |
Tuzilgan taklif → frontend uni karta sifatida ko‘rsatadi |
|
Forma ta’rifi |
Frontend uni nusxalagan (190 qator, jim xatolik) |
Asbob egasi tomonidan taqdim etilgan (31 qator) |
Haqiqiy sharoitdagi tekshiruvda “Upload upload-test to the Global Gallery hub” degan bitta jumla avtomatik ravishda listConnectedHubs → listCatalogKollexes → uploadKollexToHub zanjirini ishga tushirdi. Tasdiqlash kartasida foydalanuvchiga tanish bo‘lgan nomlargina, masalan, “Kollex: upload-test / Hub to upload to: Global Gallery” ko‘rsatildi va ichki IDlar sizib chiqishining birorta holati ham kuzatilmadi.
Qo‘shimcha foyda sifatida, shu davrda javob hajmi muammosini ham hal qildik. So‘rov javobi ikonka tasvirini to‘liq o‘z ichiga olganligi sababli asbob javobi 1,1 MB hajmga yetgan edi, biroq server bu maydonni javobdan rekursiv tarzda olib tashlab, hajmni taxminan 5 900 belgigacha kamaytirdi. Frontendga yuborilgan asl javob o‘zgarmadi; faqat modelga uzatilgan kuzatuv qisqartirildi.
5. Cheklovlar va kelajakdagi rejalar
Birinchidan, bu bosqichda tasdiqlash faqat kartani ko‘rsatishgacha amalga oshirilgan edi. Tasdiqlash tugmasini bosish orqali ishga tushadigan haqiqiy jarayon — ya’ni tasdiqlash identifikatorini berish va tekshirish, so‘ngra dastlabki chaqiruvni davom ettirish — hali amalga oshirilmagan edi. Shu sababli kartada rostini aytib, “Tasdiqlash funksiyasi ishlab chiqilmoqda” deb ko‘rsatdik. Keyinchalik bu qismni MCP standartidagi elicitation yordamida yakunladik, bu serverga foydalanuvchidan bevosita so‘rash imkonini berdi.
Ikkinchidan, gateway funksiyasi sifatida belgilangan chaqiruvlar hajmi cheklovi amalga oshirilmagan, audit jurnallari esa faqat ilova jurnallarida qolgan edi. Nazorat vositasiga kim egalik qilishini aniqlash va o‘sha nazoratni to‘liq amalga oshirish — alohida vazifalardir.
Uchinchidan, bu yerda yaratilgan nazorat qatlami keyinchalik yana ikki marta o‘zgartirildi. Biz avtorizatsiya tekshiruvlarini yanada murakkablashtirdik, keyin esa bu ishlarning katta qismini yana olib tashladik va oxir-oqibat tasdiqlash egasi stekning quyi qatlamiga ko‘chirildi. Qurilgan qismlarni olib tashlash to‘g‘risidagi qaror qaysi asosda qabul qilinganini alohida hujjatlashtirishni rejalashtirmoqdamiz.
Bu qaror tasdiqlagan narsa shuki, nechta loyiha yaratish masalasi aslida mas’uliyatni qayerga joylashtirish masalasi bo‘lgan. Jarayonlarning o‘zini bo‘lib chiqish oson. Qiyin qismi esa bo‘linishdan keyin ham har bir qatlam faqat o‘z mas’uliyatini bajarishda davom etishini ta’minlash edi. Ushbu maqolada muhokama qilingan uchta sinov va xato tajribasining barchasi aynan shu chegaralar bir marta noto‘g‘ri moslashgan holatlar edi.
Foydalanilgan manbalar
-
Anthropic, “Model Context Protocol Specification”, https://modelcontextprotocol.io/specification
-
JSON-RPC Working Group, “JSON-RPC 2.0 Specification”, https://www.jsonrpc.org/specification
-
Spring AI, “Model Context Protocol (MCP)”, https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html
Junny