"use strict";
// Run with: npx tsx src/examples/server/elicitationFormExample.ts
//
// This example demonstrates how to use form elicitation to collect structured user input
// with JSON Schema validation via a local HTTP server with SSE streaming.
// Form elicitation allows servers to request *non-sensitive* user input through the client
// with schema-based validation.
// Note: See also elicitationUrlExample.ts for an example of using URL elicitation
// to collect *sensitive* user input via a browser.
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const types_js_1 = require("../../types.js");
const express_js_1 = require("../../server/express.js");
// Create MCP server - it will automatically use AjvJsonSchemaValidator with sensible defaults
// The validator supports format validation (email, date, etc.) if ajv-formats is installed
const mcpServer = new mcp_js_1.McpServer({
    name: 'form-elicitation-example-server',
    version: '1.0.0'
}, {
    capabilities: {}
});
/**
 * Example 1: Simple user registration tool
 * Collects username, email, and password from the user
 */
mcpServer.registerTool('register_user', {
    description: 'Register a new user account by collecting their information',
    inputSchema: {}
}, async () => {
    try {
        // Request user information through form elicitation
        const result = await mcpServer.server.elicitInput({
            mode: 'form',
            message: 'Please provide your registration information:',
            requestedSchema: {
                type: 'object',
                properties: {
                    username: {
                        type: 'string',
                        title: 'Username',
                        description: 'Your desired username (3-20 characters)',
                        minLength: 3,
                        maxLength: 20
                    },
                    email: {
                        type: 'string',
                        title: 'Email',
                        description: 'Your email address',
                        format: 'email'
                    },
                    password: {
                        type: 'string',
                        title: 'Password',
                        description: 'Your password (min 8 characters)',
                        minLength: 8
                    },
                    newsletter: {
                        type: 'boolean',
                        title: 'Newsletter',
                        description: 'Subscribe to newsletter?',
                        default: false
                    }
                },
                required: ['username', 'email', 'password']
            }
        });
        // Handle the different possible actions
        if (result.action === 'accept' && result.content) {
            const { username, email, newsletter } = result.content;
            return {
                content: [
                    {
                        type: 'text',
                        text: `Registration successful!\n\nUsername: ${username}\nEmail: ${email}\nNewsletter: ${newsletter ? 'Yes' : 'No'}`
                    }
                ]
            };
        }
        else if (result.action === 'decline') {
            return {
                content: [
                    {
                        type: 'text',
                        text: 'Registration cancelled by user.'
                    }
                ]
            };
        }
        else {
            return {
                content: [
                    {
                        type: 'text',
                        text: 'Registration was cancelled.'
                    }
                ]
            };
        }
    }
    catch (error) {
        return {
            content: [
                {
                    type: 'text',
                    text: `Registration failed: ${error instanceof Error ? error.message : String(error)}`
                }
            ],
            isError: true
        };
    }
});
/**
 * Example 2: Multi-step workflow with multiple form elicitation requests
 * Demonstrates how to collect information in multiple steps
 */
mcpServer.registerTool('create_event', {
    description: 'Create a calendar event by collecting event details',
    inputSchema: {}
}, async () => {
    try {
        // Step 1: Collect basic event information
        const basicInfo = await mcpServer.server.elicitInput({
            mode: 'form',
            message: 'Step 1: Enter basic event information',
            requestedSchema: {
                type: 'object',
                properties: {
                    title: {
                        type: 'string',
                        title: 'Event Title',
                        description: 'Name of the event',
                        minLength: 1
                    },
                    description: {
                        type: 'string',
                        title: 'Description',
                        description: 'Event description (optional)'
                    }
                },
                required: ['title']
            }
        });
        if (basicInfo.action !== 'accept' || !basicInfo.content) {
            return {
                content: [{ type: 'text', text: 'Event creation cancelled.' }]
            };
        }
        // Step 2: Collect date and time
        const dateTime = await mcpServer.server.elicitInput({
            mode: 'form',
            message: 'Step 2: Enter date and time',
            requestedSchema: {
                type: 'object',
                properties: {
                    date: {
                        type: 'string',
                        title: 'Date',
                        description: 'Event date',
                        format: 'date'
                    },
                    startTime: {
                        type: 'string',
                        title: 'Start Time',
                        description: 'Event start time (HH:MM)'
                    },
                    duration: {
                        type: 'integer',
                        title: 'Duration',
                        description: 'Duration in minutes',
                        minimum: 15,
                        maximum: 480
                    }
                },
                required: ['date', 'startTime', 'duration']
            }
        });
        if (dateTime.action !== 'accept' || !dateTime.content) {
            return {
                content: [{ type: 'text', text: 'Event creation cancelled.' }]
            };
        }
        // Combine all collected information
        const event = {
            ...basicInfo.content,
            ...dateTime.content
        };
        return {
            content: [
                {
                    type: 'text',
                    text: `Event created successfully!\n\n${JSON.stringify(event, null, 2)}`
                }
            ]
        };
    }
    catch (error) {
        return {
            content: [
                {
                    type: 'text',
                    text: `Event creation failed: ${error instanceof Error ? error.message : String(error)}`
                }
            ],
            isError: true
        };
    }
});
/**
 * Example 3: Collecting address information
 * Demonstrates validation with patterns and optional fields
 */
