Hi, how can we help? 👋

MCP Server — AI Agent Integration

Connect AI agents to Timerise using the Model Context Protocol (MCP) for real booking operations — listing services, querying availability, and managing bookings

Last updated: March 4, 2026

MCP Server — AI Agent Integration

The Timerise API includes a built-in Model Context Protocol (MCP) server that lets AI agents perform real booking operations — listing services, querying availability, and creating or managing bookings — without custom integration code.

How It Works

The MCP server is mounted directly on the Timerise API at /mcp using the Streamable HTTP transport. There is no separate process to run.

The session authentication flow works as follows:

  1. The agent sends initialize to POST /mcp with an Authorization: Bearer <api-key> header.
  2. The server creates a private MCP session and returns an mcp-session-id header.
  3. Every subsequent tool call in that session uses the same API key, forwarded to the GraphQL layer.
  4. The agent sends DELETE /mcp (or disconnects) to end the session.

All Timerise access controls apply — the API key determines which projects and services the agent can see.

Prerequisites

Before connecting an AI agent, you need:

  • A Timerise API key — obtain one from your Timerise dashboard under Settings > API, or see the API Keys & Authentication guide.
  • The Timerise API running locally (npm start) or a deployed instance URL.
  • API keys are prefixed by environment: SANDBOX-... for sandbox, PROD-... for production.

Available Tools

The MCP server exposes 16 tools organized in four groups.

Service Discovery

ToolDescription
list_projectsList all projects accessible with the API key. Use this first to obtain a projectId.
get_projectGet full details of a single project.
list_servicesList services within a project.
get_serviceGet service details including required formFields — always call before creating a booking.
list_locationsList physical or virtual locations for a project.

Slot Availability

ToolDescription
list_available_slotsQuery available booking slots for a service within a date range. Returns slotId values used when creating bookings.

Booking Management

ToolDescription
create_bookingCreate a new booking. Supports both slot-based and range-based services.
confirm_bookingMove a booking from NEW to CONFIRMED.
cancel_bookingCancel a booking.
reschedule_bookingMove a booking to a different slot or time range.
list_bookingsList bookings for a project with optional filters (status, service, date range).
get_booking_detailsGet full details of a specific booking.

Developer Tools

ToolDescription
get_api_schemaGet the full GraphQL API schema as SDL for discovery and integration development.
list_api_usersList API users and their API keys for a project.
create_api_keyCreate a new API key for a project. The key is shown only once.
delete_api_keyDelete an API key by its ID.

Client Configuration

Claude Desktop

Claude Desktop has native MCP support via a JSON config file.

macOS — edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "timerise": {
      "url": "https://sandbox-api.timerise.io/mcp",
      "headers": {
        "Authorization": "Bearer SANDBOX-your-api-key-here"
      }
    }
  }
}

Windows — edit %APPDATA%\Claude\claude_desktop_config.json with the same content.

Linux — edit ~/.config/Claude/claude_desktop_config.json.

After saving, restart Claude Desktop. A hammer icon in the toolbar confirms MCP tools are loaded. Ask Claude "What Timerise projects can you see?" to verify.

Claude Code (CLI)

Add the server with one command:

claude mcp add timerise \
  --transport http \
  --url https://sandbox-api.timerise.io/mcp \
  --header "Authorization: Bearer SANDBOX-your-api-key-here"

Verify with:

claude mcp list          # shows "timerise" in the list
claude mcp get timerise  # shows connection details

To remove later: claude mcp remove timerise.

Cursor

Edit ~/.cursor/mcp.json (or use Cursor Settings > MCP > Add Server):

{
  "mcpServers": {
    "timerise": {
      "url": "https://sandbox-api.timerise.io/mcp",
      "headers": {
        "Authorization": "Bearer SANDBOX-your-api-key-here"
      }
    }
  }
}

For project-scoped config, place .cursor/mcp.json in the project root instead.

Windsurf

Edit ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "timerise": {
      "serverType": "http",
      "url": "https://sandbox-api.timerise.io/mcp",
      "headers": {
        "Authorization": "Bearer SANDBOX-your-api-key-here"
      }
    }
  }
}

Restart Windsurf — the Timerise tools will appear in the Cascade panel.

Claude.ai (OAuth 2.1)

Claude.ai connects using OAuth 2.1 Authorization Code + PKCE instead of direct Bearer token auth.

Step 1 — Create OAuth credentials via GraphQL (requires ADMIN role):

mutation {
  mcpOAuthClientCreate(projectId: "your-project-id", label: "Claude.ai") {
    clientId
    clientSecret
    label
    apiUser {
      apiUserId
      fullName
      projects
    }
  }
}

