# Frontend Elicitations Integration Guide
**Status**: Production Ready
**Date**: January 14, 2026
**Feature**: Dynamic Form Rendering for MCP Elicitations
---
## 🎯 Overview
This guide shows how to integrate the dynamic elicitation system into your web chat frontend. The system is designed to be **plug-and-play** - no hardcoding required for any elicitation type.
---
## 🚀 Quick Start
### Step 1: Basic Integration
```javascript
// In your web chat frontend
class WebChatInterface {
constructor() {
this.elicitationCallbacks = new Map();
}
async sendMessage(message) {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ message })
});
const data = await response.json();
// Handle different response types
if (data.metadata?.type === 'elicitation') {
this.handleElicitation(data);
} else {
this.displayMessage(data.response);
}
return data;
}
handleElicitation(response) {
// Inject the form HTML into chat
this.displayElicitationForm(response);
// Inject CSS and JavaScript
this.injectElicitationAssets(response);
}
}
```
### Step 2: Handle Elicitation Responses
```javascript
// Add these methods to your chat interface
displayElicitationForm(response) {
const formContainer = document.createElement('div');
formContainer.className = 'elicitation-message';
formContainer.innerHTML = `
<div class="elicitation-header">
<strong>📋 Form Required</strong>
</div>
<div class="elicitation-form-container">
${response.form_html}
</div>
`;
this.chatMessages.appendChild(formContainer);
this.scrollToBottom();
}
injectElicitationAssets(response) {
// Inject CSS
if (!document.getElementById('elicitation-styles')) {
const style = document.createElement('style');
style.id = 'elicitation-styles';
style.textContent = response.css;
document.head.appendChild(style);
}
// Inject JavaScript
if (!document.getElementById('elicitation-scripts')) {
const script = document.createElement('script');
script.id = 'elicitation-scripts';
script.textContent = response.javascript;
document.head.appendChild(script);
}
}
```
### Step 3: Global Response Handlers
```javascript
// Add these global functions (they're called by the injected JavaScript)
window.handleElicitationResponse = async (elicitationId, action, data) => {
try {
const response = await fetch(`/elicitations/response`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
elicitation_id: elicitationId,
action: action,
data: data
})
});
const result = await response.json();
if (result.success) {
// Remove the form from UI
this.removeElicitationForm(elicitationId);
// Continue the conversation
if (result.mcp_response) {
// Send the MCP response back to the chat
this.sendMCPResponse(result.mcp_response);
}
} else {
this.showError(result.error);
}
} catch (error) {
this.showError('Failed to submit form: ' + error.message);
}
};
window.submitElicitation = (elicitationId) => {
const form = document.querySelector(`[data-elicitation-id="${elicitationId}"]`);
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// Handle checkboxes
form.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
data[checkbox.name] = checkbox.checked;
});
// Handle multi-selects
form.querySelectorAll('select[multiple]').forEach(select => {
data[select.name] = Array.from(select.selectedOptions).map(option => option.value);
});
window.handleElicitationResponse(elicitationId, 'accept', data);
};
window.declineElicitation = (elicitationId) => {
window.handleElicitationResponse(elicitationId, 'decline', null);
};
window.cancelElicitation = (elicitationId) => {
window.handleElicitationResponse(elicitationId, 'cancel', null);
};
```
---
## 🔧 Complete Integration Example
### React Component Example
```jsx
import React, { useState, useEffect, useRef } from 'react';
const ChatInterface = ({ token }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const messagesEndRef = useRef(null);
const [elicitationAssets, setElicitationAssets] = useState({
css: null,
js: null
});
// Inject elicitation assets
useEffect(() => {
if (elicitationAssets.css && !document.getElementById('elicitation-styles')) {
const style = document.createElement('style');
style.id = 'elicitation-styles';
style.textContent = elicitationAssets.css;
document.head.appendChild(style);
}
if (elicitationAssets.js && !document.getElementById('elicitation-scripts')) {
const script = document.createElement('script');
script.id = 'elicitation-scripts';
script.textContent = elicitationAssets.js;
document.head.appendChild(script);
}
}, [elicitationAssets]);
// Global handlers
useEffect(() => {
window.handleElicitationResponse = handleElicitationResponse;
window.submitElicitation = submitElicitation;
window.declineElicitation = declineElicitation;
window.cancelElicitation = cancelElicitation;
return () => {
delete window.handleElicitationResponse;
delete window.submitElicitation;
delete window.declineElicitation;
delete window.cancelElicitation;
};
}, []);
const sendMessage = async () => {
if (!input.trim()) return;
const userMessage = { role: 'user', content: input };
setMessages(prev => [...prev, userMessage]);
setInput('');
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ message: input })
});
const data = await response.json();
if (data.metadata?.type === 'elicitation') {
// Handle elicitation
setElicitationAssets({
css: data.css,
js: data.javascript
});
const elicitationMessage = {
role: 'assistant',
type: 'elicitation',
content: data.response,
elicitationId: data.metadata.elicitation_id,
formHtml: data.form_html,
expiresAt: data.expires_at
};
setMessages(prev => [...prev, elicitationMessage]);
} else {
// Regular message
const assistantMessage = {
role: 'assistant',
content: data.response
};
setMessages(prev => [...prev, assistantMessage]);
}
} catch (error) {
console.error('Error sending message:', error);
setMessages(prev => [...prev, {
role: 'assistant',
content: 'Sorry, I encountered an error. Please try again.'
}]);
}
};
const handleElicitationResponse = async (elicitationId, action, data) => {
try {
const response = await fetch('/elicitations/response', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
elicitation_id: elicitationId,
action: action,
data: data
})
});
const result = await response.json();
if (result.success) {
// Remove elicitation from messages
setMessages(prev => prev.filter(msg =>
msg.elicitationId !== elicitationId
));
// Show success message
setMessages(prev => [...prev, {
role: 'assistant',
content: action === 'accept'
? 'Form submitted successfully!'
: action === 'decline'
? 'Form declined.'
: 'Form cancelled.'
}]);
} else {
setMessages(prev => [...prev, {
role: 'assistant',
content: `Error: ${result.error}`
}]);
}
} catch (error) {
console.error('Error handling elicitation:', error);
}
};
const submitElicitation = (elicitationId) => {
const form = document.querySelector(`[data-elicitation-id="${elicitationId}"]`);
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// Handle special fields
form.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
data[checkbox.name] = checkbox.checked;
});
form.querySelectorAll('select[multiple]').forEach(select => {
data[select.name] = Array.from(select.selectedOptions).map(option => option.value);
});
handleElicitationResponse(elicitationId, 'accept', data);
};
const declineElicitation = (elicitationId) => {
handleElicitationResponse(elicitationId, 'decline', null);
};
const cancelElicitation = (elicitationId) => {
handleElicitationResponse(elicitationId, 'cancel', null);
};
const renderMessage = (message, index) => {
if (message.type === 'elicitation') {
return (
<div key={index} className="message elicitation-message">
<div className="elicitation-header">
<strong>📋 Information Required</strong>
</div>
<div
dangerouslySetInnerHTML={{ __html: message.formHtml }}
/>
{message.expiresAt && (
<div className="elicitation-expiry">
⏰ Expires: {new Date(message.expiresAt).toLocaleString()}
</div>
)}
</div>
);
}
return (
<div key={index} className={`message ${message.role}`}>
<div className="message-content">
{message.content}
</div>
</div>
);
};
return (
<div className="chat-interface">
<div className="messages" ref={messagesEndRef}>
{messages.map(renderMessage)}
</div>
<div className="input-area">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type your message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
);
};
export default ChatInterface;
```
---
## 📊 API Endpoints Reference
### Chat Endpoint
```
POST /chat
Headers: Authorization: Bearer <token>
Body: { "message": "Create a vendor" }
Response: Regular message or elicitation data
```
### Elicitation Endpoints
#### Submit Response
```
POST /elicitations/response
Body: {
"elicitation_id": "uuid",
"action": "accept|decline|cancel",
"data": { "field_name": "value" }
}
```
#### Get Status
```
GET /elicitations/{elicitation_id}/status
Response: {
"success": true,
"status": "displayed",
"expires_at": "2024-01-01T12:00:00Z"
}
```
#### Cancel Elicitation
```
POST /elicitations/{elicitation_id}/cancel
Response: {
"success": true,
"message": "Elicitation cancelled"
}
```
#### System Stats
```
GET /elicitations/stats
Response: {
"success": true,
"stats": {
"active_sessions": 5,
"status_distribution": { "displayed": 3, "submitted": 2 }
}
}
```
---
## 🎨 Customization Guide
### CSS Customization
The system uses Bootstrap classes by default, but you can override them:
```css
/* Custom elicitation styles */
.mcp-elicitation-form {
border: 2px solid #your-brand-color;
border-radius: 12px;
background: #your-bg-color;
}
.elicitation-header h3 {
color: #your-brand-color;
font-family: 'Your Font', sans-serif;
}
.form-control:focus {
border-color: #your-brand-color;
box-shadow: 0 0 0 0.2rem rgba(your-color, 0.25);
}
.btn-primary {
background-color: #your-brand-color;
border-color: #your-brand-color;
}
```
### JavaScript Customization
You can extend the form behavior:
```javascript
// Add custom validation
window.customValidation = (fieldName, value) => {
if (fieldName === 'vendor_email' && !value.includes('@')) {
return 'Please enter a valid email address';
}
return null;
};
// Add custom submission handling
window.beforeSubmit = (elicitationId, data) => {
console.log('Submitting data:', data);
// Add custom logic here
return true; // Return false to prevent submission
};
```
### Field Type Extensions
The system automatically handles these field types:
- ✅ String, Integer, Number, Boolean
- ✅ Email, URL, Date, DateTime
- ✅ Single-select and Multi-select enums
- ✅ Arrays with enum options
For custom field types, extend the renderer:
```javascript
// Add custom field renderer
window.customFieldRenderers = {
'custom-type': (config, elicitationId) => {
return `<custom-input data-config="${JSON.stringify(config)}"></custom-input>`;
}
};
```
---
## 🔍 Testing Guide
### Manual Testing
1. **Basic Form Test**
```bash
curl -X POST http://localhost:8001/chat \
-H "Authorization: Bearer $TOKEN" \
-d '{"message": "Create a vendor"}'
```
2. **Form Submission Test**
```javascript
// In browser console
submitElicitation('your-elicitation-id');
```
3. **Status Check Test**
```bash
curl http://localhost:8001/elicitations/{id}/status \
-H "Authorization: Bearer $TOKEN"
```
### Automated Testing
```javascript
// Jest test example
describe('Elicitation Integration', () => {
test('should display form for incomplete vendor creation', async () => {
const response = await sendMessage('Create a vendor');
expect(response.metadata.type).toBe('elicitation');
expect(response.form_html).toContain('vendor_name');
});
test('should handle form submission', async () => {
const result = await handleElicitationResponse('id', 'accept', {
vendor_name: 'Test Vendor'
});
expect(result.success).toBe(true);
});
});
```
---
## 🚀 Production Deployment
### Environment Variables
```env
# Elicitation settings
ELICITATION_SESSION_TIMEOUT_MINUTES=30
ELICITATION_MAX_ACTIVE_SESSIONS=100
ELICITATION_CLEANUP_INTERVAL_MINUTES=5
```
### Monitoring
Monitor these metrics:
- Active elicitation sessions
- Form submission success rate
- Average time to complete forms
- Error rates by elicitation type
### Performance Optimization
1. **CSS/JS Caching**: Assets are cached automatically
2. **Session Cleanup**: Automatic cleanup of expired sessions
3. **Form Validation**: Client-side validation reduces server load
4. **Asset Injection**: Assets injected only when needed
---
## 📝 Summary
### What You Get
✅ **Complete Integration** - Frontend ready for any elicitation
✅ **Dynamic Forms** - No hardcoding required
✅ **Production Ready** - Error handling, validation, logging
✅ **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
✅ **Customizable** - Easy to style and extend
✅ **MCP Compliant** - Follows official specification
### Key Benefits
🎯 **Zero Configuration** - Works out of the box
🎯 **Automatic Field Detection** - Handles any schema
🎯 **Built-in Validation** - Client and server side
🎯 **Session Management** - Automatic cleanup and expiration
🎯 **Error Handling** - Graceful failure recovery
### Next Steps
1. **Integrate the code** - Add to your web chat frontend
2. **Test with vendors** - Try the create vendor flow
3. **Customize styling** - Match your brand
4. **Monitor performance** - Track usage metrics
5. **Extend to other tools** - Apply to contracts, users, etc.
---
## 🎉 Ready to Use!
The dynamic elicitation system is **production-ready** and will automatically handle any future elicitation types without code changes!
**Just integrate the frontend code and you're ready to go!** 🚀