Skip to main content
Glama
CTMJSON

ctm-number-provisioner

Official
by CTMJSON

ctm-number-provisioner

A small, standalone MCP server that lets an AI assistant search for, buy, and configure CallTrackingMetrics (CTM) tracking numbers using your own CTM API credentials.

It exposes six tools: identify the account, search available numbers, buy numbers (exact numbers or "any N in this area code"), list routing targets, apply a name / tracking source / call route to one or many numbers, and release numbers.

It is a plain Python package with no internal dependencies. It talks only to the public CTM API at https://api.calltrackingmetrics.com/api/v1.


Contents


Related MCP server: AgentPhone MCP Server

Safety model

Buying phone numbers costs money and cannot be undone except by releasing the number. This server is built so an assistant cannot spend your money or touch the wrong account by accident:

  • buy_numbers defaults to dry_run=True. It returns a plan and makes zero write calls. You must see the plan and explicitly confirm before the assistant calls it again with dry_run=False.

  • test=True buys free test numbers. Use it for all evaluation and development. The README and the tool docstrings both default you toward test=True until you are ready for real numbers.

  • whoami is the first tool you should call. It shows exactly which CTM account the credentials resolve to, so you never buy on an unexpected account. See How the wrong-account risk is prevented.

  • release_numbers refuses to run unless confirm=True. It is destructive and irreversible.

  • Every successful purchase is appended to purchases.log (JSON lines: UTC timestamp, account id, TPN id, number, test flag) as a local audit trail. This file is git-ignored.

  • Secrets are never printed. The token is read into memory, used as an HTTP Authorization header, and reported only as its source (env or env.txt:<name>), never its value.


Requirements

  • Python 3.10+

  • A CTM API basic-auth token (base64 access:secret) for the account you want to work in.


Install

git clone https://github.com/<your-org>/ctm-number-provisioner.git
cd ctm-number-provisioner

python3.12 -m venv .venv          # any Python >= 3.10
.venv/bin/pip install -e '.[dev]' # omit [dev] if you don't want test tools

This installs a console script at .venv/bin/ctm-number-provisioner. Use that absolute path in the MCP client configuration below.


Configure credentials

The server resolves credentials in this order:

  1. CTM_BASIC_AUTH — the raw base64 access:secret token. Use this for a single account.

  2. CTM_ENV_FILE (default ~/.ctm/env) plus CTM_TOKEN_NAME — a named line in a name:token credentials file. Use this to keep several accounts side by side and switch per tool call.

The CTM account id comes from CTM_ACCOUNT_ID. Every tool also accepts an optional account_id override, and an optional token_name override to select a different line from the credentials file.

Option A: single account, environment variable

export CTM_BASIC_AUTH="$(printf '%s' 'ACCESS:SECRET' | base64)"
export CTM_ACCOUNT_ID="12345"

Option B: multiple accounts, credentials file

Create ~/.ctm/env (chmod it 600) with one line per account:

acme_main:<base64-access-secret>
acme_test:<base64-access-secret>

Then select a line:

export CTM_ENV_FILE="$HOME/.ctm/env"
export CTM_TOKEN_NAME="acme_main"
export CTM_ACCOUNT_ID="12345"

Lines starting with # and blank lines are ignored. Only the first : on a line splits the name from the token, so tokens containing : are preserved.

Never commit your token. .gitignore already excludes .env, *.env, .ctm/, and credentials.json.


Run the server

The server speaks MCP over stdio (stdout is reserved for the protocol):

CTM_BASIC_AUTH="…" CTM_ACCOUNT_ID="12345" .venv/bin/ctm-number-provisioner

On startup it prints the resolved account to stderr only, for example:

[ctm-number-provisioner] account 12345 (Acme Tracking) via env

Most users never run this by hand — the MCP client launches it. The sections below wire it into each client.


Use with Claude Code / Claude Desktop

Claude Code (CLI)

Register the server once at user scope:

claude mcp add ctm-numbers -s user \
  -e CTM_BASIC_AUTH="<base64-access:secret>" \
  -e CTM_ACCOUNT_ID="12345" \
  -- "$PWD/.venv/bin/ctm-number-provisioner"

Verify it connected and lists the six tools:

claude mcp list

Then, in a session, try:

Call ctm-numbers whoami, then search for available numbers in area code 443.

Multiple accounts: register one server per account with different names and CTM_TOKEN_NAME values, e.g. ctm-numbers-acme and ctm-numbers-acme-test.

