Skip to main content
Glama
jherr

Fretboard Voicing Explorer MCP Server

by jherr

Fretboard Voicing Explorer — MCP Apps dual-host demo

A runnable demo of MCP Apps portability: a TanStack Start app hosts its own HTTP MCP server, consumes that server to render an interactive fretboard ui:// widget inline in chat, and the same server is consumed by Goose to render the same widget in a different host.

One MCP server. One ui:// resource. Two hosts.

What's here

src/
  mcp/
    voicing.ts                # pure music model: chord→voicing, voice-leading analysis
    voicing.test.ts           # vitest unit tests (deterministic)
    server.ts                 # MCP server: show_voicing, evaluate_voicing, analyze_progression
    widget/fretboard.html     # host-agnostic ui:// widget (self-contained, no app imports)
  routes/
    fretboard.tsx             # chat host UI + <MCPAppResource> render
    api.mcp.ts                # the Streamable HTTP MCP server endpoint
    api.fretboard.chat.ts     # chat route: createMCPClient(self) + Anthropic adapter
    api.mcp-app.call-tool.ts  # bridge call endpoint (allowlisted)
  sandbox/sandbox_proxy.html  # separate-origin MCP Apps sandbox proxy (served on :3100)
scripts/sandbox-server.mjs    # tiny static server for the sandbox proxy
goose/config-snippet.md       # how to point Goose at /api/mcp

Related MCP server: guitar-pro-mcp

Prerequisites

  • Node 20+, pnpm

  • ANTHROPIC_API_KEY in .env.local (the chat route's Anthropic adapter reads it)

  • Goose Desktop ≥ v1.19.0 for the cross-host half (verified on v1.39.0)

Run

pnpm install
cp .env.example .env            # ports: app 4321, sandbox proxy 3100
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env.local

pnpm dev                        # starts the app (:4321) AND the sandbox proxy (:3100)

Open http://localhost:4321/fretboard. The app uses a fixed port (4321, set in vite.config.ts with strictPort) so the origin is stable for Goose; if it's taken, free it or change the port in vite.config.ts + Goose config.

pnpm test                       # runs the pure-model unit tests

Architecture

  Browser (host #1) ──▶ /fretboard          chat UI, renders <MCPAppResource>
                        /api/fretboard/chat  createMCPClient(self) + Anthropic
   ┌── HTTP MCP ──────▶ /api/mcp             MCP server (3 tools + ui:// resource)
   │                    /api/mcp-app/call-tool  bridge endpoint (evaluate_voicing only)
   │
   │   widget iframe ──▶ :3100 sandbox proxy  separate origin (security isolation)
   │
   └── Goose (host #2) ── points at /api/mcp ── renders the SAME ui:// widget

The ui:// widget speaks the mcp-ui postMessage protocol; the server wraps it with the mcp-apps adapter (@mcp-ui/server) which translates to the MCP Apps (ext-apps) protocol both hosts speak — so the widget is host-agnostic.

Resolve-first findings (verified from source)

  1. ui:// reconciliation is a pull model. @tanstack/ai-mcp@0.2.0 reads the tool's _meta.ui.resourceUri at discovery, stamps it, then fetches the resource via resources/read and matches content.uri === uiResourceUri. So show_voicing sets _meta.ui.resourceUri (and openai/outputTemplate for Goose/Apps SDK) and the server exposes the resource via resources/read.

  2. Adapter: anthropicText('claude-sonnet-4-6') from @tanstack/ai-anthropic.

  3. Transport: the MCP SDK's WebStandardStreamableHTTPServerTransport (RequestResponse, stateless per-request) serves /api/mcp straight from a TanStack Start route — no Node bridging.

  4. @mcp-ui/server@6.1.0 wrapHtmlWithAdapters + getAdapterMimeType emit text/html;profile=mcp-app.

  5. Sandbox proxy is the ext-apps double-iframe proxy, self-hosted on a separate origin (:3100) — @mcp-ui/client@7.1.1's AppRenderer requires it.

Demo script

  1. TanStack app. /fretboard → "Show me a Cmaj7 voicing." The fretboard renders inline. Drag a note → evaluate_voicing → the analysis updates. "What comes next?" → sends a prompt that advances the progression.

  2. Goose, same server. Follow goose/config-snippet.md, restart Goose, ask for the same chord. The same widget renders in Goose; the same drag yields the same evaluate_voicing result; narration comes from Goose's own model.

  3. Honest divergence + security. Same tool result, host-specific narration. A javascript: link is rejected by both hosts' scheme allowlist. A text-only client still gets a usable voicing via the mandatory text fallback.

Status

  • ✅ One /api/mcp server with 3 tools + the ui:// resource (verified over the wire).

  • ✅ TanStack host renders the ui:// widget cross-origin through the sandbox proxy.

  • show_voicing runs, the model narrates the computed voicing; text fallback present.

  • ✅ Pure evaluate_voicing model is unit-tested and deterministic.

  • ✅ Live in-widget host bridge works: the widget is seeded with the computed voicing (delivered as ui/notifications/tool-input), and an in-widget tool call round-trips evaluate_voicing through the bridge endpoint and renders the result.

  • ✅ Bridge endpoint enforces an allowlist (evaluate_voicing only).

Isolated widget harness

/fretboard-test renders the widget with a hardcoded Cmaj7 seed and the real bridge — no chat, no API key — handy for working on the widget in isolation.

Notes / gotchas

  • The widget HTML is inlined into the server module via Vite ?raw, so after editing src/mcp/widget/fretboard.html you need a full dev restart (HMR alone serves a stale widget).

  • The bridge result comes back double-wrapped (the bridge returns a full CallToolResult and the host wraps it again); the widget unwraps to the analysis. See structuredFrom in the widget.


This app was scaffolded with TanStack Start (file-based routing, Nitro server, Tailwind). See https://tanstack.com/start for framework docs.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A composition-focused server built on music21 for generative music workflows, enabling melody generation, musical transformations, chord reharmonization, counterpoint creation, and MIDI export through constraint-based algorithmic composition tools.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A Message Control Protocol (MCP) server for manipulating Guitar Pro files. It provides tools for opening, modifying, and saving Guitar Pro files through a standardized interface.
    22
    MIT
  • F
    license
    A
    quality
    B
    maintenance
    MCP server that transforms cognitive state into generative music by mapping 13 epistemic vectors to Strudel live coding patterns, with mood presets and a web UI for interactive control.
    5
    4
    -