#
tokens: 30714/50000 30/30 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitattributes
├── .gitignore
├── agents
│   ├── agent_integration.py
│   ├── agent_server.py
│   ├── Dockerfile
│   └── requirements.txt
├── client
│   ├── css
│   │   └── styles.css
│   ├── index.html
│   └── js
│       ├── animations.js
│       └── main.js
├── DEVELOPER.md
├── docker-compose.yml
├── Dockerfile
├── PROJECT_SUMMARY.md
├── QUICK_START.md
├── README.md
├── server
│   ├── app.py
│   ├── config.py
│   ├── forms_api.py
│   ├── mcp_handler.py
│   ├── requirements.txt
│   ├── static
│   │   ├── animations.js
│   │   ├── main.js
│   │   └── styles.css
│   ├── templates
│   │   └── index.html
│   └── utils
│       ├── __init__.py
│       └── logger.py
├── setup_credentials.sh
├── start_with_creds.sh
├── start.sh
└── use_provided_creds.sh
```

# Files

--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------

```
# Auto detect text files and perform LF normalization
* text=auto

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Environment variables
.env
.env.*

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Virtual Environment
venv/
env/
ENV/

# Docker
.dockerignore

# IDE files
.idea/
.vscode/
*.swp
*.swo
.DS_Store

# Logs
logs/
*.log

# Testing
.coverage
htmlcov/
.pytest_cache/
.tox/
.nox/
coverage.html
coverage.xml
*.cover

# Google API credentials
credentials.json
token.json 
```

--------------------------------------------------------------------------------
/client/js/animations.js:
--------------------------------------------------------------------------------

```javascript

```

--------------------------------------------------------------------------------
/server/utils/__init__.py:
--------------------------------------------------------------------------------

```python
"""Utility functions for the Google Forms MCP Server.""" 
```

--------------------------------------------------------------------------------
/agents/requirements.txt:
--------------------------------------------------------------------------------

```
requests==2.28.2
flask==2.2.3
flask-cors==3.0.10
werkzeug==2.2.3
numpy==1.24.2
websockets==11.0.2
pymongo==4.3.3
camel-ai
Pillow
# Add Google Generative AI SDK
google-generativeai

```

--------------------------------------------------------------------------------
/server/requirements.txt:
--------------------------------------------------------------------------------

```
flask==2.2.3
flask-cors==3.0.10
werkzeug==2.2.3
google-auth==2.17.3
google-auth-oauthlib==1.0.0
google-auth-httplib2==0.1.0
google-api-python-client==2.86.0
python-dotenv==1.0.0
requests==2.28.2
gunicorn==20.1.0
websockets==11.0.2
uuid==1.30

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM python:3.9-slim

WORKDIR /app

# Copy requirements first to leverage Docker caching
COPY server/requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy server code
COPY server /app/server
COPY .env /app/.env

# Set working directory to server
WORKDIR /app/server

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Expose port
EXPOSE 5000

# Run the server
CMD ["python", "app.py"]

```

--------------------------------------------------------------------------------
/agents/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM python:3.9-slim

# Install curl for healthcheck
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy requirements first to leverage Docker caching
COPY agents/requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy agent code
COPY agents /app/agents

# Set working directory to agents
WORKDIR /app/agents

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Expose port
EXPOSE 5001

# Use --app to specify the application file
CMD ["flask", "--app", "agent_server:app", "run", "--host=0.0.0.0", "--port=5001"] 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
version: '3.8'

services:
  # MCP Server Service
  mcp-server:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: google-form-mcp-server
    ports:
      - "5005:5000"
    volumes:
      - ./server:/app/server
    env_file:
      - .env
    environment:
      - FLASK_ENV=development
      - DEBUG=True
      - AGENT_ENDPOINT=http://agents:5001/process
    depends_on:
      agents:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - mcp-network

  # CamelAIOrg Agents Service
  agents:
    build:
      context: .
      dockerfile: agents/Dockerfile
    container_name: camelai-agents
    ports:
      - "5006:5001"
    volumes:
      - ./agents:/app/agents
    environment:
      - MCP_SERVER_URL=http://mcp-server:5000/api/process
      - PORT=5001
      - GOOGLE_API_KEY={{GOOGLE_API_KEY}}
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:5001/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    networks:
      - mcp-network

networks:
  mcp-network:
    driver: bridge

```

--------------------------------------------------------------------------------
/use_provided_creds.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Setup the project with user-provided credentials

echo "================================================"
echo "  Google Forms MCP Server - Use Provided Credentials"
echo "================================================"
echo ""

# Check if credentials were provided
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
    echo "Usage: $0 <client_id> <client_secret> <refresh_token>"
    echo ""
    echo "Example:"
    echo "$0 123456789-abcdef.apps.googleusercontent.com GOCSPX-abc123def456 1//04abcdefghijklmnop"
    exit 1
fi

# Get credentials from arguments
CLIENT_ID=$1
CLIENT_SECRET=$2
REFRESH_TOKEN=$3

# Create .env file with provided credentials
cat > .env << EOF
# Google API Credentials
GOOGLE_CLIENT_ID=$CLIENT_ID
GOOGLE_CLIENT_SECRET=$CLIENT_SECRET
GOOGLE_REFRESH_TOKEN=$REFRESH_TOKEN

# Server Configuration
FLASK_ENV=development
PORT=5000
DEBUG=True

# CamelAIOrg Agents Configuration
AGENT_ENDPOINT=http://agents:5001/process
AGENT_API_KEY=demo_key
EOF

echo "Created .env file with your provided credentials."
echo ""
echo "Starting the project now..."
echo ""

# Start the project
./start.sh 
```

--------------------------------------------------------------------------------
/server/config.py:
--------------------------------------------------------------------------------

```python
import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

# Google API settings
GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
GOOGLE_REFRESH_TOKEN = os.getenv('GOOGLE_REFRESH_TOKEN')

# API scopes needed for Google Forms
# Include more scopes to ensure proper access
SCOPES = [
    'https://www.googleapis.com/auth/forms',
    'https://www.googleapis.com/auth/forms.body',
    'https://www.googleapis.com/auth/drive',
    'https://www.googleapis.com/auth/drive.file',
    'https://www.googleapis.com/auth/drive.metadata'
]

# Server settings
PORT = int(os.getenv('PORT', 5000))
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'

# CamelAIOrg Agent settings
AGENT_ENDPOINT = os.getenv('AGENT_ENDPOINT', 'http://agents:5001/process')
AGENT_API_KEY = os.getenv('AGENT_API_KEY')

# MCP Protocol settings
MCP_VERSION = "1.0.0"
MCP_TOOLS = [
    "create_form",
    "add_question",
    "get_responses"
]

# LLM Settings / Gemini Settings (Update this section)
# REMOVE LLM_API_KEY and LLM_API_ENDPOINT
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
# The specific model endpoint will be constructed in the agent

```

--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Display welcome message
echo "================================================"
echo "  Google Forms MCP Server with CamelAIOrg Agents"
echo "================================================"
echo ""

# Check if .env file exists
if [ ! -f ".env" ]; then
    echo "Error: .env file not found!"
    echo "Please create a .env file with your Google API credentials."
    echo "Example format:"
    echo "GOOGLE_CLIENT_ID=your_client_id"
    echo "GOOGLE_CLIENT_SECRET=your_client_secret"
    echo "GOOGLE_REFRESH_TOKEN=your_refresh_token"
    echo ""
    echo "PORT=5000"
    echo "DEBUG=True"
    echo ""
    exit 1
fi

# Build and start containers
echo "Starting services with Docker Compose..."
docker-compose up --build -d

# Wait for services to start
echo "Waiting for services to start..."
sleep 5

# Display service status
echo ""
echo "Service Status:"
docker-compose ps

# Display access information
echo ""
echo "Access Information:"
echo "- MCP Server: http://localhost:5005"
echo "- Agent Server: http://localhost:5006"
echo "- Client Interface: Open client/index.html in your browser"
echo ""
echo "To view logs:"
echo "  docker-compose logs -f"
echo ""
echo "To stop the services:"
echo "  docker-compose down"
echo ""
echo "Enjoy using the Google Forms MCP Server!" 
```

--------------------------------------------------------------------------------
/start_with_creds.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Setup the project with provided credentials

echo "================================================"
echo "  Google Forms MCP Server - Quick Start"
echo "================================================"
echo ""
echo "This script will set up the project with provided credentials"
echo "and start the services."
echo ""

# Create .env file with provided credentials
cat > .env << EOF
# Google API Credentials
GOOGLE_CLIENT_ID=your_client_id_here
GOOGLE_CLIENT_SECRET=your_client_secret_here
GOOGLE_REFRESH_TOKEN=your_refresh_token_here

# Server Configuration
FLASK_ENV=development
PORT=5000
DEBUG=True

# CamelAIOrg Agents Configuration
AGENT_ENDPOINT=http://agents:5001/process
AGENT_API_KEY=demo_key
EOF

echo "Created .env file with placeholder credentials."
echo ""
echo "IMPORTANT: You need to edit the .env file with your actual Google API credentials."
echo "You can do this now by running:"
echo "  nano .env"
echo ""
echo "After updating your credentials, start the project with:"
echo "  ./start.sh"
echo ""
echo "Would you like to edit the .env file now? (y/n)"
read edit_now

if [ "$edit_now" == "y" ]; then
    ${EDITOR:-nano} .env
    
    echo ""
    echo "Now that you've updated your credentials, would you like to start the project? (y/n)"
    read start_now
    
    if [ "$start_now" == "y" ]; then
        ./start.sh
    else
        echo "You can start the project later by running ./start.sh"
    fi
else
    echo "Remember to update your credentials in .env before starting the project."
fi 
```

--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Google Forms MCP Client</title>
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <div class="container">
        <h1>Google Forms MCP Client</h1>
        <p>This client connects to the Google Forms MCP Server and CamelAIOrg Agents to create forms using natural language.</p>
        
        <div class="server-status">
            <h2>Server Status</h2>
            <p>MCP Server: <span id="serverStatus">Unknown</span></p>
        </div>
        
        <a href="http://localhost:5005" class="button pulse" id="connectButton">Connect to Server</a>
        
        <div class="status loading" id="loadingMessage">
            Connecting to server...
        </div>
        
        <div class="status error" id="errorMessage">
            Could not connect to the server. Please make sure the MCP server is running.
        </div>
        
        <div class="features">
            <h2>Features</h2>
            <ul>
                <li>Create Google Forms with natural language</li>
                <li>Multiple question types support</li>
                <li>View form creation process in real-time</li>
                <li>Monitor API requests and responses</li>
            </ul>
        </div>
        
        <div class="footer">
            <p>Google Forms MCP Server with CamelAIOrg Agents Integration</p>
        </div>
    </div>
    
    <script src="js/main.js"></script>
</body>
</html>

```

--------------------------------------------------------------------------------
/setup_credentials.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Google Forms MCP Server Credentials Setup Script

echo "================================================"
echo "  Google Forms MCP Server - Credentials Setup"
echo "================================================"
echo ""
echo "This script will help you set up your Google API credentials."
echo "You'll need to provide your Client ID, Client Secret, and Refresh Token."
echo ""
echo "If you don't have these credentials yet, please follow the instructions in README.md"
echo ""

# Check if .env already exists
if [ -f ".env" ]; then
    read -p ".env file already exists. Do you want to overwrite it? (y/n): " overwrite
    if [ "$overwrite" != "y" ]; then
        echo "Setup cancelled."
        exit 0
    fi
fi

# Get credentials
read -p "Enter your Google Client ID: " client_id
read -p "Enter your Google Client Secret: " client_secret
read -p "Enter your Google Refresh Token: " refresh_token

# Get port settings
read -p "Enter port for MCP Server [5000]: " port
port=${port:-5000}

# Get debug setting
read -p "Enable debug mode? (y/n) [y]: " debug_mode
debug_mode=${debug_mode:-y}

if [ "$debug_mode" == "y" ]; then
    debug="True"
else
    debug="False"
fi

# Create .env file
cat > .env << EOF
# Google API Credentials
GOOGLE_CLIENT_ID=$client_id
GOOGLE_CLIENT_SECRET=$client_secret
GOOGLE_REFRESH_TOKEN=$refresh_token

# Server Configuration
FLASK_ENV=development
PORT=$port
DEBUG=$debug

# CamelAIOrg Agents Configuration
AGENT_ENDPOINT=http://agents:5001/process
AGENT_API_KEY=demo_key
EOF

echo ""
echo "Credentials saved to .env file."
echo ""
echo "You can now start the project with:"
echo "./start.sh"
echo ""
echo "If you need to update your credentials later, you can run this script again"
echo "or edit the .env file directly." 
```

--------------------------------------------------------------------------------
/server/utils/logger.py:
--------------------------------------------------------------------------------

```python
import logging
import sys
import json
from datetime import datetime

# Configure logger
logger = logging.getLogger("mcp_server")
logger.setLevel(logging.INFO)

# Console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)

# Format
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)

# Add handler to logger
logger.addHandler(console_handler)


def log_mcp_request(request_data):
    """Log an incoming MCP request."""
    try:
        transaction_id = request_data.get('transaction_id', 'unknown')
        tool_name = request_data.get('tool_name', 'unknown')
        
        logger.info(f"MCP Request [{transaction_id}] - Tool: {tool_name}")
        logger.debug(f"Request data: {json.dumps(request_data, indent=2)}")
        
    except Exception as e:
        logger.error(f"Error logging MCP request: {str(e)}")


def log_mcp_response(response_data):
    """Log an outgoing MCP response."""
    try:
        transaction_id = response_data.get('transaction_id', 'unknown')
        status = response_data.get('status', 'unknown')
        
        logger.info(f"MCP Response [{transaction_id}] - Status: {status}")
        logger.debug(f"Response data: {json.dumps(response_data, indent=2)}")
        
    except Exception as e:
        logger.error(f"Error logging MCP response: {str(e)}")


def log_error(message, error=None):
    """Log an error."""
    try:
        if error:
            logger.error(f"{message}: {str(error)}")
        else:
            logger.error(message)
    except Exception as e:
        # Last resort if logging itself fails
        print(f"Logging error: {str(e)}")


def get_logger():
    """Get the configured logger."""
    return logger

```

--------------------------------------------------------------------------------
/client/css/styles.css:
--------------------------------------------------------------------------------

```css
/* Google Forms MCP Client Styles */
:root {
    --bg-color: #121212;
    --panel-bg: #1e1e1e;
    --panel-border: #333333;
    --text-color: #e0e0e0;
    --highlight-color: #00ccff;
    --secondary-highlight: #00ff9d;
    --danger-color: #ff4757;
    --warning-color: #ffa502;
    --success-color: #2ed573;
    --muted-color: #747d8c;
}