Claude Desktop

Edit claude_desktop_config.json (~/Library/Application Support/Claude/ on macOS, %APPDATA%\Claude\ on Windows):

{
  "mcpServers": {
    "ctm-numbers": {
      "command": "/absolute/path/to/ctm-number-provisioner/.venv/bin/ctm-number-provisioner",
      "env": {
        "CTM_BASIC_AUTH": "<base64-access:secret>",
        "CTM_ACCOUNT_ID": "12345"
      }
    }
  }
}

Restart Claude Desktop. The ctm-numbers tools appear in the tool picker.

Bundled skill (optional)

If you use Claude Code, copy skills/ctm-buy-numbers/SKILL.md into ~/.claude/skills/ctm-buy-numbers/SKILL.md. It teaches the assistant the safe order of operations (whoami → search → dry-run → confirm → buy → configure) so users don't have to prompt step by step.


Use with Codex CLI

Codex supports MCP servers over stdio. Add a block to ~/.codex/config.toml:

[mcp_servers.ctm-numbers]
command = "/absolute/path/to/ctm-number-provisioner/.venv/bin/ctm-number-provisioner"
env = { CTM_BASIC_AUTH = "<base64-access:secret>", CTM_ACCOUNT_ID = "12345" }

Then start Codex and ask it to use the ctm-numbers tools:

Use the ctm-numbers MCP server: call whoami, then search area code 443.

Prefer CTM_ENV_FILE + CTM_TOKEN_NAME in the env table if you'd rather not put the token literal in config.toml.


Use with a local LLM

Any MCP-capable local client works, because this server is just a stdio process.

Generic stdio MCP config (Open WebUI, LibreChat, Continue, Cline, custom scripts, etc.):

{
  "mcpServers": {
    "ctm-numbers": {
      "command": "/absolute/path/to/ctm-number-provisioner/.venv/bin/ctm-number-provisioner",
      "env": { "CTM_BASIC_AUTH": "…", "CTM_ACCOUNT_ID": "12345" }
    }
  }
}

Local model quality note. Number search returns large lists and the tools return JSON. A capable tool-calling model (e.g. a 30B+ instruct model with function calling, such as Qwen or Llama derivatives) handles this well. Smaller models often forget to call whoami first or skip the dry-run confirmation — the bundled skill file is worth including in the system prompt for those.

No MCP support? Drive the tools directly from Python. Each tool is an ordinary async function:

import asyncio, json
from ctm_numbers.server import whoami, search_available_numbers

async def main():
    print(json.dumps(await whoami(), indent=2))
    found = await search_available_numbers(area_code="443")
    print(found["count"], "numbers available")

asyncio.run(main())

Set CTM_BASIC_AUTH and CTM_ACCOUNT_ID in the environment first.


Tools

Tool

Writes?

What it does

whoami

no

Resolves and reports the account id + name and the token source.

search_available_numbers

no

Finds available numbers by area code, ZIP/address, prefix, toll-free, or international pattern.

buy_numbers

yes

Buys numbers. dry_run=True by default; test=True buys free test numbers.

list_routing_targets

no

Lists tracking sources, receiving numbers, queues, voice menus, users, smart/conditional routers, geo routers, routing tables, and VoiceAI bots.

configure_numbers

yes

Applies a name, tracking source, and one call route to one or many numbers.

release_numbers

yes

Releases numbers. Requires confirm=True.

buy_numbers arguments

  • phone_numbers — exact numbers from search_available_numbers, or

  • area_code + quantity — let CTM pick N numbers in that area code

  • test — buy free test numbers (default False)

  • dry_run — plan only, no writes (default True)

configure_numbers routes

Pick at most one route. All of these are verified against the live API except routing_table_id (see the note below).

Argument

Id format

Routes to

receiving_number_ids

RPN...

One or more receiving numbers (dial[] populated).

queue_id

CQU...

A call queue.

voice_menu_id

VOM...

A voice menu / IVR.

user_id

USR...

An agent, with user_no_answer_seconds and user_default_action.

conditional_router_id

numeric (e.g. 196)

A smart / conditional router.

geo_route_id

GEO...

A geo router.

voice_bot_id

VBT...

A VoiceAI bot.

routing_table_id

RTT...

A routing table.

route_override

—

A raw {"virtual_phone_number": {…}} body, if none of the above fit.