Save the clientSecret — it is only shown once. An APIADMIN user is auto-created.

To create credentials for an existing user, pass apiUserId instead of projectId:

mcpOAuthClientCreate(apiUserId: "existing-user-id", label: "Claude.ai") { ... }

Step 2 — In Claude.ai, go to Settings > MCP Servers, add a new server with URL https://sandbox-api.timerise.io/mcp, and enter the clientId and clientSecret in the advanced authentication settings.

Step 3 — Start a conversation and ask Claude to list your projects to verify.

This OAuth flow also works with ChatGPT, Google Gemini, and any MCP client that implements OAuth 2.1 + PKCE.

Managing OAuth clients:

List API users with their OAuth clients and keys:

query {
  apiUsers(projectId: "your-project-id") {
    apiUserId
    fullName
    projects
    apiKeys {
      apiKeyId
      label
      createdAt
    }
    oauthClients {
      clientId
      label
      createdAt
    }
  }
}

Delete an OAuth client (also deletes the associated API key):

mutation {
  mcpOAuthClientDelete(clientId: "mcp_xxx")
}

Delete an API user entirely (removes all keys and OAuth clients):

mutation {
  apiUserDelete(apiUserId: "the-api-user-id")
}

The OAuth endpoints served automatically include:

EndpointPurpose
GET /.well-known/oauth-protected-resourceResource metadata (RFC 9728)
GET /.well-known/oauth-authorization-serverAuthorization server metadata (RFC 8414)
GET /mcp/oauth/authorizeAuthorization endpoint (auto-approve)
POST /mcp/oauth/tokenToken exchange endpoint
POST /mcp/oauth/registerReturns 405 — use GraphQL API instead

Expired authorization codes and pending consent requests are automatically cleaned up by a daily cron job.

OpenAI Agents SDK

The OpenAI Agents SDK (Python) has built-in MCP support.

Install the SDK:

pip install openai-agents

Basic agent example:

import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp

async def main():
    async with MCPServerStreamableHttp(
        url="https://sandbox-api.timerise.io/mcp",
        headers={"Authorization": "Bearer SANDBOX-your-api-key-here"},
    ) as timerise:
        agent = Agent(
            name="Booking Assistant",
            instructions=(
                "You help users book appointments using the Timerise tools. "
                "Always call list_projects first, then list_services, then "
                "list_available_slots before creating a booking."
            ),
            mcp_servers=[timerise],
        )

        result = await Runner.run(
            agent,
            "Book me a slot for next Monday morning if anything is available.",
        )
        print(result.final_output)

asyncio.run(main())

With streaming output:

async with MCPServerStreamableHttp(
    url="https://sandbox-api.timerise.io/mcp",
    headers={"Authorization": "Bearer SANDBOX-your-api-key-here"},
) as timerise:
    agent = Agent(
        name="Booking Assistant",
        instructions="Help users manage Timerise bookings.",
        mcp_servers=[timerise],
    )

    async for event in Runner.run_streamed(agent, "Show me today's bookings"):
        if event.type == "text_delta":
            print(event.delta, end="", flush=True)

Tool lists are fetched once per connection. Reuse the same context manager across multiple agent invocations rather than opening a new connection per call.

Testing with MCP Inspector

The MCP Inspector is a browser-based tool for testing any MCP server interactively.

npx @modelcontextprotocol/inspector

This opens a local UI at http://localhost:5173. Select Streamable HTTP, enter the server URL (https://sandbox-api.timerise.io/mcp), add the Authorization header, and click Connect.

You can also test with curl. Initialize a session:

curl -si -X POST https://sandbox-api.timerise.io/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer SANDBOX-your-api-key-here" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": { "name": "test", "version": "0.1" }
    },
    "id": 1
  }'

Copy the mcp-session-id from the response header, then list tools:

curl -s -X POST https://sandbox-api.timerise.io/mcp \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: <paste-session-id-here>" \
  -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}'

Tutorial: Book a Slot End-to-End

This walkthrough shows the exact tool call sequence an agent follows to complete a booking.

Step 1 — Discover the project

Call list_projects with no arguments. This returns all projects your API key can access:

{
  "projects": [{ "projectId": "prj_abc123", "title": "Haircut Studio" }]
}

Step 2 — Find the right service

Call list_services with the project ID:

{ "projectId": "prj_abc123" }

Response:

{
  "services": [
    { "serviceId": "svc_xyz789", "title": "Men's Haircut", "price": 35 }
  ]
}

Step 3 — Check booking form requirements

Call get_service to see the required form fields:

