MCP Webex Calling Server
An MCP (Model Context Protocol) server for administering Webex Calling. It exposes 90 tools over MCP stdio, so an AI assistant such as Claude Desktop can query and manage your Webex Calling organization in natural language:
- "What call queues do we have configured?"
- "Generate an activation code for user john@example.com"
- "Get call statistics for the last 24 hours"
The server talks to the official Webex REST APIs with your access token. It is a thin, honest layer over those APIs: every tool maps to documented Webex endpoints, and where the public API has limits (see API limitations), the docs and the tool descriptions say so instead of pretending otherwise.
Quickstart
Requirements: Python 3.10+. Setup walks you through the Webex side.
1. Install
Once released on PyPI, the one-line route is uv:
uvx mcp-webexcalling --version # runs without installing anything
Or install from a clone (works today):
git clone https://github.com/lutztalk/mcp-webexcalling.git
cd mcp-webexcalling
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
python -m pip install -U pip # editable installs need a reasonably new pip
pip install -e .
2. Sign in
mcp-webexcalling setup
It offers two routes:
- Sign in properly (recommended). It walks you through creating a private Webex app — it prints the exact redirect address and the exact permissions to tick — then opens your browser to approve it. Takes about five minutes, once. After that the server renews its own access and you never sign in again.
- Quick trial. Paste a personal access token from developer.webex.com. Fastest way to look around, but it stops working after about 12 hours.
Either way, setup checks the connection before it finishes and tells you who you signed in as and whether you have admin rights.
3. Connect your MCP client
mcp-webexcalling print-config
That prints the exact JSON block for Claude Desktop (with the absolute path
already filled in) and the one-line claude mcp add command for Claude Code.
No token goes in the configuration — credentials live in your own config
directory, not in a file you might share.
Restart your client, then ask it to run test_connection.
Something not working?
mcp-webexcalling doctor
One line per check: version, where credentials came from, when they expire, whether renewal is working, whether you have admin rights, and which permissions are missing — each with the fix.
For a step-by-step walkthrough, see SETUP.md. For example conversations, see USAGE.md.
Installing in Claude Desktop without a terminal
Each release also ships a .mcpb bundle on the
releases page.
Download it and double-click: Claude Desktop shows an install dialog and, if
you want to try it with a pasted token, a field to type it into. To get the
sign-in that renews itself, run mcp-webexcalling setup afterwards — the
bundle reads the same credentials.
What can it do?
The server registers exactly 90 tools. The complete reference — every tool with its arguments and description, generated directly from the code so it cannot drift — is in docs/TOOLS.md. By category:
| Category | Tools | Highlights |
|---|---|---|
| Diagnostics | 1 | test_connection health/permission check |
| Organizations & Locations | 7 | list/create/update/delete locations, per-location feature summary |
| Users | 9 | CRUD, search by display name or email, extensions, calling settings |
| Licenses | 5 | list licenses, assign/remove per user |
| Phone Numbers | 6 | list/lookup, assign to users or locations, find unassigned numbers |
| Devices | 11 | list/details, activation codes, create by MAC, re-home, remove |
| Call Queues | 8 | CRUD plus agent management (location-scoped) |
| Auto Attendants | 5 | CRUD (location-scoped) |
| Hunt Groups | 7 | CRUD plus member management (location-scoped) |
| Trunks & Call Park | 4 | trunk groups, call park extensions, per-user park monitoring |
| User Calling Features | 5 | call forwarding, simultaneous ring, feature bundle updates |
| Voicemail | 5 | per-user settings; the authenticated user's messages |
| Call Recordings | 2 | list and fetch recording metadata (Converged Recordings API) |
| Call History, CDRs & Analytics | 10 | detailed call records, statistics, per-user/queue/state breakdowns, CSV/JSON export |
| Webhooks | 5 | CRUD for webhook registrations |
Webex API limitations worth knowing
These are constraints of the public Webex APIs, not bugs in this server. The tools work within them and say so in their output:
- Call history / analytics windows are short. Every CDR-based tool
(
get_call_history,get_call_detail_records, all*_statistics_*and analytics tools) uses the Detailed Call History feed, which only accepts time ranges between 48 hours and 5 minutes ago — you cannot pull last month's calls. Tools whose times are optional default omitted bounds into that window. An explicitly supplied out-of-window range (e.g. "last month") is rejected by most tools: the API's 400 comes back annotated with exactly which bound is out of range. The exceptions are the statistics toolsget_call_statistics_from_cdr,get_user_call_statistics, andget_call_statistics_by_state, which clamp a supplied range into the window instead. The feed also requires the "Webex Calling Detailed Call History API access" role on the token's (admin) user, granted in Control Hub. - No real-time metrics or server-side export.
get_real_time_call_metricsandexport_call_recordsaggregate the same CDR feed locally (the feed lags real time by at least 5 minutes); Webex has no public real-time metrics or export endpoint. - Devices cannot be reassigned in place. The Devices API has no "move to
another user" operation.
associate_device_to_userperforms the documented flow instead: delete the device, then generate an activation code for the new owner. Activation codes are short-lived (days, not months) — the response'sexpiryTimeis authoritative. - Voicemail messages are me-scoped.
list_voicemail_messages,get_voicemail_message, anddelete_voicemail_messageoperate on the authenticated user's mailbox only; the WebexvoiceMessagesAPI cannot target another user's messages. (Per-user voicemail settings are admin-manageable.) - Number search is inventory, not shopping.
search_available_phone_numberslists unassigned numbers your organization already owns at a location; the public API cannot purchase new numbers. - Webhook tools manage registrations only. They create/update/delete webhook subscriptions pointing at your HTTPS receiver. A stdio MCP server has no HTTP listener and does not itself receive events.
Authentication & tokens
mcp-webexcalling setup handles all of this for you. This section explains
what it does and why, for anyone who wants to know or needs to do it by hand.
Why you create your own Webex app
Webex has no secret-free public OAuth client: every token-issuing call
requires an app's client secret. A public repository cannot ship one, so
"sign in with Webex" using our app is impossible — everybody gets their own
app instead. setup makes that a guided three-minute task: it prints the
exact redirect address to paste and the exact permissions to tick, then opens
the browser to approve it.
The three routes
| Route | Lifetime | Good for |
|---|---|---|
Your own integration (setup, recommended) |
Renews itself indefinitely | Everyday use |
Personal access token (setup --trial) |
~12 hours, no renewal | Evaluating the server |
| Service App | Renews itself; org-owned | Unattended use that must survive its author leaving |
Access tokens from an integration last about 14 days and refresh tokens about 90 — and the 90-day clock resets every time it is used, so a server that refreshes itself keeps working indefinitely (see Webex's integrations guide). That renewal is now built in; it used to require an external process.
Service Apps are supported in the sense that their tokens work here, but
setup does not automate them: they need a full administrator to authorize
them in Control Hub, and it is unconfirmed whether call-history scopes work
under one.
When renewal stops working
Renewal is not literally forever. It ends if the sign-in goes unused for 90
days, an administrator revokes the app, or the authorizing user's account
changes. When that happens every tool returns a plain-language error telling
you to run mcp-webexcalling setup again, and doctor reports what failed.
Permissions (scopes)
This is the canonical list, generated from
mcp_webexcalling/scopes.py,
which is also what setup prints and what doctor checks against — the three
cannot drift apart.
| Scope | Needed for |
|---|---|
spark-admin:organizations_read |
organization info and the test_connection admin probe |
spark-admin:locations_read / spark-admin:locations_write |
location tools |
spark-admin:people_read / spark-admin:people_write |
user tools, number assignment, license assignment |
spark-admin:telephony_config_read / spark-admin:telephony_config_write |
queues, auto attendants, hunt groups, phone numbers, trunks, call park, and per-user calling features |
spark-admin:devices_read / spark-admin:devices_write |
device tools, activation codes, MAC provisioning |
spark-admin:licenses_read |
license listing and assignment lookups |
spark:webhooks_read / spark:webhooks_write |
webhook tools |
spark-admin:calling_cdr_read |
call detail records, analytics, and every call-statistics tool — additionally requires the 'Webex Calling Detailed Call History API access' role on the authorizing admin in Control Hub — a role, not a scope |
spark:calls_read / spark:calls_write |
the voicemail message tools (list, get, delete) — these are user-context scopes: the voicemail message API reads the authorizing user's own mailbox, not the organization's |
spark-admin:recordings_read / spark-admin:recordings_write |
the call recording tools — a compliance officer uses spark-compliance:recordings_read / _write instead |
setup reports any permission you did not grant, and doctor re-checks them
by probing one endpoint per family, so a missed checkbox is named rather than
surfacing later as unexplained failures.
Admin scopes only work when the person approving is a full or read-only
administrator of the organization. If someone else installs the server, send
them the sign-in address setup prints so an administrator can approve it.
A warning about bot tokens
Webex bot tokens do not work for Webex Calling administration. Bots cannot
be granted spark-admin:* scopes or admin roles, so /telephony/config/*,
org-wide /people, /licenses, and the CDR feed all return 403 with a
bot token. A bot is only useful here for the unrelated
push-notification workflow. Don't
put a bot token in WEBEX_ACCESS_TOKEN expecting the telephony tools to work.
Where credentials live, and how to revoke them
setup writes them to a file in your own configuration directory
(~/.config/mcp-webexcalling/credentials.json, or %APPDATA% on Windows),
which you can override with WEBEX_CONFIG_DIR. On Unix the file is created
mode 0600 and is never even briefly world-readable. On Windows those
permission bits do not exist; the file relies on the same per-user profile
permissions that protect the rest of %APPDATA%.
Be clear-eyed about what that file holds in the recommended mode: an access
token, a self-renewing refresh token, and the app's client secret. Anyone
who reads it can keep minting admin tokens. It protects against other local
users, casual file sharing, and backups of your MCP client's config — not
against malware running as you, or an unencrypted stolen disk. That is the
same posture as the gcloud and aws CLIs.
Deleting the file does not cancel the access you granted. Webex has no public revoke endpoint, so to fully revoke:
mcp-webexcalling logout # removes the local file and names the app to revoke
then delete (or regenerate the secret of) that integration at developer.webex.com/my-apps, or have an administrator remove its authorization in Control Hub under Management → Apps. Do this whenever a laptop is lost or someone leaves the team.
Precedence
The token is resolved in this order:
WEBEX_ACCESS_TOKENin the environment — a client'senvblock, or your shell. Always wins, which keeps CI and scripted use predictable.- The credential store written by
setup— the only source that renews itself. - A
.envfile — one in the working directory, else one beside the package (which only exists for an editable clone install).
Because the environment wins, a stale token left in a client's env block
will silently mask a working sign-in. doctor checks for exactly that and
says so.
Reliability & robustness
Each of these is implemented in mcp_webexcalling/webex_client.py /
config.py and covered by tests:
- Self-renewing sign-in — with credentials from
setup, the server refreshes its own access token before it expires and again if a call is rejected, so it keeps working without anyone re-authorizing it. Refreshes are serialised within the process and across processes with a lockfile, so several MCP clients sharing one sign-in cannot clobber each other's rotated token. A refresh that genuinely fails is recorded, so subsequent calls fail fast with an actionable message instead of hammering the token endpoint. - Flexible configuration — credentials come from the environment, the
credential store, or a
.envfile, in that order. Theenvblock inclaude_desktop_config.jsonworks exactly as documented. - Automatic retries — transient failures (HTTP 429, 5xx, network/timeout
errors) are retried with exponential backoff and jitter, honoring the
Retry-Afterheader. Configurable viaWEBEX_MAX_RETRIES/WEBEX_RETRY_BACKOFF. - Connection pooling — one HTTP client is reused across all requests.
- Automatic pagination — list operations follow Webex
Linkheaders, somax_resultsabove the API's per-page cap returns the full set (max_results=0fetches everything available). - Concurrency-safe — per-request base URLs (used for the analytics/CDR host) never mutate shared client state, so parallel tool calls don't interfere.
- Actionable errors — API errors carry their HTTP status and explanation;
missing or wrongly-typed tool arguments are rejected with a clear message
before any API call;
test_connectiondiagnoses auth/permission problems. - Structured logging — diagnostics go to stderr (configurable with
WEBEX_LOG_LEVEL) and never corrupt the MCP stdio protocol on stdout.
Using the client library directly
The underlying async client is usable on its own. This snippet is runnable as written (see examples/basic_usage.py for a fuller, guarded version — it calls the real Webex API, so it needs a real token):
import asyncio
from mcp_webexcalling.webex_client import WebexClient
async def main() -> None:
async with WebexClient(access_token="your_token") as client:
# Who am I / does the token work?
status = await client.test_connection()
print(status)
# List call queues
queues = await client.list_call_queues()
print(f"{len(queues)} call queues")
# Call statistics — the CDR feed only accepts windows between
# 48 hours and 5 minutes ago; omitted bounds default into that window.
stats = await client.get_call_statistics_from_cdr()
print(stats)
# Generate a device activation code for a user
code = await client.generate_activation_code(person_id="person_id_here")
print(code["code"], "expires", code["expiryTime"])
asyncio.run(main())
WebexClient is an async context manager; if you don't use async with, call
await client.aclose() when done.
Development
pip install -e ".[dev]"
pytest # full test suite
ruff check . # lint (config in pyproject.toml)
The test suite needs no credentials and never touches the network: unit
tests use httpx.MockTransport, and the end-to-end suite
(tests/test_mcp_e2e.py) spawns the real server as a subprocess over stdio,
pointed at a local mock Webex API, completes the MCP handshake, and invokes
every one of the 90 tools — with valid, missing, and wrongly-typed
arguments. The OAuth flow is exercised against a local loopback listener, and
every test runs with WEBEX_CONFIG_DIR pointed at a temporary directory, so
the suite can never read or write your real credentials.
CI (.github/workflows/ci.yml) runs lint + tests on Python 3.10–3.14 on
Linux, plus macOS and Windows, for every pull request and for pushes to
main/master (pushes to other branches don't trigger it). The extra
platforms matter here: file permissions, config paths, and the loopback
listener all behave differently off Linux.
To build the Claude Desktop bundle locally:
python scripts/build_bundle.py # writes dist/mcp-webexcalling.mcpb
After adding or changing a tool, regenerate the tool reference:
python scripts/generate_tool_docs.py
Contributions are welcome — open a pull request. Please keep pytest and
ruff check . green, and remember that stdout is the MCP protocol channel:
any diagnostics must go to stderr.
GitHub Actions: Webex push notifications
Separately from the MCP server, .github/workflows/webex-notifications.yml
posts a message to a Webex space when commits land on main. This is the one
place a bot token is the right tool. Setup: create a bot at
developer.webex.com (My Apps → Bots),
add the bot to the target space, then add repository secrets
WEBEX_BOT_TOKEN and WEBEX_ROOM_ID.
License
MIT — see LICENSE.
Metadata
Release files for mcp-webexcalling 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_webexcalling-0.2.0.tar.gz | 99.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_webexcalling-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 172.1 kB
Release files / mcp_webexcalling-0.2.0.tar.gz
| Download URL | mcp_webexcalling-0.2.0.tar.gz |
|---|---|
| Size | 99.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4753187802fd82a594ae19e8dde7aba2096e99b44da83a981476c1b203b52629
|
|
BLAKE2b-256 checksum How to use checksums |
3a82c2d535731a22fbbd7cdf90d4133ba4d5f776928983ff24256b489da2c537
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency logRelease files / mcp_webexcalling-0.2.0-py3-none-any.whl
| Download URL | mcp_webexcalling-0.2.0-py3-none-any.whl |
|---|---|
| Size | 72.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c6bea11405578ff9db8820ff6e08d8c471d7559917c45c323e34b32d4af746e
|
|
BLAKE2b-256 checksum How to use checksums |
bfad0d0a5be8e9f3ae1358106605c95dd895d2478cce5218199e1cbb870afce9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency log