Przejdź do głównej zawartości

Tool Gateway

Tool Gateway to hub MCP, speedwave_<project>_mcp_hub, jedyny serwer MCP, który widzi Claude. Nieważne, ile integracji włączysz, Claude dostaje dokładnie dwa narzędzia: search_tools oraz execute_code.

search_tools wyszukuje narzędzia we wszystkich włączonych integracjach, także narzędzia systemowe. execute_code kieruje wykonanie do odpowiedniego workera, a każdą integrację widać jako nazwany obiekt, na przykład redmine.listIssueIds().

Conceptual execute_code snippet (shape, not a fixed API)
// Claude has already used search_tools to locate these calls.
const ids = await redmine.listIssueIds({ project: "speedwave" });
const issues = await batch(ids.map((id) => redmine.getIssue({ id })));
return issues.results;

Potraktuj te nazwane obiekty jako przybliżony obraz API, a nie jako stały, publiczny kontrakt.

  1. Claude wywołuje search_tools ze słowem kluczowym, a hub oddaje pasujące nazwy narzędzi razem z ich opisami.

  2. Claude pisze krótki skrypt, który wywołuje znalezione narzędzia.

  3. Claude wywołuje execute_code z tym skryptem, a hub uruchamia go w sandboksie, każde wywołanie kieruje do odpowiedniego workera i zwraca wynik.

Narzędzie zostaje ukryte, dopóki search_tools go nie wydobędzie, przez co zużycie tokenów pozostaje niskie w dużych projektach. Worker może wybrane narzędzie od razu pokazać, ustawiając _meta: { deferLoading: false }.

Każdy worker montuje wyłącznie dane logowania do własnej usługi, i to tylko do odczytu; sam hub nie przechowuje żadnych. W Modelu izolacji wyjaśniamy, dlaczego można na tym polegać. Z narzędziami systemowymi jest inaczej: hub sięga po nie na hoście przez most HTTP, który rozpoznaje wyłącznie znane nazwy usług.

flowchart LR
  Claude["Claude"] -->|search_tools| Hub
  Claude -->|execute_code| Hub["MCP hub (port 4000, zero tokenów)"]
  Hub -->|"gitlab.getMrFull()"| GL["worker gitlab (/tokens:ro)"]
  Hub -->|"redmine.listIssueIds()"| RM["worker redmine (/tokens:ro)"]
  Hub -->|"os.createEvent() via HTTP bridge"| OS["narzędzia systemowe na hoście"]

execute_code odcina niebezpieczne API, eval, require, process, dostęp do plików i sieci, jeszcze zanim skrypt ruszy, a wpuszcza tylko krótką białą listę obiektów globalnych, na przykład JSON i Array.

Cięższe sandboksy były testowane i odrzucone: isolated-vm był w trybie utrzymaniowym, z binariami niedopasowanymi do ABI Node 24, quickjs psuł batch() i Promise.allSettled przy zawieszeniach asynchronicznych, a moduł vm w Node nigdy nie był realną granicą bezpieczeństwa. Właśnie dlatego execute_code działa na AsyncFunction z listą zakazanych elementów, a prawdziwą granicą jest zahartowany kontener.

Nawet udana ucieczka z sandboksa kończy się w zahartowanym kontenerze, w którym nie ma czego ukraść, a każde kolejne wywołanie i tak wyląduje w dzienniku audytowym.

Zanim błąd dotrze do Claude, hub go czyści: ścieżki plików zastępuje wartością [file], wycina numery linii, a treść komunikatu przycina do 500 znaków. Nieznana metoda skutkuje listą wszystkich prawdziwych metod danego obiektu, a service_method dostaje podpowiedź z poprawką na camelCase. Do każdego błędu dołączona jest też flaga retryable, ustawiona na prawdę przy timeoutach. To poprawianie się wyłącznie w obrębie jednej sesji; między sesjami hub niczego nie pamięta.

Hub maskuje też wyniki, zanim zobaczy je Claude. Jak to działa, opisujemy w Tokenizacji. Odnotowuje w dzienniku każde wywołanie execute_code: jego rodzaj, integrację, do której trafiło, i jego parametry.

Wbudowane workery i wtyczki korzystają z tej samej ścieżki wykrywania i z tej samej polityki _meta; nigdzie nie ma na sztywno wpisanej listy wbudowanych narzędzi. Worker niedostępny przy starcie huba zostawia po sobie pusty wpis, dopóki nie odnajdzie go odświeżanie działające w tle. W Workerach wyjaśniamy, czym właściwie jest kontener workera.