Skip to main content
Glama
README.md
# OnceHub MCP Server

[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg)]()
[![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io)

The OnceHub MCP Server provides a standardized way for `AI models` and `agents` to interact directly with your OnceHub scheduling API. Rather than sending users a booking link and asking them to schedule manually, an AI Agent can retrieve availability and schedule meetings on the user’s behalf using MCP tools, through a natural language flow. 
This solution enables external AI Agents to access OnceHub scheduling APIs within AI-driven workflows using the standardized Model Context Protocol (MCP) remote server.

**Compatible with:** VS Code Copilot, OpenAI, and any MCP-compatible AI client.

## Table of Contents
- [Features](#features)
- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [Tools](#tools)
- [Client Configuration](#client-configuration)
- [Installation](#installation--running-locally-with-uv)
- [Testing](#testing)
- [Production Deployment](#production-deployment)
- [Contributing](#contributing)
- [License](#license)

## Features

- πŸ”Œ **MCP Protocol Support** - Works with any MCP-compatible AI client
- πŸ“… **Time Slot Retrieval** - Fetch available booking slots from OnceHub booking calendars
- πŸ—“οΈ **Meeting Scheduling** - Automatically schedule meetings with guest information
- πŸ” **Secure Authentication** - API key-based authentication via headers
- πŸ§ͺ **Well Tested** - 92% code coverage with comprehensive unit tests
- 🐳 **Docker Ready** - Containerized for easy deployment
- πŸ“ **AI-Friendly Prompts** - Built-in workflow guidance for AI assistants

## Quick Start

Get started with the OnceHub MCP Server in your AI client:

### 1. Get Your API Key
Obtain your OnceHub API key from the [Authentication documentation](https://developers.oncehub.com/docs/overview/authentication/).

### 2. Configure Your Client
Create `.vscode/mcp.json` in your workspace:

```json
{
  "servers": {
    "oncehub": {
      "url": "https://mcp.oncehub.com/sse",
      "type": "http",
      "headers": {
        "authorization": "Bearer YOUR_ONCEHUB_API_KEY"
      }
    }
  }
}
```

Replace `YOUR_ONCEHUB_API_KEY` with your actual API key.

### 3. Start Using
Ask your AI assistant to:
- "Show me available time slots for calendar BKC-XXXXXXXXXX"
- "Schedule a meeting for tomorrow at 2 PM with John Doe"

⚠️ **Running your own MCP server?** See [Installation & Running Locally](#installation--running-locally-with-uv) for setup instructions.

βœ… **Production Server:** Our hosted MCP server is available at `https://mcp.oncehub.com/sse`.

## Architecture
![Architecture](./images/mcp-server_architecture.png)


## Project Structure

```
mcp-server/
β”œβ”€β”€ main.py              # MCP server with tool definitions
β”œβ”€β”€ models.py            # Pydantic data schemas for BookingForm and Location
β”œβ”€β”€ pyproject.toml       # Project dependencies and configuration
β”œβ”€β”€ Dockerfile           # Docker image configuration
β”œβ”€β”€ .dockerignore        # Files to exclude from Docker build
└── README.md            # This file
```


## Tools

### 1. `get_booking_time_slots`
Retrieves available time slots from a booking calendar.

**Parameters:**
- `calendar_id` (str, required): The booking calendar ID (e.g., 'BKC-XXXXXXXXXX')
- `start_time` (str, optional): Filter slots from this datetime in ISO 8601 format (e.g., '2026-02-15T09:00:00Z')
- `end_time` (str, optional): Filter slots until this datetime in ISO 8601 format (e.g., '2026-02-28T17:00:00Z')
- `timeout` (int, default: 30): Request timeout in seconds

**Example Response:**
```json
{
  "success": true,
  "status_code": 200,
  "calendar_id": "BKC-XXXXXXXXXX",
  "total_slots": 5,
  "data": [
    {"start_time": "2026-02-10T10:00:00Z", "end_time": "2026-02-10T11:00:00Z"},
    {"start_time": "2026-02-10T14:00:00Z", "end_time": "2026-02-10T15:00:00Z"}
  ]
}
```

### 2. `schedule_meeting`
Schedules a meeting in a specified time slot. **Always call `get_booking_time_slots` first** to ensure the time slot is available.

**Parameters:**
- `calendar_id` (str, required): ID of the booking calendar (e.g., 'BKC-XXXXXXXXXX')
- `start_time` (str, required): The exact start time from an available slot in ISO 8601 format
- `guest_time_zone` (str, required): Guest's timezone in IANA format (e.g., 'America/New_York', 'Europe/London')
- `guest_name` (str, required): Guest's full name
- `guest_email` (str, required): Guest's email address for confirmation
- `guest_phone` (str, optional): Guest's phone number in E.164 format (e.g., '+15551234567')
- `location_type` (str, optional): Meeting mode - 'virtual', 'virtual_static', 'physical', or 'guest_phone'
- `location_value` (str, optional): Location details based on type:
  - `virtual`: Provider name (e.g., 'zoom', 'google_meet', 'microsoft_teams')
  - `virtual_static`: Use null
  - `physical`: Address ID (e.g., 'ADD-XXXXXXXXXX')
  - `guest_phone`: Phone number in E.164 format
- `custom_fields` (dict, optional): Custom form fields as key-value pairs (e.g., `{"company": "Acme", "interests": ["Demo"]}`)
- `timeout` (int, default: 30): Request timeout in seconds

**Example Response:**
```json
{
  "success": true,
  "status_code": 200,
  "booking_id": "BKG-123456789",
  "confirmation": {
    "guest_name": "John Doe",
    "guest_email": "john@example.com",
    "scheduled_time": "2026-02-10T10:00:00Z",
    "timezone": "America/New_York"
  }
}
```

## Prerequisites

- Python 3.13 or higher
- [uv](https://github.com/astral-sh/uv) package manager (recommended)
- OnceHub API key - See [Authentication documentation](https://developers.oncehub.com/docs/overview/authentication/) to obtain your API key
- OnceHub API endpoint URL (usually `https://api.oncehub.com`)

## Environment Variables

Create a `.env` file or set these environment variables:

```bash
# Required: Your OnceHub API endpoint
ONCEHUB_API_URL=https://api.oncehub.com

# Note: API key is passed via Authorization header from MCP clients
# Do NOT commit API keys to version control
```

## Client Configuration

This MCP server is compatible with **VS Code Copilot**, **Claude Desktop**, **OpenAI**, and other MCP-compatible clients.

### VS Code / GitHub Copilot

#### Step 1: Create Configuration Directory

**Windows (PowerShell):**
```powershell
New-Item -Path ".vscode" -ItemType Directory -Force
New-Item -Path ".vscode\mcp.json" -ItemType File -Force
```

**macOS/Linux:**
```bash
mkdir -p .vscode
touch .vscode/mcp.json
```

#### Step 2: Configure the Server

Edit `.vscode/mcp.json`:

```json
{
  "servers": {
    "oncehub": {
      "url": "http://0.0.0.0:8000/sse",
      "type": "http",
      "headers": {
        "authorization": "Bearer YOUR_ONCEHUB_API_KEY"
      }
    }
  }
}
```

**Configuration Options:**
- `url`: The MCP server endpoint (change to your server URL if self-hosting)
- `authorization`: Your OnceHub API key with `Bearer ` prefix
- Server name (`oncehub`): Can be customized to any identifier

#### Step 3: Reload VS Code

After saving the configuration, reload VS Code to activate the MCP server connection.

### Claude Desktop

Edit your Claude Desktop configuration file:

**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`

**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "oncehub": {
      "url": "http://0.0.0.0:8000/sse",
      "headers": {
        "authorization": "Bearer YOUR_ONCEHUB_API_KEY"
      }
    }
  }
}
```

Restart Claude Desktop after saving.

### Other MCP Clients

For other MCP-compatible clients, configure them to connect to:
- **Endpoint:** `http://0.0.0.0:8000/sse`
- **Protocol:** HTTP with Server-Sent Events (SSE)
- **Authentication:** Bearer token in `Authorization` header

**Security Note:** Never commit API keys to version control. Use environment variables or secure secret management for production deployments.

## Installation & Running Locally with uv

### 1. Install uv (if not already installed)

**Windows (PowerShell):**
```powershell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
```

**macOS/Linux:**
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### 2. Clone the repository
```bash
git clone https://github.com/scheduleonce/mcp-server.git
cd mcp-server
```

### 3. Set environment variables

**Windows (PowerShell):**
```powershell
$env:ONCEHUB_API_URL="https://api.oncehub.com"
```

**macOS/Linux:**
```bash
export ONCEHUB_API_URL="https://api.oncehub.com"
```

**Or create a `.env` file:**
```bash
echo "ONCEHUB_API_URL=https://api.oncehub.com" > .env
```

> **Note:** The API key is passed via the `Authorization` header from MCP clients (not as an environment variable).

### 4. Run the server using uv

**Option 1: Direct execution**
```powershell
uv run main.py
```

**Option 2: Using the script entry point**
```powershell
uv run mcp-server
```

The server will start on `http://0.0.0.0:8000`

### 5. Verify the server is running

**Health check:**
```powershell
curl http://localhost:8000/health
```

**Connect to SSE endpoint:**
```powershell
curl http://localhost:8000/sse
```

## Running with Docker

### Build the Docker image
```powershell
docker build -t mcp-server .
```

### Run the container
```powershell
docker run -p 8000:8000 mcp-server
```

### With environment variables
```bash
docker run -p 8000:8000 \
  -e ONCEHUB_API_URL="https://api.oncehub.com" \
  mcp-server
```

### Using Docker Compose (recommended)

Create `docker-compose.yml`:
```yaml
version: '3.8'
services:
  mcp-server:
    build: .
    ports:
      - "8000:8000"
    environment:
      - ONCEHUB_API_URL=https://api.oncehub.com
    restart: unless-stopped
```

Then run:
```bash
docker-compose up -d
```

## Data Schemas

### `Location`
Represents meeting location details.

**Fields:**
- `type` (str): Location type ("physical", "virtual", "phone")
- `value` (str): Location details (address, URL, phone number)

## Logging

The server includes comprehensive logging for HTTP requests to OnceHub:
- Outgoing requests (β†’)
- Response status codes (←)
- Request/response bodies

Logs are output to the console with timestamps and log levels.

## API Endpoints

- `GET /health` - Health check endpoint
- `GET /healthy` - Alternate health check endpoint
- `GET /sse` - Server-Sent Events endpoint for MCP protocol communication
- `GET /tools` - Returns the registered MCP tools with their descriptions, required parameters, and input field details

## Development

### Install dependencies manually
```powershell
uv pip install fastmcp pydantic httpx
```

### Install test dependencies
```powershell
uv pip install -e ".[test]"
```

### Run with different log levels
Edit `main.py` and change `log_level` parameter:
```python
asyncio.run(mcp.run_sse_async(host="0.0.0.0", port=8000, log_level="debug"))
```

Available log levels: `"debug"`, `"info"`, `"warning"`, `"error"`, `"critical"`

## Testing

The project includes comprehensive unit tests for all tools and functions.

### Install Test Dependencies

```powershell
# Install test dependencies
uv pip install pytest pytest-asyncio pytest-cov pytest-mock
```

### Run Tests

**Run all tests:**
```bash
uv run pytest
```

**Run tests with coverage report:**
```bash
uv run pytest --cov=. --cov-report=html --cov-report=term
```

**Run specific test file:**
```bash
uv run pytest test_tools.py
```

**Run specific test class:**
```bash
uv run pytest test_tools.py::TestGetBookingTimeSlots
```

**Run specific test:**
```bash
uv run pytest test_tools.py::TestGetBookingTimeSlots::test_get_time_slots_success
```

**Run with verbose output:**
```bash
uv run pytest -v
```

**Run and stop on first failure:**
```bash
uv run pytest -x
```

### Test Coverage

After running tests with coverage, open the HTML report:
```powershell
# Windows
start htmlcov/index.html

# macOS
open htmlcov/index.html

# Linux
xdg-open htmlcov/index.html
```

### Test Structure

- `test_tools.py` - Unit tests for all tool functions
  - `TestGetApiKeyFromContext` - Tests for API key extraction
  - `TestGetBookingTimeSlots` - Tests for time slot retrieval
  - `TestScheduleMeeting` - Tests for meeting scheduling

All tests use mocking to avoid actual API calls and ensure fast, reliable test execution.

## Production Deployment

### Security Best Practices

1. **API Key Management**
   - Never commit API keys to version control
   - Use environment variables or secret management systems
   - Rotate API keys regularly

2. **Network Security**
   - Use HTTPS in production (add reverse proxy like nginx)
   - Implement rate limiting to prevent abuse
   - Use firewall rules to restrict access

3. **Monitoring**
   - Monitor the `/health` endpoint for availability
   - Set up logging aggregation (e.g., ELK stack, CloudWatch)
   - Track API response times and error rates

### Deployment Options

#### Docker Deployment
```bash
# Build and tag the image
docker build -t oncehub-mcp-server:1.0.0 .

# Run with restart policy
docker run -d \
  --name oncehub-mcp \
  --restart unless-stopped \
  -p 8000:8000 \
  -e ONCEHUB_API_URL="https://api.oncehub.com" \
  oncehub-mcp-server:1.0.0
```

#### Cloud Deployment
- **AWS**: Use ECS, Fargate, or EC2 with Application Load Balancer
- **Azure**: Deploy to Azure Container Instances or App Service
- **GCP**: Use Cloud Run or Google Kubernetes Engine

### Performance Considerations

- Default timeout is 30 seconds - adjust based on your needs
- The server uses asyncio for concurrent request handling
- Consider horizontal scaling for high-traffic scenarios
- Use connection pooling for database/cache if added

### Health Monitoring

```bash
# Simple health check script
curl -f http://localhost:8000/health || exit 1

# Docker healthcheck (add to Dockerfile)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1
```

## Troubleshooting

### Error: "API key is required"
Ensure you're passing the `api_key` parameter when calling the tools.

### Error: "404 Not Found" on `/health`
Make sure the server is running and accessible on port 8000.

### Error: "405 Method Not Allowed" on `/sse`
The SSE endpoint requires a GET request, not POST. Use proper MCP client libraries or curl with GET.

### No logs showing for OnceHub requests
Check that logging is properly configured in `main.py` and the server is running with `log_level="info"` or `"debug"`.

## License

This project is licensed under the terms of the MIT open source license. Please refer to [MIT](LICENSE) for the full terms.

## Support

- **Documentation**: [OnceHub API Docs](https://developers.oncehub.com)
- **Issues**: [GitHub Issues](https://github.com/scheduleonce/mcp-server/issues)
- **MCP Protocol**: [Model Context Protocol](https://modelcontextprotocol.io)

## Acknowledgments

- Built with [FastMCP](https://github.com/jlowin/fastmcp)
- Compatible with the [Model Context Protocol](https://modelcontextprotocol.io)
- Powered by [OnceHub API](https://developers.oncehub.com)

---

**Made by the Oncehub team**