Callboard docs

Connect an MCP client

Agents

Agents: point your MCP client here

Callboard includes a separate streamable-HTTP MCP Worker. It uses the public /v1 API only: no D1, R2, or product bindings, and exactly the access carried by the event-scoped key you send.

A live instance runs against the demo conference:

https://callboard-mcp.conor-afe.workers.dev/mcp

Mint a key at the demo's API keys page (one-click organizer sign-in, no signup), then:

export CALLBOARD_MCP_URL='https://callboard-mcp.conor-afe.workers.dev/mcp'
claude mcp add --transport http --header "x-access-token: $CALLBOARD_KEY" callboard "$CALLBOARD_MCP_URL"

get_openapi answers without any key, so a client can connect first and discover the REST API before you mint one.

Or add a project .mcp.json without committing the key:

{
  "mcpServers": {
    "callboard": {
      "type": "http",
      "url": "${CALLBOARD_MCP_URL}",
      "headers": { "x-access-token": "${CALLBOARD_KEY}" }
    }
  }
}
Tool Scope
list_events read:events
get_schedule read:sessions
list_submissions read:sessions
get_submission read:sessions
search_speakers read:contacts
list_tracks read:metadata
capture_abstract write:sessions
get_openapi none

Mint a least-privilege event key at /admin/api-keys. Deployment, connector, authentication, curl, and complete tool examples are in docs/MCP.md.

Authentication

Send a Callboard API key on the MCP request as x-access-token. The MCP Worker forwards that value as the same header on /v1 calls. It also accepts Authorization: Bearer <key> as a convenience. Credentials are never accepted in the URL and never appear in tool output.

Keys are event-scoped and scopes do not cascade. Mint one at /admin/api-keys on the configured Callboard deployment. When an event-scoped tool omits event_id, the server calls list_events once per MCP request and reuses the result. That inference needs read:events; callers without it can pass event_id explicitly.

Tool Required scope
list_events read:events
get_schedule read:sessions
list_submissions read:sessions
get_submission read:sessions
search_speakers read:contacts
list_tracks read:metadata
capture_abstract write:sessions
get_openapi None

Tool reference

All results are compact JSON in a text content block. Collection tools retain the public API's pagination block. Session descriptions are converted from HTML to text and bounded; truncated values include their original length and a visible recovery hint.

list_events

Inputs: none. Returns the id, name, slug, and timezone of the single event the key can reach, plus pagination.

get_schedule

Inputs: event_id?, track? (id or exact name), day? (YYYY-MM-DD), and limit? (default 50, maximum 100). Returns programme sessions (is_abstract:false) in start-time order. Null start times are marked unscheduled; a day filter excludes them. Day filtering happens after fetching the first 100 upstream rows, so a note warns when the upstream result spans more than that fetched page.

list_submissions

Inputs: event_id?, status? (one or more of draft, pending, accept_queue, accepted, decline_queue, declined, withdrawn), text?, track?, limit? (default 25, maximum 100), and page?. Returns compact CFP records (is_abstract:true) and pagination.

get_submission

Inputs: event_id? and required submission_id. Returns a fuller compact record, including CFP answers as custom_fields, participant names and contact details, and a longer plain-text description. Review scores are not exposed by the public API and are therefore not returned.

search_speakers

Inputs: event_id?, query?, limit? (default 25, maximum 100), and page?. Returns {id,name,company,email,about} rows plus pagination.

list_tracks

Inputs: event_id?. Returns track ids, names, colours, order, and pagination.

capture_abstract

This tool writes to the conference. Inputs: event_id?, required non-empty title, description?, track? (a track id), status? (default pending), and custom_fields? (string keys and values). It always creates is_abstract:true and returns the new id, friendly_id, title, status, and admin_url when present. The public create endpoint does not attach speakers, so this tool does not accept participant fields.

get_openapi

No key is required. Input: section?. With no section, returns a compact index containing OpenAPI version, info, server URL, and each method/path with its operation id and summary. Pass an operation id for that operation's full OpenAPI fragment. Pass full only when the complete document is genuinely needed.

Claude Code

Set the two shell variables, then add the remote server. CLI options must appear before the server name:

export CALLBOARD_MCP_URL='https://callboard-mcp.conor-afe.workers.dev/mcp'
export CALLBOARD_KEY='cb_...'
claude mcp add --transport http --header "x-access-token: $CALLBOARD_KEY" callboard "$CALLBOARD_MCP_URL"

For project configuration, .mcp.json supports environment expansion so the key need not be committed:

{
  "mcpServers": {
    "callboard": {
      "type": "http",
      "url": "${CALLBOARD_MCP_URL}",
      "headers": {
        "x-access-token": "${CALLBOARD_KEY}"
      }
    }
  }
}

claude.ai custom connector

An organization administrator can open Settings → Connectors, add a custom connector, and enter the Worker /mcp URL. For API-key deployments, configure a static request header named x-access-token with the scoped Callboard key. Static request-header authentication is a beta hosted-Claude feature and the credential is shared at the organization level; if that option is unavailable, use Claude Code or configure the Worker's optional single-tenant fallback key.

Raw streamable-HTTP exchange

These requests initialize the protocol, acknowledge initialization, list tools, and call get_schedule. The stateless server does not require a session id.

export MCP_URL='https://callboard-mcp.conor-afe.workers.dev/mcp'
export CALLBOARD_KEY='cb_...'

curl -i -X POST "$MCP_URL" \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H "x-access-token: $CALLBOARD_KEY" \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

curl -i -X POST "$MCP_URL" \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H "x-access-token: $CALLBOARD_KEY" \
  --data '{"jsonrpc":"2.0","method":"notifications/initialized"}'

curl -sS -X POST "$MCP_URL" \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H "x-access-token: $CALLBOARD_KEY" \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

curl -sS -X POST "$MCP_URL" \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H "x-access-token: $CALLBOARD_KEY" \
  --data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_schedule","arguments":{"event_id":"EVENT_ID","limit":20}}}'

Self-hosting

Set APP_ORIGIN in wrangler.mcp.jsonc to the HTTPS origin of the Callboard deployment, then deploy the separate Worker:

npx wrangler deploy --config wrangler.mcp.jsonc

Normally every MCP client supplies its own event-scoped key. A single-tenant deployment may instead set a fallback secret; request headers still take precedence:

npx wrangler secret put CALLBOARD_API_KEY --config wrangler.mcp.jsonc

The fallback is never included in /, /health, logs, errors, or tool output.

Design notes and limits

  • Isolation is deliberate: no D1, R2, Durable Object, AI, cron, or product module binding is present. The Worker can do only what /v1 and its key allow.
  • Abstracts and scheduled sessions are one API resource split by is_abstract; MCP names make the two common workflows explicit.
  • Search records are projected and long text is bounded because tool schemas and results consume the model's context window directly.
  • Review scores are not available on /v1.
  • Schedule day filtering is client-side over the first upstream page and warns when that page cannot prove completeness.
  • MCP writes are limited to creating abstracts. Deeper operations remain available to authorized integrations through the OpenAPI-described REST API.