Skip to main content
Glama
Justin-Gladiator

Zoom Phone + Virtual Agent MCP Server

README.md
# Zoom Phone + Virtual Agent MCP Server

A custom MCP server exposing Zoom Phone call data and Zoom Virtual Agent knowledge-base/engagement
data as tools for an AI client (Claude Code, Claude Desktop, etc). Authenticates with
**Server-to-Server (Account) OAuth** — no end-user login flow, no browser redirect. The server
acts as your Zoom account directly.

## 1. Create the Marketplace app (one-time)

1. Go to https://marketplace.zoom.us/ → **Develop** → **Build App**.
2. Choose **Server-to-Server OAuth**.
3. Note the **Account ID**, **Client ID**, and **Client Secret** shown after creation.
4. Under **Scopes**, add (granular scopes):
   - `phone:read:call_history:admin`
   - `phone:read:call_log:admin`
   - `phone:read:call_history`
   - `phone:read:call_log`
   - `phone:read:list_call_handling_settings:admin`
   - `phone:update:call_handling_settings:admin`
   - `km:read:list_articles:admin` (or the current KM read scope shown in the app's scope picker)
   - `km:write:article:admin`
   - `km:write:sync:admin`
   - `virtual_agent:read:list_engagements:admin`
   - `virtual_agent:read:list_transcripts:admin`

   Scope names occasionally get renamed by Zoom — use the scope picker in your app as the source
   of truth; add only what the tools you actually plan to call require.
5. Activate the app.

## 2. Configure credentials

```bash
cp .env.example .env
# fill in ZOOM_ACCOUNT_ID, ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET
```

The server reads these from `process.env` — either export them in your shell, use a `.env`
loader of your choice, or (recommended) pass them via your MCP client's config as shown below,
so the secret never lives in a file you might commit.

## 3. Build

```bash
npm install
npm run build
```

## 4. Register with an MCP client

**Claude Code:**
```bash
claude mcp add zoom-phone-va \
  --command node \
  --args dist/index.js \
  --env ZOOM_ACCOUNT_ID=xxx \
  --env ZOOM_CLIENT_ID=xxx \
  --env ZOOM_CLIENT_SECRET=xxx
```

**Claude Desktop** (`claude_desktop_config.json`):
```json
{
  "mcpServers": {
    "zoom-phone-va": {
      "command": "node",
      "args": ["/absolute/path/to/zoom-mcp-server/dist/index.js"],
      "env": {
        "ZOOM_ACCOUNT_ID": "xxx",
        "ZOOM_CLIENT_ID": "xxx",
        "ZOOM_CLIENT_SECRET": "xxx"
      }
    }
  }
}
```

## Tools exposed

### Zoom Phone
| Tool | Purpose |
|---|---|
| `phone_get_account_call_history` | Account-wide call history, by date range |
| `phone_get_account_call_logs` | Account-wide call logs, by date range |
| `phone_get_call_log_details` | Single call log detail |
| `phone_get_call_element` | Single call leg/segment detail |
| `phone_get_user_call_history` | One user's call history |
| `phone_get_user_call_logs` | One user's call logs |
| `phone_get_user_call_handling_settings` | Read business/closed/holiday hour routing for a user |
| `phone_update_user_call_handling_setting` | **Write** — update a user's call routing config |
| `phone_get_auto_receptionist_call_handling_settings` | Read routing for an auto receptionist (IVR) |
| `phone_update_auto_receptionist_call_handling_setting` | **Write** — update auto receptionist routing config |

### Zoom Virtual Agent
| Tool | Purpose |
|---|---|
| `va_get_articles` | List KB articles |
| `va_get_article` | Get one KB article |
| `va_create_article` | **Write** — create a KB article |
| `va_update_article` | **Write** — update a KB article |
| `va_sync_knowledge_base` | **Write** — trigger an external KB sync job |
| `va_get_sync_status` | Poll a sync job's status |
| `va_get_engagements` | Bot conversation session report |
| `va_get_query_details` | Query-level detail (what was asked, answered or not) |
| `va_get_transcripts` | Full conversation transcripts |

## Notes on the auth model

- S2S access tokens expire after 1 hour; `src/zoomAuth.ts` caches the token in memory and
  transparently refreshes ~60s before expiry. There is no refresh token in this flow — a new
  token is simply requested.
- All API calls run with the **account's** permissions — every tool call can see data across
  the whole account (subject to granted scopes), not just one user's. Scope your Marketplace
  app's granted scopes to only what you intend these tools to do.
- This flow is appropriate for backend automation on your own account. If you later need this
  server to act on behalf of *other* Zoom accounts/users, that requires switching to User OAuth
  (authorization code + refresh tokens) instead — a materially different auth model, not a
  drop-in swap.

## Project layout

```
src/
  index.ts          # MCP server entry (stdio transport), registers all tools
  zoomAuth.ts        # S2S token fetch + in-memory cache/refresh
  zoomClient.ts      # Thin authenticated fetch wrapper for api.zoom.us/v2
  tools/
    phone.ts         # Zoom Phone tools
    virtualAgent.ts  # Zoom Virtual Agent tools
```

TDQS

B3.1/5.0

Scored across 19 tools

Disambiguation4/5

Most tools are clearly distinct: phone_ tools handle call data/routing while va_ tools handle virtual agent content and analytics. There's some potential confusion between phone_get_account_call_history and phone_get_account_call_logs (similar purpose, distinguished by legacy-style), and between phone_get_call_log_details and phone_get_call_element (both detail views of call segments, but one is by log ID vs element ID).

Naming Consistency4/5

Names follow a consistent prefix_verb_noun pattern: phone_/va_ prefixes cleanly separate domains, and verbs like get/update/create are used consistently. The single deviation is va_sync_knowledge_base, which uses 'sync' as a verb rather than the otherwise consistent get/create/update pattern, making it slightly inconsistent with the established naming style.

Tool Count4/5

19 tools is at the upper edge of the reasonable range, but it spans two distinct product domains (Zoom Phone and Virtual Agent) with rich feature sets. Each tool covers a meaningful operation. Slightly heavy because it combines two domains, but each tool earns its place within its domain.

Completeness4/5

The Virtual Agent side is well-covered with articles CRUD (create/get/update but missing delete), sync operations, and analytics queries. The phone side covers call history, logs, and call handling settings with read/update but lacks create operations (which makes sense for read-centric call data). Minor gaps: no delete article, and the call-handling settings lack full lifecycle coverage, but the core workflows are complete.

Maintenance

ActivitySlowing
ResponsivenessSyncing