body {
    font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
    background-color: var(--bg-color);
    color: var(--text-color);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh;
    margin: 0;
    padding: 20px;
    text-align: center;
}

.container {
    max-width: 800px;
    padding: 30px;
    background-color: var(--panel-bg);
    border-radius: 8px;
    border: 1px solid var(--panel-border);
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

h1 {
    color: var(--highlight-color);
    margin-bottom: 10px;
}

h2 {
    color: var(--secondary-highlight);
    font-size: 1.4rem;
    margin: 20px 0 15px 0;
}

p {
    color: var(--muted-color);
    margin-bottom: 30px;
    line-height: 1.5;
}

.button {
    background-color: var(--highlight-color);
    color: #000;
    font-weight: 500;
    padding: 12px 24px;
    border: none;
    border-radius: 4px;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.3s;
    display: inline-block;
}

.button:hover {
    background-color: #00a3cc;
}

.button-secondary {
    background-color: transparent;
    border: 1px solid var(--secondary-highlight);
    color: var(--secondary-highlight);
}

.button-secondary:hover {
    background-color: rgba(0, 255, 157, 0.1);
}

.status {
    margin-top: 20px;
    padding: 15px;
    border-radius: 4px;
    display: none;
}

.status.loading {
    background-color: rgba(255, 165, 2, 0.1);
    color: var(--warning-color);
    border: 1px solid var(--warning-color);
}

.status.success {
    background-color: rgba(46, 213, 115, 0.1);
    color: var(--success-color);
    border: 1px solid var(--success-color);
}

.status.error {
    background-color: rgba(255, 71, 87, 0.1);
    color: var(--danger-color);
    border: 1px solid var(--danger-color);
}

.footer {
    margin-top: 50px;
    color: var(--muted-color);
    font-size: 0.8rem;
}

/* Animation */
@keyframes pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(0, 204, 255, 0.7);
    }
    70% {
        box-shadow: 0 0 0 10px rgba(0, 204, 255, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(0, 204, 255, 0);
    }
}

.pulse {
    animation: pulse 1.5s infinite;
}

```

--------------------------------------------------------------------------------
/client/js/main.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Google Forms MCP Client
 * Main JavaScript file for handling server connection and UI updates
 */

document.addEventListener('DOMContentLoaded', function() {
    // DOM Elements
    const connectButton = document.getElementById('connectButton');
    const loadingMessage = document.getElementById('loadingMessage');
    const errorMessage = document.getElementById('errorMessage');
    const serverStatusElement = document.getElementById('serverStatus');
    
    // Server configuration
    const config = {
        mcpServerUrl: 'http://localhost:5005',
        agentServerUrl: 'http://localhost:5006'
    };
    
    // Initialize
    function init() {
        // Add event listeners
        if (connectButton) {
            connectButton.addEventListener('click', handleServerConnect);
        }
        
        // Check server status
        checkServerStatus();
    }
    
    // Check if MCP server is available
    async function checkServerStatus() {
        if (serverStatusElement) {
            serverStatusElement.textContent = 'Checking...';
            serverStatusElement.className = 'checking';
        }
        
        try {
            const response = await fetch(`${config.mcpServerUrl}/api/health`);
            
            if (response.ok) {
                const data = await response.json();
                if (serverStatusElement) {
                    serverStatusElement.textContent = `Online (v${data.version})`;
                    serverStatusElement.className = 'online';
                }
                return true;
            } else {
                if (serverStatusElement) {
                    serverStatusElement.textContent = 'Error';
                    serverStatusElement.className = 'offline';
                }
                return false;
            }
        } catch (error) {
            console.error('Server status check error:', error);
            if (serverStatusElement) {
                serverStatusElement.textContent = 'Offline';
                serverStatusElement.className = 'offline';
            }
            return false;
        }
    }
    
    // Handle server connection button click
    function handleServerConnect(e) {
        if (e) {
            e.preventDefault();
        }
        
        if (loadingMessage) {
            loadingMessage.style.display = 'block';
        }
        
        if (errorMessage) {
            errorMessage.style.display = 'none';
        }
        
        // Attempt to connect to the server
        fetch(`${config.mcpServerUrl}/api/health`)
            .then(response => {
                if (response.ok) {
                    window.location.href = config.mcpServerUrl;
                } else {
                    throw new Error('Server error');
                }
            })
            .catch(error => {
                console.error('Connection error:', error);
                if (loadingMessage) {
                    loadingMessage.style.display = 'none';
                }
                
                if (errorMessage) {
                    errorMessage.style.display = 'block';
                }
            });
    }
    
    // Start the application
    init();
});

```

--------------------------------------------------------------------------------
/PROJECT_SUMMARY.md:
--------------------------------------------------------------------------------

```markdown
# Google Form MCP Server with CamelAIOrg Agents - Project Summary

## Project Overview

This project provides a complete implementation of an **MCP (Model Context Protocol) Server** integrated with the **Google Forms API** and **CamelAIOrg Agents**. The system enables the creation of Google Forms through natural language instructions, with a visually appealing UI that displays the request flow.

## Key Components

### 1. MCP Server (Python/Flask)
- Implements the Model Context Protocol
- Exposes tools for Google Forms operations
- Handles authentication with Google APIs
- Processes structured API requests/responses

### 2. CamelAIOrg Agents (Python/Flask)
- Processes natural language requests
- Extracts intent and parameters
- Converts natural language to structured MCP calls
- Handles form creation logic

### 3. Frontend UI (HTML/CSS/JavaScript)
- Dark-themed modern interface
- Real-time flow visualization with animations
- MCP packet logging and display
- Form result presentation

### 4. Dockerized Deployment
- Docker and Docker Compose configuration
- Separate containers for server and agents
- Environment configuration
- Easy one-command deployment

## Feature Highlights

### Natural Language Form Creation
Users can create forms with simple instructions like:
- "Create a customer feedback form with rating questions"
- "Make a survey about remote work preferences"
- "Set up an RSVP form for my event"

### Question Type Support
The system supports multiple question types:
- Text (short answer)
- Paragraph (long answer)
- Multiple-choice
- Checkbox

### Visual Flow Representation
The UI visualizes the flow of requests and responses:
- Frontend → Agent → MCP Server → Google Forms API
- Animated particles showing data movement
- Active node highlighting
- Error visualization

### MCP Protocol Implementation
Full implementation of the Model Context Protocol:
- Structured tool definitions
- Transaction-based processing
- Schema validation
- Error handling

### Security Considerations
- OAuth2 authentication with Google APIs
- Environment-based configuration
- Credential management
- Input validation

## Technical Achievements

1. **Modular Architecture**: Clean separation between MCP server, agent logic, and UI
2. **Interactive Visualization**: Real-time animation of request/response flows
3. **Agent Intelligence**: Natural language processing for form creation
4. **Protocol Implementation**: Complete MCP protocol implementation
5. **Containerized Deployment**: Docker-based deployment for easy setup

## User Experience

The system provides a seamless user experience:
1. User enters a natural language request
2. Request is visualized flowing through the system components
3. Form is created with appropriate questions
4. User receives a link to view/edit the form
5. Questions and responses can be managed

## Future Enhancements

Potential areas for future development:
1. Advanced NLP for more complex form requests
2. Additional question types and form features
3. Integration with other Google Workspace products
4. Form templates and preset configurations
5. User authentication and form management

## Conclusion

This project demonstrates the power of combining AI agents with structured protocols (MCP) to enable natural language interfaces for productivity tools. The implementation showcases modern web development practices, API integration, and containerized deployment, all while providing an intuitive and visually appealing user interface. 
```

--------------------------------------------------------------------------------
/QUICK_START.md:
--------------------------------------------------------------------------------

```markdown
# Quick Start Guide

Follow these steps to get the Google Forms MCP Server up and running quickly.

## Prerequisites

- Docker and Docker Compose installed
- Google account with access to Google Forms
- Google Cloud Platform project with Forms API enabled

## Setup in 5 Steps

### 1. Clone the Repository

```bash
git clone https://github.com/yourusername/google-form-mcp-server.git
cd google-form-mcp-server
```

### 2. Get Google API Credentials

If you already have your credentials, proceed to step 3. Otherwise:

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a project and enable Google Forms API and Google Drive API
3. Create OAuth 2.0 credentials (Web application type)
4. Use the OAuth 2.0 Playground to get a refresh token:
   - Go to: https://developers.google.com/oauthplayground/
   - Configure with your credentials (⚙️ icon)
   - Select Forms and Drive API scopes
   - Authorize and exchange for tokens

### 3. Run the Credentials Setup Script

```bash
./setup_credentials.sh
```

Enter your Google API credentials when prompted.

### 4. Start the Services

```bash
./start.sh
```

This will build and start the Docker containers for the MCP Server and CamelAIOrg Agents.

### 5. Access the Application

Open your browser and navigate to:

- Server UI: http://localhost:5005
- Manual client: Open `client/index.html` in your browser

## Using the Application

1. Enter natural language instructions like:
   - "Create a customer feedback form with a rating question"
   - "Create a survey about remote work preferences"
   - "Make an RSVP form for my event"

2. Watch the request flow through the system visualization

3. View the generated form details and links

## Troubleshooting

- **Server not starting**: Check Docker is running and ports 5005/5006 are available
- **Authentication errors**: Verify your credentials in the .env file
- **Connection errors**: Ensure your network allows API calls to Google

## What's Next?

- Read the full README.md for detailed information
- Check DEVELOPER.md for technical documentation
- Explore the codebase to understand the implementation

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Google Forms MCP Client</title>
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <div class="container">
        <h1>Google Forms MCP Client</h1>
        <p>This client connects to the Google Forms MCP Server and CamelAIOrg Agents to create forms using natural language.</p>
        
        <div class="server-status">
            <h2>Server Status</h2>
            <p>MCP Server: <span id="serverStatus">Unknown</span></p>
        </div>
        
        <a href="http://localhost:5005" class="button pulse" id="connectButton">Connect to Server</a>
        
        <div class="status loading" id="loadingMessage">
            Connecting to server...
        </div>
        
        <div class="status error" id="errorMessage">
            Could not connect to the server. Please make sure the MCP server is running.
        </div>
        
        <div class="features">
            <h2>Features</h2>
            <ul>
                <li>Create Google Forms with natural language</li>
                <li>Multiple question types support</li>
                <li>View form creation process in real-time</li>
                <li>Monitor API requests and responses</li>
            </ul>
        </div>
        
        <div class="footer">
            <p>Google Forms MCP Server with CamelAIOrg Agents Integration</p>
        </div>
    </div>
    
    <script src="js/main.js"></script>
</body>
</html> 
```

--------------------------------------------------------------------------------
/agents/agent_server.py:
--------------------------------------------------------------------------------

```python
"""
Agent Server for CamelAIOrg Integration with Google Forms MCP

This module provides a Flask-based REST API that accepts natural language requests,
processes them through the FormAgent, and returns the results.
"""

from flask import Flask, request, jsonify
from flask_cors import CORS
import json
import os
import logging

from agent_integration import FormAgent

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("agent_server")

# Create Flask app
app = Flask(__name__)
CORS(app)  # Enable CORS for all routes

# Initialize form agent
form_agent = FormAgent()

# Configuration
PORT = int(os.getenv('PORT', 5001))
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'

@app.route('/health', methods=['GET'])
def health_check():
    """Health check endpoint."""
    return jsonify({
        "status": "ok",
        "service": "CamelAIOrg Agent Server",
        "version": "1.0.0"
    })

@app.route('/process', methods=['POST'])
def process_request():
    """
    Process a natural language request by passing it to the FormAgent.
    The FormAgent is responsible for NLP and interacting with the MCP server.
    
    Request format:
    {
        "request_text": "Create a feedback form with 3 questions"
    }
    
    Response format (on success):
    {
        "status": "success",
        "result": { ... form details ... }
    }
    Response format (on error):
    {
        "status": "error",
        "message": "Error description"
    }
    """
    try:
        if not request.is_json:
            return jsonify({
                "status": "error",
                "message": "Request must be JSON"
            }), 400
        
        data = request.get_json()
        
        if 'request_text' not in data:
            return jsonify({
                "status": "error",
                "message": "Missing required parameter 'request_text'"
            }), 400
        
        request_text = data['request_text']
        logger.info(f"Agent server received request: {request_text}")
        
        # Process the request through the FormAgent
        # The FormAgent's process_request method will handle NLP,
        # MCP packet creation, and communication with the MCP server.
        result = form_agent.process_request(request_text)
        
        # The agent's response (success or error) is returned directly
        return jsonify(result)
    
    except Exception as e:
        logger.error(f"Error processing agent request: {str(e)}", exc_info=True)
        return jsonify({
            "status": "error",
            "message": f"Internal Agent Server Error: {str(e)}"
        }), 500

@app.route('/schema', methods=['GET'])
def get_schema():
    """Return the agent capabilities schema."""
    schema = {
        "name": "Google Forms Creator Agent",
        "description": "Creates Google Forms from natural language requests",
        "capabilities": [
            {
                "name": "create_form",
                "description": "Create a new Google Form with questions"
            },
            {
                "name": "add_question",
                "description": "Add a question to an existing form"
            },
            {
                "name": "get_responses",
                "description": "Get responses from an existing form"
            }
        ],
        "example_requests": [
            "Create a customer feedback form with rating questions",
            "Make a survey about remote work preferences",
            "Set up an RSVP form for my event"
        ]
    }
    
    return jsonify({
        "status": "success",
        "schema": schema
    })

# Run the Flask app
if __name__ == '__main__':
    logger.info(f"Starting CamelAIOrg Agent Server on port {PORT}")
    logger.info(f"Debug mode: {DEBUG}")
    
    app.run(host='0.0.0.0', port=PORT, debug=DEBUG) 
```

--------------------------------------------------------------------------------
/server/app.py:
--------------------------------------------------------------------------------

