Skip to content

Releases: pghdma/callrail-mcp

v1.1.3 — Mutation-testing audit: vacuous tests fixed, py.typed shipped

Choose a tag to compare

@pghdma pghdma released this 03 Jul 18:44

[1.1.3] - 2026-07-03

Fixed — deep-audit round 4: mutation testing + shipped-artifact audit

Round 4 turned the audit on the TEST SUITE and the PACKAGING — layers
no code-reading or fuzzing round can reach. A 14-mutant battery
(deliberate bugs injected into critical lines, one at a time) exposed
two vacuous regression tests guarding changelog-advertised guarantees:

  • test_v042_post_does_NOT_retry_on_5xx never tested its claim.
    The fixture builds the client with max_retries=0 — NOTHING retries
    in that configuration, so the test passed even with POST added to
    the idempotent-retry set. The CRITICAL double-write guard from
    v0.4.2 (duplicate $3/mo trackers) had zero effective coverage. Now
    runs with retries enabled.
  • test_v050_compare_periods_no_overlap asserted on its own local
    re-implementation
    of the window arithmetic — it never called
    compare_periods, so the v0.5.0 double-count fix could regress
    without any test failing. Rewritten end-to-end against the window
    boundaries the tool actually returns.
  • 36500-day cap had no boundary coverage — a mutant raising the
    cap 1000× survived (the existing test used 10**18, which any large
    cap rejects). Added days=36500/36501 boundary test.
  • 1 equivalent mutant identified and documented (paginate total_pages
    min-clamp is shadowed by the while-loop bound — differs only in
    warning emission). Final score: 13/13 non-equivalent mutants killed.

Fixed — packaging

  • py.typed was missing from the wheel (PEP 561). The project
    ships mypy-strict annotations and documents library usage, but
    without the marker downstream type checkers ignore ALL annotations.
    Added marker + package-data config; verified present in the built
    wheel.

Documentation

  • Docstring Args drift fixed on update_user (5 params undocumented),
    get_text_message, get_webhook (account_id undocumented) — found
    by a mechanical docstring-vs-signature sweep across all 59 tools.

Verified

  • Shipped PyPI artifact (1.1.2) smoke-tested in a clean venv on
    Python 3.14: installs, imports, registers all 59 tools.
  • Tests 395 → 396.

v1.1.2 — Response-shape fuzz + property-testing hardening

Choose a tag to compare

@pghdma pghdma released this 03 Jul 18:26

[1.1.2] - 2026-07-03

Fixed — deep-audit round 3: response-shape fuzz + property-based testing

Round 2 fuzzed tool INPUTS; round 3 fuzzed what CallRail sends BACK
(2,891 calls with structurally-valid-but-garbage-typed responses) and
brought hypothesis property testing to invariants no prior round had
mechanically verified.

Response-shape hardening (182 fuzz failures, 8 roots)

  • paginate() now yields only dict items. A malformed collection
    containing strings/ints/nulls crashed all six paginate-consuming
    tools (call_summary, usage_summary, compare_periods,
    spam_detector, search_calls_by_number, bulk_update_calls) with
    raw AttributeError on item.get(). Non-dict items are skipped with
    a warning.
  • call_eligibility_check — wrong-typed utm_source/source/
    source_name crashed .lower(); now safely coerced.
  • search_calls_by_number — non-string customer_phone_number in
    a call record crashed _digits_only; now skipped.

Hypothesis findings (property-based, permanent in

tests/test_properties.py)

  • _date_window OverflowError — reachable in production. A
    valid-format ancient end_date (e.g. "0001-01-01") minus a large
    days lookback lands before year 1, which date cannot represent —
    list_calls(days=36500, end_date="0001-01-01") crashed with a raw
    OverflowError despite the 36500-day cap. Now clamped to date.min.
  • Money invariant now machine-verified: per-company cost shares in
    usage_summary sum to estimated_cycle_total exactly (cent-level)
    across 120 randomized fleets per run.
  • Also pinned: _safe_path can never emit a dot-segment or unencoded
    reserved char for ANY input; _parse_retry_after always lands in
    [0, 60] and finite; _is_toll_free and _date_window are total
    functions over garbage.

Added — tests

  • tests/test_properties.py — 6 hypothesis property suites.
  • 4 targeted regressions (ancient end_date, paginate non-dict skip,
    wrong-typed eligibility fields, non-string phone field).
  • Tests 385 → 395.

