refactor(tools): execute-маппинг в SHARED_TOOL_SPECS + автопроводка обоих хостов — тул регистрируется в одном месте #445

Open
opened 2026-07-10 06:50:33 +03:00 by agent_vscode · 0 comments
Collaborator

Зачем

Реестр SHARED_TOOL_SPECS (#294) разделяет между хостами только метаданные (имя, схему, описание, tier) — но не execute-логику. Каждый shared-тул по-прежнему имеет два ручных execute-тела с копией маппинга аргументов:

  • внешний MCP: registerShared(spec, execute) в packages/mcp/src/index.ts;
  • in-app агент: key: sharedTool(spec, execute) в apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts, где маппинг вручную зеркалит MCP-транспорт (например, для drawio: дефолт format ?? 'xml', перегруппировка плоской схемы в объект where, позиционный baseHash).

Это корневая причина повторяющихся parity-багов: f46d89ea (drawio-тулы добавлены в реестр, но проводка сделана только в MCP — in-app сломался, чинили постфактум по красному develop CI), ранее f8d26420 (stashPage), fc9088b7 (in-app не парсил строковые node-аргументы как MCP). Добавление одного shared-тула сегодня = 7–9 согласованных ручных правок в двух пакетах.

Механика

  1. Расширить SharedToolSpec (packages/mcp/src/tool-specs.ts) полем:
    /** Single canonical mapping from validated schema args to the client call.
     *  Plain JS — crosses the zod-major boundary freely. */
    execute: (client: DocmostClientLike, args: Record<string, unknown>) => Promise<unknown>;
    
    Двухмажорность zod (v3 в mcp / v4 на сервере) блокирует шаринг схем-объектов, но не блокирует шаринг execute — это обычная функция.
  2. Оба хоста становятся циклами по реестру:
    for (const spec of Object.values(SHARED_TOOL_SPECS))
      register(spec, (args) => spec.execute(client, args));
    
    Добавление спека автоматически регистрирует тул в ОБОИХ хостах.
  3. Осознанные per-layer различия (guardrails create_comment/check_new_comments на MCP-стороне, отсутствие deleteComments у in-app transformPage, hybrid-search) оформить явными override-полями спека (mcpExecute?/inAppExecute?/mcpOnly?/inAppOnly?), а не параллельными inline-определениями — чтобы различие было видно в одном файле рядом с каноном.
  4. Существующие guard-тесты (contract-parity, phantom-catalog) сохранить — после рефакторинга они превращаются из «последней линии обороны» в инварианты, которые не ломаются забывчивостью.

Порядок

  • После переноса оставшихся inline-тулов в реестр (сейчас их 8 на in-app стороне: searchPages, getCurrentPage, updatePageContent, listSidebarPages, getTable, getComment, getPageHistory, transformPage; часть уедет по #411/#412 — tableGet сможет войти в реестр после camelCase).
  • Строго до breaking-окна #412: переименование тогда пройдёт по одной точке истины.
  • До drawio стадии 3 (#425) и новых read-тулов (#443) — каждый новый тул до этого рефакторинга проходит минное поле вручную.

Критерии приёмки

  • Добавление спека в tool-specs.ts делает тул доступным в обоих хостах без правок index.ts и ai-chat-tools.service.ts.
  • Ни одного дублированного маппинга аргументов между хостами (grep по sharedTool( с телом — только override-случаи).
  • Сценарий PR #434 («спек есть, in-app проводки нет») невозможен по построению.

Связано: #294 (реестр — предшественник), #416 (роадмап), #412 (breaking-окно), коммиты f46d89ea / f8d26420 / fc9088b7.

## Зачем Реестр `SHARED_TOOL_SPECS` (#294) разделяет между хостами только метаданные (имя, схему, описание, tier) — но **не execute-логику**. Каждый shared-тул по-прежнему имеет **два ручных execute-тела** с копией маппинга аргументов: - внешний MCP: `registerShared(spec, execute)` в `packages/mcp/src/index.ts`; - in-app агент: `key: sharedTool(spec, execute)` в `apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts`, где маппинг **вручную зеркалит** MCP-транспорт (например, для drawio: дефолт `format ?? 'xml'`, перегруппировка плоской схемы в объект `where`, позиционный `baseHash`). Это корневая причина повторяющихся parity-багов: `f46d89ea` (drawio-тулы добавлены в реестр, но проводка сделана только в MCP — in-app сломался, чинили постфактум по красному develop CI), ранее `f8d26420` (stashPage), `fc9088b7` (in-app не парсил строковые node-аргументы как MCP). Добавление одного shared-тула сегодня = 7–9 согласованных ручных правок в двух пакетах. ## Механика 1. Расширить `SharedToolSpec` (`packages/mcp/src/tool-specs.ts`) полем: ```ts /** Single canonical mapping from validated schema args to the client call. * Plain JS — crosses the zod-major boundary freely. */ execute: (client: DocmostClientLike, args: Record<string, unknown>) => Promise<unknown>; ``` Двухмажорность zod (v3 в mcp / v4 на сервере) блокирует шаринг схем-объектов, но **не блокирует** шаринг execute — это обычная функция. 2. Оба хоста становятся циклами по реестру: ```ts for (const spec of Object.values(SHARED_TOOL_SPECS)) register(spec, (args) => spec.execute(client, args)); ``` Добавление спека автоматически регистрирует тул в ОБОИХ хостах. 3. Осознанные per-layer различия (guardrails create_comment/check_new_comments на MCP-стороне, отсутствие `deleteComments` у in-app `transformPage`, hybrid-search) оформить **явными override-полями спека** (`mcpExecute?`/`inAppExecute?`/`mcpOnly?`/`inAppOnly?`), а не параллельными inline-определениями — чтобы различие было видно в одном файле рядом с каноном. 4. Существующие guard-тесты (contract-parity, phantom-catalog) сохранить — после рефакторинга они превращаются из «последней линии обороны» в инварианты, которые не ломаются забывчивостью. ## Порядок - После переноса оставшихся inline-тулов в реестр (сейчас их 8 на in-app стороне: searchPages, getCurrentPage, updatePageContent, listSidebarPages, getTable, getComment, getPageHistory, transformPage; часть уедет по #411/#412 — `tableGet` сможет войти в реестр после camelCase). - **Строго до breaking-окна #412**: переименование тогда пройдёт по одной точке истины. - До drawio стадии 3 (#425) и новых read-тулов (#443) — каждый новый тул до этого рефакторинга проходит минное поле вручную. ## Критерии приёмки - Добавление спека в `tool-specs.ts` делает тул доступным в обоих хостах **без правок** `index.ts` и `ai-chat-tools.service.ts`. - Ни одного дублированного маппинга аргументов между хостами (grep по `sharedTool(` с телом — только override-случаи). - Сценарий PR #434 («спек есть, in-app проводки нет») невозможен по построению. Связано: #294 (реестр — предшественник), #416 (роадмап), #412 (breaking-окно), коммиты f46d89ea / f8d26420 / fc9088b7.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#445