```python
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
import json
import os
import requests # Import requests library

from mcp_handler import MCPHandler
from utils.logger import log_mcp_request, log_mcp_response, log_error, get_logger
import config
from forms_api import GoogleFormsAPI

# Initialize Flask application
app = Flask(__name__)
CORS(app)  # Enable CORS for all routes

# Initialize the MCP handler
mcp_handler = MCPHandler()
logger = get_logger()
forms_api = GoogleFormsAPI()

@app.route('/')
def index():
    """Render the main page of the application."""
    return render_template('index.html')

@app.route('/api/schema', methods=['GET'])
def get_schema():
    """Return the MCP tools schema."""
    try:
        schema = mcp_handler.get_tools_schema()
        return jsonify({
            "status": "success",
            "tools": schema,
            "version": config.MCP_VERSION
        })
    except Exception as e:
        log_error("Error returning schema", e)
        return jsonify({
            "status": "error",
            "message": str(e)
        }), 500

@app.route('/api/process', methods=['POST'])
def process_mcp_request():
    """Process an MCP request."""
    try:
        if not request.is_json:
            return jsonify({
                "status": "error",
                "message": "Request must be JSON"
            }), 400
        
        request_data = request.get_json()
        log_mcp_request(request_data)
        
        response = mcp_handler.process_request(request_data)
        log_mcp_response(response)
        
        return jsonify(response)
    
    except Exception as e:
        log_error("Error processing MCP request", e)
        return jsonify({
            "status": "error",
            "message": str(e)
        }), 500

@app.route('/api/health', methods=['GET'])
def health_check():
    """Health check endpoint."""
    return jsonify({
        "status": "ok",
        "version": config.MCP_VERSION
    })

# WebSocket for real-time UI updates
@app.route('/ws')
def websocket():
    """WebSocket endpoint for real-time updates."""
    return render_template('websocket.html')

@app.route('/api/forms', methods=['POST'])
def forms_api():
    """Handle form operations."""
    try:
        data = request.json
        action = data.get('action')
        
        logger.info(f"Received form API request: {action}")
        
        if action == 'create_form':
            title = data.get('title', 'Untitled Form')
            description = data.get('description', '')
            
            logger.info(f"Creating form with title: {title}")
            result = forms_api.create_form(title, description)
            logger.info(f"Form created with ID: {result.get('form_id')}")
            logger.info(f"Form response URL: {result.get('response_url')}")
            logger.info(f"Form edit URL: {result.get('edit_url')}")
            
            return jsonify({"status": "success", "result": result})
        
        return jsonify({"status": "error", "message": f"Unknown action: {action}"})
    except Exception as e:
        logger.error(f"Error in forms API: {str(e)}")
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/api/agent_proxy', methods=['POST'])
def agent_proxy():
    """Proxy requests from the frontend to the agent server."""
    try:
        frontend_data = request.get_json()
        if not frontend_data or 'request_text' not in frontend_data:
            log_error("Agent proxy received invalid data from frontend", frontend_data)
            return jsonify({"status": "error", "message": "Invalid request data"}), 400

        agent_url = config.AGENT_ENDPOINT
        if not agent_url:
             log_error("Agent endpoint URL is not configured.", None)
             return jsonify({"status": "error", "message": "Agent service URL not configured."}), 500
        
        logger.info(f"Proxying request to agent at {agent_url}: {frontend_data}")

        # Forward the request to the agent server
        # Note: Consider adding timeout and error handling for the requests.post call
        agent_response = requests.post(
            agent_url,
            json=frontend_data, 
            headers={'Content-Type': 'application/json'}
            # Potentially add API key header if agent requires it: 
            # headers={"Authorization": f"Bearer {config.AGENT_API_KEY}"}
        )
        
        # Check agent response status
        agent_response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)

        agent_data = agent_response.json()
        logger.info(f"Received response from agent: {agent_data}")
        
        # Return the agent's response directly to the frontend
        return jsonify(agent_data)

    except requests.exceptions.RequestException as e:
        log_error(f"Error communicating with agent server at {config.AGENT_ENDPOINT}", e)
        return jsonify({"status": "error", "message": f"Failed to reach agent service: {str(e)}"}), 502 # Bad Gateway
    except Exception as e:
        log_error("Error in agent proxy endpoint", e)
        return jsonify({"status": "error", "message": f"Internal proxy error: {str(e)}"}), 500

if __name__ == '__main__':
    port = config.PORT
    debug = config.DEBUG
    
    logger.info(f"Starting Google Forms MCP Server on port {port}")
    logger.info(f"Debug mode: {debug}")
    
    app.run(host='0.0.0.0', port=port, debug=debug)

```

--------------------------------------------------------------------------------
/server/templates/index.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Google Forms MCP Server</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container-fluid">
        <header class="header">
            <div class="logo-container">
                <h1>Google Forms MCP Server</h1>
                <p class="subtitle">Integrated with CamelAIOrg Agents</p>
            </div>
            <div class="status-indicator">
                <span id="statusDot" class="status-dot"></span>
                <span id="statusText">Connecting...</span>
            </div>
        </header>

        <div class="row main-content">
            <!-- Left Panel: Form Controls -->
            <div class="col-md-4">
                <div class="panel">
                    <h2>Form Request</h2>
                    <div class="form-group">
                        <label for="requestInput">Natural Language Request:</label>
                        <textarea id="requestInput" class="form-control" rows="4" placeholder="Create a feedback form with 3 questions about customer service..."></textarea>
                    </div>
                    <button id="sendRequestBtn" class="btn btn-primary">Process Request</button>
                    <div class="demo-actions mt-4">
                        <h3>Quick Demo Actions</h3>
                        <button class="btn btn-outline-secondary demo-btn" data-request="Create a customer feedback form with a rating question from 1-5 and a text question for additional comments">Feedback Form</button>
                        <button class="btn btn-outline-secondary demo-btn" data-request="Create a survey about remote work preferences with 3 multiple choice questions">Work Survey</button>
                        <button class="btn btn-outline-secondary demo-btn" data-request="Create an event RSVP form with name, email and attendance options">RSVP Form</button>
                    </div>
                </div>
            </div>

            <!-- Middle Panel: Flow Visualization -->
            <div class="col-md-4">
                <div class="panel flow-panel">
                    <h2>Request Flow</h2>
                    <div class="stage-indicator">
                        Current: <span id="stageIndicator">Ready for request</span>
                    </div>
                    <div class="flow-container">
                        <div class="node" id="frontendNode">
                            <div class="node-icon">
                                <i class="node-glow"></i>
                            </div>
                            <span class="node-label">Frontend</span>
                        </div>
                        <div class="flow-line" id="frontendToAgent">
                            <div class="flow-particle-container"></div>
                        </div>
                        <div class="node" id="agentNode">
                            <div class="node-icon">
                                <i class="node-glow"></i>
                            </div>
                            <span class="node-label">CamelAIOrg Agent</span>
                        </div>
                        <div class="flow-line" id="agentToMCP">
                            <div class="flow-particle-container"></div>
                        </div>
                        <div class="node" id="mcpNode">
                            <div class="node-icon">
                                <i class="node-glow"></i>
                            </div>
                            <span class="node-label">MCP Server</span>
                        </div>
                        <div class="flow-line" id="mcpToGoogle">
                            <div class="flow-particle-container"></div>
                        </div>
                        <div class="node" id="googleNode">
                            <div class="node-icon">
                                <i class="node-glow"></i>
                            </div>
                            <span class="node-label">Google Forms</span>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Right Panel: MCP Packet & Results -->
            <div class="col-md-4">
                <div class="panel result-panel">
                    <h2>Results</h2>
                    <div id="formResult" class="hidden">
                        <h3 id="formTitle">Form Title</h3>
                        <div class="form-links">
                            <a id="formViewLink" href="#" target="_blank" class="btn btn-sm btn-outline-info">View Form</a>
                            <a id="formEditLink" href="#" target="_blank" class="btn btn-sm btn-outline-warning">Edit Form</a>
                        </div>
                        <div id="formQuestions" class="mt-3">
                            <h4>Questions:</h4>
                            <ul id="questionsList" class="list-group">
                                <!-- Questions will be added here -->
                            </ul>
                        </div>
                    </div>
                    <div id="mcp-log">
                        <h3>MCP Packet Log</h3>
                        <div id="packetLog" class="log-container">
                            <!-- Packet logs will be added here -->
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Templates for dynamic content -->
    <template id="packetTemplate">
        <div class="packet-entry">
            <div class="packet-header">
                <span class="transaction-id"></span>
                <span class="packet-time"></span>
                <span class="packet-direction"></span>
            </div>
            <pre class="packet-content"></pre>
        </div>
    </template>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
    <script src="{{ url_for('static', filename='animations.js') }}"></script>
    <script src="{{ url_for('static', filename='main.js') }}"></script>
</body>
</html>

```

--------------------------------------------------------------------------------
/DEVELOPER.md:
--------------------------------------------------------------------------------

```markdown
# Google Forms MCP Server - Developer Guide

This document provides technical details for developers who want to understand, modify, or extend the Google Forms MCP Server and CamelAIOrg Agents integration.

## Architecture Overview

The system is built on a modular architecture with the following key components:

1. **MCP Server (Flask)**: Implements the Model Context Protocol and interfaces with Google Forms API
2. **CamelAIOrg Agents (Flask)**: Processes natural language and converts it to structured MCP calls
3. **Frontend UI (HTML/CSS/JS)**: Visualizes the flow and manages user interactions
4. **Google Forms API**: External service for form creation and management

### Component Communication

```
Frontend → Agents → MCP Server → Google Forms API
```

Each component communicates via HTTP requests, with MCP packets being the primary data format for the MCP Server.

## MCP Protocol Specification

The MCP (Model Context Protocol) is designed to standardize tool usage between AI agents and external services. Our implementation follows these guidelines:

### Request Format

```json
{
  "transaction_id": "unique_id",
  "tool_name": "tool_name",
  "parameters": {
    "param1": "value1",
    "param2": "value2"
  }
}
```

### Response Format

```json
{
  "transaction_id": "unique_id",
  "status": "success|error",
  "result": {
    "key1": "value1",
    "key2": "value2"
  },
  "error": {
    "message": "Error message"
  }
}
```

## Google Forms API Integration

The integration with Google Forms API is handled by the `GoogleFormsAPI` class in `forms_api.py`. Key methods include:

- `create_form(title, description)`: Creates a new form
- `add_question(form_id, question_type, title, options, required)`: Adds a question
- `get_responses(form_id)`: Retrieves form responses

### Authentication Flow

1. OAuth2 credentials are stored in environment variables
2. Credentials are loaded and used to create a Google API client
3. API requests are authenticated using these credentials

## CamelAIOrg Agent Implementation

The agent implementation in `agent_integration.py` provides the natural language processing interface. Key methods:

- `process_request(request_text)`: Main entry point for NL requests
- `_analyze_request(request_text)`: Analyzes and extracts intent from text
- `_handle_create_form(params)`: Creates a form based on extracted parameters

## Frontend Visualization

The UI uses a combination of CSS and JavaScript to visualize the request flow:

- **Flow Lines**: Represent the path between components
- **Particles**: Animated elements that travel along flow lines
- **Nodes**: Represent each component (Frontend, Agents, MCP, Google)
- **MCP Packet Log**: Shows the actual MCP packets being exchanged

### Animation System

The animation system in `animations.js` provides these key features:

- `animateFlow(fromNode, toNode, direction)`: Animates flow between nodes
- `animateRequestResponseFlow()`: Animates a complete request-response cycle
- `animateErrorFlow(errorStage)`: Visualizes errors at different stages

## Extending the System

### Adding New Question Types

To add a new question type:

1. Update the `add_question` method in `forms_api.py`
2. Add the new type to the validation in `_handle_add_question` in `mcp_handler.py`
3. Update the UI logic in `main.js` to handle the new question type

### Adding New MCP Tools

To add a new MCP tool:

1. Add the tool name to `MCP_TOOLS` in `config.py`
2. Add tool schema to `get_tools_schema` in `mcp_handler.py`
3. Create a handler method `_handle_tool_name` in `mcp_handler.py`
4. Implement the underlying functionality in `forms_api.py`

### Enhancing Agent Capabilities

To improve the agent's language processing:

1. Enhance the `_analyze_request` method in `agent_integration.py`
2. Add new intents and parameters recognition
3. Adjust the question generation logic based on request analysis

## Testing

### Unit Testing

Test individual components:

```bash
# Test MCP handler
python -m unittest tests/test_mcp_handler.py

# Test Google Forms API
python -m unittest tests/test_forms_api.py

# Test agent integration
python -m unittest tests/test_agent_integration.py
```

### Integration Testing

Test the entire system:

```bash
# Start the servers
docker-compose up -d

# Run integration tests
python -m unittest tests/test_integration.py
```

## Performance Considerations

- **Caching**: Implement caching for frequently accessed forms data
- **Rate Limiting**: Be aware of Google Forms API rate limits
- **Error Handling**: Implement robust error handling and retry logic
- **Load Testing**: Use tools like Locust to test system performance

## Security Best Practices

- **Credential Management**: Never commit credentials to version control
- **Input Validation**: Validate all user input and MCP packets
- **CORS Configuration**: Configure CORS appropriately for production
- **Rate Limiting**: Implement rate limiting to prevent abuse
- **Auth Tokens**: Use proper authentication for production deployments

## Deployment

### Production Deployment

For production deployment:

1. Use a proper container orchestration system (Kubernetes, ECS)
2. Set up a reverse proxy (Nginx, Traefik) for TLS termination
3. Use a managed database for persistent data
4. Implement proper monitoring and logging
5. Set up CI/CD pipelines for automated testing and deployment

### Environment-Specific Configuration

Create environment-specific configuration:

- `config.dev.py` - Development settings
- `config.test.py` - Testing settings
- `config.prod.py` - Production settings

## Troubleshooting

Common issues and solutions:

1. **Google API Authentication Errors**:
   - Verify credentials in `.env` file
   - Check that required API scopes are included
   - Ensure refresh token is valid

2. **Docker Network Issues**:
   - Make sure services can communicate on the network
   - Check port mappings in `docker-compose.yml`

3. **UI Animation Issues**:
   - Check browser console for JavaScript errors
   - Verify DOM element IDs match expected values

4. **MCP Protocol Errors**:
   - Validate request format against MCP schema
   - Check transaction IDs are being properly passed

## Contributing

Please follow these guidelines when contributing:

1. Create a feature branch from `main`
2. Follow the existing code style and conventions
3. Write unit tests for new functionality
4. Document new features or changes
5. Submit a pull request with a clear description of changes 
```

--------------------------------------------------------------------------------
/server/mcp_handler.py:
--------------------------------------------------------------------------------

```python
import json
import uuid
from forms_api import GoogleFormsAPI
import config

