gatebound-support
Integrates with Discord for the support ticket flow, including ticket handling and staff webhook notifications.
Provides MCP tools for Gatebound's ElevenLabs support agent, handles the post-call webhook, and syncs markdown knowledge-base files into an ElevenLabs agent's knowledge base via the kb-sync CLI.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gatebound-supportopen a Discord ticket for player reporting a lost match reward"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
gatebound-support
Python/FastAPI backend for Gatebound's ElevenLabs support agent: MCP tools for the agent, the Discord ticket flow, the post-call webhook, and the knowledge-base sync CLI.
docs/SPEC.md is the contract this service is built against — read it for exact env names,
endpoint paths, tool shapes, and the ticket flow. docs/ARCHITECTURE.md has the component
map, trust boundaries and failure modes; docs/DECISIONS.md the reasoning (no database
access from this stack, MCP instead of webhook tools, ticket links instead of agent-created
tickets, knowledge base synced from Git, cheapest models while iterating). This file is just
the "how do I run it" quick start.
Live: agent "Gatebound Support" in the text-first widget on
gatebound.rosenvall.se; service at
gatebound-support.rosenvall.se, deployed from the private homelab GitOps repo.
ElevenLabs agent as code (elevenlabs/)
Everything about the agent lives in the ElevenLabs CLI project layout and is pushed, never edited in the UI:
$env:ELEVENLABS_API_KEY = "..."
cd elevenlabs
python scripts/build_agent_config.py # prompt + settings -> agent_configs/Gatebound-Support.json
elevenlabs agents push # create/update the agent
elevenlabs tests push # the six behaviour tests in test_configs/
python scripts/run_agent_tests.py # run them, table + exit code (add --verbose for transcripts)
python scripts/sync_agent_refs.py # mirror live knowledge_base + mcp_server_ids into the config
$env:MCP_SECRET = "..."; uv run python scripts/probe_mcp.py # talk to the deployed /mcp like ElevenLabs doesThe MCP server entry and its bearer secret are registered once per workspace (see
docs/DECISIONS.md); the agent references it by id in mcp_server_ids.
Related MCP server: support-agent-mcp
Run locally
uv sync
copy .env.example .env # edit as needed; every integration defaults to "unset" (disabled)
uv run gatebound-support serveThe server listens on PORT (default 8080). With everything left unset it still starts and
answers /healthz and /status; the ticket flow works without Discord (tickets land as
pending_manual), and the MCP endpoint at /mcp rejects all calls until MCP_SECRET is set.
Tests
uv run pytest -q
uv run ruff check .kb-sync
Syncs a directory of markdown files into an ElevenLabs agent's knowledge base (SPEC §7):
uv run gatebound-support kb-sync --dir path\to\docs --manifest path\to\kb-manifest.json --agent <agent_id>
uv run gatebound-support kb-sync --dir ... --manifest ... --agent ... --dry-run # print the plan, write nothing
uv run gatebound-support kb-sync --dir ... --manifest ... --agent ... --check # exit 1 on drift, for CI/CronJobRequires ELEVENLABS_API_KEY.
Discord voice
gatebound-support voicebot is a second, separate long-lived process (own container
command, own Kubernetes Deployment — same image, command: ["gatebound-support", "voicebot"], no HTTP service) that runs the Discord gateway connection and bridges voice:
/supportor/call(from anywhere — the player does not need to already be in a voice channel): the bot creates a private temporary voice channel (support-<display name>, in the same category as the support text channel if it has one) that only the player, the bot, and roles with Administrator/Manage Guild can see or join — a support call must never be overhearable or joinable by other guild members. The player gets an ephemeral reply with a link to the channel ("join it and I'll pick up"); if they're already in a voice channel and the bot has Move Members, it pulls them straight in instead. A ticket + private Discord thread is opened for the call up front (sourcediscord_voice). The ElevenLabs Conversational AI session — the same agent the website widget talks to, over the SDK'sConversation+ a customAudioInterface(seesrc/gatebound_support/voicebot/) — only starts once the player actually joins that channel; if nobody joins within 2 minutes the channel is deleted and "Call not started" is posted to the ticket thread. The post-call transcript webhook posts the summary and transcript to the thread when a real call ends, exactly like the website ticket flow./hangup: ends the current call early.The call also ends when the player leaves the private channel, the agent says goodbye (its own
end_calltool), or afterVOICE_MAX_MINUTES(default 15) — in every case the temporary voice channel is deleted and "Call ended (<duration>)" is posted to the ticket thread. On startup the bot also sweeps for and deletes any leftover emptysupport-*channels from a previous crash/restart.One call per Discord server at a time, from the moment
/supportreserves the guild's slot (channel creation, waiting for join, and the call itself) — a second/supportwhile busy gets an ephemeral "the line is busy" reply./ticket, and a persistent "Open a ticket" button pinned in the support channel, open a text ticket via a modal (category + description) without going through the ElevenLabs agent at all — same private-thread flow, sourcediscord_text. One open ticket per Discord user at a time; opening a second one just links back to the first./close, a persistent "Close ticket" button on every ticket's opening post and on the voice call's "Call ended" message, and reacting with ✅ or 🔒 to any bot-authored message inside a ticket thread, all close the ticket the same way: only the ticket's own Discord user or a member with Manage Threads on the parent channel may do it (everyone else gets an ephemeral refusal, or — for a reaction, which has no ephemeral channel — a short message posted in the thread). Closing calls the internal API (status →closed), ends an active voice call for that ticket first if there is one, posts "Ticket closed by<mention>.", renames the thread to[closed] <old name>, and archives + locks it. A closed ticket never blocks its owner from opening a new one.Reaction shortcuts on the pinned instructions message (an alternative to typing slash commands): react, and the bot removes your reaction once it's acted on it.
Reaction
Action
🎫
Same as
/ticket, but without a modal (reactions can't open one): categoryother, summary "Opened by reaction; the player describes the problem in the thread" — the player is asked to describe the problem in the thread itself. Already has an open ticket → a reminder is posted there instead of opening a new one.📞
Same as
/support: creates the private voice channel, ticket and thread, and waits for the join — since there's no ephemeral reply for a reaction, the "join<channel>and I'll pick up" text is posted as the thread's first message instead.A per-user in-flight guard ignores a reaction that arrives while that user's previous ticket-creation/call-start reaction is still being handled, so double-tapping doesn't double-fire.
The bot talks to this same service's /internal API (bearer MCP_SECRET, not part of the
public contract in docs/SPEC.md) to create, update, and close tickets, since it's a
separate process from the one holding SQLite.
Discord bot permissions needed (added when inviting/authorizing the bot, or updating its
existing OAuth2 scopes): View Channel, Send Messages, Create Private Threads, Send Messages in Threads, Manage Threads (needed for the text ticket flow and to let
Manage-Threads staff close any ticket, not just their own); plus, for voice, Connect,
Speak, Use Voice Activity, and — permanent, not optional — Manage Channels and Manage Roles (needed to create the private per-call voice channel and
set its permission overwrites every time /support runs). Move Members is optional:
when granted, a player already in a voice channel is pulled straight into their private call
channel instead of having to click the link themselves. The reaction shortcuts additionally
want Manage Messages so the bot can remove a player's reaction from the pinned message
after acting on it — without it the reaction still triggers the action, it just isn't
auto-removed (a Forbidden from the removal attempt is caught and ignored).
Cost note: ElevenLabs bills voice conversation minutes at a materially higher rate than
text conversations — a /support call is not "free" the way a widget chat message is.
Run it locally the same way as serve:
uv run gatebound-support voicebot
uv run gatebound-support voicebot --check # validates settings + opus + imports, no connection, exits 0/1Docker
docker build --platform linux/amd64 -t gatebound-support:dev .
docker run --rm -p 8080:8080 --env-file .env gatebound-support:devRuns as uid 1000, writes SQLite to DATA_DIR (default /data in the container).
Environment variables
See docs/SPEC.md sections 1 and 8 for the full list and what each one is for. Quick
reference (all default to "unset", meaning that integration is disabled):
Variable | Purpose |
| runtime |
| this service's own URL, the web app's internal API, and its public URL |
| shared secret for calls to the web support API |
| HS256 key for the player identity JWT |
| bearer secret ElevenLabs sends on |
| ElevenLabs API access, post-call webhook HMAC key, and agent id |
| the ticket flow's Discord integration ( |
| voicebot: max length of a |
| voicebot: in-cluster base URL of this service's own |
Copy .env.example to .env and fill in what you need; everything else can stay unset.
This server cannot be deployed
Maintenance
Related MCP Connectors
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Unified MCP Server is a remote MCP connector for AI agents and vertical AI products that provides access to 22,000+ authorized SaaS tools across 400+ integrations and 24 categories directly inside LLMs (Claude, GPT, Gemini, Cohere). Tools operate only on explicitly authorized customer connections, enabling agents to safely read and write against live third-party systems.
Human-input bridge for AI agents with voice-first answer links, MCP tools, and HTTP APIs.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to drive Relay, an AI support-triage agent, by exposing tools for customer lookup, documentation search, ticket classification, reply, and escalation, with full guardrails and read-only mode option.MIT
- AlicenseNot gradedqualityBmaintenanceExposes order status lookup and knowledge base search tools from the Support Agent AI over MCP, enabling MCP clients to handle customer support queries with grounded, citation-backed answers.MIT
- AlicenseNot gradedqualityCmaintenanceEnables an AI to perform customer support workflows by looking up customers, retrieving orders, and creating support tickets through MCP tools.2 npmX11 no permit persons clause
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to search and retrieve support tickets, add notes, close tickets, and access SLA policies through MCP tools, resources, and prompts over Streamable HTTP.MIT