Skip to main content
Glama
CTMJSON

ctm-multi-mcp

Official
by CTMJSON
README.md
# ctm-multi-mcp — Multi-account CTM data extraction

A read-only [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server
for extracting data across multiple CallTrackingMetrics sub-accounts using a single
parent/reseller API key.

Built for resellers and agencies managing multiple CTM accounts who need to pull
call data, reports, number inventory, contacts, sources, and configuration
into custom Claude bots.

## How It Works

1. **Auto-discovery** — On startup, the server uses the parent API key to list
   all sub-accounts automatically. No manual configuration needed.
2. **Filtering** — Optionally limit which accounts are queried via env var or
   a JSON config file with optional labels.
3. **Cross-account tools** — Every tool runs across *all* (or selected) accounts
   and returns results with account attribution. One failing account doesn't
   block the rest.
4. **Read-only** — No write tools. Safe for analysis, reporting, and data
   extraction without risk of modifying production config.

## Install

Not yet published to PyPI — install from source for now:

```bash
git clone https://github.com/CTMJSON/ctm-multi-mcp
cd ctm-multi-mcp
python3 -m venv .venv
./.venv/bin/pip install -e .
```

This installs a `ctm-multi-mcp` executable at `.venv/bin/ctm-multi-mcp` inside
the cloned folder — use that full path when connecting a Claude client below.

Once published, install will be:

```bash
pip install ctm-multi-mcp
# or, with no install, via uv:
uvx ctm-multi-mcp
```

## Configuration

| Variable | Required | Description |
|---|---|---|
| `CTM_BASIC_AUTH` | yes | Base64-encoded `access_key:secret_key` for your **parent/reseller** account. Get the keys from CTM → Settings → Account Settings → API, or generate the encoded value with `echo -n "access_key:secret_key" \| base64` |
| `CTM_BASE_URL` | no | Override API base URL (default: `https://api.calltrackingmetrics.com/api/v1`) |
| `CTM_ACCOUNT_IDS` | no | Comma-separated account IDs to limit scope |
| `CTM_ACCOUNTS_FILE` | no | Path to JSON config file (default: `~/.ctm-accounts.json`) |

### Filtering sub-accounts

To limit which accounts the server queries, set `CTM_ACCOUNT_IDS`:

```bash
export CTM_ACCOUNT_IDS=12345,67890,11111
```

Or create `~/.ctm-accounts.json`:

```json
{
  "account_ids": ["12345", "67890", "11111"],
  "labels": {
    "12345": "Client A (Enterprise)",
    "67890": "Client B (SMB)"
  }
}
```

Labels appear in tool output for easy identification. If neither filter is set,
all sub-accounts discoverable by the parent key are included.

## Connecting to Claude Desktop

Edit your Claude Desktop config file
(macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`;
Windows: `%APPDATA%\Claude\claude_desktop_config.json`).
**If the file doesn't exist yet, create it** with a `mcpServers` block and add
an entry for ctm-multi-mcp. Use the full path to the `ctm-multi-mcp` executable
from the [Install](#install) step:

```json
{
  "mcpServers": {
    "ctm-multi-mcp": {
      "command": "/absolute/path/to/ctm-multi-mcp/.venv/bin/ctm-multi-mcp",
      "env": {
        "CTM_BASIC_AUTH": "<base64 access_key:secret_key>"
      }
    }
  }
}
```

Restart Claude Desktop for the change to take effect. To limit which accounts
are queried, add `"CTM_ACCOUNT_IDS": "12345,67890,11111"` to `env`.

## Connecting to Claude Code

```bash
claude mcp add ctm-multi-mcp \
  --env CTM_BASIC_AUTH="<base64 access_key:secret_key>" \
  -- /absolute/path/to/ctm-multi-mcp/.venv/bin/ctm-multi-mcp
```

With account filtering:

```bash
claude mcp add ctm-multi-mcp \
  --env CTM_BASIC_AUTH="<base64 access_key:secret_key>" \
  --env CTM_ACCOUNT_IDS=12345,67890,11111 \
  -- /absolute/path/to/ctm-multi-mcp/.venv/bin/ctm-multi-mcp
```

Once published to PyPI, `-- uvx ctm-multi-mcp` can replace the explicit path
in both of the examples above.

## Connecting to Codex

Edit `~/.codex/config.toml`:

```toml
[mcp_servers.ctm-multi-mcp]
command = "/absolute/path/to/ctm-multi-mcp/.venv/bin/ctm-multi-mcp"

[mcp_servers.ctm-multi-mcp.env]
CTM_BASIC_AUTH = "<base64 access_key:secret_key>"
CTM_ACCOUNT_IDS = "12345,67890"
```

## Claude.ai (web)

Not currently supported — claude.ai's connectors require a hosted/remote MCP
server (reachable over HTTP), while ctm-multi-mcp runs as a local stdio
process. Use Claude Desktop or Claude Code instead until a hosted version
exists.

## Example Prompts

Once connected, you can drive the server in plain English:

- **"Give me a call summary for all accounts for the last 7 days."**
- **"Show me call dispositions across all accounts — what percentage hang up vs answer?"**
- **"Break down call volume by source across all accounts for August."**
- **"Which Google/Bing campaigns drove calls for account 12345 this week, and how many were answered?"**
- **"Export every answered call over 60 seconds for account 12345 yesterday, with name, city, and state."**
- **"Which accounts have voice bots configured and what are their names?"**
- **"List all tracking numbers across all accounts with their routing destination."**
- **"Show me the call logs for account 12345 and 67890 from yesterday."**
- **"Which accounts have the most sources configured?"**
- **"Pull all contacts from every account that have 'test' in the name."**

## Tool Surface

All tools are **read-only** and operate across sub-accounts:

| Category | Tools |
|---|---|
| **Accounts** | `list_sub_accounts`, `account_summary` |
| **Calls** | `search_calls`, `get_call`, `call_export` |
| **Numbers** | `list_numbers`, `get_number` |
| **Contacts** | `list_contacts`, `get_contact` |
| **Sources** | `list_sources`, `get_source` |
| **Queues** | `list_queues`, `get_queue` |
| **Voice Bots** | `list_voice_bots`, `get_voice_bot` |
| **Voice Menus** | `list_voice_menus`, `get_voice_menu` |
| **Schedules** | `list_schedules`, `get_schedule` |
| **Geo Routes** | `list_geo_routes`, `get_geo_route` |
| **Users** | `list_users`, `get_user` |
| **Tags** | `list_tags` |
| **Reports** | `call_summary_report`, `call_disposition_report`, `call_crosstab`, `reports_series`, `reports_series_dimensions`, `account_ledger`, `account_summary` |
| **Texts** | `list_texts` |

Every tool accepts an optional `account_ids` parameter (comma-separated string)
to limit which accounts are queried. Omit to query all discovered accounts.

### Working with large result sets

CTM's API caps `per_page` at roughly 100 records, so a naive integration
either misses data past the first page or has to loop pages by hand. This
server handles that for you in two ways:

- **List tools** (`list_contacts`, `list_numbers`, `list_sources`,
  `list_queues`, `list_users`, `list_voice_bots`, `list_voice_menus`,
  `list_schedules`, `list_geo_routes`, `list_tags`, `list_texts`, and the
  calls-specific `call_disposition_report`/`call_crosstab`/`call_export`)
  page through the CTM API automatically up to a `max_items` (or
  `max_calls`) cap — default 500 for most, higher for calls — and flag a
  `WARNING` in the output if any account had more records than the cap, so
  truncation is never silent. Raise the cap rather than paging manually.
- **Detail tools** (`get_call`, `get_contact`, `get_number`, `get_source`,
  `get_queue`, `get_voice_bot`, `get_voice_menu`, `get_schedule`,
  `get_geo_route`, `get_user`) return the full record by default — which
  for calls in particular can be 100+ fields including transcripts and
  touchpoint history — but accept an optional `fields` parameter (a
  comma-separated list of top-level keys) to return just what's needed.
  Cheaper for anything that checks the same few fields across many records.

For call-level analysis specifically, reach for `call_disposition_report`
(aggregate stats), `call_crosstab` (two-dimension breakdown, e.g. source ×
status or campaign × status), or `call_export` (row-level CSV) rather than
paging through `search_calls` — see each tool's docstring for details on
why (in short: `search_calls` filters and totals only reflect a single
page, and CTM's calls-list payload already includes fields like name,
city, and state, so there's no need to loop `get_call` afterward to fill
those in).

`call_crosstab`/`call_export` can also group or filter by real ad-platform
attribution — `campaign`, `campaign_id`, `keyword`, `ad_group`, `medium`,
`paid_source` — pulled from CTM's per-call "paid" object, not just CTM's
own tracking-source label.

## Architecture

```
ctm-multi-mcp
├── server.py          FastMCP server, credential bootstrap
├── config.py          Account filtering config from env/file
├── ctm_client.py      Standalone CTM HTTP client (auth, paginate, paginate_capped, rate-limiting)
├── accounts.py        Sub-account discovery, resolution, status filtering
├── client.py          MultiAccountClient — concurrent operations across accounts
└── tools/
    ├── accounts.py    list_sub_accounts
    ├── calls.py       search_calls, get_call
    ├── contacts.py    list_contacts, get_contact
    ├── geo_routes.py  list_geo_routes, get_geo_route
    ├── numbers.py     list_numbers, get_number
    ├── queues.py      list_queues, get_queue
    ├── reports.py     call_summary_report, call_disposition_report,
    │                  call_crosstab, call_export, reports_series,
    │                  reports_series_dimensions, account_ledger,
    │                  account_summary
    ├── schedules.py   list_schedules, get_schedule
    ├── sources.py     list_sources, get_source
    ├── tags.py        list_tags
    ├── texts.py       list_texts
    ├── users.py       list_users, get_user
    ├── voice_bots.py  list_voice_bots, get_voice_bot
    └── voice_menus.py list_voice_menus, get_voice_menu
```
## Support

email: jason.smith@ctm.com 

## License

MIT

TDQS

A4/5.0

Scored across 32 tools

Disambiguation4/5

The list_*/get_* pairs are each clearly scoped to a distinct entity (numbers, contacts, sources, etc.). The reporting tools (call_disposition_report, call_summary_report, call_export, call_crosstab, reports_series) have overlapping call-data purposes but their descriptions clarify distinct output formats and use cases, so an agent can usually pick correctly.

Naming Consistency4/5

The dominant pattern is list_<entity> / get_<entity>, which is consistent and predictable. The reporting tools deviate with names like call_export, call_crosstab, and reports_series, which are still descriptive but don't follow a uniform verb_noun convention. Overall the set is orderly, not chaotic.

Tool Count3/5

At 32 tools, the server is on the heavy side, exceeding the 3-15 tool sweet spot. However, the count is largely justified by the need to cover 12+ entity types with list/get pairs plus several reporting tools. It's not bloated with redundant duplicates, but it does tax an agent's context window.

Completeness4/5

The server covers a wide range of call-tracking resources (calls, numbers, contacts, sources, queues, bots, menus, schedules, routes, users, tags, texts) and provides multiple reporting/export options. Minor gaps exist: no get_text, get_tag, or get_sub_account, and no mutation operations, but for a read-heavy reporting API this is largely complete.

Maintenance

ActivitySlowing
ResponsivenessNo issues