class MCPHandler:
    """
    Handler for Model Context Protocol (MCP) packets.
    
    Processes incoming MCP requests for Google Forms operations and returns
    appropriately formatted MCP responses.
    """
    
    def __init__(self):
        """Initialize the MCP handler with a GoogleFormsAPI instance."""
        self.forms_api = GoogleFormsAPI()
        self.version = config.MCP_VERSION
        self.tools = config.MCP_TOOLS
    
    def get_tools_schema(self):
        """Return the schema for all available tools."""
        return {
            "create_form": {
                "description": "Creates a new Google Form",
                "parameters": {
                    "title": {
                        "type": "string",
                        "description": "The title of the form"
                    },
                    "description": {
                        "type": "string",
                        "description": "Optional description for the form"
                    }
                },
                "required": ["title"]
            },
            "add_question": {
                "description": "Adds a question to an existing Google Form",
                "parameters": {
                    "form_id": {
                        "type": "string",
                        "description": "The ID of the form to add the question to"
                    },
                    "question_type": {
                        "type": "string",
                        "description": "The type of question (text, paragraph, multiple_choice, checkbox)",
                        "enum": ["text", "paragraph", "multiple_choice", "checkbox"]
                    },
                    "title": {
                        "type": "string",
                        "description": "The question title/text"
                    },
                    "options": {
                        "type": "array",
                        "description": "Options for multiple choice or checkbox questions",
                        "items": {
                            "type": "string"
                        }
                    },
                    "required": {
                        "type": "boolean",
                        "description": "Whether the question is required",
                        "default": False
                    }
                },
                "required": ["form_id", "question_type", "title"]
            },
            "get_responses": {
                "description": "Gets responses for a Google Form",
                "parameters": {
                    "form_id": {
                        "type": "string",
                        "description": "The ID of the form to get responses for"
                    }
                },
                "required": ["form_id"]
            }
        }
    
    def process_request(self, request_data):
        """
        Process an incoming MCP request.
        
        Args:
            request_data: Dict containing the MCP request data
            
        Returns:
            dict: MCP response packet
        """
        try:
            # Extract MCP request components
            transaction_id = request_data.get('transaction_id', str(uuid.uuid4()))
            tool_name = request_data.get('tool_name')
            parameters = request_data.get('parameters', {})
            
            # Validate tool name
            if tool_name not in self.tools:
                return self._create_error_response(
                    transaction_id,
                    f"Unknown tool '{tool_name}'. Available tools: {', '.join(self.tools)}"
                )
            
            # Process the request based on the tool name
            if tool_name == "create_form":
                return self._handle_create_form(transaction_id, parameters)
            elif tool_name == "add_question":
                return self._handle_add_question(transaction_id, parameters)
            elif tool_name == "get_responses":
                return self._handle_get_responses(transaction_id, parameters)
            
            # Shouldn't reach here due to validation above
            return self._create_error_response(transaction_id, f"Tool '{tool_name}' not implemented")
        
        except Exception as e:
            return self._create_error_response(
                request_data.get('transaction_id', str(uuid.uuid4())),
                f"Error processing request: {str(e)}"
            )
    
    def _handle_create_form(self, transaction_id, parameters):
        """Handle a create_form MCP request."""
        if 'title' not in parameters:
            return self._create_error_response(transaction_id, "Missing required parameter 'title'")
        
        title = parameters['title']
        description = parameters.get('description', "")
        
        result = self.forms_api.create_form(title, description)
        
        return {
            "transaction_id": transaction_id,
            "status": "success",
            "result": result
        }
    
    def _handle_add_question(self, transaction_id, parameters):
        """Handle an add_question MCP request."""
        # Validate required parameters
        required_params = ['form_id', 'question_type', 'title']
        for param in required_params:
            if param not in parameters:
                return self._create_error_response(transaction_id, f"Missing required parameter '{param}'")
        
        # Extract parameters
        form_id = parameters['form_id']
        question_type = parameters['question_type']
        title = parameters['title']
        options = parameters.get('options', [])
        required = parameters.get('required', False)
        
        # Validate question type
        valid_types = ['text', 'paragraph', 'multiple_choice', 'checkbox']
        if question_type not in valid_types:
            return self._create_error_response(
                transaction_id,
                f"Invalid question_type '{question_type}'. Valid types: {', '.join(valid_types)}"
            )
        
        # Validate options for choice questions
        if question_type in ['multiple_choice', 'checkbox'] and not options:
            return self._create_error_response(
                transaction_id,
                f"Options are required for '{question_type}' questions"
            )
        
        result = self.forms_api.add_question(form_id, question_type, title, options, required)
        
        return {
            "transaction_id": transaction_id,
            "status": "success",
            "result": result
        }
    
    def _handle_get_responses(self, transaction_id, parameters):
        """Handle a get_responses MCP request."""
        if 'form_id' not in parameters:
            return self._create_error_response(transaction_id, "Missing required parameter 'form_id'")
        
        form_id = parameters['form_id']
        result = self.forms_api.get_responses(form_id)
        
        return {
            "transaction_id": transaction_id,
            "status": "success",
            "result": result
        }
    
    def _create_error_response(self, transaction_id, error_message):
        """Create an MCP error response."""
        return {
            "transaction_id": transaction_id,
            "status": "error",
            "error": {
                "message": error_message
            }
        }

```

--------------------------------------------------------------------------------
/server/static/styles.css:
--------------------------------------------------------------------------------

```css
/* Global Styles */
:root {
    --bg-color: #121212;
    --panel-bg: #1e1e1e;
    --panel-border: #333333;
    --text-color: #e0e0e0;
    --highlight-color: #00ccff;
    --secondary-highlight: #00ff9d;
    --danger-color: #ff4757;
    --warning-color: #ffa502;
    --success-color: #2ed573;
    --muted-color: #747d8c;
    --flow-line-color: #333333;
    --flow-node-border: #444444;
    --frontend-color: #00ccff;
    --agent-color: #00ff9d;
    --mcp-color: #ff9f43;
    --google-color: #ff6b81;
}

body {
    background-color: var(--bg-color);
    color: var(--text-color);
    font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
    margin: 0;
    padding: 0;
    min-height: 100vh;
}

.container-fluid {
    padding: 20px;
}

/* Header Styles */
.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 0;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--panel-border);
}

.logo-container h1 {
    margin: 0;
    font-size: 1.8rem;
    color: var(--highlight-color);
}

.subtitle {
    color: var(--muted-color);
    margin-top: 5px;
}

.status-indicator {
    display: flex;
    align-items: center;
}

.status-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background-color: var(--warning-color);
    margin-right: 8px;
    display: inline-block;
    transition: background-color 0.3s ease;
}

.status-dot.connected {
    background-color: var(--success-color);
}

.status-dot.error {
    background-color: var(--danger-color);
}

/* Panel Styles */
.panel {
    background-color: var(--panel-bg);
    border-radius: 8px;
    border: 1px solid var(--panel-border);
    padding: 20px;
    margin-bottom: 20px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    height: calc(100vh - 150px);
    overflow-y: auto;
}

.panel h2 {
    color: var(--secondary-highlight);
    margin-top: 0;
    margin-bottom: 20px;
    font-size: 1.4rem;
    border-bottom: 1px solid var(--panel-border);
    padding-bottom: 10px;
}

/* Form Controls */
.form-group {
    margin-bottom: 20px;
}

.form-control {
    background-color: #2a2a2a;
    border: 1px solid var(--panel-border);
    color: var(--text-color);
    border-radius: 4px;
}

.form-control:focus {
    background-color: #2a2a2a;
    border-color: var(--highlight-color);
    color: var(--text-color);
    box-shadow: 0 0 0 0.25rem rgba(0, 204, 255, 0.25);
}

.btn-primary {
    background-color: var(--highlight-color);
    border-color: var(--highlight-color);
    color: #000;
    font-weight: 500;
}

.btn-primary:hover {
    background-color: #00a3cc;
    border-color: #00a3cc;
    color: #000;
}

.btn-outline-secondary {
    color: var(--text-color);
    border-color: var(--panel-border);
}

.btn-outline-secondary:hover {
    background-color: #2a2a2a;
    color: var(--highlight-color);
    border-color: var(--highlight-color);
}

.btn-outline-info, .btn-outline-warning {
    color: var(--text-color);
}

.demo-actions {
    margin-top: 30px;
}

.demo-actions h3 {
    font-size: 1.1rem;
    margin-bottom: 15px;
    color: var(--muted-color);
}

.demo-btn {
    margin-bottom: 10px;
    display: block;
    width: 100%;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Flow Visualization */
.flow-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding-top: 20px;
}

.flow-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 20px 0;
    position: relative;
}

.node {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 10px 0;
    position: relative;
    z-index: 2;
    transition: transform 0.3s ease, filter 0.3s ease;
}

.node:hover {
    transform: scale(1.05);
}

.node-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background: var(--panel-bg);
    border: 2px solid var(--flow-node-border);
    position: relative;
    transition: all 0.3s ease;
    overflow: hidden;
}

/* Add highlighted state */
.node.highlighted .node-icon {
    transform: scale(1.1);
    border-width: 3px;
}

/* Active state */
.node.active .node-icon {
    border-width: 3px;
    animation: pulse 1.5s infinite;
}

#frontendNode .node-icon {
    border-color: var(--frontend-color);
}

#agentNode .node-icon {
    border-color: var(--agent-color);
}

#mcpNode .node-icon {
    border-color: var(--mcp-color);
}

#googleNode .node-icon {
    border-color: var(--google-color);
}

#frontendNode.active .node-icon {
    border-color: var(--frontend-color);
    box-shadow: 0 0 15px var(--frontend-color);
}

#agentNode.active .node-icon {
    border-color: var(--agent-color);
    box-shadow: 0 0 15px var(--agent-color);
}

#mcpNode.active .node-icon {
    border-color: var(--mcp-color);
    box-shadow: 0 0 15px var(--mcp-color);
}

#googleNode.active .node-icon {
    border-color: var(--google-color);
    box-shadow: 0 0 15px var(--google-color);
}

.node-glow {
    position: absolute;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background: transparent;
    z-index: 1;
}

.node-label {
    margin-top: 8px;
    font-size: 0.9rem;
    color: var(--muted-color);
    transition: color 0.3s ease;
}

.active .node-label {
    color: var(--text-color);
    font-weight: bold;
}

.highlighted .node-label {
    color: var(--highlight-color);
}

.flow-line {
    width: 3px;
    height: 80px;
    background-color: var(--flow-line-color);
    position: relative;
    z-index: 1;
    transition: all 0.3s ease;
}

.flow-line.active {
    background-color: var(--highlight-color);
    box-shadow: 0 0 10px 2px var(--highlight-color);
    animation: lineGlow 1.5s infinite;
}

.flow-particle-container {
    position: absolute;
    top: 0;
    left: -2px;
    width: 7px;
    height: 100%;
    overflow: visible;
}

.flow-particle {
    border-radius: 50%;
    position: absolute;
    transition: top 0.8s ease-out, opacity 0.8s ease-out;
}

/* Error state */
.node.error .node-icon {
    border-color: var(--danger-color);
    box-shadow: 0 0 15px var(--danger-color);
    animation: errorPulse 1.5s infinite;
}

/* Result Panel */
.result-panel {
    height: calc(100vh - 150px);
    display: flex;
    flex-direction: column;
}

.form-links {
    margin: 15px 0;
}

.form-links a {
    margin-right: 10px;
}

#formResult {
    background-color: #252525;
    border-radius: 6px;
    padding: 15px;
    margin-bottom: 20px;
}

#formResult.hidden {
    display: none;
}

#formTitle {
    font-size: 1.2rem;
    margin-top: 0;
    color: var(--secondary-highlight);
}

.log-container {
    background-color: #1a1a1a;
    border-radius: 6px;
    padding: 10px;
    height: 100%;
    overflow-y: auto;
    font-family: 'Consolas', monospace;
    font-size: 0.8rem;
}

.packet-entry {
    margin-bottom: 15px;
    border-bottom: 1px solid var(--panel-border);
    padding-bottom: 10px;
}

.packet-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 5px;
    font-size: 0.7rem;
}

.transaction-id {
    color: var(--highlight-color);
}

.packet-time {
    color: var(--muted-color);
}

.packet-direction {
    font-weight: bold;
}

.packet-direction.request {
    color: var(--secondary-highlight);
}

.packet-direction.response {
    color: var(--warning-color);
}

.packet-content {
    margin: 0;
    white-space: pre-wrap;
    color: #bbb;
    font-size: 0.7rem;
    max-height: 200px;
    overflow-y: auto;
}

/* Animations */
@keyframes pulse {
    0% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(0, 204, 255, 0.7);
    }
    50% {
        transform: scale(1.05);
        box-shadow: 0 0 0 10px rgba(0, 204, 255, 0);
    }
    100% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(0, 204, 255, 0.7);
    }
}

@keyframes errorPulse {
    0% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(255, 71, 87, 0.7);
    }
    50% {
        transform: scale(1.05);
        box-shadow: 0 0 0 10px rgba(255, 71, 87, 0);
    }
    100% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(255, 71, 87, 0.7);
    }
}

@keyframes lineGlow {
    0% {
        opacity: 0.7;
        box-shadow: 0 0 5px 1px var(--highlight-color);
    }
    50% {
        opacity: 1;
        box-shadow: 0 0 10px 2px var(--highlight-color);
    }
    100% {
        opacity: 0.7;
        box-shadow: 0 0 5px 1px var(--highlight-color);
    }
}

.active .node-icon {
    border-color: var(--highlight-color);
}

.active .node-glow {
    animation: glow 1.5s infinite;
}

.node.pulse {
    animation: nodePulse 1s infinite;
}

/* List group customization */
.list-group-item {
    background-color: #252525;
    color: var(--text-color);
    border-color: var(--panel-border);
}

.packet-log-entry .packet-direction.agent-step {
    background-color: #e8f0fe; /* Light blue background */
    color: #1a73e8; /* Google blue text */
    border: 1px solid #d2e3fc;
}

.packet-log-entry .packet-direction.user-request {
    background-color: #fef7e0; /* Light yellow */
    color: #ea8600; /* Amber */
    border: 1px solid #fcefc9;
}

.packet-log-entry .packet-direction.error-log {
    background-color: #fce8e6; /* Light red */
    color: #d93025; /* Google red */
    border: 1px solid #f9d6d3;
}

/* Add styles for the stage indicator */
.stage-indicator {
    background-color: #252525;
    border-radius: 4px;
    padding: 10px;
    margin-bottom: 15px;
    text-align: center;
    border: 1px solid var(--panel-border);
}

#stageIndicator {
    font-weight: 500;
    color: var(--highlight-color);
}

/* Add styles for highlighted nodes */
.node.highlighted {
    transform: scale(1.08);
}

.node.highlighted .node-icon {
    border-width: 3px;
    border-color: var(--highlight-color);
    box-shadow: 0 0 10px var(--highlight-color);
}

.node.highlighted .node-label {
    color: var(--highlight-color);
    font-weight: 500;
}

