Projektowanie efektywnych pod kątem tokenów powierzchni narzędziowych dla serwerów MCP typu agencyjnego
Dowiedz się, jak spakować dziesiątki definicji narzędzi MCP w kilka narzędzi skupionych na konkretnym domenie i sterowanych przez konkretne działania, nie tracąc przy tym żadnej z podstawowych funkcjonalności.
Źródła: **From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface**
Typowym problemem w nowoczesnych systemach agentowych nie jest brak narzędzi.
Jest nim ich zbyt duża liczba.
Gdy możliwości agenta się rozszerzają, powszechną praktyką jest połączenie go z Jira, GitLab, Confluence, Sentry, Elasticsearch, Jaeger, różnymi bazami danych, panelami monitoringu, wewnętrznymi API, pipeline’ami do wdrażania oraz niezliczonymi innymi systemami.
Każda z tych integracji zazwyczaj umożliwia wykonywanie długiej listy operacji:
Jira
├── searchIssues
├── getIssue
├── createIssue
├── updateIssue
├── addComment
├── transitionIssue
├── getTransitions
├── assignIssue
└── ...
GitLab
├── listProjects
├── getProject
├── listIssues
├── createIssue
├── updateIssue
├── listMergeRequests
├── getMergeRequest
├── createComment
└── ...
Confluence
├── searchPages
├── getPage
├── createPage
├── updatePage
└── ...
Niedługo potem trzeba już zarządzać 30, 50, a nawet setkami definicji narzędzi.
Na pierwszy rzut oka wydaje się to problemem samej liczby możliwości.
W rzeczywistości jest to problem powierzchni narzędziowej.
Agent nie potrzebuje mniej rzeczy, które może robić.
Potrzebuje natomiast mniej narzędzi najwyższego poziomu, spośród których może wybierać.
Tekst ten przedstawia podejście do skurczenia rozbudowanych interfejsów narzędzi MCP lub innych narzędzi do zaledwie kilku narzędzi skupionych na konkretnym domenie, bez utraty żadnej z podstawowych funkcjonalności.
Główna idea jest prosta:
Skróć interfejs narzędzia, a nie zbiór dostępnych funkcji.
Zamiast udostępniać coś takiego jak:
30 MCP tools
można udostępniać:
3–5 domain tools
i pole action służy do kierowania każdym wywołaniem wewnętrznie.
Zatem zbiór takowy jak:
jira_search
jira_get_issue
jira_create_issue
jira_update_issue
jira_add_comment
jira_transition_issue
...
przekształca się w coś takiego jak:
jira({
action: "search",
...
})
jira({
action: "getIssue",
...
})
jira({
action: "createIssue",
...
})
Tło systemowe zachowuje wszystkie oryginalne funkcje.
Z perspektywy modelu jednak interfejs narzędzia jest znacznie mniejszy.
1. Problem z dużymi interfejsami narzędzi
Serwer MCP to nie tylko mechanizm wykonywania zadań.
Staje się to również częścią tego, co model musi przechowywać w kontekście.
Za każdym razem, gdy agent łączy się z serwerem MCP, model zazwyczaj musi przetworzyć:
- nazwy narzędzi
- opisy
- schematy danych wejściowych
- parametry
- enumy
- ograniczenia
- czasami dodatkowe metadane
Wyobraź sobie agenta połączonego z 40 różnymi narzędziami.
Nawet jeśli każdy pojedynczy schemat jest zwięzły, łączny rozmiar danych może szybko rosnąć.
Ponadto model musi teraz podejmować większą liczbę decyzji:
User request
│
▼
Which tool?
│
┌───┼────┬────┬────┬────┐
▼ ▼ ▼ ▼ ▼ ▼
T1 T2 T3 T4 T5 ...
Must odróżnić wiele operacji, które są koncepcyjnie powiązane.
Weźmy ten przykład:
jira_search_issues
jira_search_projects
jira_get_issue
jira_get_issue_comments
jira_get_issue_transitions
jira_get_issue_worklogs
Wszystkie one należą do tego samego obszaru koncepcyjnego.
Traktowanie każdej z nich jako całkowicie niezależnej funkcji najwyższego poziomu nie dodaje wielkiej wartości.
2. Podstawowa idea: Multiplexing narzędzi
Sekret polega na dodaniu warstwy pośredniej – pola dyskryminatora:
{
"action": "search",
"query": "authentication bug"
}
Zamiast definiować:
jira_search
exponujesz jedynie:
jira
Sam narzędzie staje się lekkim routerem.
Strukturalnie wygląda to tak:
jira
│
action field
│
┌──────────────┼──────────────┐
▼ ▼ ▼
search getIssue createIssue
│ │ │
▼ ▼ ▼
searchHandler issueHandler createHandler
Kluczowym spostrzeżeniem jest to, że narzędzie nie jest tym samym co funkcjonalność.
Narzędzie to po prostu interfejs skierowany do zewnętrznego użytkownika.
Pole działań to to, co faktycznie przenosi funkcjonalność.
Rezultat:
30 capabilities
↓
3 domain-oriented tools
bez żadnej utraty funkcjonalności w trakcie procesu.
3. Używaj granic domeny, a nie arbitralnego grupowania
Częstym błędem jest myślenie w ten sposób:
"Mam 30 narzędzi, więc połączę w każdym z nich 10 operacji."
To nie jest sedno sprawy.
Grupowanie powinno być kierowane przez domeny semantyczne, a nie arbitralną wielkością grup.
Naprzимер:
jira
├── search
├── issue
├── comment
├── transition
└── project
gitlab
├── project
├── issue
├── mergeRequest
└── pipeline
observability
├── search
├── trace
├── log
└── error
Odpowiednie grupowanie będzie się różnić w zależności od integracji.
Dla bazy danych coś takiego jak:
db_tables
db_query
db_advanced
może dobrze zadziałać.
Dla Jira można by preferować:
jira_issues
jira_projects
jira_search
Dla GitLab odpowiedni układ może być:
gitlab_repository
gitlab_issues
gitlab_mergeRequests
lepiej dopasowany.
Nie istnieje stała liczba, która pasowałaby we wszystkich przypadkach.
Celem jest znalezienie najmniejszej powierzchni narzędzia, która nadal zachowuje jasne, znaczące granice semantyczne.
4. Przykład bazy danych
Konektor do bazy danych stanowi dobry przykład, ponieważ interfejsy API baz danych zazwyczaj udostępniają długą listę pojedynczych operacji.
Zamiast definiować oddzielne pozycje takie jak:
listTables
getTableSchema
getSampleData
getTableSize
executeQuery
getDatabaseInfo
listRelationships
getIndexes
profileColumn
searchSchema
listProcedures
getTriggers
compareSchemas
można je połączyć w:
db_tables
db_query
db_advanced
Pierwszy z tych narzędzi może opierać się na schemacie takim jak:
const tablesSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("list"),
schema: schemaField,
}),
z.object({
action: z.literal("schema"),
tableName: z.string(),
schema: schemaField,
}),
z.object({
action: z.literal("sampleData"),
tableName: z.string(),
schema: schemaField,
rowCount: z.number().optional().default(10),
}),
z.object({
action: z.literal("size"),
tableName: z.string(),
schema: schemaField,
}),
]);
Z punktu widzenia modelu istnieje tylko jedno narzędzie:
db_tables
oparte na jasno zdefiniowanym zestawie możliwych działań:
list
schema
sampleData
size
W rzeczywistości czas wykonywania nadal kieruje żądania do czterech odrębnych obsługujących:
switch (args.action) {
case "list":
return handleListTables(args.schema);
case "schema":
return handleGetTableSchema(
args.tableName,
args.schema
);
case "sampleData":
return handleGetSampleData(
args.tableName,
args.schema,
args.rowCount
);
case "size":
return handleGetTableSize(
args.tableName,
args.schema
);
}
To rozdzielenie ma znaczenie:
Fuzja odbywa się na poziomie interfejsu MCP, a nie w ramach leżącej u jego podstaw logiki biznesowej.
Każdy wewnętrzny obsługujący pozostaje niezależnie testowalny i łatwy w utrzymaniu.
5. Zod Discriminated Unions są idealnym rozwiązaniem
W kodach typu TypeScript funkcja z.discriminatedUnion() oferuje prosty sposób na modelowanie tego wzorca.
Naprzимер:
const querySchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("execute"),
query: z.string(),
params: z.record(z.string()).optional(),
limit: z.number().optional().default(100),
}),
z.object({
action: z.literal("info"),
}),
]);
Po tym można bezpośrednio wywnioskować typ:
type QueryInput = z.infer<typeof querySchema>;
Ten podejście daje trzy praktyczne korzyści:
- Jedno narzędzie MCP.
- Jasne kierowanie ruchem oparte na polu akcji.
- Strażna walidacja wprowadzana w czasie wykonywania.
Ogólny kształt architektury wygląda następująco:
MCP Tool
│
▼
Discriminated Union
│
┌─────┴─────┐
│ action │
└─────┬─────┘
│
┌────────┼────────┐
▼ ▼ ▼
Handler A Handler B Handler C
│ │ │
└────────┼────────┘
▼
Backend
Dla modelu jest to o wiele łatwiejsze do nawigacji niż dziesiątki niepowiązanych narzędzi najwyższego poziomu.
6. Liczba narzędzi to nie liczba możliwości
To jest chyba najważniejsza koncepcja w tym podejściu.
Załóżmy, że ostatecznie mamy:
3 tools
13 actions
To nie oznacza, że zniknęło dziesięć możliwości. W rzeczywistości mamy:
3 external interfaces
13 internal capabilities
Krótko mówiąc:
Tool count ≠ capability count
To rozróżnienie staje się coraz ważniejsze w miarę wzrostu skali systemów agentowych. Zestaw narzędzi dużej organizacji może wyglądać następująco:
Jira 20 operations
GitLab 25 operations
Confluence 15 operations
Sentry 8 operations
ELK 10 operations
Jaeger 6 operations
Database 20 operations
co może łatwo doprowadzić do ponad 100 oddzielnych operacji. Bezpośrednie udostępnianie ich wszystkich modelowi powoduje niepotrzebnie rozbudowaną strukturę narzędzia. Zamiast tego można je połączyć w coś takiego jak:
jira
gitlab
confluence
sentry
observability
database
przy jednoczesnym zachowaniu całkowitej liczby podstawowych operacji bez zmian.
7. Dlaczego to może zmniejszyć zużycie tokenów
Główna korzyść polega na zmniejszeniu ilości metadanych narzędzia, które model musi analizować. Zamiast wymieniać:
Tool 1
Tool 2
Tool 3
Tool 4
...
Tool 30
prezentuje się:
Tool A
Tool B
Tool C
To może zmniejszyć ilość:
- opisów narzędzi
- powtarzających się metadanych parametrów
- duplikowanych wyjaśnień semantycznych
- złożoności wyboru narzędzi najwyższego poziomu
- przestrzeni kontekstowej zajmowanej przez definicje narzędzi
Niemniej jednak nie należy tego traktować jako gwarantowanej proporcjonalnej oszczędności. Przechodząc od:
30 tools → 3 tools
nie przekłada się automatycznie na:
90% lower token cost
ponieważ połączone schematy same mogą stać się większe. Dokładniejszy sposób sformułowania tego jest następujący:
Łączenie kilku operacji w jedno narzędzie zazwyczaj zmniejsza objętość metadanych, które agent musi przeczytać, chociaż to, ile faktycznie zaoszczędzisz w tokenach lub czasie odpowiedzi, zależy od takich czynników jak rozmiar połączonego schematu, stopień szczegółowości opisów pól, specyfiki sposobu obsługi definicji narzędzi przez dostawcę modeli oraz konkretny sposób, w jaki twoja ramowa struktura agenta zarządza wywołaniami narzędzi.
Tę niuansę warto mieć na uwadze.
8. Łączenie wszystkiego razem nie jest automatycznie lepsze
Istnieje tu kompromis, który warto wspomnieć.
Załóżmy, że jedno narzędzie oferuje 40 różnych działań:
enterprise({
action: ...
})
z działaniami takimi jak:
createCustomer
deleteCustomer
searchInvoice
rotateCredentials
deployService
createRepository
getTrace
searchLogs
...
Technicznie jest to osiągalne.
Jest to jednak zły pomysł projektowy.
Model jest teraz uwięziony w parsowaniu jednego ogromnego schematu.
Nie rozwiązałeś pierwotnego problemu, tylko go przeniosłeś z:
30 tools
na:
1 giant tool
Lepszym rozwiązaniem jest konsolidacja według linii domenowych.
Naprzимер:
customer
billing
repository
observability
deployment
Prawidłowym celem może być pięć narzędzi, a nie jedno.
To prowadzi do zasady kierującej:
Zmniejsz zakres narzędzia, ale nie rób tego lekkomyślnie.
9. Schemat pełni również rolę części interfejsu agenta
Gdy jedno narzędzie obejmuje kilka funkcji, parametr action ma duże znaczenie.
Słaba wersja:
action: z.string()
Ulepszenie:
action: z.enum([
"search",
"get",
"create",
"update"
])
Najlepsza opcja, gdy każda akcja wymaga odrębnych parametrów:
z.discriminatedUnion("action", [
searchSchema,
getSchema,
createSchema,
updateSchema,
])
Tutaj wybrana akcja oraz jej wymagane argumenty są połączone dzięki prawdziwej bezpieczeństwu typów.
Na przykład:
action = "search"
→ query required
action = "get"
→ issueId requiredaction = "create"
→ title + description required
To jest o wiele dokładniejsze niż pojedynczy schemat o luźnym typowaniu wypełniony polami opcjonalnymi dla każdej możliwej akcji.
10. Komplikacje związane ze schematami JSON w ramach MCP
W praktyce pojawia się dodatkowy problem.
Schematy narzędzi MCP są ostatecznie przekształcane w schematy JSON.
w zależności od używanego konwertera, Zod discriminated union może wygenerować:
{
"anyOf": [...]
}
lub:
{
"oneOf": [...]
}
Części procesu przetwarzania schematu MCP zakładają, że korzeń schematu to zwykły obiekt.
To założenie nie zawsze jest prawdziwe.
W co najmniej jednej rzeczywistej implementacji warstwa kompatybilności Zod wewnątrz MCP SDK została zbudowana wokół kształtów obiektów i nie radziła sobie poprawnie z uniami dyskryminowanymi.
Rozwiązaniem jest dodanie kroku normalizacji dla wygenerowanego schematu.
Koncepcyjnie wygląda to tak:
Zod discriminated union
│
▼
JSON Schema
│
▼
MCP normalization
│
▼
MCP-compatible object schema
Główny wniosek architektoniczny jest następujący:
Zmień kształt schematu pokazywanego modelowi, nie zmieniając jednocześnie surowości twojego rzeczywistego walidatora.
Pierwotny schemat Zod powinien pozostać jedynym źródłem prawdy dla procesu walidacji.
11. Dwa różne schematy: jeden dla modelu, drugi do wykonywania w czasie rzeczywistym
To wskazuje na szerszą różnicę, którą warto jasno określić.
Tutaj faktycznie mamy do czynienia z dwiema odrębnymi zadaniami:
LLM-facing representation
│
▼
Tool selection and argument generation
Runtime representation
│
▼
Validation and execution
Schemat pokazywany LLM powinien być:
- łatwy do odczytania
Schemat używany w czasie działania powinien być:
- ścisły
- bezpieczny
- przewidywalny
- ostateczny
Te dwa przedstawienia nie muszą się dokładnie pokrywać. Uproszona, spłaszczona struktura obiektu może pomóc modelowi łatwiej wybrać właściwą akcję, podczas gdy leżąca u jej podstaw zjednoczona struktura nadal zapewnia walidację w tle.
To uogólnia się na szerszą zasadę dla systemów agentowych:
Dostosuj schematy do sposobu, w jaki je odczytują modele, ale nigdy nie polegaj na schemacie przeznaczonym dla modeli jako na swojej jedynej linii obrony.
12. Opisy mają większe znaczenie po połączeniu narzędzi
Przy 30 oddzielnych narzędziach każde z nich może pozwolić sobie na wąski, specyficzny opis.
Za pomocą 3 narzędzi opis musi przedstawić cały zakres możliwych działań.
Naprzимер:
Unified tool for advanced database analysis.
ACTIONS:
relationships
List foreign key relationships.
indexes
Get indexes for a table.
profileColumn
Analyze nullability, cardinality and top values.
searchSchema
Search tables and columns.
listProcedures
List stored procedures.
triggers
Get triggers.
compareSchemas
Compare two table definitions.
To nie jest tylko dokumentacja dla ludzi.
Funkcjonuje jako element sposobu, w jaki model kieruje swoje wywołania.
Pomocny sposób na to spojrzenie:
Tool name
+
Description
+
Action enum
+
Parameter schema
↓
Agent routing signal
Dlatego konsolidacja narzędzi powinna skłonić cię do pisania bardziej dokładnych opisów, a nie mniej ich.
13. Ten wzorzec nie jest ograniczony do baz danych
Ta sama metoda przynosi jeszcze większe korzyści, gdy zastosujesz ją w szerszym zakresie systemów inżynieryjnych.
Jira
Zamiast ujawniać:
searchIssues
getIssue
createIssue
updateIssue
addComment
transitionIssue
assignIssue
getTransitions
ujawniać:
jira_issues
z zestawem działań takim jak:
search
get
create
update
comment
transition
assign
GitLab
Zamiast dostarczania oddzielnych narzędzi dla repozytoriów, zadań, wniosków o połączenie i pipeline’ów:
gitlab_repository
gitlab_issues
gitlab_mergeRequests
gitlab_pipelines
Można je połączyć w jeden punkt wejścia, przy czym każdy z nich będzie miał własny zestaw akcji do wykonania.
Confluence / Wiki
Zamiast rozproszać operacje między kilkoma narzędziami:
searchPages
getPage
createPage
updatePage
deletePage
getChildren
połączyć je w jedno:
wiki
kierowane listą akcji taka jak:
search
get
create
update
children
Sentry
Taka sama struktura sprawdza się również tutaj:
sentry
oparte na:
searchIssues
getIssue
events
releases
projects
ELK
To samo zasada dotyczy wyszukiwania logów:
logs
z:
search
aggregate
fields
indices
Jaeger
A jeśli chodzi o rozproszone śledzenie:
tracing
z:
search
getTrace
services
operations
Żaden z tych przykładów nie zależy od wybranej nazwy. Ważny jest podstawowy podejście architektoniczne, a nie dokładne etykiety używane dla każdej akcji.
14. Można otoczyć istniejące serwery MCP zamiast je odbudowywać
Jedną z bardziej praktycznych zalet tego rozwiązania jest to, że rzadko musisz ponownie implementować integrację od zera. Dla wielu popularnych platform istnieją już gotowe serwery MCP. Problem polega na tym, że ich domyślna struktura narzędzi często nie jest dostosowana do potrzeb danego agenta.
Zamiast łączyć agenta bezpośrednio z rozległym zestawem narzędzi:
Agent
│
├── 20 Jira tools
├── 20 GitLab tools
└── 15 Confluence tools
możesz wstawić cienką, specjalnie stworzoną warstwę pomiędzy nimi:
Agent
│
Custom MCP Layer
│
┌──────────┼──────────┐
▼ ▼ ▼
Jira GitLab Confluence
│ │ │
existing existing existing
APIs MCP MCP
Ta spersonalizowana warstwa pełni rolę facylitatora dla agenta. Jest szczególnie przydatna, gdy podstawowa integracja funkcjonuje poprawnie, ale dostarcza znacznie bardziej szczegółowych operacji, niż agent powinien mieć możliwość bezpośredniego obsługi.
15. Traktowanie MCP jako warstwy adaptacyjnej
To prowadzi do bardziej ogólnej koncepcji architektonicznej. Konwencjonalne rozwiązanie wygląda następująco:
Agent → MCP → Service
Zaawansowana konfiguracja wygląda następująco:
Agent
↓
Agent-oriented MCP facade
↓
Service-specific MCP/API
↓
Service
Fasada pośrednia może przejąć takie obowiązki jak:
- konsolidacja narzędzi
- wymuszanie sprawdzania uprawnień
- normalizacja danych wejściowych
- normalizacja danych wyjściowych
- rejestrowanie działań
- śledzenie procesów
- caching
- ograniczanie szybkości działania
- stosowanie zasad specyficznych dla organizacji
Z tego punktu widzenia warstwa MCP przestaje być jedynie mechanizmem transportowym i staje się rzeczywistą warstwą interfejsu dla agenta.
16. Kontrola dostępu nadal należy do poziomu działań
Istnieje konsekwencja bezpieczeństwa, na którą warto zwrócić uwagę. Jeśli połączysz:
jira_create
jira_update
jira_delete
w jedną strukturę wywołania w stylu:
jira({
action: ...
})
nie możesz po prostu przyznać ogólne uprawnienia w postaci:
permission: jira
i zakładajmy, że każda podstawowa operacja niesie równy poziom ryzyka. Sprawdzanie uprawnień musi odbywać się na poziomie poszczególnych działań, na przykład:
jira.search → allowed
jira.get → allowed
jira.create → allowed
jira.update → approval required
jira.delete → denied
Narzędzie konsolidowane jest wyłącznie uproszczeniem na poziomie interfejsu — nie powinno stać się skrótem, który w tajemnicy zapewnia szerszy dostęp, niż zamierzano.
17. Logi muszą rejestrować działanie, a nie tylko narzędzie
Gdy narzędzia zostaną połączone, rejestrowanie jedynie nazwy narzędzia w logach traci sens. Słaby wpis do logu wygląda tak:
tool = jira
Lepszy zawiera informację o dokonanym działaniu:
{
"tool": "jira",
"action": "search",
"duration_ms": 142
}
A jeszcze bardziej szczegółowy dodaje dodatkowe informacje o wykonaniu:
{
"tool": "jira",
"action": "search",
"status": "success",
"duration_ms": 142,
"request_id": "...",
"agent_id": "...",
"user_id": "..."
}
Prawdziwą jednostką wykonania jest teraz para:
tool + action
a nie samo narzędzie:
tool
To rozróżnienie ma duże znaczenie, gdy próbujesz zlokalizować błąd i sprawdzić, co dokładnie zrobił agent.
18. Trzymaj oddzielne obsługiwacze
Konsolidacja powinna odbywać się na granicy dostępnej dla agenta, a nie wewnątrz twojej implementacji. Unikaj łączenia wszystkiego w jeden ogromny obsługiwacz:
async function jira(args) {
// everything
}
Zamiast tego kieruj żądania do odrębnych funkcji:
async function jira(args: JiraInput) {
switch (args.action) {
case "search":
return searchIssues(args);
case "get":
return getIssue(args);
case "create":
return createIssue(args);
case "update":
return updateIssue(args);
}
}
Każdy obsługiwacz pozostaje samodzielnie testowalny:
jira()
│
├── searchIssues()
├── getIssue()
├── createIssue()
└── updateIssue()
Dzięki temu twoja baza kodu pozostaje łatwa w utrzymaniu, nawet gdy liczba narzędzi zewnętrznych maleje.
19. Praktyczna zasada decydowania o fuzji
Dobra heurystyka określająca, czy dwie operacje powinny należeć do tego samego narzędzia, brzmi:
Czy inżynier naturalnie zgrupowałby te operacje pod jedną funkcjonalnością?
Na przykład te operacje wyraźnie należą do siebie:
Jira search
Jira get issue
Jira update issue
Ale te nie:
Jira update issue
Elasticsearch search logs
Create AWS infrastructure
Kolejna przydatna weryfikacja:
Czy podejmowanie decyzji pomiędzy tymi operacjami zazwyczaj wymaga tych samych informacji kontekstowych?
Jeśli odpowiedź brzmi tak, ich połączenie zwykle przynosi korzyści. Jeśli nie, lepiej zachować je jako odrębne narzędzia.
20. Kiedy łączenie narzędzi to zła decyzja
Czasami lepszym rozwiązaniem jest po prostu zachowanie narzędzi oddzielnie.
Niezwiązane domeny
database
payments
infrastructure
email
nie powinno być spłaszczane do czegoś w rodzaju:
enterprise({
action: ...
})
Mieszanie niezwiązanych domen w jeden dysponent tylko dlatego, że można to zrobić, nie sprawia, że interfejs staje się bardziej spójny.
Zbiory działań, które są zbyt duże
Jeśli jeden zintegrowany narzędzie obejmie pięćdziesiąt różnych działań, powstała struktura może stać się nadmiernie rozbudowana i utrudnić modelowi rozumienie sytuacji, co unieważnia cel integracji.
Operacje z podwyższonymi uprawnieniami
Działania takie jak:
deleteProductionData
rotateCredentials
deployProduction
mogą wymagać własnych granic uprawnień, nawet jeśli technicznie należą do tego samego domeny co operacje o niższym ryzyku.
Operacje z różnymi gwarancjami cyklu życia
Wezwania tylko do odczytu oraz destrukcyjne wezwania czasami lepiej funkcjonują jako oddzielne interfejsy:
jira_read
jira_write
zamiast być połączone w jedno nadmiarowo rozbudowane narzędzie.
Jak wcześniej, nie ma żadnej stałej formuły — zależy to od systemu.
21. Mniej narzędzi to nie jest prawdziwy cel
Celem nie jest zmniejszenie wszystkiego do:
30 → 1
Prawdziwym celem jest:
30 → smallest useful semantic surface
W jednym systemie właściwa liczba może wyglądać tak:
30 → 3
W innym:
30 → 7
A w jeszcze innym:
30 → 12
To, co faktycznie optymalizujesz, to:
minimize unnecessary tool metadata
bez rezygnacji z:
semantic clarity
validation
security
maintainability
22. Użyteczny kształt architektury
Skalowalna konfiguracja zazwyczaj przyjmuje taki kształt:
Agent
│
▼
┌──────────────────┐
│ Agent MCP Layer │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
jira gitlab wiki
│ │ │
┌────┼────┐ ┌────┼────┐ ┌────┼────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
search get create issues MR pipeline search get update
│ │ │
└──────────────┼──────────────┘
▼
Existing APIs/MCPs
Model wchodzi w interakcję z małą, uporządkowaną powierzchnią. Poniżej warstwa integracji nadal ma dostęp do pełnego zakresu operacji.
23. Przemyślenie przeznaczenia MCP
Powszechny model mentalny dotyczący MCP brzmi:
">Zmień każdą wywołanie API na oddzielne narzędzie."
To dobry punkt wyjścia.
Jednak w przypadku systemów działających w produkcji bardziej przydatne staje się pytanie:
Co faktycznie powinien widzieć model?
Te dwa podejścia nie zawsze prowadzą do tego samego projektu.
Rozważmy API typu REST, które udostępnia:
GET /issues
GET /issues/:id
POST /issues
PATCH /issues/:id
POST /issues/:id/comments
POST /issues/:id/transition
Nie ma wymogu, by każda trasa HTTP była oddzielnym narzędziem skierowanym do modelu. Warstwa MCP może zamiast tego pełnić rolę semantycznego opakowania tych punktów końcowych — podobnie jak w przypadku projektowania API dostosowanego do potrzeb konkretnego użytkownika, zamiast bezpośredniego udostępniania podstawowej usługi lub schematu bazy danych.
24. Traktowanie projektowania narzędzi jako problemu UX agenta
Klasyczne projektowanie API zakłada pytanie:
Jakie punkty końcowe istnieją?
Projektowanie z myślą o agencie stawia inne pytanie:
Jaką decyzję model faktycznie próbuje podjąć?
Inżynier ludzki integrujący się z API może korzystać z:
getIssue
getIssueComments
getIssueTransitions
getIssueWorklogs
ponieważ każda wywołanie odpowiada jasno jednej operacji.
Z drugiej strony agent może lepiej funkcjonować z:
jira_issue({
action: "get",
...
})
w połączeniu z:
jira_issue({
action: "comments",
...
})
ponieważ najpierw wybiera domenę, a następnie zawęża wybór do konkretnej akcji — to dwuetapowy proces routingu:
Domain
↓
Action
↓
Arguments
Zamiast zmuszania modelu do rozwiązania:
One large global tool-selection problem
25. Minimalny wzorzec, który można ponownie wykorzystać
To podejście łatwo przenosi się do TypeScript:
const toolSchema = z.discriminatedUnion("action", [
searchSchema,
getSchema,
createSchema,
updateSchema,
]);
server.registerTool(
"jira",
{
description: "...",
inputSchema: toolSchema,
},
async (args) => {
switch (args.action) {
case "search":
return search(args);
case "get":
return get(args);
case "create":
return create(args);
case "update":
return update(args);
}
}
);
Sam kod pozostaje dość prosty. Prawdziwa praca projektowa polega na odpowiedziach na pytania:
- Które operacje logicznie się łączą?
- Jaka konwencja nazewnicza powinna być stosowana dla akcji?
- Cóż powinno znaleźć się w opisie narzędzia?
- Które parametry są obowiązkowe dla każdej akcji?
- Które akcje wymagają dodatkowych sprawdzeń uprawnień?
- Jak powinny być spójnie formatowane odpowiedzi?
- Jak należy rejestrować i śledzić każdą akcję?
26. Śledź wpływ za pomocą rzeczywistych danych
Konsolidacja narzędzi nie powinna być decyzją opartą wyłącznie na wierze — musisz to zweryfikować za pomocą danych.
Przed zmianą możesz zarejestrować coś takiego:
Tools: 32
Tool schema tokens: X
Average tool-selection latency: Y
Tool-selection errors: Z
Po wprowadzeniu projektu typu multiplexed ponownie zbierasz te same wskaźniki:
Tools: 5
Tool schema tokens: X'
Average tool-selection latency: Y'
Tool-selection errors: Z'
Najbardziej informacyjne sygnały do śledzenia to:
- ile narzędzi jest dostępnych
- całkowita wielkość schematów
- ile tokenów zużywa prompt lub kontekst
- jak dokładnie agent wybiera odpowiednie narzędzie
- jak często wysyła nieważne żądania do narzędzi
- jak często zawodzi walidacja argumentów
- opóźnienie w odpowiedzi
- jak często wykonania faktycznie się udają
- czy agent ukończa całe zadanie
Celem nie jest po prostu ściganie:
fewer tools
lecz optymalizacja pod kątem:
better agent performance per unit of context
27. Szerzej pojęty wzorzec dla API przeznaczonych dla agentów
To rozwiązanie znajduje zastosowanie daleko poza samym MCP.
Tę samą zasadę można stosować w przypadku:
- wywoływania funkcji
- definicji narzędzi w stylu OpenAI
- SDK-ów
- spersonalizowanych środowisk uruchamiania agentów
- silników przepływów pracy
- architektur pluginów
- adapterów REST do agentów
- mostków GraphQL do agentów
W istocie wzorzec wygląda w ten sposób:
Traditional API
↓
Many low-level operations
↓
Agent-oriented facade
↓
Small semantic tool surface
↓
Action routing
↓
Strict execution layer
To nie jest trik specyficzny dla baz danych.
Jest to ogólny wzorzec służący do projektowania interfejsów wykorzystywanych przez agenty.
28. Podstawowa zasada
Model myślowy, który należy zachować, jest prosty:
┌──────────────────────┐
│ 30 APIs │
│ capabilities │
└──────────┬───────────┘
│
Consolidation
│
▼
┌──────────────────────┐
│ 3–5 tools │
│ semantic domains │
└──────────┬───────────┘
│
Action routing
│
▼
┌──────────────────────┐
│ 30 handlers │
│ original abilities │
└──────────────────────┘
Praktyczna zmiana nie polega na:
30 capabilities → 3 capabilities
Zamiast tego chodzi o:
30 exposed tools → 3 exposed interfaces
Nic się nie zmienia w tym, co system potrafi robić.
Zmienia się to, jaka część tej powierzchni musi być bezpośrednio rozważana przez model.
Podsumowanie
W miarę jak systemy agentowe są integrowane z coraz większą częścią waszej infrastruktury, liczba dostępnych dla nich narzędzi będzie się stale zwiększać.
Narzędzia takie jak Jira, GitLab, Confluence, Sentry, ELK, Jaeger, różne bazy danych, dostawcy chmury oraz usługi wewnętrzne mogą każde z siebie dostarczyć dziesiątki lub setki pojedynczych operacji.
Przekazywanie każdej z tych operacji modelowi jako odrębnego narzędzia rzadko stanowi właściwą architekturę.
Bardziej skuteczną strategią jest stworzenie warstwy narzędzi przeznaczonych dla agentów na ich podstawie:
Many low-level operations
↓
Semantic domain grouping
↓
Discriminated action schemas
↓
Small MCP tool surface
↓
Strict runtime validation
↓
Original capabilities
Zasada kierująca jest następująca:
Zmniejsz liczbę narzędzi, o których musi myśleć model — a nie liczbę funkcji, które faktycznie oferuje wasz system.
Integracja z trzydziestoma podstawowymi operacjami nie musi oznaczać obecności trzydziestu narzędzi widocznych dla modelu.
Może wystarczyć zaledwie trzy starannie zaprojektowane interfejsy, z których każdy ma dobrze określony zakres działań.
Staje się to tym cenniejsze, gdy przestaniemy traktować istniejące serwery MCP jako niezmienne, niedostępne umowy, a zamiast tego jako elementy budulcowe, które możemy otoczyć, przekształcić i dostosować do potrzeb naszego agenta.
W przyszłości dobre projektowanie MCP może polegać mniej na pokazywaniu każdej operacji obsługiwanej przez API, a bardziej na tworzeniu najmniejszego, najpraktyczniejszego interfejsu, którego agent potrzebuje do wykonania swojej pracy.
Literatura pokrewna
- Bounded Agentic Loops: Pewne patterny TypeScript do używania w narzędziach LLM — Dowiedz się, jak projektować agenty LLM w TypeScript przy użyciu bounded agentic loops wraz z walidacją Zod, deterministyczną eksploatacją narzędzi oraz mechanizmami naprawy błędów dla niezawodności na poziomie produkcyjnym.
- Projektowanie przestrzeni pracy agentic AI dostosowanych do rzeczywistych sposobów działania zespołów — Wyjaśnia dziesięć podstawowych zasad budowy przestrzeni pracy agentic AI, które łączą kontekst, wiedzę, autonomię, dostęp do narzędzi oraz automatyzację procesów w jednolity system.