{ "serviceId": "svc_xyz789" }

Response:

{
  "service": {
    "serviceId": "svc_xyz789",
    "formFields": [
      {
        "fieldId": "f1",
        "fieldType": "SYSTEM_FULL_NAME",
        "required": true,
        "label": "Your name"
      },
      {
        "fieldId": "f2",
        "fieldType": "SYSTEM_EMAIL_ADDRESS",
        "required": true,
        "label": "Email"
      },
      {
        "fieldId": "f3",
        "fieldType": "SYSTEM_PHONE_NUMBER",
        "required": false,
        "label": "Phone"
      }
    ]
  }
}

Step 4 — Find an available slot

Call list_available_slots with a date range:

{
  "serviceId": "svc_xyz789",
  "dateTimeFrom": "2026-03-03T09:00:00Z",
  "dateTimeTo": "2026-03-03T18:00:00Z"
}

Response:

{
  "service": {
    "slots": [
      {
        "slotId": "slot_a1b2",
        "dateTimeFromISO": "2026-03-03T10:00:00Z",
        "dateTimeToISO": "2026-03-03T11:00:00Z",
        "quantity": 1
      }
    ]
  }
}

Step 5 — Create the booking

Call create_booking with the slot ID and required form fields:

{
  "serviceId": "svc_xyz789",
  "slots": ["slot_a1b2"],
  "formFields": {
    "SYSTEM_FULL_NAME": "Jane Doe",
    "SYSTEM_EMAIL_ADDRESS": "jane@example.com"
  },
  "locale": "en",
  "timeZone": "Europe/Warsaw"
}

Response:

{
  "bookingCreate": {
    "bookingId": "bkg_9z8y7x",
    "status": "NEW",
    "dateTimeFromISO": "2026-03-03T10:00:00Z"
  }
}

Step 6 — Confirm the booking

If required by the service configuration, call confirm_booking:

{ "bookingId": "bkg_9z8y7x" }

Response:

{
  "bookingConfirm": {
    "bookingId": "bkg_9z8y7x",
    "status": "CONFIRMED"
  }
}

Range-based bookings

Some services don't use pre-defined slots. Skip steps 4-5 and pass date ranges directly to create_booking:

{
  "serviceId": "svc_range001",
  "dateTimeFrom": "2026-03-05T14:00:00Z",
  "dateTimeTo": "2026-03-05T15:30:00Z",
  "formFields": {
    "SYSTEM_FULL_NAME": "Jane Doe",
    "SYSTEM_EMAIL_ADDRESS": "jane@example.com"
  }
}

Use get_service and check viewConfig.displayType to determine the booking style:

  • LIST / DAYS / CALENDAR — slot-based, use slots: [slotId]
  • PREORDER — range-based, use dateTimeFrom / dateTimeTo

Session Lifecycle

Each connected agent gets an isolated MCP session:

HTTP methodPathmcp-session-idPurpose
POST/mcpabsentInitialize new session (must include Authorization header)
POST/mcppresentSend a JSON-RPC message to an existing session
GET/mcppresentOpen SSE stream for server-to-client notifications
DELETE/mcppresentTerminate and clean up session

Sessions are stored in-memory and do not survive API restarts. Agents should re-initialize after a server restart.

Troubleshooting

404 Session not found

The session was not initialized or the API was restarted. Send a new initialize request (without mcp-session-id) to start a fresh session.

400 Missing mcp-session-id header

A GET or DELETE request was sent without the session ID header. Only POST without a session ID is valid (for initialization).

GraphQL errors

The forwarded GraphQL query failed. Common causes:

ErrorCauseFix
Not authorizedAPI key lacks access to the projectCheck key permissions in Timerise dashboard
Project not foundWrong projectIdCall list_projects again to get valid IDs
Service not foundWrong serviceIdCall list_services(projectId) to refresh
Slot not availableSlot was booked by someone elseCall list_available_slots again

Tools don't appear in Claude Desktop

  1. Confirm the API is running: curl http://localhost:3000/ should return {"apiTitle":"Timerise API",...}
  2. Check the config file path — it must be exact (case-sensitive on Linux/macOS).
  3. Restart Claude Desktop fully (Quit, not just close the window).
  4. Check Claude Desktop logs: ~/Library/Logs/Claude/ (macOS).

Session auth — which key to use

The API key in the Authorization header of the initialize request determines the session's identity. Subsequent requests in the same session use the same key — there is no way to change auth mid-session. Open a new session with a different key if you need different permissions.

For production use, replace the sandbox URL with https://api.timerise.io/mcp and use a PROD- prefixed key.