```

--------------------------------------------------------------------------------
/server/static/animations.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Flow Animation Module for Google Forms MCP Server
 * Manages the visual animations of data flowing between components
 */

class FlowAnimator {
    constructor() {
        // Flow nodes
        this.nodes = {
            frontend: document.getElementById('frontendNode'),
            agent: document.getElementById('agentNode'),
            mcp: document.getElementById('mcpNode'),
            google: document.getElementById('googleNode')
        };
        
        // Flow lines
        this.lines = {
            frontendToAgent: document.getElementById('frontendToAgent'),
            agentToMCP: document.getElementById('agentToMCP'),
            mcpToGoogle: document.getElementById('mcpToGoogle')
        };
        
        // Particle colors
        this.colors = {
            outgoing: '#00ccff', // Cyan for outgoing requests
            incoming: '#00ff9d', // Green for incoming responses
            error: '#ff4757'     // Red for errors
        };
        
        // Animation state
        this.activeAnimations = [];
        this.isAnimating = false;
        this.currentActiveNode = null;
        this.currentActiveLine = null;
        
        // Animation intervals for continuous particle generation
        this.particleIntervals = {};
    }
    
    /**
     * Activate a node with a glowing effect
     * @param {string} nodeName - The name of the node to activate
     */
    activateNode(nodeName) {
        if (this.nodes[nodeName]) {
            // Deactivate previous node if exists
            if (this.currentActiveNode && this.nodes[this.currentActiveNode]) {
                this.nodes[this.currentActiveNode].classList.remove('active');
            }
            
            // Add active class to the node
            this.nodes[nodeName].classList.add('active');
            this.currentActiveNode = nodeName;
            
            // Return a cleanup function
            return () => {
                // Only remove active class if this is still the current active node
                if (this.currentActiveNode === nodeName) {
                    this.nodes[nodeName].classList.remove('active');
                    this.currentActiveNode = null;
                }
            };
        }
        return () => {};
    }
    
    /**
     * Activate a flow line
     * @param {string} lineName - The name of the line to activate
     */
    activateLine(lineName) {
        if (this.lines[lineName]) {
            // Deactivate previous line if exists
            if (this.currentActiveLine && this.lines[this.currentActiveLine]) {
                this.lines[this.currentActiveLine].classList.remove('active');
            }
            
            // Add active class to the line
            this.lines[lineName].classList.add('active');
            this.currentActiveLine = lineName;
            
            // Return a cleanup function
            return () => {
                // Only remove active class if this is still the current active line
                if (this.currentActiveLine === lineName) {
                    this.lines[lineName].classList.remove('active');
                    this.currentActiveLine = null;
                }
            };
        }
        return () => {};
    }
    
    /**
     * Create and animate a particle flowing through a line
     * @param {string} lineName - The name of the line to animate
     * @param {string} direction - 'outgoing' or 'incoming' to determine color and direction
     * @param {number} duration - Animation duration in milliseconds
     */
    createParticle(lineName, direction, duration = 800) {
        const line = this.lines[lineName];
        if (!line) return null;
        
        const container = line.querySelector('.flow-particle-container');
        if (!container) return null;
        
        // Create particle element
        const particle = document.createElement('div');
        particle.className = 'flow-particle';
        particle.style.position = 'absolute';
        particle.style.width = '7px';
        particle.style.height = '7px';
        particle.style.borderRadius = '50%';
        particle.style.backgroundColor = this.colors[direction] || this.colors.outgoing;
        particle.style.boxShadow = `0 0 8px 2px ${this.colors[direction] || this.colors.outgoing}`;
        
        // Set starting position based on direction
        if (direction === 'incoming') {
            particle.style.top = 'calc(100% - 7px)';
            particle.style.transform = 'translateY(0)';
        } else {
            particle.style.top = '0';
            particle.style.transform = 'translateY(0)';
        }
        
        // Add particle to container
        container.appendChild(particle);
        
        // Animate the particle
        const animation = particle.animate([
            { 
                top: direction === 'incoming' ? 'calc(100% - 7px)' : '0',
                opacity: 1 
            },
            { 
                top: direction === 'incoming' ? '0' : 'calc(100% - 7px)',
                opacity: 0.8 
            }
        ], {
            duration: duration,
            easing: 'ease-out',
            fill: 'forwards'
        });
        
        // Remove particle when animation completes
        animation.onfinish = () => {
            if (container.contains(particle)) {
                container.removeChild(particle);
            }
        };
        
        return animation;
    }
    
    /**
     * Start continuous particle animation on a line
     * @param {string} lineName - The line to animate
     * @param {string} direction - Direction of flow
     */
    startContinuousParticles(lineName, direction) {
        // Clear any existing interval for this line
        this.stopContinuousParticles(lineName);
        
        // Create new interval
        const interval = setInterval(() => {
            this.createParticle(lineName, direction, 800);
        }, 200);
        
        // Store the interval
        this.particleIntervals[lineName] = interval;
    }
    
    /**
     * Stop continuous particle animation on a line
     * @param {string} lineName - The line to stop animating
     */
    stopContinuousParticles(lineName) {
        if (this.particleIntervals[lineName]) {
            clearInterval(this.particleIntervals[lineName]);
            delete this.particleIntervals[lineName];
            
            // Clear any remaining particles
            const line = this.lines[lineName];
            if (line) {
                const container = line.querySelector('.flow-particle-container');
                if (container) {
                    container.innerHTML = '';
                }
            }
        }
    }
    
    /**
     * Stop all continuous particle animations
     */
    stopAllContinuousParticles() {
        Object.keys(this.particleIntervals).forEach(lineName => {
            this.stopContinuousParticles(lineName);
        });
    }
    
    /**
     * Animate flow from one node to another
     * @param {string} fromNode - Source node name
     * @param {string} toNode - Target node name
     * @param {string} direction - 'outgoing' or 'incoming'
     * @returns {Promise} - Resolves when animation completes
     */
    async animateFlow(fromNode, toNode, direction = 'outgoing') {
        // Define flow paths
        const flowPaths = {
            'frontend-agent': 'frontendToAgent',
            'agent-mcp': 'agentToMCP',
            'mcp-google': 'mcpToGoogle',
            'google-mcp': 'mcpToGoogle',
            'mcp-agent': 'agentToMCP',
            'agent-frontend': 'frontendToAgent'
        };
        
        const pathKey = `${fromNode}-${toNode}`;
        const lineName = flowPaths[pathKey];
        
        if (!lineName) {
            console.error(`No flow path defined for ${pathKey}`);
            return;
        }
        
        // Stop any existing continuous animations
        this.stopAllContinuousParticles();
        
        // Activate source node
        const cleanupSource = this.activateNode(fromNode);
        
        // Activate the flow line
        const cleanupLine = this.activateLine(lineName);
        
        // Start continuous particles
        this.startContinuousParticles(lineName, direction);
        
        // Create promise that resolves when animation completes
        return new Promise(resolve => {
            setTimeout(() => {
                // Activate target node
                const cleanupTarget = this.activateNode(toNode);
                
                // Stop continuous particles
                this.stopContinuousParticles(lineName);
                
                // Cleanup source node after delay
                cleanupSource();
                
                // Cleanup line
                cleanupLine();
                
                // Resolve the promise
                resolve();
            }, 1500); // Longer wait to show the flow more clearly
        });
    }
    
    /**
     * Animate a complete request-response flow
     * @param {string} scenario - The flow scenario to animate
     */
    async animateRequestResponseFlow(scenario = 'form-creation') {
        // Prevent multiple animations
        if (this.isAnimating) return;
        this.isAnimating = true;
        
        try {
            // Common flow for all scenarios
            // Frontend -> Agent -> MCP -> Google -> MCP -> Agent -> Frontend
            
            // Request flow
            await this.animateFlow('frontend', 'agent', 'outgoing');
            await this.animateFlow('agent', 'mcp', 'outgoing');
            await this.animateFlow('mcp', 'google', 'outgoing');
            
            // Response flow
            await this.animateFlow('google', 'mcp', 'incoming');
            await this.animateFlow('mcp', 'agent', 'incoming');
            await this.animateFlow('agent', 'frontend', 'incoming');
            
        } catch (error) {
            console.error('Animation error:', error);
        } finally {
            this.isAnimating = false;
        }
    }
    
    /**
     * Animate a flow with an error
     * @param {string} errorStage - The stage where the error occurs
     */
    async animateErrorFlow(errorStage = 'google') {
        if (this.isAnimating) return;
        this.isAnimating = true;
        
        try {
            // Initial flow
            await this.animateFlow('frontend', 'agent', 'outgoing');
            
            if (errorStage === 'agent') {
                // Error at agent
                this.nodes.agent.classList.add('error');
                setTimeout(() => {
                    this.nodes.agent.classList.remove('error');
                    this.nodes.agent.classList.remove('active');
                }, 2000);
                return;
            }
            
            await this.animateFlow('agent', 'mcp', 'outgoing');
            
            if (errorStage === 'mcp') {
                // Error at MCP server
                this.nodes.mcp.classList.add('error');
                setTimeout(() => {
                    this.nodes.mcp.classList.remove('error');
                    this.nodes.mcp.classList.remove('active');
                }, 2000);
                return;
            }
            
            await this.animateFlow('mcp', 'google', 'outgoing');
            
            if (errorStage === 'google') {
                // Error at Google Forms API
                this.nodes.google.classList.add('error');
                setTimeout(() => {
                    this.nodes.google.classList.remove('error');
                    this.nodes.google.classList.remove('active');
                    
                    // Error response flow
                    this.animateFlow('google', 'mcp', 'error');
                    this.animateFlow('mcp', 'agent', 'error');
                    this.animateFlow('agent', 'frontend', 'error');
                }, 2000);
            }
            
        } catch (error) {
            console.error('Error animation error:', error);
        } finally {
            setTimeout(() => {
                this.isAnimating = false;
            }, 3000);
        }
    }
    
    /**
     * Reset all animations and active states
     */
    resetAll() {
        // Stop all continuous particles
        this.stopAllContinuousParticles();
        
        // Reset nodes
        Object.values(this.nodes).forEach(node => {
            node.classList.remove('active');
            node.classList.remove('error');
            node.classList.remove('pulse');
        });
        
        // Reset lines
        Object.values(this.lines).forEach(line => {
            line.classList.remove('active');
            const container = line.querySelector('.flow-particle-container');
            if (container) {
                container.innerHTML = '';
            }
        });
        
        this.currentActiveNode = null;
        this.currentActiveLine = null;
        this.isAnimating = false;
    }
    
    /**
     * Pulse animation for a specific node
     * @param {string} nodeName - Name of the node to pulse
     * @param {number} duration - Duration in milliseconds
     */
    pulseNode(nodeName, duration = 2000) {
        const node = this.nodes[nodeName];
        if (!node) return;
        
        node.classList.add('pulse');
        
        setTimeout(() => {
            node.classList.remove('pulse');
        }, duration);
    }
    
    /**
     * Highlight a specific node to show it's the current active component
     * @param {string} nodeName - Name of the node to highlight
     */
    highlightNode(nodeName) {
        // First clear any existing highlights
        Object.keys(this.nodes).forEach(name => {
            this.nodes[name].classList.remove('highlighted');
        });
        
        // Set the new highlight
        if (this.nodes[nodeName]) {
            this.nodes[nodeName].classList.add('highlighted');
        }
    }
}

// Initialize flow animator when document loads
document.addEventListener('DOMContentLoaded', function() {
    window.flowAnimator = new FlowAnimator();
    
    // For demo purposes, animate the request flow on load to demonstrate functionality
    setTimeout(() => {
        if (window.flowAnimator) {
            window.flowAnimator.animateRequestResponseFlow();
        }
    }, 2000);
});

```

--------------------------------------------------------------------------------
/server/static/main.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Google Forms MCP Server
 * Main JavaScript file for handling server interactions, UI updates, and flow visualization
 */

// State management
const state = {
    isConnected: false,
    currentForm: null,
    currentTransaction: null,
    requestInProgress: false,
    currentStage: null, // Tracks the current stage in the flow
    questions: []
};

// Stages in the flow
const STAGES = {
    IDLE: 'idle',
    FRONTEND: 'frontend',
    AGENT: 'agent',
    MCP: 'mcp',
    GOOGLE: 'google',
    COMPLETE: 'complete',
    ERROR: 'error'
};

// API endpoints
const API = {
    health: '/api/health',
    form_request: '/api/form_request',
    agent_proxy: '/api/agent_proxy',
    form_status: '/api/form_status'
};

// DOM Elements
const elements = {
    statusDot: document.getElementById('statusDot'),
    statusText: document.getElementById('statusText'),
    requestInput: document.getElementById('requestInput'),
    sendRequestBtn: document.getElementById('sendRequestBtn'),
    formResult: document.getElementById('formResult'),
    formTitle: document.getElementById('formTitle'),
    formViewLink: document.getElementById('formViewLink'),
    formEditLink: document.getElementById('formEditLink'),
    questionsList: document.getElementById('questionsList'),
    packetLog: document.getElementById('packetLog'),
    demoBtns: document.querySelectorAll('.demo-btn'),
    stageIndicator: document.getElementById('stageIndicator')
};

// Templates
const templates = {
    packetEntry: document.getElementById('packetTemplate')
};

/**
 * Initialize the application
 */
function init() {
    // Set up event listeners
    setupEventListeners();
    
    // Check server connection
    checkServerConnection();
    
    // Initialize stage
    updateStage(STAGES.IDLE);
    
    // Pulse frontend node on initial load to indicate entry point
    setTimeout(() => {
        if (window.flowAnimator) {
            window.flowAnimator.pulseNode('frontend', 3000);
        }
    }, 1000);
}

/**
 * Update the current stage in the flow
 * @param {string} stage - The current stage
 */
function updateStage(stage) {
    state.currentStage = stage;
    
    // Update visual indication of current stage
    if (window.flowAnimator) {
        // Highlight the appropriate node based on stage
        switch (stage) {
            case STAGES.FRONTEND:
                window.flowAnimator.highlightNode('frontend');
                break;
            case STAGES.AGENT:
                window.flowAnimator.highlightNode('agent');
                break;
            case STAGES.MCP:
                window.flowAnimator.highlightNode('mcp');
                break;
            case STAGES.GOOGLE:
                window.flowAnimator.highlightNode('google');
                break;
            default:
                // Clear highlights for IDLE, COMPLETE, ERROR
                Object.keys(window.flowAnimator.nodes).forEach(nodeName => {
                    window.flowAnimator.nodes[nodeName].classList.remove('highlighted');
                });
                break;
        }
    }
    
    // Update stage indicator text if present
    if (elements.stageIndicator) {
        let stageText = '';
        switch (stage) {
            case STAGES.IDLE:
                stageText = 'Ready for request';
                break;
            case STAGES.FRONTEND:
                stageText = 'Processing in frontend';
                break;
            case STAGES.AGENT:
                stageText = 'Agent processing request';
                break;
            case STAGES.MCP:
                stageText = 'MCP Server building form';
                break;
            case STAGES.GOOGLE:
                stageText = 'Interacting with Google Forms';
                break;
            case STAGES.COMPLETE:
                stageText = 'Form creation complete';
                break;
            case STAGES.ERROR:
                stageText = 'Error occurred';
                break;
            default:
                stageText = 'Processing...';
        }
        elements.stageIndicator.textContent = stageText;
    }
}

/**
 * Set up event listeners
 */
function setupEventListeners() {
    if (elements.sendRequestBtn) {
        elements.sendRequestBtn.addEventListener('click', handleSendRequest);
    }
    
    // Demo buttons
    elements.demoBtns.forEach(btn => {
        btn.addEventListener('click', function() {
            const requestText = this.getAttribute('data-request');
            if (elements.requestInput && requestText) {
                elements.requestInput.value = requestText;
                // Automatically trigger request after a short delay
                setTimeout(() => {
                    handleSendRequest();
                }, 500);
            }
        });
    });
}

/**
 * Check if server is connected
 */
async function checkServerConnection() {
    try {
        const response = await fetch(API.health);
        if (response.ok) {
            const data = await response.json();
            updateConnectionStatus(true, `Connected (v${data.version})`);
            return true;
        } else {
            updateConnectionStatus(false, 'Server Error');
            return false;
        }
    } catch (error) {
        console.error('Server connection error:', error);
        updateConnectionStatus(false, 'Disconnected');
        return false;
    }
}

/**
 * Update connection status indicator
 */
function updateConnectionStatus(isConnected, statusMessage) {
    state.isConnected = isConnected;
    
    if (elements.statusDot) {
        elements.statusDot.className = 'status-dot ' + (isConnected ? 'connected' : 'error');
    }
    
    if (elements.statusText) {
        elements.statusText.textContent = statusMessage || (isConnected ? 'Connected' : 'Disconnected');
    }
}

/**
 * Handle the form request submission
 */
async function handleSendRequest() {
    // Validation & state check
    if (!elements.requestInput || !elements.requestInput.value.trim()) {
        alert('Please enter a request');
        return;
    }
    
    if (state.requestInProgress) {
        return;
    }
    
    // Reset any previous results
    resetResults();
    
    // Update UI
    state.requestInProgress = true;
    elements.sendRequestBtn.disabled = true;
    elements.sendRequestBtn.textContent = 'Processing...';
    
    // Get the request text
    const requestText = elements.requestInput.value.trim();
    
    // Log the user request
    logPacket({ request_text: requestText }, 'User Request');
    
    try {
        // Start at frontend
        updateStage(STAGES.FRONTEND);
        
        // Pulse frontend node to start the flow
        window.flowAnimator.pulseNode('frontend', 1000);
        
        // Animate the request flow from frontend to agent
        await window.flowAnimator.animateFlow('frontend', 'agent', 'outgoing');
        
        // Update stage to agent
        updateStage(STAGES.AGENT);
        
        // Process the request with the agent
        const agentResponse = await processWithAgent(requestText);
        
        // Log agent proxy response
        logPacket(agentResponse, 'Agent Processing');
        
        if (agentResponse.status === 'error') {
            // Show error animation in the flow diagram
            window.flowAnimator.animateErrorFlow('agent');
            updateStage(STAGES.ERROR);
            throw new Error(agentResponse.message || 'Agent processing failed');
        }
        
        // Continue flow to MCP server
        await window.flowAnimator.animateFlow('agent', 'mcp', 'outgoing');
        updateStage(STAGES.MCP);
        
        // Simulate MCP server processing time
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        // Continue flow to Google Forms
        await window.flowAnimator.animateFlow('mcp', 'google', 'outgoing');
        updateStage(STAGES.GOOGLE);
        
        // Simulate Google Forms API interaction time
        await new Promise(resolve => setTimeout(resolve, 1500));
        
        // Begin response flow
        // From Google back to MCP
        await window.flowAnimator.animateFlow('google', 'mcp', 'incoming');
        updateStage(STAGES.MCP);
        
        // From MCP back to agent
        await window.flowAnimator.animateFlow('mcp', 'agent', 'incoming');
        updateStage(STAGES.AGENT);
        
        // Final response from agent to frontend
        await window.flowAnimator.animateFlow('agent', 'frontend', 'incoming');
        updateStage(STAGES.FRONTEND);
        
        // Check agent response status
        if (agentResponse.status === 'success' && agentResponse.result) {
            if (agentResponse.result.form_id) { 
                // Log the final successful response from the agent/MCP flow
                logPacket(agentResponse, 'Final Response');
                
                // Update the UI with the final form details
                updateFormResult(agentResponse.result, agentResponse.result.questions || []); 
                
                // Update stage to complete
                updateStage(STAGES.COMPLETE);
            } else {
                logPacket(agentResponse, 'Agent Response (No Form)');
                alert('Agent processed the request, but no form details were returned.');
                updateStage(STAGES.ERROR);
            }
        } else {
            // Log the error response from the agent
            logPacket(agentResponse, 'Agent Error');
            
            // Show error animation in the flow diagram
            window.flowAnimator.animateErrorFlow('agent');
            updateStage(STAGES.ERROR);
            
            throw new Error(agentResponse.message || 'Agent processing failed');
        }
        
    } catch (error) {
        console.error('Error during form creation process:', error);
        alert(`An error occurred: ${error.message}`);
        // Log error packet if possible
        logPacket({ error: error.message }, 'Processing Error');
        updateStage(STAGES.ERROR);
    } finally {
        elements.sendRequestBtn.disabled = false;
        elements.sendRequestBtn.textContent = 'Process Request';
        state.requestInProgress = false;
        
        // Reset flow animator if there was an error
        if (state.currentStage === STAGES.ERROR) {
            setTimeout(() => {
                if (window.flowAnimator) {
                    window.flowAnimator.resetAll();
                    updateStage(STAGES.IDLE);
                }
            }, 3000);
        }
    }
}

/**
 * Sends the natural language request to the agent server.
 * @param {string} requestText - The raw natural language text.
 * @returns {Promise<Object>} - The response from the agent server.
 */
async function processWithAgent(requestText) {
    console.log(`Sending request via proxy to agent: ${requestText}`); // Debug log
    try {
        const response = await fetch(API.agent_proxy, { // Use the PROXY endpoint
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ request_text: requestText })
        });

        // Log raw response status
        console.log(`Agent response status: ${response.status}`);

        if (!response.ok) {
            let errorData;
            try {
                errorData = await response.json();
            } catch (e) {
                errorData = { message: await response.text() || 'Failed to parse error response' };
            }
            console.error('Agent API Error:', response.status, errorData);
            // Try to construct a meaningful error message
            let errorMsg = `Agent request failed: ${response.status} ${response.statusText || ''}`;
            if (errorData && errorData.message) {
                errorMsg += ` - ${errorData.message}`;
            }
            throw new Error(errorMsg);
        }
        
        const responseData = await response.json();
        console.log('Agent response data:', responseData); // Debug log
        return responseData;

    } catch (error) {
        console.error('Error sending request to agent:', error);
        // Return a structured error object for the UI handler
        return { 
            status: 'error', 
            message: error.message || 'Failed to communicate with agent server'
        };
    }
}

/**
 * Create an MCP packet for a tool call
 * @param {string} toolName - Name of the MCP tool to call
 * @param {Object} parameters - Parameters for the tool
 * @returns {Object} - Formatted MCP packet
 */
function createMCPPacket(toolName, parameters) {
    return {
        transaction_id: 'tx_' + Math.random().toString(36).substr(2, 9),
        tool_name: toolName,
        parameters: parameters
    };
}

/**
 * Log an MCP packet or Agent Step to the UI
 * @param {Object} item - The packet or log entry object
 * @param {string} type - 'MCP Request', 'MCP Response', 'Agent Step', etc.
 */
function logItem(item, type) {
    // Clone the template
    const template = templates.packetEntry.content.cloneNode(true);
    
    // Set the data
    template.querySelector('.transaction-id').textContent = item.transaction_id || item.step_type || 'N/A';
    template.querySelector('.packet-time').textContent = item.timestamp ? new Date(item.timestamp).toLocaleTimeString() : new Date().toLocaleTimeString();
    
    const directionEl = template.querySelector('.packet-direction');
    directionEl.textContent = type;
    
    // Add specific classes for styling
    if (type.includes('Request')) {
        directionEl.classList.add('request');
    } else if (type.includes('Response')) {
        directionEl.classList.add('response');
    } else if (type.includes('Agent')) {
         directionEl.classList.add('agent-step'); // Add a class for agent steps
    } else if (type.includes('User')) {
         directionEl.classList.add('user-request');
    } else if (type.includes('Error')) {
         directionEl.classList.add('error-log');
    }
    
    // Format the content (use item.data for agent steps)
    const contentToDisplay = type === 'Agent Step' ? item.data : item;
    template.querySelector('.packet-content').textContent = JSON.stringify(contentToDisplay, null, 2);
    
    // Add to the log
    elements.packetLog.prepend(template);
}

/**
 * Logs agent processing steps provided by the backend.
 * @param {Array<Object>} logEntries - Array of log entry objects from the agent.
 */
function logAgentSteps(logEntries) {
    // Log entries in reverse order so newest appear first in the UI log
    // Or log them sequentially as they happened?
    // Let's log sequentially as they happened for chronological understanding.
    logEntries.forEach(entry => {
        logItem(entry, 'Agent Step');
    });
}

/**
 * Modify the old logPacket function to use logItem
 */
function logPacket(packet, direction) {
    logItem(packet, direction);
}

/**
 * Reset the results UI
 */
function resetResults() {
    elements.formResult.classList.add('hidden');
    elements.questionsList.innerHTML = '';
    state.currentForm = null;
    state.currentTransaction = null;
    state.questions = [];
    
    // Reset animation state
    if (window.flowAnimator) {
        window.flowAnimator.resetAll();
    }
    
    // Reset to idle state
    updateStage(STAGES.IDLE);
}

/**
 * Update the form result UI
 * @param {Object} formData - Form data from the API
 * @param {Array} questions - Questions to display
 */
function updateFormResult(formData, questions) {
    state.currentForm = formData;
    
    elements.formTitle.textContent = formData.title;
    elements.formViewLink.href = formData.response_url;
    elements.formEditLink.href = formData.edit_url;
    
    // Add questions to the list
    elements.questionsList.innerHTML = '';
    questions.forEach(question => {
        const li = document.createElement('li');
        li.className = 'list-group-item';
        
        let questionText = `<strong>${question.title}</strong><br>`;
        questionText += `Type: ${question.type}`;
        
        if (question.options && question.options.length > 0) {
            questionText += `<br>Options: ${question.options.join(', ')}`;
        }
        
        li.innerHTML = questionText;
        elements.questionsList.appendChild(li);
    });
    
    // Show the form result
    elements.formResult.classList.remove('hidden');
    
    // Pulse frontend node to indicate completion
    window.flowAnimator.pulseNode('frontend');
}

// Initialize the application when the DOM is loaded
document.addEventListener('DOMContentLoaded', init);

```

--------------------------------------------------------------------------------
/server/forms_api.py:
--------------------------------------------------------------------------------

```python
import google.oauth2.credentials
from googleapiclient.discovery import build
from google.oauth2 import service_account
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import json
import config