name accepts {n} (1-based index) and {number} placeholders, e.g. "Google Ads {n}".


  1. whoami — confirm the account. Tell the user which account you'll act on.

  2. search_available_numbers — show the results and let the user pick.

  3. buy_numbers(dry_run=True) — show the plan.

  4. Get explicit confirmation, then buy_numbers(dry_run=False) with test=True unless the user says it's for real.

  5. list_routing_targets — present the options; let the user choose the tracking source and route target. Route kinds include basic routes (receiving number, queue, voice menu, agent) and advanced routers (smart/conditional router, geo router, routing table, VoiceAI bot).

  6. configure_numbers — apply the name, source, and exactly one route.

  7. release_numbers(…, confirm=True) — only if the user explicitly asks, typically to clean up test numbers.

The skill at skills/ctm-buy-numbers/SKILL.md encodes this flow.


CTM API endpoints used

Base URL: https://api.calltrackingmetrics.com/api/v1 (override with CTM_BASE_URL).

All requests send Authorization: Basic <token>, Accept: application/json, and Content-Type: application/json. Query parameters with None values are dropped. Non-2xx responses raise CTMError(status, body, method, path); tools catch it per item so one failure can't abort a batch.

Purpose

Method + path

Identify account (whoami, startup check)

GET /accounts/{aid}

Search available numbers

GET /accounts/{aid}/numbers/search.json

Buy an exact number

POST /accounts/{aid}/numbers

Buy in an area code

POST /accounts/{aid}/numbers/areacode

Read a number

GET /accounts/{aid}/numbers/{TPN}

Rename / set custom fields

POST /accounts/{aid}/numbers/{TPN}/update_number

Attach a tracking source

POST /accounts/{aid}/sources/{TSO}/numbers/{TPN}/add

Add a receiving number

POST /accounts/{aid}/numbers/{TPN}/receiving_numbers/{RPN}/add

Set the call route

PUT /accounts/{aid}/numbers/{TPN}/dial_routes

Release a number

DELETE /accounts/{aid}/numbers/{TPN}

List tracking sources

GET /accounts/{aid}/sources

List receiving numbers

GET /accounts/{aid}/receiving_numbers

List queues

GET /accounts/{aid}/queues.json

List voice menus

GET /accounts/{aid}/voice_menus

List users

GET /accounts/{aid}/users

List smart / conditional routers

GET /accounts/{aid}/conditional_routers → configs[]

List geo routers

GET /accounts/{aid}/geo_routes → geo_routes[]

List routing tables

GET /accounts/{aid}/routing_tables → routing_tables[]

List VoiceAI bots

GET /accounts/{aid}/voice_bots → voice_bots[]

Search parameters

US/Canada (country=US, the default). searchby is inferred when omitted:

Intent

Params

Area code

searchby=area&areacode=443

ZIP / street address

searchby=address&address=21201

Area code + prefix

searchby=number&number=917563

Toll-free

searchby=tollfree

International

country=GB&pattern=430[&operator=start_with|includes]

Search returns up to ~50 numbers per call and includes overlay area codes.

Call-route bodies

// queue (CQU...)
{"virtual_phone_number": {"dial_route": "call_queue", "call_queue_id": "CQU..."}}

// voice menu (VOM...)
{"virtual_phone_number": {"dial_route": "voice_menu", "voice_menu_id": "VOM..."}}

// agent (USR...)
{"virtual_phone_number": {
  "dial_route": "call_agent",
  "user_id": "USR...",
  "user_default_action_label": "voicemail",
  "user_no_answer_seconds": 25
}}

// smart / conditional router (the numeric id from GET /conditional_routers)
{"virtual_phone_number": {
  "dial_route": "conditional_router",
  "conditional_router_id": 196
}}

// geo router (GEO...)
{"virtual_phone_number": {"dial_route": "geo_config", "geo_config_id": "GEO..."}}

// routing table (RTT...)
{"virtual_phone_number": {"dial_route": "routing_table", "routing_table_id": "RTT..."}}

// VoiceAI bot (VBT...)
{"virtual_phone_number": {"dial_route": "voice_bot", "voice_bot_id": "VBT..."}}

Verified against the live API:

  • After each of the above, re-reading the number shows route_to.type equal to call_queue, voice_menu, receiving_number, conditional_router, geo_config, or voice_bot, with the id echoed under route_to.dial.

  • The queue body sets route_to.type == "call_queue" with route_to.dial.id equal to the CQU... id.

  • The tracking-source add works with the TSO... id from GET /sources (the numeric filter_id is not required).

  • GET /numbers/{TPN} returns the number object directly (not wrapped in a number key). Route changes are confirmed by re-reading route_to.

