Hail
Phone, SMS and email for AI agents. One remote MCP endpoint, OAuth login, zero install.
| Endpoint | https://mcp.hail.so (streamable-http) |
|---|---|
| Registry version | 0.1.2 |
| Now | 38 tools · grade F (49) · pin ts_50d6687af2df88b6132444f6d7a8944c · protocol 2025-06-18 |
| First / last listed | 2026-11-04 02:25 UTC / 2026-11-04 02:25 UTC |
| Grade over time | 2026-11-04: F (49), 38 tools |
Is this your server? Own a server? ToolFit hosted watch re-checks it every 24 h ($0.25 per 30 days) or 6 h ($0.60) and sends you an HMAC-signed webhook when its tools or grade change: POST https://api.youraitools.co/toolfit/v1/watch. To opt out, see About & opt-out.
Tools: place_call send_email upload_email_attachment get_call list_calls send_sms get_sms list_sms get_email list_emails get_email_raw get_email_attachment get_email_events get_email_stats get_events list_agents create_agent route_number quote_numbers acquire_number list_numbers list_number_regions get_number release_number get_verification_requirements submit_verification list_verifications get_verification cancel_verification confirm_verification get_agent update_agent delete_agent list_contacts lookup_contact list_email_domains whoami create_contact
place_call | Originate an outbound phone call.
Provide ``system_prompt`` (mode A — Hail's bundled fallback LLM
uses this prompt) or ``llm`` (mode B — bring your own
OpenAI-compatible endpoint as
``{"base_url": ..., "api_key": ..., "model": ...}``), or both, or
``agent_id`` (a saved agent from ``list_agents`` supplies the
prompt, first message, AI line, voice and params: to*, recipient_consent*, system_prompt, llm, from_, first_message, language, ai_disclosure, recording_enabled, metadata, tools, idempotency_key, consent_source, consent_obtained_at, message_type, agent_id |
send_email | Send an outbound email through your configured SES sender.
``to`` is a non-empty list of RFC-style email addresses. At
least one of ``body_text`` / ``body_html`` is required (both
is fine — multipart-alternative). Prefer including
``body_html``: open and click tracking only works for emails
with an HTML body. A plain-text-only email still gets sent,
params: to*, subject*, recipient_consent*, body_text, body_html, from_, from_name, cc, bcc, reply_to, metadata, idempotency_key, consent_source, consent_obtained_at, message_type, attachment_ids |
upload_email_attachment | Upload a file to attach to a future outbound email.
``content_base64`` is the file's raw bytes, base64-encoded.
Returns ``{"id": ..., "filename": ..., "content_type": ...,
"size_bytes": ...}`` — pass ``id`` in ``send_email``'s
``attachment_ids`` list. The id is reusable across many sends
and expires in 24h if never used. Files over 25MB (combined
wi params: content_base64*, filename*, content_type* |
get_call | Fetch the current state of one call by id.
Use this after ``place_call`` (or to check on any prior call)
to read the call's latest ``status`` and timing fields.
Returns the API's ``CallResponse`` as a dict, or
``{"error": "call not found"}`` for an unknown id.
params: call_id* |
list_calls | List recent calls in your organization, newest first.
Cursor-paginated: pass the previous response's ``next_cursor``
to fetch the next page. ``status`` (one of queued, dialing,
ringing, in_progress, completed, failed, busy, no_answer,
canceled) and ``to`` (E.164) are optional server-side filters.
Returns a dict ``{"items": [...], "next_cursor": <str|None>} params: cursor, limit, status, to |
send_sms | Send an outbound SMS.
``to`` must be E.164 (e.g. ``+14155551234``). ``body`` is the
message text. With no ``from_``: destinations where the sender-ID
carrier delivers a dynamic alphanumeric sender ID unchanged (about
110 countries, see core/hailhq/core/sender_id_corridors.py) use
your organization's sender ID
(or ``HAIL``, the platform default); Aus params: to*, body*, recipient_consent*, from_, metadata, idempotency_key, consent_source, consent_obtained_at, message_type |
get_sms | Fetch the current state of one SMS by id.
Use this after ``send_sms`` to check delivery status.
Example:
get_sms(sms_id="...")
params: sms_id* |
list_sms | Page through recent SMS messages for your organization.
``status`` filters to one of: queued, sent, delivered, failed,
undelivered, received. ``to`` filters to messages sent to a
specific E.164 number. Paginate with the returned
``next_cursor``.
params: cursor, limit, status, to |
get_email | Fetch the full record of one email by id.
Returns the complete row — including ``body_text`` / ``body_html``
and (for inbound mail) the ``in_reply_to`` / ``message_id`` headers
and ``spam``/``virus``/``spf``/``dkim``/``dmarc`` verdicts. Use this
after ``list_emails`` to read a received reply's body.
Returns the API's ``EmailResponse`` as a dict, or
params: email_id* |
list_emails | List emails in your organization, newest first.
Cursor-paginated: pass the previous response's ``next_cursor`` to
fetch the next page. Two optional server-side filters:
* ``direction`` — ``outbound`` or ``inbound``. Pass
``direction="inbound"`` to read replies and other received mail.
* ``status`` — one of ``queued``, ``sent``, ``failed``,
``bo params: cursor, limit, status, direction |
get_email_raw | Get a fetchable URL for an email's original MIME source.
Returns ``{"url": "<presigned-s3-url>"}`` — a short-lived
(~5 minute) link to the full raw RFC822 message. Fetch the URL
directly to read the complete original; it needs no auth header.
Raw source exists for **inbound** mail only — outbound ids return
``{"error": "resource not found"}``.
params: email_id* |
get_email_attachment | Get a fetchable URL for one inbound email attachment.
``attachment_id`` comes from an item in ``get_email``'s
``attachments`` list. Returns ``{"url": "<presigned-s3-url>"}`` —
a short-lived (~5 minute) link to the attachment bytes, fetchable
directly with no auth header. Unknown ids return
``{"error": "resource not found"}``.
params: email_id*, attachment_id* |
get_email_events | Delivery/engagement timeline (sent→delivered→opened…) for one email.
Chronological lifecycle events for a single email — use this to see
exactly what happened to one message (bounced? opened? clicked?)
rather than the account-wide aggregates ``get_email_stats`` returns.
Returns ``{"items": [...], "next_cursor": ...}`` where each item has
``kind`` (one of s params: email_id*, cursor, limit |
get_email_stats | Account-level email deliverability stats (counts, rates, time series).
Aggregates across your whole organization's outbound mail over a
window — use this for "how's deliverability doing" rather than
one message's history (``get_email_events`` covers that).
``from_`` / ``to`` are ISO 8601 timestamps (defaults: last 7 days
ending now). ``bucket`` is ``"day"` params: from_, to, bucket |
get_events | Page through events from across the org or one resource.
Pass ``id="<type>:<uuid>"`` to narrow to a single resource —
supported types are ``call``, ``email``, and ``sms`` (e.g.
``id="sms:<uuid>"`` after ``send_sms``). When narrowed to a
call, the response includes a ``call_status`` field reflecting
the call's current state. Without ``id``, returns events fr params: id, kind, cursor, limit |
list_agents | List the workspace's saved agents: the brains that answer calls
and texts on its numbers, and that ``place_call`` can use via
``agent_id``.
Returns ``{"items": [{"id", "name", "system_prompt", "first_message",
"ai_disclosure", "ai_disclosure_line", "voice_config", "tools",
"max_duration_seconds", "handover_max_duration_seconds", "voice_enabled",
"sm params: none |
create_agent | Save an agent. Then call ``route_number`` so a number answers
with it, or pass its id as ``agent_id`` to ``place_call``.
``system_prompt`` is what the agent follows. ``first_message`` is
spoken after the AI line; omit it to let the caller speak first.
``ai_disclosure_line`` replaces the workspace AI line for this
agent; ``{org}`` becomes the organization na params: name*, system_prompt*, first_message, ai_disclosure, recording_enabled, ai_disclosure_line, language, voice_id, tools, max_duration_seconds, handover_max_duration_seconds, voice_enabled, sms_enabled, handover_contacts |
route_number | Choose which agent answers a number's calls and texts.
``voice_agent_id`` makes the number answer calls with that agent
(Hail registers it for inbound at the carrier and LiveKit);
``clear_voice=True`` stops answering calls. ``sms_agent_id`` makes
the agent reply to texts; ``clear_sms=True`` stops that. A field
left out keeps its value. ``number_id`` comes f params: number_id*, voice_agent_id, sms_agent_id, clear_voice, clear_sms |
quote_numbers | Compare live phone-number offers from the carriers. Read-only: it
buys nothing and charges nothing.
``country_code`` is an ISO alpha-2 code such as ``"US"``.
``capabilities`` is ``["voice"]``, ``["sms"]`` or both (default).
``number_type`` is ``local``, ``mobile``, ``national`` or
``toll_free``; omit it to compare all types.
``area_code`` is an area params: country_code*, capabilities, number_type, area_code, city, region |
acquire_number | Buys a real phone number. It charges the setup fee and the first
month now and renews monthly. Call quote_numbers first. Pass the
offer's quote_id, country_code, number_type, and confirm_total_cents =
monthly_cents + setup_cents from that offer. If the quote expired or
the price changed (409) you get an error: quote again. Only buy when
your user asked for t params: quote_id*, country_code*, number_type*, confirm_total_cents*, idempotency_key, subject_type, verification_id |
list_numbers | List the workspace's dedicated phone numbers, newest first.
``limit`` is 1..200 (default 50). Pass the returned ``next_cursor``
as ``cursor`` for the next page.
Returns ``{"items": [{"id", "e164", "country_code",
"number_type", "capabilities", "provisioning_state", "voice_agent_id",
"sms_agent_id", ...}], "next_cursor"}``, or ``{"error": "<message>"}``. params: limit, cursor |
list_number_regions | List the US state and Canadian province codes that quote_numbers
accepts as ``region`` (optional, with ``city``).
Returns ``{"regions": [{"country_code", "code", "name"}]}``, such as
``{"country_code": "US", "code": "AZ", "name": "Arizona"}``, or
``{"error": "<message>"}``. params: none |
get_number | Fetch one dedicated number by id: its state, capabilities and which
agents answer its calls and texts. ``number_id`` comes from
``list_numbers``.
Returns the ``PhoneNumberResponse`` as a dict, or
``{"error": "<message>"}``. params: number_id* |
release_number | Release a phone number. DESTRUCTIVE and permanent.
Calls and texts to the number stop. This month's fee and any earlier
months are still billed. There is no refund. The number may not be
recoverable.
Ask the user before calling this.
``confirm_e164`` must equal the number's ``e164`` (for example
``"+14155550100"``). The tool reads the number first params: number_id*, confirm_e164* |
get_verification_requirements | Read what the carrier needs to verify the organization before it
can buy a number. Call this when quote_numbers shows an offer with
``readiness == "verification_required"``. Read-only.
``country_code`` is ISO alpha-2 (for example ``"GB"``). ``number_type``
is ``local``, ``mobile``, ``national`` or ``toll_free``.
``subject_type`` is required: ``person`` (the params: country_code*, number_type*, subject_type*, quote_id, number_id, purpose |
submit_verification | Send the user's personal or company details and identity documents
to the carrier for verification. IRREVERSIBLE: once submitted it
cannot be cancelled, and the carrier keeps the documents.
``attest_authorized``: pass true only if the person or company named
in these details and documents asked for this submission and gave you
the details and files in this params: country_code*, number_type*, fields*, documents*, files*, attest_authorized*, subject_type, address, quote_id, number_id, purpose |
list_verifications | List the organization's carrier verifications, newest first.
``purpose`` is ``number`` or ``sms``; only that kind is listed.
Omit to see every registration. ``purpose='sms'`` lists the
registrations to text US numbers from a US number.
Returns ``{"items": [{"id", "country_code", "number_type",
"subject_type", "subject_name", "purpose", "state",
"re params: purpose |
get_verification | Fetch one verification and its state. Use it to poll.
There is no notification, and review takes business days. Poll every
few minutes to hours, not every few seconds; the state refreshes from
the carrier at most once a minute.
On ``approved``: call quote_numbers again. The offer is now
``ready``; then acquire_number. On ``rejected``: read
``reject params: verification_id* |
cancel_verification | Cancel a verification that has not been sent for review, or dismiss
a rejected one (needed before submitting a new one for the same
country and number type). The draft is discarded.
Works in state ``awaiting_review`` or ``rejected``, and for an
``approved`` US SMS registration (``purpose: "sms"``): its numbers
leave the campaign, US-to-US texts from them fa params: verification_id* |
confirm_verification | When a registration shows confirmation 'code', ask the person for
the code the carrier texted and submit it here.
``verification_id`` comes from submit_verification or
list_verifications. ``code`` is 4 to 10 characters. A wrong or expired
code is an error naming ``code``; the carrier sends a new one after a
day. On success the registration moves on; poll ge params: verification_id*, code* |
get_agent | Fetch one saved agent by id. ``agent_id`` comes from ``list_agents``
or ``create_agent``.
Returns the ``AgentResponse`` as a dict (``name``, ``system_prompt``,
``first_message``, ``ai_disclosure``, ``voice_config``, ``tools``,
``voice_enabled``, ``sms_enabled``, ``status``, ...), or
``{"error": "<message>"}``. params: agent_id* |
update_agent | Change a saved agent. Safe to repeat: the same call gives the same
result.
Only the fields you pass change; a field left out keeps its value.
``status`` is ``"live"`` or ``"paused"``; a paused agent does not
answer. ``language`` is a lowercase ISO 639-1 code. Pass ``language``
or ``voice_id`` alone: the other value is kept. ``max_duration_seconds``
params: agent_id*, name, system_prompt, first_message, ai_disclosure, recording_enabled, ai_disclosure_line, language, voice_id, tools, max_duration_seconds, handover_max_duration_seconds, voice_enabled, sms_enabled, status, handover_contacts, clear_fields |
delete_agent | Delete a saved agent. DESTRUCTIVE. Ask the user before calling this.
Numbers that used the agent stop answering with it: calls ring out
and texts reach webhooks only. Reroute them first with
``route_number`` if that matters.
``confirm_name`` must equal the agent's ``name``. The tool reads the
agent first and refuses with ``{"error": ...}`` on a mismatch,
params: agent_id*, confirm_name* |
list_contacts | List the workspace's contacts: org members (with their phone/email)
plus manually saved contacts. Use ``lookup_contact`` for name searches.
``q`` optionally filters server-side (matches name/email/phone).
``limit`` caps the page (server default 100, max 500).
Returns ``{"items": [{"id", "kind", "name", "phone_e164", "email",
"role"}, ...]}`` — ``kind`` is params: q, limit |
lookup_contact | Find a contact by name, email, or phone fragment. Resolve a person
to their ``phone_e164``/``email`` BEFORE calling ``place_call``,
``send_sms``, or ``send_email`` — do not guess a contact's number.
Returns up to 10 matches, same item shape as ``list_contacts``.
Example:
lookup_contact(query="maya")
params: query* |
list_email_domains | List the addresses this workspace can send email from. Call this
BEFORE ``send_email`` when you do not already know the ``from_``
address — do not guess a domain.
Returns ``{"items": [...], "next_cursor": ..., "default_from": ...}``.
Each item carries ``domain``, ``kind`` (``"custom"`` or
``"hail_mail"``) and ``verification_status``; only ``"verified"``
params: cursor, limit |
whoami | Identify the human whose credentials this session runs under.
Use it to sign or route mail as that person: pass their address as
``send_email(reply_to=...)`` so replies reach them rather than the
sending domain, which is often an unattended ``noreply@``.
Returns ``{"auth_kind", "organization_id", "user_id", "email",
"name"}``. ``user_id``/``email``/``name` params: none |
create_contact | Save a new contact for the workspace. Provide at least one of
``phone_e164`` (E.164, e.g. ``+14155551234``) or ``email`` — the API
rejects (422) a contact with neither. A duplicate phone or email on
an existing contact returns 409.
Example:
create_contact(name="Maya Chen", phone_e164="+14155551234")
Returns the created contact entry as a dict. params: name*, phone_e164, email |
Entries describe changes in tool definitions. They are not statements about a server, its quality or its author. The grade measures how the tool definitions fit model providers (token cost, schema compatibility, confusable names) as scored by the ToolFit engine at the time of the listing. JSON: append .json. Opt-out: how or hello@youraitools.co.