Skip to content

fix(handlers): route read intents (α=read) → GET /world (D3 rehearsal block)#16

Merged
DubovskiyIM merged 1 commit into
mainfrom
fix/read-intent-routing
May 9, 2026
Merged

fix(handlers): route read intents (α=read) → GET /world (D3 rehearsal block)#16
DubovskiyIM merged 1 commit into
mainfrom
fix/read-intent-routing

Conversation

@DubovskiyIM
Copy link
Copy Markdown
Member

Что

Real-world block найденный в D3 rehearsal 2026-05-09. mcp-server диспетчил все intents (включая α=read) через POST /api/agent/:domain/exec. runtime-local v0.6 для read-intent'ов возвращает 400 (read_intent_not_executable, by design A1.2).

Claude Desktop получал tool error → ответил пользователю «инструменты read не работают, я не могу показать продукты».

User: «Покажи список продуктов»
Claude → tool list_products → POST /exec/list_products → 400
Claude: «Запрос на список товаров вернул ошибку»

Решение

makeToolCallHandler теперь маршрутит по intent.alpha:

α=read           → GET /api/agent/:domain/world + filter rows (этот PR)
α=add/replace/   → POST /api/agent/:domain/exec/:intentId (как было)
  remove/batch
intent unknown   → fallback POST /exec (backward compat)

Архитектурное обоснование

runtime-local намеренно отделяет:

  • POST /exec — mutation pipeline через engine.submit (invariants + lifecycle.requiresApproval + Φ ingest)
  • GET /world — read-only snapshot из engine.foldWorld()

В MCP-spec'е read intents должны быть resources, не tools. Но Claude Desktop часто спонтанно дёргает tools по имени (когда видит list_products в наборе). Поэтому tool-call handler сам routes по alpha — для UX, без потери arch-purity (resources всё равно работают через idf://domain/collection URI).

После этого PR Claude может равноценно использовать как tool name (list_products), так и resource URI (idf://custom-culture/products) — зависит от его модели и контекста.

Demo flow после fix'а

User: «Покажи список продуктов»
Claude → list_products tool → mcp-server: alpha=read → GET /api/agent/custom-culture/world
runtime-local: { world: { products: [...] } }
mcp-server filter: collection="products" → 3 rows
Claude: «В магазине 3 продукта: Худи 4990₽, Футболка 1990₽, Кепка 1490₽ (нет в наличии)»

Тесты

5 новых + 41 existing = 46/46 passing:

  • read intent → GET /world + filter → {status:'confirmed', collection:'products', count:3, rows:[...]}
  • mutation intent → POST /exec → standard payload
  • intent не в map → fallback POST (backward compat для старых call sites)
  • read с target='Entity.field' → base entity for collection name
  • read когда /world 500 → isError: true + world_fetch_failed

Что НЕ в этом PR (отложено)

  • Per-row filtering by role.visibleFields — пока возвращаем full rows, а runtime-local v0.6 не делает role-based filter в /world. Future runtime-local v0.7 + mcp-server v1.1 add field-level filter.
  • Pagination — read intents возвращают весь collection. Большие collections (Order > 10k) — batch'и через resources URI с query параметром.

Bump

@intent-driven/mcp-server: 1.0.3 → 1.0.4

После merge для D3 rehearsal

После npm publish:

  • npx -y @intent-driven/mcp-server подтянет 1.0.4
  • Claude Desktop переподключит MCP
  • Покажи продукты → работает
  • Cancel order ORD-... → 202 ApprovalRequest (mutation flow as before)

Refs: D3 rehearsal log 2026-05-09 19:04 UTC.

…exec

Real-world block из custom-culture D3 rehearsal 2026-05-09. mcp-server диспетчил
все intents (включая read) через POST /api/agent/:domain/exec. runtime-local v0.6
для read-intent'ов возвращает 400 (read_intent_not_executable, by design в A1.2).
Claude Desktop получал tool error → не мог list_products / list_orders / etc.

Что внутри:
 - makeToolCallHandler принимает intentsById map (intentId → intent)
 - Если intent.alpha === "read" → GET /api/agent/:domain/world + filter rows
   через toCollectionName helper (matches /resources/list pluralization)
 - Иначе (add/replace/remove/batch) — POST /exec как было
 - Fallback (intent не найден в map) — POST /exec для backward compat

Архитектурное обоснование: runtime-local намеренно отделяет mutation (/exec
с invariants + lifecycle.requiresApproval) от read (/world resource snapshot).
Корректный MCP-маппинг: read → resource в spec'е, но Claude Desktop часто
spontaneous'но дёргает tools по имени. Поэтому tool-call handler сам routes
по alpha — для UX, без потери arch-purity.

Tests: 5 новых + 41 existing = 46/46 passing
 - read intent → GET /world + filter → confirmed/count/rows shape
 - mutation intent → POST /exec
 - intent не в map → fallback POST (backward compat)
 - read с target='Entity.field' — base entity для collection-name
 - read когда /world 500 → isError + world_fetch_failed

Bump 1.0.3 → 1.0.4.

Refs: D3 rehearsal log 2026-05-09 19:04 UTC, Claude prompt
"Покажи список продуктов" → 400 read_intent_not_executable.
@DubovskiyIM DubovskiyIM merged commit 257f59e into main May 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants