Elicitation (User Input) Example â
Complete example demonstrating user input elicitation in MCP clients with schema validation, multiple interface types, and security considerations.
Overview â
This example shows how to implement a comprehensive elicitation system that:
- Collects structured user input with JSON schema validation
- Supports multiple interface types (CLI, web, GUI)
- Implements security and privacy protections
- Handles all user response actions (accept/decline/cancel)
Complete Implementation â
php
#!/usr/bin/env php
<?php
/**
* Elicitation (User Input) Example
*
* Demonstrates comprehensive user input collection including:
* - Schema-based form generation
* - Multiple interface types
* - Security and privacy protection
* - Complete user interaction flows
*/
require_once __DIR__ . '/vendor/autoload.php';
use MCP\Client\Client;
use MCP\Client\Transport\StdioClientTransport;
use MCP\Types\Implementation;
use MCP\Types\McpError;
use MCP\Types\ErrorCode;
use function Amp\async;
class InteractiveElicitationClient extends Client
{
private UserInterface $ui;
private ElicitationValidator $validator;
private SecurityManager $security;
private array $elicitationHistory = [];
public function __construct(UserInterface $ui)
{
parent::__construct(
new Implementation('interactive-client', '1.0.0'),
[
'capabilities' => [
'elicitation' => [
'supportsCancel' => true,
'supportsDecline' => true
]
]
]
);
$this->ui = $ui;
$this->validator = new ElicitationValidator();
$this->security = new SecurityManager();
$this->setElicitationHandler([$this, 'handleElicitationRequest']);
}
public function handleElicitationRequest(array $request): array
{
try {
echo "đ Elicitation request received\n";
$message = $request['message'] ?? 'Please provide the requested information';
$schema = $request['requestedSchema'] ?? ['type' => 'object'];
// Security check
$securityCheck = $this->security->validateElicitationRequest($request);
if (!$securityCheck->allowed) {
echo "đĄī¸ Request blocked for security reasons: {$securityCheck->reason}\n";
return ['action' => 'decline'];
}
// Show request to user
echo "đŦ Server message: {$message}\n";
$userResponse = $this->ui->collectUserInput($message, $schema);
// Record elicitation in history
$this->recordElicitation($request, $userResponse);
return $this->processUserResponse($userResponse, $schema);
} catch (\Exception $e) {
echo "â Elicitation error: {$e->getMessage()}\n";
return [
'action' => 'cancel',
'error' => $e->getMessage()
];
}
}
private function processUserResponse(array $userResponse, array $schema): array
{
$action = $userResponse['action'];
if ($action === 'accept') {
// Validate user input
$validation = $this->validator->validate($userResponse['content'] ?? [], $schema);
if (!$validation->isValid) {
echo "â ī¸ Validation failed: " . implode(', ', $validation->errors) . "\n";
// Ask user if they want to try again
$retry = $this->ui->askRetry($validation->errors);
if ($retry) {
// Re-collect input (simplified for example)
return ['action' => 'cancel']; // In real implementation, would retry
}
return ['action' => 'cancel'];
}
echo "â
Input validated successfully\n";
return [
'action' => 'accept',
'content' => $userResponse['content']
];
}
echo "âšī¸ User {$action}d the request\n";
return ['action' => $action];
}
private function recordElicitation(array $request, array $response): void
{
$this->elicitationHistory[] = [
'timestamp' => time(),
'message' => $request['message'] ?? '',
'schema' => $request['requestedSchema'] ?? [],
'action' => $response['action'],
'server_id' => $this->getCurrentServerId()
];
}
public function getElicitationHistory(): array
{
return $this->elicitationHistory;
}
}
// Command-line user interface
class CliUserInterface implements UserInterface
{
public function collectUserInput(string $message, array $schema): array
{
echo "\n" . str_repeat("=", 50) . "\n";
echo "đ Information Request\n";
echo str_repeat("=", 50) . "\n";
echo "Message: {$message}\n\n";
if ($schema['type'] === 'object') {
echo "Required information:\n";
foreach ($schema['properties'] ?? [] as $propName => $propSchema) {
$title = $propSchema['title'] ?? ucfirst(str_replace('_', ' ', $propName));
$description = $propSchema['description'] ?? '';
$required = in_array($propName, $schema['required'] ?? []);
echo " - {$title}" . ($required ? ' (required)' : ' (optional)');
if ($description) {
echo ": {$description}";
}
echo "\n";
}
}
echo "\nOptions:\n";
echo " (a)ccept - Provide the requested information\n";
echo " (d)ecline - Decline to provide information\n";
echo " (c)ancel - Cancel the request\n";
while (true) {
echo "\nYour choice (a/d/c): ";
$choice = strtolower(trim(fgets(STDIN)));
if ($choice === 'a' || $choice === 'accept') {
$content = $this->collectSchemaInput($schema);
return ['action' => 'accept', 'content' => $content];
} elseif ($choice === 'd' || $choice === 'decline') {
return ['action' => 'decline'];
} elseif ($choice === 'c' || $choice === 'cancel') {
return ['action' => 'cancel'];
}
echo "Invalid choice. Please enter 'a', 'd', or 'c'.\n";
}
}
private function collectSchemaInput(array $schema): array
{
$content = [];
if ($schema['type'] !== 'object') {
return $content;
}
echo "\nđ Please provide the following information:\n";
echo str_repeat("-", 40) . "\n";
foreach ($schema['properties'] ?? [] as $propName => $propSchema) {
$value = $this->collectFieldInput($propName, $propSchema, $schema['required'] ?? []);
if ($value !== null) {
$content[$propName] = $value;
}
}
return $content;
}
private function collectFieldInput(string $fieldName, array $fieldSchema, array $required): mixed
{
$title = $fieldSchema['title'] ?? ucfirst(str_replace('_', ' ', $fieldName));
$description = $fieldSchema['description'] ?? '';
$type = $fieldSchema['type'] ?? 'string';
$isRequired = in_array($fieldName, $required);
while (true) {
echo "\n{$title}" . ($isRequired ? ' (required)' : ' (optional)');
if ($description) {
echo "\n {$description}";
}
// Show options for enum fields
if (isset($fieldSchema['enum'])) {
$options = $fieldSchema['enum'];
$optionNames = $fieldSchema['enumNames'] ?? $options;
echo "\n Options:";
foreach ($options as $i => $option) {
$name = $optionNames[$i] ?? $option;
echo "\n {$option} - {$name}";
}
}
// Show constraints
if ($type === 'integer' || $type === 'number') {
$min = $fieldSchema['minimum'] ?? null;
$max = $fieldSchema['maximum'] ?? null;
if ($min !== null || $max !== null) {
echo "\n Range: " . ($min ?? 'no min') . " to " . ($max ?? 'no max');
}
}
if ($type === 'string') {
$minLen = $fieldSchema['minLength'] ?? null;
$maxLen = $fieldSchema['maxLength'] ?? null;
if ($minLen !== null || $maxLen !== null) {
echo "\n Length: " . ($minLen ?? 'no min') . " to " . ($maxLen ?? 'no max') . " characters";
}
}
echo "\n> ";
$input = trim(fgets(STDIN));
// Handle empty input
if (empty($input)) {
if ($isRequired) {
echo "This field is required. Please provide a value.";
continue;
}
return null;
}
// Validate and convert input
$validation = $this->validateFieldInput($input, $fieldSchema);
if ($validation->isValid) {
return $this->convertInputType($input, $type);
}
echo "Invalid input: " . implode(', ', $validation->errors) . "\n";
echo "Please try again.";
}
}
private function validateFieldInput(string $input, array $schema): ValidationResult
{
$errors = [];
$type = $schema['type'] ?? 'string';
// Type validation
if ($type === 'integer' && !ctype_digit($input)) {
$errors[] = 'Must be an integer';
} elseif ($type === 'number' && !is_numeric($input)) {
$errors[] = 'Must be a number';
} elseif ($type === 'boolean' && !in_array(strtolower($input), ['true', 'false', '1', '0', 'yes', 'no'])) {
$errors[] = 'Must be true/false, yes/no, or 1/0';
}
// Enum validation
if (isset($schema['enum']) && !in_array($input, $schema['enum'])) {
$errors[] = 'Must be one of: ' . implode(', ', $schema['enum']);
}
// String constraints
if ($type === 'string') {
if (isset($schema['minLength']) && strlen($input) < $schema['minLength']) {
$errors[] = "Must be at least {$schema['minLength']} characters";
}
if (isset($schema['maxLength']) && strlen($input) > $schema['maxLength']) {
$errors[] = "Must be no more than {$schema['maxLength']} characters";
}
if (isset($schema['pattern']) && !preg_match('/' . $schema['pattern'] . '/', $input)) {
$errors[] = 'Does not match required pattern';
}
}
// Number constraints
if ($type === 'integer' || $type === 'number') {
$numValue = (float)$input;
if (isset($schema['minimum']) && $numValue < $schema['minimum']) {
$errors[] = "Must be at least {$schema['minimum']}";
}
if (isset($schema['maximum']) && $numValue > $schema['maximum']) {
$errors[] = "Must be no more than {$schema['maximum']}";
}
}
return new ValidationResult(empty($errors), $errors);
}
private function convertInputType(string $input, string $type): mixed
{
return match($type) {
'integer' => (int)$input,
'number' => (float)$input,
'boolean' => in_array(strtolower($input), ['true', '1', 'yes']),
default => $input
};
}
public function askRetry(array $errors): bool
{
echo "\nValidation errors occurred:\n";
foreach ($errors as $error) {
echo " - {$error}\n";
}
echo "\nWould you like to try again? (y/n): ";
$response = strtolower(trim(fgets(STDIN)));
return in_array($response, ['y', 'yes']);
}
}
// Security manager for elicitation
class SecurityManager
{
private array $sensitiveFields = [
'password', 'token', 'secret', 'key', 'credential',
'ssn', 'social_security', 'passport', 'license'
];
private array $requestCounts = [];
private int $maxRequestsPerHour = 10;
public function validateElicitationRequest(array $request): SecurityCheckResult
{
// Check for sensitive fields
if ($this->containsSensitiveFields($request['requestedSchema'] ?? [])) {
return new SecurityCheckResult(
false,
'Request contains sensitive fields that cannot be collected'
);
}
// Check rate limiting
$serverId = $this->extractServerId($request);
if (!$this->isRateLimitOk($serverId)) {
return new SecurityCheckResult(
false,
'Rate limit exceeded for this server'
);
}
// Check message content for suspicious patterns
$message = $request['message'] ?? '';
if ($this->containsSuspiciousContent($message)) {
return new SecurityCheckResult(
false,
'Request message contains suspicious content'
);
}
$this->recordRequest($serverId);
return new SecurityCheckResult(true, 'Request approved');
}
private function containsSensitiveFields(array $schema): bool
{
if ($schema['type'] === 'object') {
foreach ($schema['properties'] ?? [] as $propName => $propSchema) {
$lowerName = strtolower($propName);
$title = strtolower($propSchema['title'] ?? '');
$description = strtolower($propSchema['description'] ?? '');
foreach ($this->sensitiveFields as $sensitiveField) {
if (strpos($lowerName, $sensitiveField) !== false ||
strpos($title, $sensitiveField) !== false ||
strpos($description, $sensitiveField) !== false) {
return true;
}
}
// Check nested objects
if ($propSchema['type'] === 'object') {
if ($this->containsSensitiveFields($propSchema)) {
return true;
}
}
}
}
return false;
}
private function containsSuspiciousContent(string $message): bool
{
$suspiciousPatterns = [
'/password/i',
'/credit\s*card/i',
'/ssn|social\s*security/i',
'/bank\s*account/i',
'/personal\s*identification/i'
];
foreach ($suspiciousPatterns as $pattern) {
if (preg_match($pattern, $message)) {
return true;
}
}
return false;
}
private function isRateLimitOk(string $serverId): bool
{
$now = time();
$hourAgo = $now - 3600;
// Clean old requests
if (isset($this->requestCounts[$serverId])) {
$this->requestCounts[$serverId] = array_filter(
$this->requestCounts[$serverId],
fn($timestamp) => $timestamp > $hourAgo
);
}
$currentCount = count($this->requestCounts[$serverId] ?? []);
return $currentCount < $this->maxRequestsPerHour;
}
private function recordRequest(string $serverId): void
{
if (!isset($this->requestCounts[$serverId])) {
$this->requestCounts[$serverId] = [];
}
$this->requestCounts[$serverId][] = time();
}
private function extractServerId(array $request): string
{
// Extract server ID from request context
return $request['server_id'] ?? 'unknown';
}
}
// Validation system
class ElicitationValidator
{
public function validate(array $data, array $schema): ValidationResult
{
$errors = [];
if ($schema['type'] === 'object') {
// Check required fields
foreach ($schema['required'] ?? [] as $requiredField) {
if (!isset($data[$requiredField]) || $data[$requiredField] === '') {
$errors[] = "Field '{$requiredField}' is required";
}
}
// Validate each field
foreach ($schema['properties'] ?? [] as $propName => $propSchema) {
if (isset($data[$propName])) {
$fieldErrors = $this->validateField($data[$propName], $propSchema, $propName);
$errors = array_merge($errors, $fieldErrors);
}
}
}
return new ValidationResult(empty($errors), $errors);
}
private function validateField(mixed $value, array $schema, string $fieldName): array
{
$errors = [];
$type = $schema['type'] ?? 'string';
// Type validation
if (!$this->isCorrectType($value, $type)) {
$errors[] = "Field '{$fieldName}' must be of type {$type}";
return $errors; // Don't continue if type is wrong
}
// Type-specific validation
if ($type === 'string') {
$errors = array_merge($errors, $this->validateString($value, $schema, $fieldName));
} elseif ($type === 'integer' || $type === 'number') {
$errors = array_merge($errors, $this->validateNumber($value, $schema, $fieldName));
} elseif ($type === 'array') {
$errors = array_merge($errors, $this->validateArray($value, $schema, $fieldName));
}
return $errors;
}
private function isCorrectType(mixed $value, string $expectedType): bool
{
return match($expectedType) {
'string' => is_string($value),
'integer' => is_int($value),
'number' => is_numeric($value),
'boolean' => is_bool($value),
'array' => is_array($value),
default => true
};
}
private function validateString(string $value, array $schema, string $fieldName): array
{
$errors = [];
if (isset($schema['minLength']) && strlen($value) < $schema['minLength']) {
$errors[] = "Field '{$fieldName}' must be at least {$schema['minLength']} characters";
}
if (isset($schema['maxLength']) && strlen($value) > $schema['maxLength']) {
$errors[] = "Field '{$fieldName}' must be no more than {$schema['maxLength']} characters";
}
if (isset($schema['pattern']) && !preg_match('/' . $schema['pattern'] . '/', $value)) {
$errors[] = "Field '{$fieldName}' does not match required pattern";
}
if (isset($schema['enum']) && !in_array($value, $schema['enum'])) {
$errors[] = "Field '{$fieldName}' must be one of: " . implode(', ', $schema['enum']);
}
if (isset($schema['format'])) {
$formatErrors = $this->validateFormat($value, $schema['format'], $fieldName);
$errors = array_merge($errors, $formatErrors);
}
return $errors;
}
private function validateFormat(string $value, string $format, string $fieldName): array
{
$errors = [];
switch ($format) {
case 'email':
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Field '{$fieldName}' must be a valid email address";
}
break;
case 'uri':
if (!filter_var($value, FILTER_VALIDATE_URL)) {
$errors[] = "Field '{$fieldName}' must be a valid URL";
}
break;
case 'date':
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) || !strtotime($value)) {
$errors[] = "Field '{$fieldName}' must be a valid date in YYYY-MM-DD format";
}
break;
}
return $errors;
}
}
// Demo server that uses elicitation
class ElicitationDemoServer
{
private McpServer $server;
private InteractiveElicitationClient $elicitationClient;
public function __construct(InteractiveElicitationClient $elicitationClient)
{
$this->server = new McpServer(
new Implementation('elicitation-demo-server', '1.0.0')
);
$this->elicitationClient = $elicitationClient;
$this->registerElicitationTools();
}
private function registerElicitationTools(): void
{
$this->server->tool(
'setup_user_profile',
'Interactive user profile setup',
['type' => 'object'],
function (): array {
$result = $this->elicitationClient->requestElicitation([
'message' => 'Please set up your user profile to personalize your experience:',
'requestedSchema' => [
'type' => 'object',
'properties' => [
'display_name' => [
'type' => 'string',
'title' => 'Display Name',
'description' => 'How you\'d like to be addressed',
'minLength' => 1,
'maxLength' => 50
],
'email' => [
'type' => 'string',
'title' => 'Email Address',
'description' => 'Your email for notifications',
'format' => 'email'
],
'role' => [
'type' => 'string',
'title' => 'Professional Role',
'enum' => ['developer', 'designer', 'manager', 'analyst', 'student', 'other'],
'enumNames' => ['Developer', 'Designer', 'Manager', 'Analyst', 'Student', 'Other']
],
'experience_years' => [
'type' => 'integer',
'title' => 'Years of Experience',
'description' => 'Years of professional experience',
'minimum' => 0,
'maximum' => 50
],
'interests' => [
'type' => 'array',
'title' => 'Areas of Interest',
'items' => ['type' => 'string'],
'description' => 'Technologies or topics you\'re interested in'
],
'notifications' => [
'type' => 'boolean',
'title' => 'Enable Notifications',
'description' => 'Receive email notifications for important updates',
'default' => true
]
],
'required' => ['display_name', 'email', 'role']
]
])->await();
if ($result['action'] === 'accept') {
$profile = $result['content'];
// Save profile (in real implementation)
$this->saveUserProfile($profile);
return [
'content' => [[
'type' => 'text',
'text' => "Profile created successfully for {$profile['display_name']}!\n\n" .
"Summary:\n" .
"- Email: {$profile['email']}\n" .
"- Role: {$profile['role']}\n" .
"- Experience: {$profile['experience_years']} years\n" .
"- Notifications: " . ($profile['notifications'] ? 'enabled' : 'disabled')
]]
];
}
return [
'content' => [[
'type' => 'text',
'text' => "Profile setup was {$result['action']}led by user"
]]
];
}
);
$this->server->tool(
'collect_feedback',
'Collect user feedback with structured questions',
[
'type' => 'object',
'properties' => [
'feature' => [
'type' => 'string',
'description' => 'Feature to collect feedback about'
]
]
],
function (array $params): array {
$feature = $params['feature'] ?? 'general';
$result = $this->elicitationClient->requestElicitation([
'message' => "Please provide feedback about: {$feature}",
'requestedSchema' => [
'type' => 'object',
'properties' => [
'rating' => [
'type' => 'integer',
'title' => 'Overall Rating',
'description' => 'Rate from 1 (poor) to 5 (excellent)',
'minimum' => 1,
'maximum' => 5
],
'liked' => [
'type' => 'string',
'title' => 'What did you like?',
'description' => 'What worked well for you',
'maxLength' => 500
],
'disliked' => [
'type' => 'string',
'title' => 'What could be improved?',
'description' => 'Areas for improvement',
'maxLength' => 500
],
'would_recommend' => [
'type' => 'boolean',
'title' => 'Would you recommend this to others?',
'default' => true
]
],
'required' => ['rating']
]
])->await();
if ($result['action'] === 'accept') {
$feedback = $result['content'];
return [
'content' => [[
'type' => 'text',
'text' => "Thank you for your feedback!\n\n" .
"Rating: {$feedback['rating']}/5 stars\n" .
"Recommendation: " . ($feedback['would_recommend'] ? 'Yes' : 'No') . "\n" .
"Your feedback has been recorded and will help us improve."
]]
];
}
return [
'content' => [[
'type' => 'text',
'text' => "Feedback collection was {$result['action']}led"
]]
];
}
);
}
private function saveUserProfile(array $profile): void
{
// In a real implementation, save to database
echo "đž Saving user profile: {$profile['display_name']}\n";
}
public function start(): void
{
async(function () {
echo "đ§ Interactive Elicitation Demo Server starting...\n";
echo "This server demonstrates user input collection capabilities.\n\n";
$transport = new StdioServerTransport();
$this->server->connect($transport)->await();
})->await();
}
}
// Supporting classes
class ValidationResult
{
public function __construct(
public readonly bool $isValid,
public readonly array $errors = []
) {}
}
class SecurityCheckResult
{
public function __construct(
public readonly bool $allowed,
public readonly string $reason
) {}
}
interface UserInterface
{
public function collectUserInput(string $message, array $schema): array;
public function askRetry(array $errors): bool;
}
// Usage example
echo "đ Interactive Elicitation Client Demo\n";
echo "=====================================\n";
// Create client with CLI interface
$ui = new CliUserInterface();
$client = new InteractiveElicitationClient($ui);
// Create demo server
$demoServer = new ElicitationDemoServer($client);
echo "đ Available tools:\n";
echo " - setup_user_profile: Interactive profile setup\n";
echo " - collect_feedback: Structured feedback collection\n\n";
echo "đ¯ Starting demo server...\n";
echo "Connect with an MCP client and try the interactive tools!\n\n";
$demoServer->start();Web Interface Example â
HTML Form Generation â
php
class WebElicitationInterface implements UserInterface
{
private string $sessionId;
public function collectUserInput(string $message, array $schema): array
{
// Generate HTML form from schema
$formHtml = $this->generateFormHtml($message, $schema);
// Send to web interface (WebSocket, HTTP, etc.)
$this->sendToWebInterface([
'type' => 'elicitation_form',
'message' => $message,
'form_html' => $formHtml,
'schema' => $schema
]);
// Wait for user response
return $this->waitForWebResponse();
}
private function generateFormHtml(string $message, array $schema): string
{
$html = "<div class='elicitation-form'>\n";
$html .= "<h3>Information Required</h3>\n";
$html .= "<p class='message'>" . htmlspecialchars($message) . "</p>\n";
$html .= "<form id='elicitation-form'>\n";
if ($schema['type'] === 'object') {
foreach ($schema['properties'] ?? [] as $propName => $propSchema) {
$html .= $this->generateFieldHtml($propName, $propSchema, $schema['required'] ?? []);
}
}
$html .= "<div class='form-actions'>\n";
$html .= "<button type='submit' class='btn btn-primary'>Submit</button>\n";
$html .= "<button type='button' class='btn btn-secondary' onclick='declineRequest()'>Decline</button>\n";
$html .= "<button type='button' class='btn btn-outline' onclick='cancelRequest()'>Cancel</button>\n";
$html .= "</div>\n";
$html .= "</form>\n";
$html .= "</div>\n";
return $html;
}
private function generateFieldHtml(string $fieldName, array $fieldSchema, array $required): string
{
$type = $fieldSchema['type'] ?? 'string';
$title = $fieldSchema['title'] ?? ucfirst(str_replace('_', ' ', $fieldName));
$description = $fieldSchema['description'] ?? '';
$isRequired = in_array($fieldName, $required);
$html = "<div class='form-field'>\n";
$html .= "<label for='{$fieldName}'>{$title}" . ($isRequired ? ' *' : '') . "</label>\n";
if ($description) {
$html .= "<p class='field-description'>{$description}</p>\n";
}
$html .= $this->generateInputHtml($fieldName, $fieldSchema) . "\n";
$html .= "</div>\n";
return $html;
}
private function generateInputHtml(string $fieldName, array $fieldSchema): string
{
$type = $fieldSchema['type'] ?? 'string';
return match($type) {
'string' => $this->generateStringInput($fieldName, $fieldSchema),
'integer', 'number' => $this->generateNumberInput($fieldName, $fieldSchema),
'boolean' => $this->generateBooleanInput($fieldName, $fieldSchema),
'array' => $this->generateArrayInput($fieldName, $fieldSchema),
default => "<input type='text' name='{$fieldName}' id='{$fieldName}'>"
};
}
private function generateStringInput(string $fieldName, array $fieldSchema): string
{
if (isset($fieldSchema['enum'])) {
$html = "<select name='{$fieldName}' id='{$fieldName}'>\n";
$options = $fieldSchema['enum'];
$optionNames = $fieldSchema['enumNames'] ?? $options;
foreach ($options as $index => $value) {
$label = $optionNames[$index] ?? $value;
$html .= "<option value='{$value}'>{$label}</option>\n";
}
$html .= "</select>";
return $html;
}
$attributes = [
'type' => 'text',
'name' => $fieldName,
'id' => $fieldName
];
if (isset($fieldSchema['minLength'])) {
$attributes['minlength'] = $fieldSchema['minLength'];
}
if (isset($fieldSchema['maxLength'])) {
$attributes['maxlength'] = $fieldSchema['maxLength'];
}
if (isset($fieldSchema['pattern'])) {
$attributes['pattern'] = $fieldSchema['pattern'];
}
$attrString = implode(' ', array_map(
fn($k, $v) => "{$k}='{$v}'",
array_keys($attributes),
$attributes
));
return "<input {$attrString}>";
}
private function generateNumberInput(string $fieldName, array $fieldSchema): string
{
$inputType = $fieldSchema['type'] === 'integer' ? 'number' : 'number';
$attributes = [
'type' => $inputType,
'name' => $fieldName,
'id' => $fieldName
];
if (isset($fieldSchema['minimum'])) {
$attributes['min'] = $fieldSchema['minimum'];
}
if (isset($fieldSchema['maximum'])) {
$attributes['max'] = $fieldSchema['maximum'];
}
if ($fieldSchema['type'] === 'number' && !isset($attributes['step'])) {
$attributes['step'] = 'any';
}
$attrString = implode(' ', array_map(
fn($k, $v) => "{$k}='{$v}'",
array_keys($attributes),
$attributes
));
return "<input {$attrString}>";
}
private function generateBooleanInput(string $fieldName, array $fieldSchema): string
{
$default = $fieldSchema['default'] ?? false;
$checked = $default ? 'checked' : '';
return "<input type='checkbox' name='{$fieldName}' id='{$fieldName}' {$checked}>";
}
}
// Usage demonstration
echo "đ¯ Starting elicitation demonstration...\n";
$client = new InteractiveElicitationClient(new CliUserInterface());
$server = new ElicitationDemoServer($client);
echo "Try these commands:\n";
echo " - setup_user_profile: Interactive profile creation\n";
echo " - collect_feedback: Structured feedback collection\n\n";
$server->start();Key Features Demonstrated â
1. Schema-Based Input Collection â
- Dynamic Form Generation: Creates appropriate inputs based on JSON schema
- Type Validation: Validates input types (string, integer, boolean, array)
- Constraint Checking: Enforces length limits, ranges, patterns
- Format Validation: Validates emails, URLs, dates
2. Security and Privacy â
- Sensitive Field Detection: Prevents collection of passwords, tokens, etc.
- Rate Limiting: Limits elicitation requests per server per hour
- Content Filtering: Blocks suspicious request messages
- User Consent: Always requires explicit user action
3. User Experience â
- Clear Messaging: Explains what information is needed and why
- Progressive Disclosure: Shows field descriptions and constraints
- Error Feedback: Provides specific validation error messages
- Multiple Actions: Supports accept, decline, and cancel actions
4. Multiple Interface Types â
- Command Line: Interactive CLI with prompts and validation
- Web Interface: HTML form generation with JavaScript handling
- GUI Support: Framework for desktop application interfaces
Testing â
Elicitation Testing â
php
class ElicitationExampleTest extends TestCase
{
private InteractiveElicitationClient $client;
private MockUserInterface $mockUI;
protected function setUp(): void
{
$this->mockUI = new MockUserInterface();
$this->client = new InteractiveElicitationClient($this->mockUI);
}
public function testUserAcceptance(): void
{
$this->mockUI->setResponse([
'action' => 'accept',
'content' => [
'username' => 'testuser',
'email' => 'test@example.com'
]
]);
$result = $this->client->handleElicitationRequest([
'message' => 'Enter your details',
'requestedSchema' => [
'type' => 'object',
'properties' => [
'username' => ['type' => 'string'],
'email' => ['type' => 'string', 'format' => 'email']
],
'required' => ['username', 'email']
]
]);
$this->assertEquals('accept', $result['action']);
$this->assertEquals('testuser', $result['content']['username']);
}
public function testSecurityBlocking(): void
{
$result = $this->client->handleElicitationRequest([
'message' => 'Enter your password',
'requestedSchema' => [
'type' => 'object',
'properties' => [
'password' => ['type' => 'string']
]
]
]);
$this->assertEquals('decline', $result['action']);
}
}