↓Перейти к содержанию
  1. Gortex/

GCX1 — компактный формат передачи данных для MCP-инструментов

·7 минут·

Медиана −27,4% экономии tiktoken по сравнению с JSON, 100% целостность при round-trip, на 20 репрезентативных MCP-ответах. Готово к использованию, стабильно, референсные декодеры на Go и TypeScript.

Ответы MCP-инструментов в большинстве своём табличные: вызов search_symbols возвращает N строк одной формы; find_usages — N рёбер; analyze — N находок. JSON — плохой формат для табличных данных: за каждое имя ключа на каждой строке платите вы, а токены платит LLM. При 20-строчном результате поиска "file_path", "start_line", "kind" повторяются двадцать раз.

GCX1 появился, чтобы исправить это для Gortex MCP-сервера, а потом выяснилось, что формат не привязан к конкретному инструменту. Теперь он подключается опционально на уровне отдельного вызова через format: "gcx". Вот как это устроено, почему экономия такая, и где она не работает.

Конкретный пример #

Вызов search_symbols, возвращающий 5 результатов.

JSON (720 байт, 181 tiktoken-токен):

[
  {"id":"internal/mcp/server.go::NewServer","kind":"function","name":"NewServer","file_path":"internal/mcp/server.go","start_line":62},
  {"id":"internal/mcp/server.go::Server.Start","kind":"method","name":"Start","file_path":"internal/mcp/server.go","start_line":118},
  {"id":"internal/mcp/server.go::Server.Shutdown","kind":"method","name":"Shutdown","file_path":"internal/mcp/server.go","start_line":140},
  {"id":"internal/mcp/tools_core.go::registerCoreTools","kind":"method","name":"registerCoreTools","file_path":"internal/mcp/tools_core.go","start_line":307},
  {"id":"internal/mcp/tools_coding.go::registerCodingTools","kind":"method","name":"registerCodingTools","file_path":"internal/mcp/tools_coding.go","start_line":1}
]

GCX1 (522 байта, 125 токенов — −27,5% / −30,9%):

GCX1 tool=search_symbols fields=id,kind,name,path,line,sig rows=5 total=5 truncated=false
internal/mcp/server.go::NewServer	function	NewServer	internal/mcp/server.go	62
internal/mcp/server.go::Server.Start	method	Start	internal/mcp/server.go	118
internal/mcp/server.go::Server.Shutdown	method	Shutdown	internal/mcp/server.go	140
internal/mcp/tools_core.go::registerCoreTools	method	registerCoreTools	internal/mcp/tools_core.go	307
internal/mcp/tools_coding.go::registerCodingTools	method	registerCodingTools	internal/mcp/tools_coding.go	1

Та же информация. Заголовок объявляет порядок колонок один раз; каждая строка — одна строка с разделителями-табуляциями.

Устройство формата #

Четыре ключевых решения.

1. Ориентация на токенизатор, а не на байты.

GCX1 проектировался под tiktoken cl100k_base — токенизатор, который используют Claude и модели класса GPT-4. Табуляция (\t) — один токен. Перенос строки — один токен. "file_path":" — 4 токена. Убрать 20 копий этого одного повторяющегося ключа — сэкономить 80 токенов, а не просто байты. Бенчмарк измеряет именно tiktoken, потому что это число в счёте пользователя — не сырые UTF-8-байты, которые gzip и так уплощает.

2. Round-trip по конструкции.

Каждый GCX1-пейлоад декодируется в эквивалентное JSON-значение и кодируется обратно в побайтово идентичный GCX. Агенты, работающие с JSON, не ломаются. Реализации декодеров: pkg/wire (Go) и @gortex/wire (TypeScript на npm). 20/20 round-trip в бенчмарке — не выборочное утверждение.

3. Табуляция, а не запятая.

Сигнатуры символов содержат запятые ((int, string)) и скобки. Табуляций они не содержат. CSV потребовал бы кавычек почти на каждой строке; TSV — нет. Алфавит экранирования: два байта — \t, \n, \\. Всё остальное проходит без экранирования.

4. Версионирование и безопасный fallback.