v1.1.1 — All-tool fuzz hardening (1,051 crash paths closed)

Choose a tag to compare

@pghdma pghdma released this 03 Jul 18:05

[1.1.1] - 2026-07-03

Fixed — deep-audit round 2: all-tool adversarial fuzz

A mechanical fuzz pass (every tool × every parameter × 17 garbage
values ≈ 5,000 calls) found 1,051 violations of the project's core
error contract ("tools never raise — always return a JSON envelope").
Root cause: the shared validators assumed string inputs; any non-string
type (int, list, bytes, bool, float) sent by a loose-JSON MCP client
for a string field crashed with raw TypeError/AttributeError across
essentially all 59 tools. Prior releases fuzzed only tracker tools, so
the class went unseen.

Shared-validator hardening (fixes ~all 1,051 at the root)

  • _require_non_empty — rejects non-string types with a clear
    "must be a string (got int)" envelope instead of passing them
    through to crash downstream.
  • _validate_id_shape, _validate_date, _validate_phone,
    _validate_email, _validate_length, _validate_area_code,
    _validate_pool_size — explicit type guards.
  • _clamp_per_page — coerces numeric strings ("250"), returns 1 on
    garbage (previously raw TypeError from the < comparison).
  • New _clamp_page — replaces 13 inline max(1, page) call sites
    that raised TypeError on string/None pages.
  • _clean_tag_list — non-list input returns [] with a warning
    (mirrors _tag_names_from; previously iterated ints → TypeError).
  • update_call / update_form_submission / create_form_submission
    tags type-checked as list before len().
  • call_eligibility_check — threshold coerced before the < 0
    comparison (None/"60" previously raised TypeError).

Behavior kept honest with docstrings

  • delete_tag(tag_id=812) (int form) now works — the docstring has
    promised "accepts string or numeric forms" since v0.6.1 but int
    input crashed with TypeError. get_tag/update_tag same.
  • search_calls_by_number(phone_number=4125551234) — a phone number
    arriving as a JSON number is now coerced instead of crashing.

Wire safety

  • NaN/Infinity rejected in value params (update_form_submission,
    create_form_submission, update_sms_thread). json.dumps
    serializes non-finite floats as bare NaN/Infinity tokens —
    invalid JSON on the wire to CallRail.

Response-shape robustness (client.py)

  • paginate() — a malformed response with a STRING where the items
    array belongs would yield from single characters; consumers then
    crashed on 'str'.get(). Non-list items now stop pagination with
    a warning.
  • resolve_account_id()accounts as a dict raised raw
    KeyError: 0; now a catchable CallRailError.

Added — tests

  • tests/test_fuzz_all_tools.py — the full adversarial matrix
    (59 tools × every param × 17 garbage values, ~5k calls) is now a
    permanent regression test pinning the never-raise invariant.
  • 6 targeted regressions (paginate string-items, dict accounts,
    NaN value ×3 tools, int tag_id, JSON-number phone, clamp helpers).
  • Tests 320 → 385.

v1.1.0 — Leads, SMS threads, server-side analytics

Choose a tag to compare

@pghdma pghdma released this 03 Jul 15:56

[1.1.0] - 2026-07-03

Added — 9 new tools (API caught up with us, so we caught up with it)

CallRail shipped substantial API surface between April and July 2026.
All endpoint shapes below were live-verified against a production
account on 2026-07-03 (read-only probes) before implementation.

  • list_leads / get_lead_timeline — CallRail's deduplicated
    person records + full cross-channel history (calls, forms, texts)
    per lead with first/last-touch attribution. Replaces the manual
    "search calls by number + search forms by email" reconstruction.
  • list_sms_threads / get_sms_thread / update_sms_thread
    — SMS-thread lead management. update_sms_thread closes the write
    gap where texting leads couldn't be tagged / noted / qualified via
    API (notes, value, tags with append_tags, lead_qualification).
  • call_stats — server-side aggregation via /calls/summary.json
    (group_by source/keywords/campaign/referrer/landing_page/company).
    One request instead of paginating every call.
  • call_timeseries — per-day call-volume trend via
    /calls/timeseries.json.
  • form_stats — server-side form totals via /forms/summary.json.
  • get_call_page_views — the visitor's page-view journey behind a
    call; pairs with call_eligibility_check for conversion debugging.