class GoogleFormsAPI:
    """
    Handler for Google Forms API operations.
    Handles authentication and provides methods to create forms, add questions, and get responses.
    """
    
    def __init__(self):
        self.credentials = self._get_credentials()
        self.forms_service = self._build_service('forms', 'v1')
        self.drive_service = self._build_service('drive', 'v3')
    
    def _get_credentials(self):
        """Create OAuth2 credentials from environment variables."""
        try:
            print("DEBUG: Creating credentials")
            print(f"DEBUG: Client ID: {config.GOOGLE_CLIENT_ID[:10]}...")
            print(f"DEBUG: Client Secret: {config.GOOGLE_CLIENT_SECRET[:10]}...")
            print(f"DEBUG: Refresh Token: {config.GOOGLE_REFRESH_TOKEN[:15]}...")
            print(f"DEBUG: Scopes: {config.SCOPES}")
            
            credentials = google.oauth2.credentials.Credentials(
                token=None,  # We don't have a token yet
                refresh_token=config.GOOGLE_REFRESH_TOKEN,
                client_id=config.GOOGLE_CLIENT_ID,
                client_secret=config.GOOGLE_CLIENT_SECRET,
                token_uri='https://oauth2.googleapis.com/token',
                scopes=[]  # Start with empty scopes to avoid validation during refresh
            )
            
            # Try to validate the credentials
            print("DEBUG: Validating credentials...")
            credentials.refresh(Request())
            print(f"DEBUG: Credentials valid and refreshed! Token valid until: {credentials.expiry}")
            
            # Add scopes after successful refresh
            credentials = google.oauth2.credentials.Credentials(
                token=credentials.token,
                refresh_token=credentials.refresh_token,
                client_id=credentials.client_id,
                client_secret=credentials.client_secret,
                token_uri=credentials.token_uri,
                scopes=config.SCOPES
            )
            
            return credentials
        except Exception as e:
            print(f"DEBUG: Credentials error: {str(e)}")
            raise
    
    def _build_service(self, api_name, version):
        """Build and return a Google API service."""
        try:
            print(f"DEBUG: Building {api_name} service v{version}")
            service = build(api_name, version, credentials=self.credentials)
            print(f"DEBUG: Successfully built {api_name} service")
            return service
        except Exception as e:
            print(f"DEBUG: Error building {api_name} service: {str(e)}")
            raise
    
    def create_form(self, title, description=""):
        """
        Create a new Google Form.
        
        Args:
            title: Title of the form
            description: Optional description for the form
            
        Returns:
            dict: Response containing form ID and edit URL
        """
        try:
            # Debug info
            print("DEBUG: Starting form creation")
            print(f"DEBUG: Using client_id: {config.GOOGLE_CLIENT_ID[:10]}...")
            print(f"DEBUG: Using refresh_token: {config.GOOGLE_REFRESH_TOKEN[:10]}...")

            # Create a simpler form body with ONLY title as required by the API
            form_body = {
                "info": {
                    "title": title
                }
            }
            
            # Debug info
            print("DEBUG: About to create form")
            print("DEBUG: Form body: " + str(form_body))
            
            try:
                form = self.forms_service.forms().create(body=form_body).execute()
                form_id = form['formId']
                print(f"DEBUG: Form created successfully with ID: {form_id}")
                print(f"DEBUG: Full form creation response: {json.dumps(form, indent=2)}") # Log full response
                
                # Get the actual URLs from the form response
                initial_responder_uri = form.get('responderUri')
                print(f"DEBUG: Initial responderUri from create response: {initial_responder_uri}")
                
                edit_url = f"https://docs.google.com/forms/d/{form_id}/edit" # Edit URL format is consistent
                print(f"DEBUG: Tentative Edit URL: {edit_url}")
                
                # If description is provided, update the form with it
                if description:
                    print("DEBUG: Adding description through batchUpdate")
                    update_body = {
                        "requests": [
                            {
                                "updateFormInfo": {
                                    "info": {
                                        "description": description
                                    },
                                    "updateMask": "description"
                                }
                            }
                        ]
                    }
                    self.forms_service.forms().batchUpdate(
                        formId=form_id,
                        body=update_body
                    ).execute()
                    print("DEBUG: Description added successfully")
                
                # Update form settings to make it public and collectable
                print("DEBUG: Setting form settings to make it public")
                settings_body = {
                    "requests": [
                        {
                            "updateSettings": {
                                "settings": {
                                    "quizSettings": {
                                        "isQuiz": False
                                    }
                                },
                                "updateMask": "quizSettings.isQuiz"
                            }
                        }
                    ]
                }
                settings_response = self.forms_service.forms().batchUpdate(
                    formId=form_id,
                    body=settings_body
                ).execute()
                print("DEBUG: Form settings updated")
                print(f"DEBUG: Full settings update response: {json.dumps(settings_response, indent=2)}") # Log full response
                
                # Check if the settings response has responderUri
                settings_responder_uri = None
                if 'form' in settings_response and 'responderUri' in settings_response['form']:
                    settings_responder_uri = settings_response['form']['responderUri']
                    form['responderUri'] = settings_responder_uri # Update form dict if found
                    print(f"DEBUG: Found responderUri in settings response: {settings_responder_uri}")
                
                # Explicitly publish the form to force it to be visible - These might be redundant/incorrect
                # response_url = f"https://docs.google.com/forms/d/{form_id}/viewform"
                # edit_url = f"https://docs.google.com/forms/d/{form_id}/edit"
                
            except Exception as form_error:
                print(f"DEBUG: Form creation error: {str(form_error)}")
                # Create a mock form for testing
                form = {
                    'formId': 'form_error_state',
                    'responderUri': 'https://docs.google.com/forms/d/e/form_error_state/viewform' # Use /e/ format
                }
                form_id = form['formId']
                print("DEBUG: Created mock form for testing")
                
            # Make the form public via Drive API - only if we have a real form
            try:
                if form_id != 'form_error_state':
                    print(f"DEBUG: About to set Drive permissions for form {form_id}")
                    
                    # First try to get file to verify it exists in Drive
                    try:
                        file_check = self.drive_service.files().get(
                            fileId=form_id,
                            fields="id,name,permissions,webViewLink,webContentLink" # Added webContentLink just in case
                        ).execute()
                        print(f"DEBUG: File exists in Drive: {file_check.get('name', 'unknown')}")
                        print(f"DEBUG: Full Drive file get response: {json.dumps(file_check, indent=2)}") # Log full response
                        
                        # Store the web view link for later use
                        drive_web_view_link = file_check.get('webViewLink')
                        if drive_web_view_link:
                            print(f"DEBUG: Drive webViewLink found: {drive_web_view_link}")
                    except Exception as file_error:
                        print(f"DEBUG: Cannot find/get file in Drive: {str(file_error)}")
                        drive_web_view_link = None # Ensure it's None if error occurs
                    
                    # Set public permission
                    permission = {
                        'type': 'anyone',
                        'role': 'reader',
                        'allowFileDiscovery': True
                    }
                    perm_result = self.drive_service.permissions().create(
                        fileId=form_id,
                        body=permission,
                        fields='id',
                        sendNotificationEmail=False
                    ).execute()
                    print(f"DEBUG: Permissions set successfully: {perm_result}")
                    print(f"DEBUG: Full permissions create response: {json.dumps(perm_result, indent=2)}") # Log full response
                    
                    # Check permissions after setting
                    permissions = self.drive_service.permissions().list(
                        fileId=form_id,
                         fields="*" # Get all fields
                    ).execute()
                    print(f"DEBUG: Full permissions list response after setting: {json.dumps(permissions, indent=2)}") # Log full response
                    
                    # Try to publish the file using the Drive API - This might be unnecessary/problematic
                    try:
                        publish_body = {
                            'published': True,
                            'publishedOutsideDomain': True,
                            'publishAuto': True
                        }
                        self.drive_service.revisions().update(
                            fileId=form_id,
                            revisionId='head',
                            body=publish_body
                        ).execute()
                        print("DEBUG: Form published successfully via Drive API")
                    except Exception as publish_error:
                        print(f"DEBUG: Non-critical publish error: {str(publish_error)}")
            except Exception as perm_error:
                print(f"DEBUG: Permission error: {str(perm_error)}")
                # Continue even if permission setting fails
            
            # Determine the final response_url based on availability and priority
            # Priority: responderUri from settings, initial responderUri, Drive webViewLink (less reliable for view), fallback
            response_url = None
            if settings_responder_uri:
                 response_url = settings_responder_uri
                 print(f"DEBUG: FINAL URL: Using responderUri from settings response: {response_url}")
            elif initial_responder_uri:
                response_url = initial_responder_uri
                print(f"DEBUG: FINAL URL: Using initial responderUri from create response: {response_url}")
            elif drive_web_view_link and "/viewform" in drive_web_view_link: # Only use webViewLink if it looks like a view link
                response_url = drive_web_view_link
                print(f"DEBUG: FINAL URL: Using Drive webViewLink (as it contained /viewform): {response_url}")
            else:
                # Fallback to manual construction if absolutely nothing else is found
                response_url = f"https://docs.google.com/forms/d/e/{form_id}/viewform" # Use /e/ format for fallback
                print(f"DEBUG: FINAL URL: Using manually constructed fallback (/e/ format): {response_url}")
                # Also log the potentially problematic webViewLink if it existed but wasn't used
                if drive_web_view_link:
                    print(f"DEBUG: Note: Drive webViewLink found but not used as final URL: {drive_web_view_link}")

            # Ensure edit_url is correctly set (it's usually stable)
            edit_url = f"https://docs.google.com/forms/d/{form_id}/edit"
            print(f"DEBUG: FINAL Edit URL: {edit_url}")
            
            return {
                "form_id": form_id,
                "response_url": response_url,
                "edit_url": edit_url,
                "title": title
            }
        except Exception as e:
            print(f"Error creating form: {str(e)}")
            raise
    
    def add_question(self, form_id, question_type, title, options=None, required=False):
        """
        Add a question to an existing Google Form.
        
        Args:
            form_id: ID of the form to add the question to
            question_type: Type of question (text, paragraph, multiple_choice, etc.)
            title: Question title/text
            options: List of options for multiple choice questions
            required: Whether the question is required
            
        Returns:
            dict: Response containing question ID
        """
        try:
            print(f"DEBUG: Adding {question_type} question to form {form_id}")
            # Get the current form
            form = self.forms_service.forms().get(formId=form_id).execute()
            
            # Determine the item ID for the new question
            item_id = len(form.get('items', []))
            print(f"DEBUG: New question will have item_id: {item_id}")
            
            # Create base request
            request = {
                "requests": [{
                    "createItem": {
                        "item": {
                            "title": title,
                            "questionItem": {
                                "question": {
                                    "required": required
                                }
                            }
                        },
                        "location": {
                            "index": item_id
                        }
                    }
                }]
            }
            
            # Set up question type specific configuration
            if question_type == "text":
                print("DEBUG: Setting up text question")
                request["requests"][0]["createItem"]["item"]["questionItem"]["question"]["textQuestion"] = {}
            
            elif question_type == "paragraph":
                print("DEBUG: Setting up paragraph question")
                # Google Forms API uses textQuestion with different properties for paragraphs
                request["requests"][0]["createItem"]["item"]["questionItem"]["question"]["textQuestion"] = {
                    "paragraph": True
                }
            
            elif question_type == "multiple_choice" and options:
                print(f"DEBUG: Setting up multiple choice question with {len(options)} options")
                choices = [{"value": option} for option in options]
                request["requests"][0]["createItem"]["item"]["questionItem"]["question"]["choiceQuestion"] = {
                    "type": "RADIO",
                    "options": choices,
                    "shuffle": False
                }
            
            elif question_type == "checkbox" and options:
                print(f"DEBUG: Setting up checkbox question with {len(options)} options")
                choices = [{"value": option} for option in options]
                request["requests"][0]["createItem"]["item"]["questionItem"]["question"]["choiceQuestion"] = {
                    "type": "CHECKBOX",
                    "options": choices,
                    "shuffle": False
                }
            
            print(f"DEBUG: Request body: {request}")
            
            # Execute the request
            update_response = self.forms_service.forms().batchUpdate(
                formId=form_id, 
                body=request
            ).execute()
            print(f"DEBUG: Question added successfully: {update_response}")
            
            return {
                "form_id": form_id,
                "question_id": item_id,
                "title": title,
                "type": question_type
            }
        except Exception as e:
            print(f"Error adding question: {str(e)}")
            raise
    
    def get_responses(self, form_id):
        """
        Get responses for a Google Form.
        
        Args:
            form_id: ID of the form to get responses for
            
        Returns:
            dict: Form responses
        """
        try:
            # Get the form to retrieve question titles
            form = self.forms_service.forms().get(formId=form_id).execute()
            questions = {}
            
            for item in form.get('items', []):
                question_id = item.get('itemId', '')
                title = item.get('title', '')
                questions[question_id] = title
            
            # Get form responses
            response_data = self.forms_service.forms().responses().list(formId=form_id).execute()
            responses = response_data.get('responses', [])
            
            formatted_responses = []
            for response in responses:
                answer_data = {}
                answers = response.get('answers', {})
                
                for question_id, answer in answers.items():
                    question_title = questions.get(question_id, question_id)
                    
                    if 'textAnswers' in answer:
                        text_values = [text.get('value', '') for text in answer.get('textAnswers', {}).get('answers', [])]
                        answer_data[question_title] = text_values[0] if len(text_values) == 1 else text_values
                    
                    elif 'choiceAnswers' in answer:
                        choice_values = answer.get('choiceAnswers', {}).get('answers', [])
                        answer_data[question_title] = choice_values
                
                formatted_responses.append({
                    'response_id': response.get('responseId', ''),
                    'created_time': response.get('createTime', ''),
                    'answers': answer_data
                })
            
            return {
                "form_id": form_id,
                "form_title": form.get('info', {}).get('title', ''),
                "response_count": len(formatted_responses),
                "responses": formatted_responses
            }
        except Exception as e:
            print(f"Error getting responses: {str(e)}")
            raise

```

--------------------------------------------------------------------------------
/agents/agent_integration.py:
--------------------------------------------------------------------------------

```python
"""
CamelAIOrg Agent Integration for Google Forms MCP Server

This module provides integration with CamelAIOrg's agent framework,
enabling natural language processing to create Google Forms through MCP.
"""

import os
import json
import logging
import requests
import datetime

# REMOVE: Import our mock CamelAI implementation
# from camelai import create_agent

# Load environment variables
# load_dotenv()

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("agent_integration")

# Configuration
MCP_SERVER_URL = os.getenv('MCP_SERVER_URL', 'http://mcp-server:5000/api/process')
AGENT_API_KEY = os.getenv('AGENT_API_KEY', 'demo_key') # Might be used for a real LLM API key

# Import Camel AI components (assuming structure)
from camel.agents import ChatAgent
from camel.messages import BaseMessage
from camel.types import ModelPlatformType, ModelType, RoleType # Added RoleType
from camel.models import ModelFactory

class FormAgent:
    """
    Agent for handling natural language form creation requests.
    Uses NLP (simulated via _call_llm_agent) to process natural language
    and convert it to MCP tool calls.
    """
    
    def __init__(self):
        """Initialize the agent."""
        self.logger = logger
        self.log_entries = [] # Initialize log storage
        # REMOVE: self.camel_agent = create_agent("FormAgent", ...)
        self.logger.info("FormAgent initialized (using simulated LLM)")

    def _log_step(self, step_type, data):
        """Adds a structured log entry for the frontend."""
        entry = {
            "timestamp": datetime.datetime.now().isoformat(),
            "step_type": step_type, 
            "data": data
        }
        self.log_entries.append(entry)
        # Also log to server console for debugging
        self.logger.info(f"AGENT LOG STEP: {step_type} - {data}") 
    
    def process_request(self, request_text):
        """
        Processes a natural language request using a simulated LLM call.
        1. Calls a simulated LLM to parse the request into structured JSON.
        2. Determines the necessary MCP tool calls based on the JSON.
        3. Executes the tool calls by sending requests to the MCP server.
        4. Orchestrates multi-step processes.
        5. Returns the final result or error.
        """
        self.log_entries = [] # Clear log for new request
        self._log_step("Request Received", {"text": request_text})
        
        try:
            # 1. Analyze request using simulated LLM call
            self._log_step("NLP Analysis Start", {})
            structured_form_data = self._call_llm_agent(request_text)
            self.logger.info(f"Simulated LLM Structured Output: {json.dumps(structured_form_data, indent=2)}")
            self._log_step("NLP Analysis Complete", {"structured_data": structured_form_data})
            
            # Basic validation of LLM output
            if not structured_form_data or 'formTitle' not in structured_form_data:
                 self.logger.error("Simulated LLM did not return valid structured data.")
                 return { "status": "error", "message": "Failed to understand the request structure using LLM." }

            # Extract sections and settings - Basic structure assumes one form for now
            form_params = {
                "title": structured_form_data.get('formTitle'),
                "description": structured_form_data.get('formDescription', '')
                # We would also handle settings here if the API supported it
            }
            sections = structured_form_data.get('sections', [])
            if not sections:
                 self.logger.error("Simulated LLM did not return any form sections/questions.")
                 return { "status": "error", "message": "LLM did not identify any questions for the form." }

            # 2. Determine and execute tool calls (simplified flow: create form, add all questions)
            # NOTE: This doesn't handle multiple sections, logic, or advanced settings yet
            all_questions = []
            for section in sections:
                # TODO: Add support for creating sections/page breaks if API allows
                # self.logger.info(f"Processing section: {section.get('title', 'Untitled Section')}")
                all_questions.extend(section.get('questions', []))
            
            # Pass the extracted questions to the creation flow
            form_params['questions'] = all_questions
            # Execute the flow, which will populate self.log_entries further
            final_response = self._execute_create_form_flow(form_params)

            # Add the collected logs to the final response if successful
            if final_response.get("status") == "success":
                 final_response["log_entries"] = self.log_entries
            
            return final_response
        
        except Exception as e:
            self.logger.error(f"Error in FormAgent process_request: {str(e)}", exc_info=True)
            self._log_step("Agent Error", {"error": str(e)})
            # Include logs gathered so far in the error response too
            return {
                "status": "error",
                "message": f"Agent failed to process request: {str(e)}",
                "log_entries": self.log_entries
            }

    def _call_llm_agent(self, request_text):
        """
        Uses Camel AI's ChatAgent to process the request and extract structured data.
        Falls back to a basic structure if the LLM call fails.
        """
        # Check for the API key first (using the name Camel AI expects)
        api_key = os.getenv('GOOGLE_API_KEY') 
        if not api_key:
            self.logger.error("GOOGLE_API_KEY is not set in the environment.") # Updated log message
            return self._get_fallback_structure(request_text)

        try:
            # 1. Setup Model
            # Assuming ModelFactory can find the key from env or we pass it
            # Adjust ModelType enum based on actual Camel AI definition if needed
            # Using a placeholder like ModelType.GEMINI_1_5_FLASH - this will likely need correction
            model_name_str = "gemini-1.5-flash-latest" # Keep the string name
            # Attempt to create model instance via factory
            # Create the required config dict - make it empty and rely on env var GOOGLE_API_KEY
            model_config_dict = { 
                 # No API key here - expecting library to read GOOGLE_API_KEY from env
            }
            # REVERT: Go back to GEMINI platform type
            llm_model = ModelFactory.create(
                model_platform=ModelPlatformType.GEMINI, 
                model_type=ModelType.GEMINI_1_5_FLASH, 
                model_config_dict=model_config_dict 
            )
            self.logger.info(f"Camel AI Model configured using: {ModelType.GEMINI_1_5_FLASH}")

            # 2. Prepare messages for the ChatAgent
            system_prompt = self._build_llm_prompt(request_text)
            system_message = BaseMessage(
                role_name="System", 
                role_type=RoleType.ASSISTANT,
                meta_dict=None, 
                content=system_prompt
            )
            user_message = BaseMessage(
                role_name="User",
                role_type=RoleType.USER,
                meta_dict=None,
                content=request_text # Or maybe just a trigger like "Process the request"? Let's try request_text
            )

            # 3. Initialize and run the ChatAgent
            agent = ChatAgent(system_message=system_message, model=llm_model)
            agent.reset() # Ensure clean state

            self.logger.info("Calling Camel AI ChatAgent...")
            response = agent.step(user_message)
            
            if not response or not response.msgs:
                self.logger.error("Camel AI agent did not return a valid response.")
                return self._get_fallback_structure(request_text)

            # 4. Extract JSON content from the last message
            # Assuming the response structure contains a list of messages `msgs`
            # and the agent's reply is the last one.
            agent_reply_message = response.msgs[-1]
            content = agent_reply_message.content
            self.logger.debug(f"Raw Camel AI agent response content: {content}") 

            # --- Robust JSON Extraction --- 
            try:
                # Find the start and end of the JSON object
                json_start = content.find('{')
                json_end = content.rfind('}')
                
                if json_start != -1 and json_end != -1 and json_end > json_start:
                    json_string = content[json_start:json_end+1]
                    structured_data = json.loads(json_string)
                    self.logger.info("Successfully parsed structured JSON from Camel AI agent response.")
                    return structured_data
                else:
                    # Fallback if JSON bounds couldn't be found
                    self.logger.error(f"Could not find valid JSON object boundaries in response. Content: {content}")
                    return self._get_fallback_structure(request_text)

            except json.JSONDecodeError as e:
                 self.logger.error(f"Failed to parse JSON content from Camel AI agent: {e}. Extracted content attempt: {json_string if 'json_string' in locals() else 'N/A'}")
                 return self._get_fallback_structure(request_text)

        except ImportError as e:
             self.logger.error(f"Failed to import Camel AI components. Is 'camel-ai' installed correctly? Error: {e}")
             return self._get_fallback_structure(request_text)
        except Exception as e:
            # Catch potential errors during Camel AI model init or agent step
            self.logger.error(f"Error during Camel AI processing: {str(e)}", exc_info=True)
            return self._get_fallback_structure(request_text)

    def _build_llm_prompt(self, request_text):
        """
        Constructs the detailed prompt for the LLM.
        
        Crucial for getting reliable JSON output.
        Needs careful tuning based on the LLM used.
        """
        # Define the desired JSON structure and supported types/features
        # This helps the LLM understand the target format.
        json_schema_description = """
        { 
          "formTitle": "string",
          "formDescription": "string (optional)",
          "settings": { 
            // Note: Backend does not support settings yet, but LLM can parse them
            "collectEmail": "string (optional: 'required' or 'optional')",
            "limitToOneResponse": "boolean (optional)",
            "progressBar": "boolean (optional)",
            "confirmationMessage": "string (optional)"
          },
          "sections": [ 
            {
              "title": "string",
              "description": "string (optional)",
              "questions": [
                {
                  "title": "string",
                  "description": "string (optional)",
                  "type": "string (enum: text, paragraph, multiple_choice, checkbox, linear_scale, multiple_choice_grid, checkbox_grid)", 
                  "required": "boolean (optional, default: false)",
                  "options": "array of strings (required for multiple_choice, checkbox)" 
                           + " OR object { min: int, max: int, minLabel: string (opt), maxLabel: string (opt) } (required for linear_scale)" 
                           + " OR object { rows: array of strings, columns: array of strings } (required for grids)",
                  "logic": "object { on: string (option value), action: string (enum: submit_form, skip_section), targetSectionTitle: string (required if action=skip_section) } (optional)", // Note: Backend doesn't support logic
                  "validation": "string (optional: 'email' or other types if supported)" // Note: Backend doesn't support validation
                }
                // ... more questions ...
              ]
            }
            // ... more sections ...
          ]
        }
        """
        
        prompt = f"""Analyze the following user request to create a Google Form. Based ONLY on the request, generate a JSON object strictly adhering to the following schema. 

SCHEMA:
```json
{json_schema_description}
```

RULES:
- Output *only* the JSON object, nothing else before or after.
- If the user requests features not supported by the schema description (e.g., file uploads, specific themes, complex scoring), omit them from the JSON.
- If the request is unclear about question types or options, make reasonable assumptions (e.g., use 'text' for simple questions, provide standard options for ratings).
- Ensure the 'options' field matches the required format for the specified 'type'.
- Pay close attention to required fields in the schema description.
- If the request seems too simple or doesn't clearly describe a form, create a basic form structure with a title based on the request and a few generic questions (like Name, Email, Comments).

USER REQUEST:
```
{request_text}
```

JSON OUTPUT:
"""
        return prompt

    def _get_fallback_structure(self, request_text):
         """Returns a basic structure if LLM call fails or parsing fails."""
         self.logger.warning(f"Gemini call/parsing failed. Falling back to basic structure for: {request_text}")
         return {
             "formTitle": self._generate_title(request_text), 
             "formDescription": request_text,
             "settings": {},
             "sections": [
                 {
                     "questions": [
                         {"title": "Name", "type": "text", "required": True},
                         {"title": "Email", "type": "text", "required": False},
                         {"title": "Your Response", "type": "paragraph", "required": True}
                     ]
                 }
             ]
         }

    def _execute_create_form_flow(self, params):
        """
        Handles the complete flow for creating a form and adding its questions.
        """
        self.logger.info(f"Executing create form flow with params: {params}")
        
        # Extract potential questions from NLP parameters
        questions_to_add = params.pop('questions', []) # Remove questions from main params
        
        # A. Create the form first
        self._log_step("Create Form Start", {"params": params})
        create_form_response = self._handle_create_form(params)
        self._log_step("Create Form End", {"response": create_form_response})
        
        if create_form_response.get("status") != "success":
            self.logger.error(f"Form creation failed: {create_form_response.get('message')}")
            return create_form_response # Return the error from create_form
            
        # Get the form_id from the successful response
        form_result = create_form_response.get('result', {})
        form_id = form_result.get('form_id')
        
        if not form_id:
            self.logger.error("Form creation succeeded but no form_id returned.")
            return { "status": "error", "message": "Form created, but form_id missing in response." }
            
        self.logger.info(f"Form created successfully: {form_id}")
        
        # B. Add questions if any were identified by NLP
        question_results = []
        if questions_to_add:
            self._log_step("Add Questions Start", {"count": len(questions_to_add)})
            self.logger.info(f"Adding {len(questions_to_add)} questions to form {form_id}")
            for question_data in questions_to_add:
                question_params = {
                    "form_id": form_id,
                    # Ensure structure matches _handle_add_question needs
                    "type": question_data.get('type'), 
                    "title": question_data.get('title'),
                    "options": question_data.get('options', []), 
                    "required": question_data.get('required', False)
                }
                
                # Validate basic question params before sending
                if not question_params['type'] or not question_params['title']:
                     self.logger.warning(f"Skipping invalid question data: {question_data}")
                     continue
                     
                self._log_step("Add Question Start", {"question_index": questions_to_add.index(question_data), "params": question_params})
                add_q_response = self._handle_add_question(question_params)
                self._log_step("Add Question End", {"question_index": questions_to_add.index(question_data), "response": add_q_response})
                question_results.append(add_q_response)
                
                # Log if the question type wasn't supported by the backend
                if add_q_response.get("status") == "error" and "Invalid question_type" in add_q_response.get("message", ""):
                     self.logger.warning(f"Question '{question_params['title']}' failed: Type '{question_params['type']}' likely not supported by backend API yet.")
                elif add_q_response.get("status") != "success":
                    self.logger.error(f"Failed to add question '{question_params['title']}': {add_q_response.get('message')}")
                
                # Optional: Check if question adding failed and decide whether to stop
                if add_q_response.get("status") != "success":
                    self.logger.error(f"Failed to add question '{question_params['title']}': {add_q_response.get('message')}")
                    # Decide: continue adding others, or return error immediately?
                    # For now, let's continue but log the error.
            self._log_step("Add Questions End", {})
        else:
             self.logger.info(f"No questions identified by NLP to add to form {form_id}")
             self._log_step("Add Questions Skipped", {})

        # 5. Return the final result (details of the created form)
        # We can optionally include the results of adding questions if needed
        final_result = form_result
        # Let's add the questions that were *attempted* to be added back for the UI
        final_result['questions'] = questions_to_add 

        return {
            "status": "success",
            "result": final_result 
            # Optionally add: "question_addition_results": question_results
        }

    # Add the fallback title generation method back (or use a simpler one)
    def _generate_title(self, request_text):
        words = request_text.split()
        if len(words) <= 5:
            return request_text + " Form"
        else:
            return " ".join(words[:5]) + "... Form"
    
    # --- Methods for handling specific tool calls by sending to MCP Server ---

    def _handle_create_form(self, params):
        """
        Handle form creation by sending MCP packet to the server.
        
        Args:
            params: Parameters for form creation
            
        Returns:
            dict: Result of the form creation
        """
        self.logger.info(f"Creating form with params: {params}")
        
        # Prepare MCP packet
        mcp_packet = {
            "tool_name": "create_form",
            "parameters": {
                "title": params.get("title", "Form from NL Request"),
                "description": params.get("description", "")
            }
        }
        
        # Log *before* sending
        self._log_step("MCP Request (create_form)", mcp_packet)
        response = self._send_to_mcp_server(mcp_packet)
        # Log *after* receiving
        self._log_step("MCP Response (create_form)", response)
        return response
    
    def _handle_add_question(self, params):
        """
        Handle adding a question to a form by sending MCP packet.
        
        Args:
            params: Parameters for question addition
            
        Returns:
            dict: Result of the question addition
        """
        self.logger.info(f"Adding question with params: {params}")
        
        # Prepare MCP packet
        mcp_packet = {
            "tool_name": "add_question",
            "parameters": {
                "form_id": params.get("form_id"),
                "question_type": params.get("type", "text"),
                "title": params.get("title", "Question"),
                "options": params.get("options", []),
                "required": params.get("required", False)
            }
        }
        
        # Log *before* sending
        self._log_step("MCP Request (add_question)", mcp_packet)
        response = self._send_to_mcp_server(mcp_packet)
        # Log *after* receiving
        self._log_step("MCP Response (add_question)", response)
        return response
    
    def _handle_get_responses(self, params):
        """
        Handle getting form responses by sending MCP packet.
        
        Args:
            params: Parameters for getting responses
            
        Returns:
            dict: Form responses
        """
        self.logger.info(f"Getting responses with params: {params}")
        
        # Prepare MCP packet
        mcp_packet = {
            "tool_name": "get_responses",
            "parameters": {
                "form_id": params.get("form_id")
            }
        }
        
        # Log *before* sending
        self._log_step("MCP Request (get_responses)", mcp_packet)
        response = self._send_to_mcp_server(mcp_packet)
        # Log *after* receiving
        self._log_step("MCP Response (get_responses)", response)
        return response
    
    def _send_to_mcp_server(self, mcp_packet):
        """
        Sends an MCP packet to the MCP server URL.
        
        Args:
            mcp_packet: The MCP packet (dict) to send.
            
        Returns:
            dict: The response JSON from the MCP server.
        """
        self.logger.info(f"Sending MCP packet: {json.dumps(mcp_packet)}")
        
        try:
            response = requests.post(
                MCP_SERVER_URL,
                json=mcp_packet,
                headers={'Content-Type': 'application/json'},
                timeout=30  # Add a timeout (e.g., 30 seconds)
            )
            
            # Raise an exception for bad status codes (4xx or 5xx)
            response.raise_for_status()
            
            response_data = response.json()
            self.logger.info(f"Received MCP response: {json.dumps(response_data)}")
            return response_data
            
        except requests.exceptions.Timeout:
            self.logger.error(f"Timeout sending MCP packet to {MCP_SERVER_URL}")
            return {"status": "error", "message": "MCP server request timed out"}
        except requests.exceptions.RequestException as e:
            self.logger.error(f"Error sending MCP packet to {MCP_SERVER_URL}: {str(e)}")
            # Try to get error details from response body if possible
            error_detail = str(e)
            try:
                error_detail = e.response.text if e.response else str(e)
            except Exception:
                pass # Ignore errors parsing the error response itself
            return {"status": "error", "message": f"MCP server communication error: {error_detail}"}
        except json.JSONDecodeError as e:
             self.logger.error(f"Error decoding MCP response JSON: {str(e)}")
             return {"status": "error", "message": "Invalid JSON response from MCP server"}
        except Exception as e:
            self.logger.error(f"Unexpected error in _send_to_mcp_server: {str(e)}", exc_info=True)
            return {"status": "error", "message": f"Unexpected agent error sending MCP packet: {str(e)}"}


# For testing
if __name__ == "__main__":
    agent = FormAgent()
    
    # Test with some sample requests
    test_requests = [
        "Create a customer feedback form with a rating question",
        "Make a survey about remote work preferences",
        "Set up an RSVP form for my event on Saturday"
    ]
    
    for req in test_requests:
        print(f"\nProcessing: {req}")
        result = agent.process_request(req)
        print(f"Result: {json.dumps(result, indent=2)}")

```