Unverified: routing_table_id. The parameter name and dial_route value come from CTM's own frontend, and the request is accepted, but the test account had no routing tables to route to, so a positive route_to.type == "routing_table" could not be observed. Treat it as best-effort until confirmed on an account that has routing tables.

Note on silent failures. CTM returns 200 {"status": "success"} for a dial_routes PUT even when it ignores an unrecognized route. Always verify by re-reading the number's route_to rather than trusting the PUT response. list_routing_targets also returns an empty list for a kind with no entries (e.g. routing_tables), so "0 results" may mean "none configured" rather than "wrong endpoint".

Rate limits

CTM allows roughly 10 requests/second. The server uses a global concurrency limit of 4, retries transport errors twice, and retries 429/5xx with exponential backoff (1s, 2s, 4s). Paginated list endpoints fetch page 1, then all remaining pages concurrently.


How the wrong-account risk is prevented

A CTM token does not guarantee the account you expect: the same token can map to a different sub-account than its label suggests, and some CTM clients silently fall back to a default account. This server defends against that in several layers:

  1. whoami resolves the account from the API, not from configuration. It calls GET /accounts/{CTM_ACCOUNT_ID} and returns the name CTM reports for that id, alongside the id and the token source. If the returned name is not the account you intended, you stop before buying anything.

  2. The server prints the resolved account name on startup (to stderr), so a misconfigured client is visible immediately in the logs.

  3. Every tool takes an optional account_id override that is resolved per call, so a single server can safely target different accounts without relying on a stale global default.

  4. buy_numbers dry-runs by default and includes the resolved account id and name in the plan, so the last thing a human sees before approving a purchase is exactly which account it will hit.

  5. buy_numbers never proceeds on ambiguity: it requires exactly one of phone_numbers or area_code, and validates the quantity range up front.

The recommended assistant behavior (encoded in the bundled skill) is: call whoami first, state the account name to the user, and refuse to buy until the user confirms that name.


Development

.venv/bin/pytest -q          # 20 tests, all offline (respx, no live calls)
.venv/bin/ruff check src tests

Project layout:

src/ctm_numbers/
  __main__.py   console entry point + stderr startup check
  auth.py       credential resolution (env var or named line in a file)
  client.py     async httpx wrapper: retries, backoff, concurrent pagination
  server.py     FastMCP instance and the six tools
tests/test_tools.py

License

MIT

Available Tools

6 tools
buy_numbersA

Purchase CTM tracking numbers. Real purchases cost money unless test=True.

ALWAYS call with dry_run=True first (the default), show the user the plan (account name, count, numbers or area code), and get explicit confirmation before calling again with dry_run=False. There is no undo except release_numbers.

Args: phone_numbers: Exact numbers from search_available_numbers. area_code: Let CTM pick numbers in this area code (see quantity). quantity: How many numbers to buy in area_code mode (1-500). test: Buy free test numbers. Default False (real, billable). dry_run: Plan only, no write calls. Default True.

Returns rows with tpn_id; feed those tpn_ids to configure_numbers.

ParametersJSON Schema
NameRequiredDescriptionDefault
testNo
dry_runNo
quantityNo
area_codeNo
account_idNo
token_nameNo
phone_numbersNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It discloses that real purchases are billable, that test=True avoids charges, that dry_run makes no write calls, that there is no undo except release_numbers, and that the return rows contain tpn_id. This is excellent transparency for a mutating, money-spending tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the most critical warning and workflow requirement. The Args block is compact and each line adds value. The return-value note is brief and actionable. No sentence is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, lack of annotations, and lack of output schema, the description is nearly complete: it covers purpose, mandatory dry-run workflow, cost, irreversibility, parameter semantics, and the next step. The only notable gaps are the undocumented account_id and token_name parameters, which prevent a perfect score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds meaningful semantics for phone_numbers, area_code, quantity, test, and dry_run, including the 1-500 range, the mode distinction, and the exact source for phone numbers. However, account_id and token_name are not explained at all, which is a noticeable gap given the schema provides only titles and defaults.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Purchase CTM tracking numbers.' It clearly distinguishes this from sibling tools by positioning it as the purchasing step, and even references the downstream configure_numbers and the undo path release_numbers. The cost warning ('Real purchases cost money unless test=True') further clarifies the tool's real-world effect.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit, actionable usage rules: ALWAYS call with dry_run=True first, show the user the plan, and get explicit confirmation before dry_run=False. It also explains the relationship to search_available_numbers and configure_numbers, and names release_numbers as the only undo path. This is model behavior for guiding an agent through a high-stakes workflow.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

configure_numbersB

Apply a name, tracking source, and one call route to one or many numbers.

Get ids from list_routing_targets and let the user choose them. Pick at most ONE route: receiving_number_ids, queue_id (CQU...), voice_menu_id (VOM...), user_id (USR..., rings an agent), or route_override (raw {"virtual_phone_number": {...}} dial_routes body). Pick at most ONE route: receiving_number_ids, queue_id (CQU...), voice_menu_id (VOM...), user_id (USR...), conditional_router_id (smart router), geo_route_id (GEO...), routing_table_id (RTT...), voice_bot_id (VBT...), or route_override.

Args: tpn_ids: Tracking numbers to configure (TPN...). name: Label. Supports {n} (1-based index) and {number} placeholders, e.g. "Google Ads {n}". custom_fields: Custom field values to set on each number. source_id: Tracking source id (TSO... or numeric) to attach the numbers to. receiving_number_ids: RPN ids to forward calls to. route_override: Raw dial_routes body; only if the named routes don't fit.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNo
tpn_idsYes
user_idNo
queue_idNo
source_idNo
account_idNo
token_nameNo
geo_route_idNo
voice_bot_idNo
custom_fieldsNo
voice_menu_idNo
route_overrideNo
routing_table_idNo
user_default_actionNovoicemail
receiving_number_idsNo
conditional_router_idNo
user_no_answer_secondsNo

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It does imply mutation ('apply'), states the at-most-one-route constraint, gives ID prefix conventions (CQU..., VOM..., USR...), and notes that user_id 'rings an agent'. But it never states whether existing number configuration gets overwritten, whether the call is idempotent, or what happens to unspecified settings — a notable gap for a config-mutation tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The structure is sensible on paper — summary line, guidance paragraph, then args list — but it repeats the 'Pick at most ONE route' sentence nearly verbatim twice with different route enumerations, wasting space and creating ambiguity about which enumeration is authoritative. This is a real structural defect, not merely verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 17-parameter tool with no annotations, no output schema, and 0% schema coverage, this description is incomplete. Four parameters (account_id, token_name, user_default_action, user_no_answer_seconds) go unmentioned, and there's no guidance on defaults that materially affect behavior, such as user_default_action='voicemail' and user_no_answer_seconds=25, which an agent would need to make sound choices.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate, and it does for several parameters: tpn_ids (TPN...), name (with {n} and {number} placeholders and an example), source_id (TSO... or numeric), receiving_number_ids (RPN), and route_override. It also enumerates route params with ID formats. But account_id, token_name, user_default_action, and user_no_answer_seconds are entirely undocumented, and the route parameters lack semantic explanations of what each route type actually does.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

'Apply a name, tracking source, and one call route to one or many numbers' is a specific verb+resource+scope statement that clearly identifies the tool as a configuration operation over tracking numbers. It is distinguishable from siblings like buy_numbers (acquisition) and release_numbers (teardown). Not a 5 because the purpose is somewhat buried under the duplicated route-guidance paragraph, and the identity isn't crisply framed against the configure family.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a concrete prerequisite and interaction pattern — 'Get ids from list_routing_targets and let the user choose them' — and an explicit exclusion for route_override ('only if the named routes don't fit'). However, there is no contrast with buy_numbers or release_numbers on when to configure vs acquire/release, and the duplicated 'Pick at most ONE route' sentence lists different route sets each time, which could mislead the agent about which routes are valid.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_routing_targetsA

List everything a tracking number can be attached or routed to.

Present these options to the user and let them choose; do not pick for them. With more than four options, show a numbered list and have the user reply with a number, or use search to narrow it down.