Changed — validators updated to CallRail's newly-published enums

CallRail has now documented enums we originally discovered empirically
— and our fail-fast validation had become over-restrictive:

  • VALID_TAG_COLORS: 10 → 24 values. The docs now publish the full
    24-color table; our 10-value tuple was rejecting 14 documented-valid
    colors. Live-verified 2026-07-03: create_tag(color="cyan1")
    succeeds (probe tag created + deleted).
  • VALID_SOURCE_TYPES: 7 → 12 values. Union of the 10 documented
    values (adds landing_url, landing_params, web_referrer,
    search, mobile_ad_extension) and the 2 production-proven values
    the docs still omit (facebook_all, bing_all).

Changed — transcript gating (CallRail breaking change 2026-05-21)

  • get_call_transcript 404s now carry a disambiguation hint.
    CallRail's 2026-05-21 API change gates transcript data behind
    Premium Conversation Intelligence — without it the endpoint 404s
    even when a transcript exists in the UI. The error envelope now
    explains both possible causes instead of returning a bare 404.

Documentation

  • README: new "How this compares to CallRail's official MCP server"
    section (CallRail shipped a hosted OAuth MCP server ~30 tools;
    this project stays pip-installable, local, 59 tools, with agency
    cost-attribution tooling the official one lacks).
  • CLAUDE.md: API-coverage notes refreshed for July 2026 (outbound
    caller IDs now documented-but-403, MMS send shipped May 5 but still
    needs A2P registration, message flows + integration filters
    documented-but-403).

Added — tests

  • 14 new tests (306 → 320): all 9 new tools (happy paths + validation
    rejections + PUT body shape), enum expansion guards, transcript-404
    hint. Tag-color guard test updated to pin the 24-value set.

v1.0.3

Choose a tag to compare

@pghdma pghdma released this 25 Apr 12:57

Security: bump dependency floors to clear 6 known CVEs flagged by supply-chain scanners. mcp>=1.23.0, requests>=2.33.0. No code changes.

v1.0.2

Choose a tag to compare

@pghdma pghdma released this 25 Apr 04:21

Expand docstrings on 12 tools (7 C-grade + 5 B-grade per Glama tool-quality scoring) to match the structured Args/Returns/Notes pattern. Add glama.json declaring maintainers. No functional changes.

v1.0.1

Choose a tag to compare

@pghdma pghdma released this 24 Apr 23:00

Add mcp-name ownership marker to README so callrail-mcp can be claimed in the official MCP Registry. No code changes.

v1.0.0 — First stable release

Choose a tag to compare

@pghdma pghdma released this 24 Apr 21:03

First stable release of callrail-mcp 🎉

pipx install callrail-mcp
# or
pip install callrail-mcp

What's in the box

49 tools spanning ~85% of the CallRail REST API v3:

  • Read tools (calls, companies, trackers, users, tags, form submissions, text messages, recordings, transcripts)
  • Write tools (update_call, tag CRUD, form submission updates)
  • Tracker provisioning (full CRUD with billing-confirmation safeguard)
  • Account management (Companies CRUD, Users CRUD)
  • Notifications + Integrations (CRUD on alert rules, discovery for GMB / Google Ads / etc.)
  • Outbound calling (with confirm_dialing=True safety gate)
  • Form-submission backfill for offline leads
  • Agency aggregation tools: usage_summary, compare_periods, bulk_update_calls, spam_detector, call_eligibility_check

Out of scope (deliberately)

See README's "Out of scope" section for the documented gaps:

  • SMS send + webhook integration CRUD: blocked by CallRail account permissions (return 403 on standard accounts)
  • Outbound Caller IDs, Numbers, Call Flows, Custom Fields, DNC list: not exposed by CallRail's REST API (UI-only on standard plans)

PRs welcome from anyone whose CallRail plan exposes those endpoints.

Quality gates

  • 297 tests, all green
  • 84% line coverage
  • All 5 check tools clean: pytest -W error, mypy --strict, ruff, bandit, pyright
  • ~115 bugs caught + fixed across 18 development releases (audit-and-test discipline)

See CHANGELOG.md for the full development history.

🤖 Built with the help of Claude Code