Strona główna / Artykuły

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.

Wydawca: REACTAPP.TOP19 min czytania4404 słów

Ź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:

  1. Jedno narzędzie MCP.
  2. Jasne kierowanie ruchem oparte na polu akcji.
  3. 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
  • kompaktowy
  • jednoznaczny
  • łatwy do wyboru
  • 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:

    1. Które operacje logicznie się łączą?
    2. Jaka konwencja nazewnicza powinna być stosowana dla akcji?
    3. Cóż powinno znaleźć się w opisie narzędzia?
    4. Które parametry są obowiązkowe dla każdej akcji?
    5. Które akcje wymagają dodatkowych sprawdzeń uprawnień?
    6. Jak powinny być spójnie formatowane odpowiedzi?
    7. 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

  • Stateless MCP: Skalowanie serwerów bez sesji i handshake’ów — Jak protocol Model Context Protocol typu stateless eliminuje sesje i handshake’i, oraz w jaki sposób _meta, wieloetapowe żądania, nagłówki routingu, cache i zadania utrzymują jego użyteczność.