Zava Relocation MCP UI Demo
An MCP server that powers an interactive employee relocation intake form, enabling chat-driven completion, document upload/extraction, and local Qwen2.5 7B interpretation.
README
Zava Relocation MCP UI Demo
<a href="demo/zava-relocation-demo.mp4"><img src="demo/zava-relocation-demo-preview.png" alt="Watch the 2-minute Zava Relocation demo" width="600"></a>
Watch the demo: 2-minute narrated walkthrough
Zava Relocation Inc. helps employees move for a new job. This project is a reference demo for building an interactive MCP App with MCP-UI, a local Qwen2.5 7B model, and a synchronized conversational intake form.
The user can chat with Ava, upload an offer letter, or edit the profile directly. Extracted information is applied to the form immediately and highlighted so the user can review what changed.
For a customer-facing teaching walkthrough, see the dedicated MCP UI + LLM form-fill guide.
What the demo demonstrates
- Chat-driven form completion with live field updates
- Optional browser voice mode: speak to Ava and hear Qwen responses aloud
- PDF and DOCX offer-letter extraction in the browser
- Local interpretation with Qwen2.5 7B through Foundry Local
- Five profile sections: contact, employment, move, moving logistics, and preferences
- Grounded moving-option and reimbursement guidance from the fictional Contoso policy PDF
- Progress tracking, highlighted AI updates, reset, and completion state
- Responsive desktop and mobile layouts
- MCP Apps resource/tool linkage through
@mcp-ui/serverand@modelcontextprotocol/ext-apps - A production build that inlines the UI into one HTML resource
Demo boundary: This is a local prototype. It does not persist relocation cases, authenticate users, or submit data to a production HR system. The sample PDFs in
sample-documents/contain fictional data.
Architecture
MCP Apps host
|
| Streamable HTTP: POST /mcp
v
Node + Express MCP server
|-- start_relocation_intake tool
|-- ui://zava-relocation/intake resource
|-- POST /api/chat
v
Foundry Local (same machine)
|
v
Qwen2.5 7B
Browser UI
|-- PDF.js / Mammoth extract document text locally
|-- regex extractor gives immediate form updates
|-- /api/chat sends text and current form to local Qwen
There are two ways to use the UI:
- Standalone mode: Vite serves the React application at
http://localhost:5173. - MCP App mode: An MCP Apps-compatible host connects to
http://localhost:3001/mcp, discoversstart_relocation_intake, and renders the linkedui://zava-relocation/intakeresource.
How MCP-UI is leveraged
This project uses the MCP Apps pattern recommended by MCP-UI:
-
server/index.tscreates anMcpServerand aStreamableHTTPServerTransport. -
The production
dist/index.htmlis loaded into a UI resource usingcreateUIResource. -
registerAppResourcepublishes that resource atui://zava-relocation/intake. -
registerAppToolexposesstart_relocation_intakeand links it to the UI with:_meta: { ui: { resourceUri: relocationUI.resource.uri }, } -
The embedded UI signals readiness with
ui-lifecycle-iframe-readyand can send host messages withwindow.parent.postMessage.
The important distinction is that the MCP server does not render the form itself. It registers the tool and UI resource; the MCP Apps host decides where and how to display that resource.
Foundry Local and Qwen2.5 7B
The assistant uses Qwen2.5 7B through Foundry Local. Foundry Local runs on the same machine as this Node server and exposes an OpenAI-compatible local chat-completions endpoint. No cloud model fallback is configured.
Prerequisites
Foundry Local support depends on the host machine. On Windows, Microsoft documents Windows 11 24H2 or later, .NET 9 or later, and a DirectX 12-capable GPU for the Windows ML runtime.
Install the Foundry Local CLI:
winget install Microsoft.FoundryLocal
Close and reopen PowerShell, then verify the CLI:
foundry --version
List the model aliases available in the local catalog:
foundry model list
Start or download the Qwen model using the alias shown by the catalog. The expected demo alias is:
foundry model run qwen2.5-7b
Keep Foundry Local running while using the demo. The current Foundry Local service endpoint for this project is:
http://127.0.0.1:61563/v1/chat/completions
If the installed catalog uses a different alias or port, configure the Node server before starting it:
$env:FOUNDRY_LOCAL_ENDPOINT = "http://127.0.0.1:<actual-port>/v1/chat/completions"
$env:FOUNDRY_LOCAL_MODEL = "qwen2.5-7b-instruct-cuda-gpu"
Foundry Local assigns the service port dynamically. Use foundry service status or foundry service list to inspect the active service, and use GET http://127.0.0.1:<port>/openai/models to list available model IDs. The exact model ID can vary by hardware; on this machine the available Qwen GPU model is qwen2.5-7b-instruct-cuda-gpu.
What the model receives
server/foundryLocal.ts sends Qwen:
- The latest user message or a document-review instruction
- The current form state
- Up to 8,000 characters of extracted document text
The system prompt asks Qwen to return JSON in this shape:
{
"reply": "I found your new employer and start date.",
"fields": {
"employer": "Northstar Analytics",
"role": "Senior Product Manager",
"startDate": "2026-10-07"
}
}
Only allowlisted form keys are accepted by the server. The model cannot add arbitrary fields to the client state.
Voice mode
Qwen2.5 7B remains a text-only model. Voice mode uses browser capabilities around the existing text pipeline:
microphone
-> browser SpeechRecognition
-> transcript
-> POST /api/chat
-> Foundry Local + Qwen
-> text reply and form fields
-> browser SpeechSynthesis
-> spoken Ava response
Click the microphone button in the composer to speak. When recognition ends, the transcript is submitted through the same chat flow used by typed messages. The Ava voice on/off control enables or disables spoken replies, Voice lets you choose an installed browser voice, and Stop Ava interrupts the current response. The app prefers Microsoft/Edge natural English voices when available, such as Ava, Jenny, Aria, or Sonia. Chrome and Edge provide the best support; microphone permission is required and voice input needs localhost or HTTPS. Voice quality depends on the voices installed and exposed by the browser.
Voice input uses a guided one-field-at-a-time flow. The app identifies the next incomplete required field, asks Qwen to focus on that field, advances the active form section after the answer, and speaks one short next question. This keeps each voice turn easy to remember. Typed chat remains free-form.
Document parsing flow
The browser handles the original file; the file itself is not uploaded to a cloud service:
src/App.tsxvalidates the extension and 10 MB limit.src/documentParser.tsuses PDF.js for PDFs and Mammoth for DOCX files.- The extracted text is sent to local Qwen through
POST /api/chatfor document interpretation. - Qwen returns structured fields. The UI uses a consistent document-review message telling the user to review the form and manually complete any missing information; it does not enumerate missing fields.
- The model fields are applied and highlighted in the form.
Grounded logistics walkthrough
Upload contoso-moving-offers-and-reimbursement-guide.pdf, then ask Ava questions such as “Which option is best for a 250-mile move?” or “Can I use a rented truck for 150 miles?” The browser keeps the extracted policy text as grounding for later chat turns, and the model is instructed to answer policy questions only from that text. The Moving logistics section captures the selected method, approximate distance, reimbursement path, and notes.
The browser-side PDF.js and Mammoth libraries are text extraction utilities only; they do not decide which values belong in the relocation form. Foundry Local/Qwen is the source of truth for PDF/DOCX field extraction. If the model is unavailable, the UI reports the error instead of silently filling document fields with a non-LLM parser.
Code breadcrumbs
| Area | File | Purpose |
|---|---|---|
| Main UI | src/App.tsx |
Chat, form sections, uploads, reset, progress, MCP host messages |
| Styling | src/styles.css |
Zava layout, responsive behavior, light/dark theme variables |
| Form types | src/types.ts |
IntakeForm, FormField, Message, and blank initial state |
| PDF/DOCX parsing | src/documentParser.ts |
Browser-side PDF.js and Mammoth extraction |
| Immediate extraction | src/extraction.ts |
Labeled values, dates, phone, email, and relocation phrase matching |
| Local LLM client | server/foundryLocal.ts |
OpenAI-compatible request, JSON validation, field allowlist |
| MCP server | server/index.ts |
Express routes, MCP transport, tool/resource registration |
| Dev proxy | vite.config.ts |
Proxies browser /api calls to port 3001 |
| Sample files | sample-documents/ |
Fictional offer letters for upload testing |
| Single-file build | vite.config.ts |
vite-plugin-singlefile inlines JavaScript and CSS |
Install and run
Install Node dependencies:
npm install
Standalone development mode
Start Vite and the MCP server together:
npm run dev
Open:
http://localhost:5173
The Vite /api proxy forwards local model requests to port 3001.
MCP Apps mode
Build the UI first. The MCP server embeds the resulting dist/index.html:
npm run build
npm start
Configure the MCP Apps-compatible host with:
http://localhost:3001/mcp
Then call:
start_relocation_intake
The server also exposes a basic health check:
http://localhost:3001/health
Demo workflow
- Start Foundry Local and make the Qwen model available.
- Run
npm run dev. - Click a quick prompt or type a relocation message.
- Watch the matching fields populate and highlight.
- Upload one of the PDFs in
sample-documents/. - Review the extracted and model-enriched fields.
- Use Reset demo to return to a blank state.
Useful chat prompts:
I'm moving from Seattle to Austin for a role at Contoso.My family has 3 people.Employer: FabrikamPosition: Senior Product ManagerI need temporary housing.
Troubleshooting
Could not connect to Foundry Local
Check that Foundry Local is running, that the model has been downloaded/loaded, and that the endpoint matches FOUNDRY_LOCAL_ENDPOINT.
Model not found
Run foundry model list and set FOUNDRY_LOCAL_MODEL to an alias in the installed catalog.
The MCP server says Missing dist/index.html
Run:
npm run build
before npm start.
The form does not find fields in a document
The PDF must contain selectable text. Scanned/image-only PDFs need OCR before PDF.js can extract useful text. Labeled values such as Employee name:, Email address:, New employer:, Job title:, Start date:, Moving from:, and Moving to: are easiest for the deterministic extractor to recognize.
Scripts
| Command | Purpose |
|---|---|
npm run dev |
Start Vite and the MCP server in watch mode |
npm run dev:ui |
Start only Vite |
npm run dev:mcp |
Start only the MCP server in watch mode |
npm run build |
Type-check and create the single-file production UI |
npm start |
Start the MCP server against dist/index.html |
npm run preview |
Preview the Vite production build |
To regenerate the fictional Contoso policy PDF, install the script dependency and run the generator:
python -m pip install -r scripts/requirements.txt
python scripts/generate_contoso_policy_pdf.py
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.