Args: kinds: Subset of ["sources", "receiving_numbers", "queues", "voice_menus", "users", "conditional_routers", "geo_routes", "routing_tables", "voice_bots"]. Default: the first four. search: Case-insensitive substring filter on the row's fields.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindsNo
searchNo
account_idNo
token_nameNo

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It does disclose that the tool is a listing operation and instructs the agent to defer to the user's choice, which is useful behavioral context. However, it does not mention any side effects (likely none, but not stated), authentication requirements, rate limits, or the exact return format. The description adds some behavioral nuance but leaves significant gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the purpose, followed by usage guidance and then parameter details. It is reasonably concise, though the parameter list for kinds is somewhat verbose. Every section earns its place, and the overall length is appropriate for the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, so the description should explain what the tool returns, but it does not. It also fails to explain the purpose of account_id and token_name, and gives no indication of pagination or error behavior. While it provides usage instructions, it is incomplete for a tool with four parameters and no output schema, leaving an agent to guess about return format and omitted parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% description coverage, so the description must explain all parameters. It covers 'kinds' and 'search' with detailed semantics: the allowed subset for kinds and the default set, and the case-insensitive substring behavior for search. However, it completely omits 'account_id' and 'token_name', which are likely authentication/context parameters. This leaves half of the parameters unexplained, making the description insufficient for full parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear verb and resource: 'List everything a tracking number can be attached or routed to.' This unambiguously distinguishes it from sibling tools that handle phone numbers (search_available_numbers, buy_numbers, etc.) or identity (whoami). The purpose is specific and immediately actionable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit instructions on how to use the tool's results: present options to the user, never pick for them, and use a numbered list or search for more than four results. This is strong practical guidance. However, it does not explicitly state when NOT to use this tool or name alternative tools for other scenarios, relying on the sibling names to imply that.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

release_numbersA

Release (delete) tracking numbers. Destructive and irreversible.

Use this only when the user explicitly asks, typically to clean up test numbers. Refuses unless confirm=True; ask the user first.

ParametersJSON Schema
NameRequiredDescriptionDefault
confirmNo
tpn_idsYes
account_idNo
token_nameNo

TDQS

A3.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so excellently. It explicitly warns 'Destructive and irreversible' and discloses the confirmation requirement: 'Refuses unless confirm=True; ask the user first.' This is exactly the safety-critical behavioral disclosure an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three short sentences with the most critical warning ('Destructive and irreversible') front-loaded. Every sentence adds value, and there is no repetition or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description adequately covers safety and usage, but it leaves two optional parameters (account_id, token_name) unexplained and mentions nothing about return behavior or failure modes. Since those parameters are optional and the key confirmation behavior is documented, the gap is moderate rather than severe.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description needed to compensate for the four parameters. It only addresses confirm, mentioning that the tool refuses unless confirm=True. It does not explain tpn_ids, account_id, or token_name, leaving their semantics largely undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb and resource: 'Release (delete) tracking numbers.' The parenthetical 'delete' removes ambiguity, and the destructive nature distinguishes it from sibling tools like buy_numbers or configure_numbers. It doesn't explicitly name a sibling, so it misses the top score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a clear trigger condition: 'Use this only when the user explicitly asks,' and a typical scenario: 'to clean up test numbers.' It also describes the expected flow of asking for confirmation. It doesn't explicitly mention alternatives to prefer in non-deletion cases, so it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_available_numbersA

Search available CTM tracking numbers before buying them.

US/CA modes (searchby is inferred when omitted):

  • area_code: numbers in an area code (e.g. "443").

  • address: street or ZIP (e.g. "21201").

  • number_prefix: area code + prefix (e.g. "917563").

  • tollfree: the default when nothing else is given.

International: pass country (e.g. "GB") with pattern, and optionally operator="start_with" or "includes" (default includes).

Returns compact rows: number, friendly_name, type, region, postal_code, lata, sms, mms, addr_required, hipaa_friendly. Show them to the user and let them pick before calling buy_numbers.