Литеральный префикс GCX1 есть в каждом заголовке. Декодер, встретивший в будущем GCX2, обязан перейти к JSON, повторив MCP-вызов без format: "gcx". Расположение полей для каждого объявленного инструмента заморожено на весь срок жизни GCX1 — добавление колонки к инструменту является ломающим изменением и выйдет как GCX2, а не тихой дрейфом схемы.

Грамматика #

payload   = section { section } ;
section   = header row-line { row-line | comment } ;
header    = "GCX1" SP "tool=" token { SP key-value } SP "fields=" field-list LF ;
row-line  = value { TAB value } LF | LF ;
value     = { "\\\\" | "\\t" | "\\n" | any-byte-except-TAB-LF-BACKSLASH } ;

Многосекционные пейлоады конкатенируются последовательно — инструменты вроде get_callers выдают две секции (.nodes и .edges), что избавляет от денормализации графа в дублирование строк.

Полная спецификация: docs/wire-format.md

Бенчмарк — полный скорборд #

Воспроизводимо из bench/wire-format/ командой go run ./bench/wire-format. Стенд захватывает сырые UTF-8-байты, tiktoken-токены, gzip-сжатые байты и целостность round-trip по 20 репрезентативным ответам инструментов.

кейстокенов JSONтокенов GCXΔ%
search_symbols (small)181125−30,9%
search_symbols (large)1068742−30,5%
batch_symbols335241−28,1%
find_usages (large)569351−38,3%
analyze_hotspots506318−37,2%
smart_context471299−36,5%
analyze_dead_code288198−31,2%
find_implementations224157−29,9%
get_callers750532−29,1%
get_editing_context233171−26,6%
get_file_summary567431−24,0%
find_usages (small)335255−23,9%
contracts.list580463−20,2%
get_test_targets311262−15,8%
get_repo_outline237224−5,5%
get_symbol_source (small)257255−0,8%
get_symbol_source (large)534532−0,4%
find_cycles8790+3,4%
graph_stats162174+7,4%

Медиана: −27,4% токенов. Round-trip: 20/20.

Где GCX1 не помогает #

Три кейса дают нейтральный или отрицательный результат — и это структурные причины, а не баги.

graph_stats (+7,4%). Один скалярный объект с ~6 ключами. Заголовок GCX1 стоит фиксированно; при меньше ~5 строках заголовок съедает экономию. Для этого JSON лучше.

find_cycles (+3,4%). 3–4 строки коротких скаляров. Та же проблема.

get_symbol_source (~0%). Пейлоад доминируется полем с исходным кодом. Ни одна кодировка исходник не сжимает.

Выигрыш приходит от табличных пейлоадов с повторяющейся формой. Большинство ответов MCP-инструментов именно такие — но не все. Формат подключается опционально на уровне вызова именно по этой причине: энкодеры переходят обратно к JSON для форм, где GCX1 проигрывает.

GCX1 vs TOON #

TOON (Token-Oriented Object Notation) решает смежную задачу — сравнение здесь уместно.

TOON — это универсальная компактная кодировка для LLM-ввода: отступы в стиле YAML, скобочная нотация [N] для длин массивов и фигурные скобки {id,name,value} для заголовков полей с CSV-строками под ними. Формат обрабатывает вложенные структуры, частично табличные массивы и смешанные данные — его бенчмарки показывают 33–59% снижения токенов по сравнению с JSON в зависимости от формы данных.

GCX1 уже. Он ориентирован именно на ответы MCP-инструментов, несёт в заголовке метаданные уровня инструмента (tool=, rows=, total=, truncated=) и плоский по конструкции. Многосекционные пейлоады (.nodes + .edges) обрабатывают граф без вложенности.

TOONGCX1
Область примененияУниверсальный LLM-вводОтветы MCP-инструментов
Вложенные структурыДаНет (многосекционность вместо)
РазделительЗапятая или табуляцияТолько табуляция
Декларация длины массиваДа ([N])Через rows=N в заголовке
Метаданные инструментаНетДа (tool=, total=, truncated=)
Round-tripНе является целью проектированияДа, по конструкции
Версионирование + fallbackНетПрефикс GCX1, декодер переходит на JSON при GCX2
Заявленная экономия30–60% (разнородные данные)27,4% медиана (формы MCP-инструментов)
Интеграция с MCPСторонний серверНативный format: "gcx" на уровне вызова

