Skip to content

Commit e97730c

Browse files
committed
feat: MCP Elicitation — human-in-the-loop DSL for agentic workflows (#3.16.0)
- ask() callable namespace with typed field factories (string, number, boolean, enum) - AskResponse with fail-fast .data getter and boolean guards - .interactive() on FluentToolBuilder and FluentRouter - Zero-overhead AsyncLocalStorage transport binding in ServerAttachment - Multi-step wizard support and URL redirect mode - 72 new tests, full documentation, llms.txt updated - All packages bumped to 3.16.0
1 parent a885588 commit e97730c

34 files changed

Lines changed: 2483 additions & 37 deletions

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [3.16.0] - 2026-04-21
9+
10+
### Added
11+
12+
#### `@vurb/core` — MCP Elicitation: Human-in-the-Loop for Agentic Workflows
13+
14+
A new first-class DSL that enables MCP tool handlers to pause execution, request user input via the MCP client, and resume with typed responses — zero boilerplate, full type inference.
15+
16+
- **`ask()` callable namespace** — Single import, dual-mode API. Use as a function (`await ask('message', fields)`) for form-based elicitation or as a namespace (`ask.string()`, `ask.number()`, `ask.boolean()`, `ask.enum()`) for field construction. Produces fully typed `AskResponse<T>` with boolean guards (`.accepted`, `.declined`, `.cancelled`) and a fail-fast `.data` getter that throws `ElicitationDeclinedError` on non-accepted access — preventing silent `undefined` propagation.
17+
- **`ask.redirect(message, url)`** — URL-mode elicitation for OAuth flows and sensitive data collection. Sends the user to an external URL instead of rendering inline form fields.
18+
- **Field descriptor DSL** — Four typed field factories (`AskStringField`, `AskNumberField`, `AskBooleanField`, `AskEnumField<T>`) with fluent chaining: `.default()`, `.describe()`, `.min()`, `.max()`. Each field compiles to MCP-native JSON Schema via `.toDescriptor()` with full `anyOf` enum encoding.
19+
- **`.interactive()` on `FluentToolBuilder` and `FluentRouter`** — Opt-in capability flag that declares a tool (or all tools in a router) as eligible for elicitation. Propagated through `BuildPipeline` and `GroupedToolBuilder`. Router-level `.interactive()` is inherited by all child tool builders.
20+
- **Zero-overhead transport binding** — Uses Node.js `AsyncLocalStorage` to bind the MCP `sendRequest` capability per-request in `ServerAttachment`. No context injection (`ctx.ask()`), no handler signature changes, no overhead when `.interactive()` is not declared. The `ask()` function reads the transport from `AsyncLocalStorage` at call time.
21+
- **`ElicitationUnsupportedError`** — Thrown when `ask()` is called but the MCP client did not declare `{ capabilities: { elicitation: {} } }` during initialization. Actionable error message guides developers to check client configuration.
22+
- **Multi-step wizard support** — Sequential `await ask()` calls within a single handler create conversational flows. Each step can reference data from previous steps for dynamic prompts.
23+
- **Transport-agnostic** — Works identically across stdio, SSE, and Streamable HTTP transports. `AsyncLocalStorage` provides per-request isolation, ensuring concurrent HTTP requests never share elicitation state.
24+
25+
#### Documentation
26+
27+
- **`docs/elicitation.md`** — Full documentation page with quick start, architecture diagrams, DSL reference, multi-step wizard examples, URL mode, response handling, transport compatibility, testing guide, API reference, and best practices.
28+
- **`docs/llms.txt`** — Elicitation added to Core Concepts list and full reference section for LLM consumption.
29+
- **Sidebar** — Elicitation added to the Features section in VitePress navigation.
30+
31+
### Test Suite
32+
33+
- **72 new tests** across 4 test files:
34+
- `ask-dsl.test.ts` — 24 tests: field descriptor compilation, JSON Schema output, chaining, enum `anyOf` encoding, form compilation with mixed field types
35+
- `ask-response.test.ts` — 16 tests: boolean guards, fail-fast `.data` access, `ElicitationDeclinedError` on declined/cancelled, edge cases (empty content, null values)
36+
- `ask-transport.test.ts` — 16 tests: `AsyncLocalStorage` isolation, concurrent request safety, `ElicitationUnsupportedError` when transport unavailable, `_elicitStore` injection
37+
- `server-wiring.test.ts` — 16 tests: `ServerAttachment` integration, `.interactive()` flag propagation, pipeline execution with elicitation context, error handling format
38+
- Cumulative: **5213 tests passing** across the monorepo
39+
840
## [3.15.3] - 2026-04-20
941

1042
### Fixed

docs/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export default defineConfig({
145145
collapsed: true,
146146
items: [
147147
{ text: 'Agent Skills', link: '/skills' },
148+
{ text: 'Elicitation', link: '/elicitation' },
148149
{ text: 'Prompts & Resources', link: '/prompts' },
149150
{ text: 'State Sync', link: '/state-sync' },
150151
{ text: 'DLP & Redaction', link: '/dlp-redaction' },

0 commit comments

Comments
 (0)