ParametersJSON Schema
NameRequiredDescriptionDefault
addressNo
countryNoUS
patternNo
operatorNo
searchbyNo
area_codeNo
account_idNo
token_nameNo
number_prefixNo

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full burden of behavioral disclosure. It does a good job: it explains search behavior (inference of searchby modes), the return format (compact rows with specific fields), and instructs the agent to show results to the user before buying, which is a behavioral expectation. However, it doesn't disclose potential side effects (e.g., whether this is a read-only operation) or rate limits, but the description is still informative.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with bullet points for different modes, making it easy to scan. It fronts the purpose and then provides essential details without redundancy. Every sentence adds value: it explains modes, international usage, return fields, and next steps. It's concise yet comprehensive for a tool with many parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (9 optional parameters, no output schema), the description is quite complete: it covers the main search modes, international options, return fields, and the expected workflow (show results to user). However, it doesn't explain the meaning of all return fields (e.g., lata, hipaa_friendly) or account_id/token_name usage, which could be critical for correct invocation in some scenarios. Minor gaps remain.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, meaning the schema provides no descriptions for parameters. The description explains the semantics of area_code, address, number_prefix, searchby, and country/operator/pattern, which are the main parameters. However, it does not explain account_id or token_name, which are likely identifiers for multi-tenant use. Given the low coverage, the description compensates partially but misses some parameters, warranting a 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb ('Search') and resource ('available CTM tracking numbers') and distinguishes the tool's purpose from buying them, explicitly saying 'before buying them' and later 'let them pick before calling buy_numbers'. This differentiates it from sibling tools like buy_numbers. The scope (US/CA and International modes) is clearly outlined, making the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use the tool: it is for searching before buying, and it explains how searchby modes are inferred when omitted. It also mentions the alternative (buy_numbers) implicitly, but doesn't explicitly state when NOT to use it or name alternatives beyond 'buy_numbers' in the last line. Sibling tools like configure_numbers and release_numbers are not mentioned as alternatives, but the description's context is sufficient for most agents.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

whoamiA

Show which CTM account the server will act on, and where the token came from.

Call this first and tell the user the resolved account name before buying anything: a token can map to a different account than its label suggests.

ParametersJSON Schema
NameRequiredDescriptionDefault
account_idNo
token_nameNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full disclosure burden. It conveys that this is a read-only resolution operation and surfaces the key behavioral trait — token labels may not match the resolved account. It omits the output format, but for a simple identity check that is a minor gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences with zero filler. The core purpose is front-loaded in the first sentence, and the usage directive follows in the second. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a simple identity tool with two optional params and no output schema: the description covers purpose, sequencing, and the one critical caveat. The exact return shape would be a nice addition but is not essential for a whoami-style call.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description should compensate, but it never explains what account_id and token_name do or how passing them alters resolution. The two optional nullable parameters are left to inference, though the description's mention of 'token' and 'account' loosely maps to them.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('show') and a precise resource ('which CTM account the server will act on, and where the token came from'). It clearly differentiates itself from siblings that buy, search, configure, and release numbers — this is the identity-resolution tool in a purchase workflow.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs the agent to 'call this first' and to relay the resolved account name before buying anything, with the underlying reason stated (a token can map to a different account than its label suggests). This is direct, actionable routing guidance relative to buy_numbers and the rest of the workflow.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.1.0
    • First observedbuy_numbers
    • First observedconfigure_numbers
    • First observedlist_routing_targets
    • First observedrelease_numbers
    • First observedsearch_available_numbers
    • First observedwhoami

TDQS

A3.8/5.0

Scored across 6 tools

Disambiguation5/5

Each tool addresses a distinct stage of the number lifecycle: identity (whoami), discovery (search_available_numbers), acquisition (buy_numbers), routing options (list_routing_targets), configuration (configure_numbers), and removal (release_numbers). There is no meaningful overlap between tool purposes.

Naming Consistency4/5

Most tools follow a clear verb_noun snake_case pattern (search_available_numbers, buy_numbers, list_routing_targets, configure_numbers, release_numbers). The lone exception is 'whoami', a standard Unix-style command name, which is a minor deviation rather than a systemic inconsistency.

Tool Count5/5

Six tools provide a focused, well-scoped surface for a number-provisioning server. Each tool corresponds to a necessary step in the purchase-to-configuration workflow, and none feels redundant or extraneous.

Completeness3/5

The lifecycle is well covered from search/buy through configure/release, but there is no tool to list or inspect already-owned/configured numbers. This is a notable gap for agents needing to manage existing inventory, though the core provisioning flow can still be completed.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    B
    quality
    F
    maintenance
    Enables interaction with Telnyx's telephony, messaging, and AI assistant APIs to manage phone numbers, send messages, make calls, and create AI assistants. Includes webhook support for real-time event handling and comprehensive tools for voice, SMS, cloud storage, and embeddings.
    46
    25
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables querying telecom routing data (LRN, CNAM, DNO, LERG, toll-free routing) directly from AI assistants like Claude, ChatGPT.
    14
    71 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to purchase virtual phone numbers, retrieve SMS verification codes, and manage activations through natural language by wrapping the VirtualSMS Consumer API.
    9 npm
    MIT