mcpServer.registerTool('update_shipping_address', {
    description: 'Update shipping address with validation',
    inputSchema: {}
}, async () => {
    try {
        const result = await mcpServer.server.elicitInput({
            mode: 'form',
            message: 'Please provide your shipping address:',
            requestedSchema: {
                type: 'object',
                properties: {
                    name: {
                        type: 'string',
                        title: 'Full Name',
                        description: 'Recipient name',
                        minLength: 1
                    },
                    street: {
                        type: 'string',
                        title: 'Street Address',
                        minLength: 1
                    },
                    city: {
                        type: 'string',
                        title: 'City',
                        minLength: 1
                    },
                    state: {
                        type: 'string',
                        title: 'State/Province',
                        minLength: 2,
                        maxLength: 2
                    },
                    zipCode: {
                        type: 'string',
                        title: 'ZIP/Postal Code',
                        description: '5-digit ZIP code'
                    },
                    phone: {
                        type: 'string',
                        title: 'Phone Number (optional)',
                        description: 'Contact phone number'
                    }
                },
                required: ['name', 'street', 'city', 'state', 'zipCode']
            }
        });
        if (result.action === 'accept' && result.content) {
            return {
                content: [
                    {
                        type: 'text',
                        text: `Address updated successfully!\n\n${JSON.stringify(result.content, null, 2)}`
                    }
                ]
            };
        }
        else if (result.action === 'decline') {
            return {
                content: [{ type: 'text', text: 'Address update cancelled by user.' }]
            };
        }
        else {
            return {
                content: [{ type: 'text', text: 'Address update was cancelled.' }]
            };
        }
    }
    catch (error) {
        return {
            content: [
                {
                    type: 'text',
                    text: `Address update failed: ${error instanceof Error ? error.message : String(error)}`
                }
            ],
            isError: true
        };
    }
});
async function main() {
    const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
    const app = (0, express_js_1.createMcpExpressApp)();
    // Map to store transports by session ID
    const transports = {};
    // MCP POST endpoint
    const mcpPostHandler = async (req, res) => {
        const sessionId = req.headers['mcp-session-id'];
        if (sessionId) {
            console.log(`Received MCP request for session: ${sessionId}`);
        }
        try {
            let transport;
            if (sessionId && transports[sessionId]) {
                // Reuse existing transport for this session
                transport = transports[sessionId];
            }
            else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
                // New initialization request - create new transport
                transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
                    sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
                    onsessioninitialized: sessionId => {
                        // Store the transport by session ID when session is initialized
                        console.log(`Session initialized with ID: ${sessionId}`);
                        transports[sessionId] = transport;
                    }
                });
                // Set up onclose handler to clean up transport when closed
                transport.onclose = () => {
                    const sid = transport.sessionId;
                    if (sid && transports[sid]) {
                        console.log(`Transport closed for session ${sid}, removing from transports map`);
                        delete transports[sid];
                    }
                };
                // Connect the transport to the MCP server BEFORE handling the request
                await mcpServer.connect(transport);
                await transport.handleRequest(req, res, req.body);
                return;
            }
            else {
                // Invalid request - no session ID or not initialization request
                res.status(400).json({
                    jsonrpc: '2.0',
                    error: {
                        code: -32000,
                        message: 'Bad Request: No valid session ID provided'
                    },
                    id: null
                });
                return;
            }
            // Handle the request with existing transport
            await transport.handleRequest(req, res, req.body);
        }
        catch (error) {
            console.error('Error handling MCP request:', error);
            if (!res.headersSent) {
                res.status(500).json({
                    jsonrpc: '2.0',
                    error: {
                        code: -32603,
                        message: 'Internal server error'
                    },
                    id: null
                });
            }
        }
    };
    app.post('/mcp', mcpPostHandler);
    // Handle GET requests for SSE streams
    const mcpGetHandler = async (req, res) => {
        const sessionId = req.headers['mcp-session-id'];
        if (!sessionId || !transports[sessionId]) {
            res.status(400).send('Invalid or missing session ID');
            return;
        }
        console.log(`Establishing SSE stream for session ${sessionId}`);
        const transport = transports[sessionId];
        await transport.handleRequest(req, res);
    };
    app.get('/mcp', mcpGetHandler);
    // Handle DELETE requests for session termination
    const mcpDeleteHandler = async (req, res) => {
        const sessionId = req.headers['mcp-session-id'];
        if (!sessionId || !transports[sessionId]) {
            res.status(400).send('Invalid or missing session ID');
            return;
        }
        console.log(`Received session termination request for session ${sessionId}`);
        try {
            const transport = transports[sessionId];
            await transport.handleRequest(req, res);
        }
        catch (error) {
            console.error('Error handling session termination:', error);
            if (!res.headersSent) {
                res.status(500).send('Error processing session termination');
            }
        }
    };
    app.delete('/mcp', mcpDeleteHandler);
    // Start listening
    app.listen(PORT, error => {
        if (error) {
            console.error('Failed to start server:', error);
            process.exit(1);
        }
        console.log(`Form elicitation example server is running on http://localhost:${PORT}/mcp`);
        console.log('Available tools:');
        console.log('  - register_user: Collect user registration information');
        console.log('  - create_event: Multi-step event creation');
        console.log('  - update_shipping_address: Collect and validate address');
        console.log('\nConnect your MCP client to this server using the HTTP transport.');
    });
    // Handle server shutdown
    process.on('SIGINT', async () => {
        console.log('Shutting down server...');
        // Close all active transports to properly clean up resources
        for (const sessionId in transports) {
            try {
                console.log(`Closing transport for session ${sessionId}`);
                await transports[sessionId].close();
                delete transports[sessionId];
            }
            catch (error) {
                console.error(`Error closing transport for session ${sessionId}:`, error);
            }
        }
        console.log('Server shutdown complete');
        process.exit(0);
    });
}
main().catch(error => {
    console.error('Server error:', error);
    process.exit(1);
});
//# sourceMappingURL=elicitationFormExample.js.map