Выбор только табуляции в GCX1 — наиболее важное структурное различие. Сигнатуры кода — доминирующий строковый тип в ответах Gortex — содержат запятые (func(int, string) error), но не содержат табуляций. TOON поддерживает табуляцию как альтернативный разделитель, однако по умолчанию используется запятая с кавычками — а это накладные расходы именно на те значения, которые чаще всего встречаются в пейлоадах анализа кода. Алфавит экранирования GCX1 — два байта (\t, \n, \\), и ничто в сигнатуре символа его не триггерит.

Второе существенное различие — round-trip. TOON проектировался как формат, который отправляется LLM: модель его читает, не downstream-инструмент. Пейлоады GCX1 должны декодироваться обратно в типизированные строки, чтобы вывод одного инструмента мог стать вводом другого без повторного вызова. Это MCP-специфичное требование, которое TOON не обязан учитывать.

Если вы строите общую агентную систему, передающую разнородный JSON — записи пользователей, логи событий, конфигурационные объекты, — TOON стоит измерить. Если вы строите MCP-сервер, возвращающий табличные данные о символах, накладные расходы на вложенность TOON и разделитель по умолчанию — затраты, которые вам не нужны.

Попробовать #

Со стороны MCP-сервера — любой Go-сервер.

go get github.com/zzet/gortex/pkg/wire

pkg/wire — отдельный Go-модуль, MIT-лицензия, только стандартная библиотека, 250 строк. Остальная часть Gortex распространяется под отдельной source-available лицензией; wire-пакет — MIT, так что его можно добавить в любой MCP-сервер без проблем с лицензированием.

Со стороны агента — TypeScript.

npm install @gortex/wire
import { decode } from "@gortex/wire";
const rows = decode(mcpResponseText);
// rows имеет ту же форму, что и JSON.parse

Со стороны агента — Go.

Тот же пакет pkg/wire; wire.Decode(payload) возвращает типизированные строки.

Воспроизвести бенчмарк.

git clone https://github.com/zzet/gortex
cd gortex
go run ./bench/wire-format
cat bench/wire-format/scorecard.md

Набор фикстур находится в bench/wire-format/cases/. Каждая — YAML-файл с канонической JSON-нагрузкой. Добавьте фикстуры своего инструмента, запустите стенд и посмотрите, что GCX1 даёт на ваших формах.

Почему это появилось #

GCX1 появился для Gortex — MCP-сервера анализа кода, который индексирует репозитории в граф знаний и предоставляет его Claude Code, Cursor, Windsurf, Copilot и 11 другим AI-агентам. Сервер отдаёт много табличных данных — символы, вызывающий код, контракты, зависимости — и первое, что хотелось срезать, был токенный счёт в аккаунте каждого пользователя. Режим compact: true с текстовым выводом существовал, но не поддерживал round-trip: результат нельзя было подать обратно в другой инструмент. Нужно было что-то, что несёт схему один раз, а не повторяет её в каждой строке.

Что дальше #

GCX1 остаётся стабильным. Расположение полей для каждого инструмента заморожено на весь срок жизни GCX1. Добавление колонки — ломающее изменение, которое выйдет как GCX2.

GCX1-stream зарезервирован. Потоковая передача строка за строкой через SSE / chunked HTTP, тот же заголовок, та же грамматика строк. Пока не выпущено.

GCX2 при необходимости. Если кому-то понадобятся строки на основе CBOR или MessagePack для бинарных пейлоадов — это следующая мажорная версия. GCX1 — только текст, чтобы агенты могли читать сырые пейлоады при отладке. Это не обсуждается для v1.

Если вы строите MCP-сервер: возьмите самый большой табличный ответ, измерьте его через tiktoken, затем прогоните через энкодер и измерьте снова. Если форма табличная — вы оставляете 25–35% на столе.


Спецификация: docs/wire-format.md
Бенчмарк: bench/wire-format
Go-реализация: pkg/wire (MIT)
TypeScript-декодер: @gortex/wire на npm