Inspiration

Maya has a weekend in mind. Then one detail changes the plan: Pepper, the dog she and her partner just adopted, is coming too. A spoken request is quick. Comparing its consequences takes something you can see.

Show, Don't Tell explores an interaction pattern for complex Alexa+ requests: express the intent, inspect the plan, review the next step, and return to the saved result. Travel is our example; keeping a person's changing intent visible is the product idea.

What it does

A working self-hosted MCP server returns an interactive itinerary card in a clearly labelled Alexa+-style web simulation.

  1. Plan. “Plan a weekend in Napa for two.” The server scores a curated dataset of 22 venues against the constraints and returns two days of activities, hotel options and an estimated total.
  2. Adjust the same card. “Make it dog-friendly.” The engine filters and re-scores the options. In the recorded example, nine activities become eight and three stays become two; the fixture estimate changes from $1,369 to $609. These are computed changes to this example dataset, not a claim about real-world travel savings.
  3. Review, then confirm. Selecting a hotel produces an itemized quote with status: "requires_confirmation". The card shows the stay, taxes, fees and total, and waits for a separate confirmation click. The server validates pending state and a single-use quote token that expires after 10 minutes. Wrong, reused or expired tokens are refused. This is a simulated booking commitment: no hotel inventory is reserved and no money moves. Token validation does not authenticate a human gesture or prevent an arbitrary MCP client from calling both tools in sequence.
  4. Return to the result. A new transcript with the same simulated identity retrieves the saved trip and simulated receipt through list-trips and get-trip. Trip state lives in a file-backed server store. An earlier, separate restart harness also demonstrated recall after a complete server-process restart.

Demonstration boundary. The local MCP server, tool calls, card and persisted state are real. The Alexa+ host and speech input are simulated; typed text is routed by a deterministic phrase router, not a model-powered autonomous agent. An Agent Skill contract is included but is not loaded by a model host in this build. Actual Alexa+ rendering and integration remain unverified. Napa inventory, example prices and bookings are fixtures. Optional ntfy.sh notification code is disabled by default and was forcibly disabled for the recording; no device push or provider receipt is claimed.

How we built it

The project is an npm-workspaces monorepo built with TypeScript, Vite, Express and Zod.

  • Self-hosted MCP server: the pinned official TypeScript SDK packages are version 2.0.0. The current build serves MCP 2025-11-25 over Streamable HTTP at POST /mcp, with a fresh server and transport per request. It exposes six tools and one ui://trip/itinerary.html card resource. A newer-protocol migration remains proposed.
  • Computed itinerary: a deterministic scoring engine ranks 22 curated Napa venues by time slot, rating, price, dog-friendliness and an “iconic” weight. Changed constraints update the chosen venues and estimated total.
  • Explicit persistence: an atomic file-backed store keeps state by trip and conversation identifiers, independently of the HTTP transport's lifetime.
  • Interactive card: MCP Apps supplies the tool-to-view metadata and host bridge. The self-contained HTML card supports same-card updates, host theme variables, hand-drawn SVG scenes and reduced-motion preferences. The simulator retains one active iframe through an adjustment.
  • Portable orchestration contract: skill/show-dont-tell/SKILL.md documents when to show the card, preserve the conversation identifier and keep quote and confirmation separate. It is included documentation, not evidence of model-host execution.

The isolated source release passed a fresh build and local verification on Node 25.9.0: unit 31/31, protocol smoke 11/11, process-restart recall 7/7 and browser E2E 27/27. Notifications were disabled and non-local test-browser requests were blocked. The fresh film capture separately demonstrated plan → same-card adjustment → quote → explicit UI confirmation → recall with real local MCP traffic, a temporary store and the original data-store hash unchanged.

Challenges we ran into

Keeping a changing plan understandable. A new answer should not discard the interface the person is already using. We kept one card in place and made its changed options and estimate visible.

Separating a review step from an authorization guarantee. The card's separate click and the server's expiring token make the demonstrated state transition inspectable. They are not proof of authenticated human authorization; a production integration must supply that responsibility.

Matching the protocol claim to the endpoint. Our installed SDK exports 2025-11-25 as its default. An earlier experiment explored the newer serving entry, but that migration is outside this film build. We keep the submission claim at the version used by the current implementation.

A helper crash with omitted UI metadata. The dated development log records registerAppTool failing when optional _meta is omitted. Our workaround is _meta: {}. The recorded upstream contribution proposes config._meta ?? {} with a regression test; acceptance or merge is not claimed.

Verifying the target host. During the September 10–11 research pass, we did not establish an accessible actual Alexa+ host path to test this experience. We therefore demonstrate the real server and card through an explicitly labelled web simulation, without inventing host capabilities.

Accomplishments that we're proud of

The person can follow one complete task: create a plan, add a constraint, inspect a quote, record a simulated commitment and return to the same saved result. Each visible change is backed by a real tool result or explicit application state.

The most satisfying outcome is the continuity: the plan adapts in the same card, the commitment has its own review state, and the receipt survives the conversation. The interface gives the user something concrete to inspect at each step.

What we learned

A complex voice answer becomes a product-design problem as soon as it becomes an interface: what can change, what counts as a commitment, and what should persist?

Stateless serving made memory an explicit application responsibility. Writing the Skill contract exposed orchestration assumptions that schemas alone cannot enforce. Building the simulator showed that card sizing, updates and scrolling are part of a continuing interaction, not details to leave until after the initial render.

We also learned to distinguish support in an SDK package from the revision a particular endpoint actually serves, and to distinguish a visible confirmation control from a production authorization boundary.

What's next for Show, Don't Tell

Verify the transport, authorization and UI capabilities of an actual Alexa+ host when an applicable test path is available. Evaluate the newer MCP serving entry and Multi Round-Trip Requests with fresh runtime checks before changing the protocol claim. Validate additional destinations and constraints beyond the current Napa fixture dataset. Continue improving the upstream developer experience through the recorded contribution and friction feedback.

Built With

  • agent-skills
  • css
  • express-5
  • html
  • mcp-2025-11-25
  • mcp-apps
  • mcp-typescript-sdk-2.0.0
  • node.js
  • npm-workspaces
  • streamable-http
  • svg
  • typescript
  • vite-6
  • vite-plugin-singlefile
  • zod-4
Share this project:

Updates

Submission history