Hail
REMOTE · MCP.HAIL.SO
Phone, SMS and email for AI agents. One remote MCP endpoint, OAuth login, zero install.
Available components
Last scanned (3 hours ago)
Scores higher than 93% of remote servers
Reachable. Last reached 5 Nov 2026.
Category Communication
How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. How we score → Why this is hard to score →
Endpoint Security89
- The endpoint's TLS certificate is valid, in date, and uses a strong key. View diagnostics → Pass
- Authorisation is enforced on tool calls, advertised via RFC 9728 protected-resource metadata. Discovery is public, which costs nothing: no tool can be invoked without a token. View diagnostics → Pass
- HTTPS is enforced; there's no plaintext access path. View diagnostics → Pass
- HSTS check failed: the Strict-Transport-Security header is absent. See how to fix → View diagnostics → Fail
- DNSSEC check failed: this domain isn't protected by DNSSEC. See how to fix → View diagnostics → Fail
- The authorisation server offers only Dynamic Client Registration (RFC 7591), which MCP 2026-07-28 deprecated in favour of Client ID Metadata Documents. View diagnostics → Partial
Transport & Reachability100
- Verified streamable-http transport via a live MCP handshake. View diagnostics → Pass
Schema Quality & AI Usability57
- AI-judged instruction clarity (good).Pass
- Context-footprint check failed: tool/resource definitions use about 10379 tokens (~273/item across 38 items; 38 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management100
- No destabilizing schema changes in the last 30 days.Pass
Tool Coverage71
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 0% of tool parameters carry a description.Fail
- Structured output schemas are declared (100% of tools); any adoption earns full credit.Pass
Tool Safety100
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- All 4 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.Pass
- An AI judge read all 38 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 5 Nov 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 4 Nov 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 3 Nov 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 2 Nov 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 1 Nov 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 31 Oct 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 30 Oct 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
- 29 Oct 26 0
- This server's schema is too large to store in full, so we cannot compare its tools day to day functional
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 5 Nov 2026 · Probed https://mcp.hail.so
TLS valid
Negotiated TLS 1.3 with TLS_AES_128_GCM_SHA256 .
| Subject | Issuer | Valid from | Valid until | Key | Signature | Serial |
|---|---|---|---|---|---|---|
| CN=mcp.hail.so | CN=YE2,O=Let's Encrypt,C=US | 22 Sept 2026 | 21 Dec 2026 | ECDSA 256 | ECDSA-SHA384 | 5afb84d0ebc368652e1947aad1db950b587 |
| SANs: mcp.hail.so | ||||||
| CN=YE2,O=Let's Encrypt,C=US (CA) | CN=Root YE,O=ISRG,C=US | 3 Sept 2025 | 2 Sept 2028 | ECDSA 384 | ECDSA-SHA384 | 4df3b15dd6c0784c507cd37b58e6f115 |
| CN=Root YE,O=ISRG,C=US (CA) | CN=ISRG Root X2,O=Internet Security Research Group,C=US | 13 May 2026 | 2 Sept 2032 | ECDSA 384 | ECDSA-SHA384 | 872165fc34b6e5fba8add5b3705fb53a |
| CN=ISRG Root X2,O=Internet Security Research Group,C=US (CA) | CN=ISRG Root X1,O=Internet Security Research Group,C=US | 13 May 2026 | 2 Sept 2032 | ECDSA 384 | SHA256-RSA | 6c8f1dc727c7117f7baf853ac980f9cd |
Background: What to check on a remote MCP endpoint →
DNSSEC insecure
Validation of mcp.hail.so. — Not signed
| Zone | DS | Keys | Algorithms | Outcome |
|---|---|---|---|---|
| . | trust_anchor | 20326, 38696 | 8, 8 | Verified |
| so. | absent | Unsigned (proven) parent-signed NSEC/NSEC3 proves an unsigned delegation |
Authentication Enforced and verified
The endpoint asked for a token and published valid RFC 9728 metadata describing how to get one.
| Result | Enforced and verified |
|---|---|
| Enforced | On tool calls |
| HTTP status | 200 |
WWW-Authenticate challenge Bearer error="invalid_token", error_description="Authentication required", resource_metadata="https://mcp.hail.so/.well-known/oauth-protected-resource"
Bearer error="invalid_token", error_description="Authentication required", resource_metadata="https://mcp.hail.so/.well-known/oauth-protected-resource" Protected resource metadata
| Document | https://mcp.hail.so/.well-known/oauth-protected-resource |
|---|---|
| Retrieved | Yes |
| Resource | https://mcp.hail.so/ |
| Authorisation server | https://hail.so/api/auth |
Background: How OAuth 2.1 works in the 2026 MCP spec →
Transports 2 probes
| Transport | URL | Outcome | Status | Location |
|---|---|---|---|---|
| streamable-http | https://mcp.hail.so | Verified | 200 | |
| http (plaintext) | http://mcp.hail.so | HTTPS enforced | 308 | https://mcp.hail.so/ |
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
acquire_number Buy a phone number ~478
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 this number. After buying, call route_number to point it at an agent. A quote buys at most one number. After a timeout, retry with the same quote_id and idempotency_key, or check list_numbers. Never request a new quote after a timeout. ``idempotency_key`` is optional; to retry after a timeout or a 5xx, pass the ``idempotency_key`` that result returned, so the number is bought once. A 4xx error returns no key: the API remembers that failure under it, so drop it and quote again. ``verification_id`` picks which approved verification buys a number that needs one: the ``id`` of an approved entry in the offer's ``verifications``. Pass it when more than one is approved: show the user each ``subject_name`` (the person's or company's name) and ask which. With one approved, it is used. ``subject_type`` (``person`` or ``business``) is optional: it keeps only that kind. A 422 on ``verification_id`` means several are approved and none was picked, or the id is not an approved verification for this number. Returns the number (``id``, ``e164``, ``provisioning_state``, ``verification_id``, ``subject_name``, ...) plus ``idempotency_key``, or ``{"error": "<message>", "idempotency_key": ...}``.
| Name | Type | Req | Description |
|---|---|---|---|
| confirm_total_cents | integer | yes | – |
| country_code | string | yes | – |
| idempotency_key | – | – | – |
| number_type | string | yes | – |
| quote_id | string | yes | – |
| subject_type | – | – | – |
| verification_id | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
cancel_verification Cancel a verification ~210
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 fail again, and its monthly fee stops after the current month. Some registrations bill at least 3 monthly fees from approval, so cancelling sooner can still charge 3 months. Ask the user first. A ``submitting`` or ``submitted`` verification is under review and cannot be cancelled (409): wait for the result with get_verification. Returns the verification with ``state: "cancelled"``, or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| verification_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
confirm_verification Confirm a verification with a code ~161
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 get_verification until ``approved``. A ``confirmation`` of ``reply`` means the person answers the carrier's text instead; nothing is entered here. Returns the verification or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| code | string | yes | – |
| verification_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
create_agent Create an agent ~381
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 name. ``language`` is a lowercase ISO 639-1 code. ``max_duration_seconds`` is 60..3600. ``handover_max_duration_seconds`` (60..3600, default 1800) is the call limit once a handover contact answers. ``handover_contacts``: People the agent may hand a live call to: ``[{contact_id, note}]`` (max 10; ``note`` 1-200 chars says when to hand over). ``contact_id`` is an id from ``list_contacts``: a contact's uuid, or ``member:<user id>`` for a team member. The person needs a phone number. The agent never sees it. Returns the API's ``AgentResponse`` as a dict, or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| ai_disclosure | boolean | – | – |
| ai_disclosure_line | – | – | – |
| first_message | – | – | – |
| handover_contacts | – | – | – |
| handover_max_duration_seconds | – | – | – |
| language | – | – | – |
| max_duration_seconds | – | – | – |
| name | string | yes | – |
| recording_enabled | – | – | – |
| sms_enabled | boolean | – | – |
| system_prompt | string | yes | – |
| tools | – | – | – |
| voice_enabled | boolean | – | – |
| voice_id | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
create_contact Create a contact ~144
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. On failure returns ``{"error": "<message>"}`` instead.
| Name | Type | Req | Description |
|---|---|---|---|
| – | – | – | |
| name | string | yes | – |
| phone_e164 | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
delete_agent Delete an agent ~171
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, without deleting anything. To stop an agent without deleting it, use ``update_agent`` with ``status="paused"``. Returns ``{"deleted": true, "agent_id", "name"}``, or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| agent_id | string | yes | – |
| confirm_name | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_agent Get an agent ~110
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>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| agent_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_call Get a call ~90
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.
| Name | Type | Req | Description |
|---|---|---|---|
| call_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_email Get an email ~141
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 ``{"error": "resource not found"}`` for an unknown id.
| Name | Type | Req | Description |
|---|---|---|---|
| email_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_email_attachment Get an email attachment ~115
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"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| attachment_id | string | yes | – |
| email_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_email_events Get email events ~243
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 sent, delivered, delivery_delayed, bounced, complained, rejected, opened, clicked), ``payload``, and ``occurred_at``. ``opened`` and ``clicked`` only occur for emails sent with an HTML body. A plain-text-only email still gets sent, delivered, and bounce events, but opens and clicks are never tracked. Pass ``cursor`` from a previous ``next_cursor`` to page (``limit`` 1..1000, default 100). On failure returns ``{"error": "resource not found"}`` for an unknown id.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| email_id | string | yes | – |
| limit | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_email_raw Get raw email ~111
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"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| email_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_email_stats Get email stats ~293
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"`` (default) or ``"hour"`` — ``"hour"`` is limited to an 8-day span; any range is capped at 92 days. Returns ``{"from": ..., "to": ..., "bucket": ..., "totals": {...}, "rates": {...}, "series": [...]}`` — ``totals``/each ``series`` bucket carry counts (sent, delivered, bounced, opened, ...) and ``rates`` carries derived ratios (delivery, bounce, open, click), each ``None`` when ``totals.sent`` is 0. Open and click numbers only count HTML emails. Plain-text-only emails are never tracked for opens or clicks, so they lower those rates. On failure returns ``{"error": "<message>"}`` instead.
| Name | Type | Req | Description |
|---|---|---|---|
| bucket | string | – | – |
| from_ | – | – | – |
| to | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_events Get events ~353
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 from across the whole org. ``kind`` filters server-side by event kind (``state_change``, ``agent_turn``, ``user_turn``, ``tool_call``, ``error``, ...). This is **not** a streaming subscription — the call returns whatever events exist now plus a ``next_cursor`` if more pages remain. To follow a call to completion, loop: pass the previous response's ``next_cursor`` until ``next_cursor`` is null and, when narrowed to a call, ``call_status`` is one of ``completed``, ``failed``, ``busy``, ``no_answer``, ``canceled`` (the terminal set). Example: get_events(id="call:0c2f...-...", limit=200) Returns ``{"items": [...], "next_cursor": <str|None>, "call_status": <str|None>}`` on success, or ``{"error": ...}`` on a malformed ``id`` or upstream failure.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| id | – | – | – |
| kind | – | – | – |
| limit | integer | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_number Get a number ~74
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>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| number_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_sms Get a text message ~51
Fetch the current state of one SMS by id. Use this after ``send_sms`` to check delivery status. Example: get_sms(sms_id="...")
| Name | Type | Req | Description |
|---|---|---|---|
| sms_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_verification Get a verification ~174
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 ``rejection_reason``, ask the user, dismiss it with cancel_verification, then submit a new one. ``verification_id`` comes from submit_verification or list_verifications. Returns ``{"id", "state", "rejection_reason", ...}`` or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| verification_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
get_verification_requirements Get verification requirements ~564
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 user) or ``business`` (the user's company). Never guess it: ask the user "you or your company?". The result's ``subject_types`` lists what the carrier accepts. The organization may hold one verification of each. ``quote_id`` is the offer's ``quote_id`` from quote_numbers. It is required for ``purpose='number'``: the form is the one for that offer. ``purpose`` is ``number`` (default, to buy a number) or ``sms``. ``purpose='sms'`` registers the organization to text US numbers from a US number (country_code US, number_type local). Fees are charged from credit per step: when the details are submitted, when the carriers start their review, then monthly after approval. If the error says to pass ``number_id``, pass the ``id`` of one of the organization's US numbers from list_numbers. Returns ``required`` (false means no verification is needed and submit_verification would fail), ``fields`` (values to collect), ``documents`` (slots, each with ``options``), ``address_required``. A field of kind ``choices`` takes several of its ``options``: send their keys joined by commas (for example ``"CUSTOMER_CARE,DELIVERY_NOTIFICATION"``), between ``min_choices`` and ``max_choices`` of them. A field with ``max_length`` takes at most that many characters, counted in UTF-16 code units (an emoji counts as 2); a longer value is refused (422). Skip any document slot whose ``needs_input`` is false: do not send it.…
| Name | Type | Req | Description |
|---|---|---|---|
| country_code | string | yes | – |
| number_id | – | – | – |
| number_type | string | yes | – |
| purpose | string | – | – |
| quote_id | – | – | – |
| subject_type | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_agents List agents ~117
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", "sms_enabled", "status", "handover_contacts"}, ...]}``.
Input schema present but exposes no named parameters.
Structured output declared, but exposes no named fields.
No examples provided.
list_calls List calls ~128
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>}``.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| limit | integer | – | – |
| status | – | – | – |
| to | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_contacts List contacts ~157
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 ``"member"`` (id ``member:<user_id>``, ``role`` set) or ``"manual"`` (id is the contact's UUID, ``role`` null).
| Name | Type | Req | Description |
|---|---|---|---|
| limit | – | – | – |
| q | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_email_domains List email domains ~253
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"`` rows can send. A ``custom`` item's ``domain`` is a bare DNS name that accepts any local-part (``sales@acme.com``); a ``hail_mail`` item's ``domain`` is already a full address. ``default_from`` is the address a ``send_email`` with no ``from_`` goes out as. A verified custom domain wins over the hail-mail address. It is null when the workspace has several verified custom domains — then ``send_email`` rejects a missing ``from_`` and you must pass one from ``items``. Example: list_email_domains()
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| limit | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_emails List emails ~214
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``, ``bounced``, ``complained``, ``received``. Items are trimmed summaries (no message body). Call ``get_email`` with an item's ``id`` to read the full body. Example: list_emails(direction="inbound") Returns ``{"items": [...], "next_cursor": <str|None>}`` on success, or ``{"error": "<message>"}`` instead.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| direction | – | – | – |
| limit | integer | – | – |
| status | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_number_regions List number search regions ~92
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>"}``.
Input schema present but exposes no named parameters.
Structured output declared, but exposes no named fields.
No examples provided.
list_numbers List numbers ~124
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>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| limit | integer | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_sms List text messages ~93
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``.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | – | – | – |
| limit | integer | – | – |
| status | – | – | – |
| to | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
list_verifications List verifications ~300
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", "rejection_reason", "created_at", "updated_at", "submitted_at", "approved_at", "confirmation", "step", "identity_confirmed"}]}``. ``step`` says where a US texting filing in progress is: ``confirm_identity``, ``filing`` or ``carrier_review``. ``identity_confirmed`` on a person's newest filing, when cancelled or rejected: the next one with the same person needs no code. Dismiss a rejected filing first. ``state`` is ``awaiting_review``, ``submitting``, ``submitted``, ``approved``, ``rejected`` or ``cancelled``. Use it to check whether a submission already exists before submitting again. ``confirmation`` ``code``: call confirm_verification with the code the carrier texted the person. Errors come back as ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| purpose | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
lookup_contact Look up a contact ~97
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")
| Name | Type | Req | Description |
|---|---|---|---|
| query | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
place_call Place a phone call ~939
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 tools; fields you pass here win). At least one is required. Passing both runs your prompt on your own endpoint: it receives Hail's voice preamble plus your prompt as the leading system message. ``to`` must be E.164 (e.g. ``+14155551234``). ``from_`` is optional and defaults to the first active number on your org. ``first_message`` is spoken verbatim on pickup; omit it to let the agent open the conversation itself — it reacts to how the call was answered, or introduces itself after silence. ``language`` sets the call's spoken language for speech-to-text, text-to-speech, and turn detection, as a lowercase ISO 639-1 code (e.g. ``"da"``); 39 languages are supported (server rejects others with 422); omit for English. STT provider selection is console-BYO-only (no per-call override); configure it on the organization to pin a provider. ``ai_disclosure=False`` skips the spoken "this is an AI assistant" line at the start of the call. Leave enabled unless the user has verified it is not required for this call — US artificial-voice calls (47 CFR 64.1200(b)(1)) and several AI bot-disclosure laws require it, and Hail does not verify this. The agent still identifies itself as an AI if asked. ``recording_enabled``: omit it to follow the agent, then the workspace recording setting. ``true`` records this call and ``false`` does not, whatever the workspace setting. Pass it only when the user asked fo…
| Name | Type | Req | Description |
|---|---|---|---|
| agent_id | – | – | – |
| ai_disclosure | boolean | – | – |
| consent_obtained_at | – | – | – |
| consent_source | – | – | – |
| first_message | – | – | – |
| from_ | – | – | – |
| idempotency_key | – | – | – |
| language | – | – | – |
| llm | – | – | – |
| message_type | string | – | – |
| metadata | – | – | – |
| recipient_consent | boolean | yes | – |
| recording_enabled | – | – | – |
| system_prompt | – | – | – |
| to | string | yes | – |
| tools | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
quote_numbers Quote phone numbers ~751
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 code or the first digits of the number, as dialed in the country: ``"020"`` (London), ``"480"`` (Arizona). ``city`` is a city name such as ``"London"``. Each offer has ``locality`` and ``region`` (such as ``"Phoenix"``, ``"AZ"``): check them, since city names repeat across states. ``region`` (optional, with ``city``) is a US/CA state code; list_number_regions lists them. Omit all three to search the whole country. Returns ``{"offers": [...], "recommended_quote_id", "expires_at"}``. Each offer has ``e164``, ``number_type``, ``capabilities``, ``monthly_cents``, ``setup_cents``, ``readiness``, ``requirements``, ``subject_types``, ``verifications`` and ``quote_id``. Money fields are cents (USD). Quotes expire in 10 minutes (``expires_at``); quote again after that. ``subject_types`` says who may verify for the number: ``person`` (the user), ``business`` (the user's company), or both; empty when no verification is needed. ``verifications`` lists the verifications the offer can be bought with (``id``, ``state``, ``subject_type``, ``subject_name``), approved first: those of an accepted subject type still under review, and the approved ones the carrier still accepts for the offer. ``subject_name`` is the person's or company's name from the verification form. When several are approved, show the user the names and ask which one buys the number; pass its ``id`` as ``verification_id`` to acquire_number.…
| Name | Type | Req | Description |
|---|---|---|---|
| area_code | – | – | – |
| capabilities | – | – | – |
| city | – | – | – |
| country_code | string | yes | – |
| number_type | – | – | – |
| region | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
release_number Release a number ~221
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 and refuses with ``{"error": ...}`` on a mismatch, without releasing anything. A number whose order is still pending cannot be released (409). A number whose order failed was never bought: the call only hides it from the list and returns ``{"dismissed": true, "number_id", "e164"}``. Returns ``{"released": true, "number_id", "e164"}``, or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| confirm_e164 | string | yes | – |
| number_id | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
route_number Route a number to an agent ~165
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 from GET /numbers. Returns the updated ``PhoneNumberResponse`` as a dict, or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| clear_sms | boolean | – | – |
| clear_voice | boolean | – | – |
| number_id | string | yes | – |
| sms_agent_id | – | – | – |
| voice_agent_id | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
send_email Send an email ~844
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, delivered, and bounce events, but opens and clicks are never tracked. ``cc``, ``bcc``, and ``reply_to`` are optional and follow the usual mail conventions. Hail sets no ``reply_to`` of its own — to have replies reach the human running this session, call ``whoami`` and pass their ``email``. ``recipient_consent`` is required and must be the JSON boolean ``true`` (strings and numbers are refused): attest that you (the caller triggering this request) have obtained the lawful consent needed to email this recipient. The API rejects the request (422) if this is not ``true`` — Hail does not verify consent for you. Set ``message_type="marketing"`` for promotional email (this additionally requires a non-empty ``consent_source`` describing how/where consent was obtained) — leave as the default ``"informational"`` for transactional/service email. ``from_`` is optional while the workspace has one verified custom domain, which wins over the hail-mail address, or only a hail-mail address. With several verified custom domains, omitting it returns 422 listing them — call ``list_email_domains`` and pass one. With no verified sender, the server auto-mints a per-org hail-mail address of the form ``<user>+<org>@<base>`` if the operator configured ``HAIL_MAIL_BASE_DOMAIN`` (the ``<org>`` part is derived from your organization id; the ``<user>`` part comes from ``HAIL_MAIL_FROM``, from ``HAIL_MAIL_DEFAULT_USER_P…
| Name | Type | Req | Description |
|---|---|---|---|
| attachment_ids | – | – | – |
| bcc | – | – | – |
| body_html | – | – | – |
| body_text | – | – | – |
| cc | – | – | – |
| consent_obtained_at | – | – | – |
| consent_source | – | – | – |
| from_ | – | – | – |
| from_name | – | – | – |
| idempotency_key | – | – | – |
| message_type | string | – | – |
| metadata | – | – | – |
| recipient_consent | boolean | yes | – |
| reply_to | – | – | – |
| subject | string | yes | – |
| to | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
send_sms Send a text message ~467
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); Australia (+61) may use ``HAIL`` instead; every other destination, including the US and Canada, uses your organization's oldest active SMS-capable number and the API returns 422 when there is none. SMS never uses the shared voice-call pool. ``recipient_consent`` is required and must be the JSON boolean ``true`` (strings and numbers are refused): attest that you (the caller triggering this request) have obtained the lawful consent needed to text this recipient. The API rejects the request (422) if this is not ``true`` — Hail does not verify consent for you. Set ``message_type="marketing"`` for promotional texts (this additionally requires a non-empty ``consent_source``) — leave as the default ``"informational"`` for transactional/service texts. ``idempotency_key`` defaults to a fresh UUID and is returned in the response under ``idempotency_key`` — pass the same value on a retry to replay rather than re-send. Example: send_sms(to="+14155551234", body="Your order shipped!", recipient_consent=True) Returns the ``SmsResponse`` dict (id, status, from_e164, to_e164, segment_count, ...). On failure returns ``{"error": "<message>"}`` instead.
| Name | Type | Req | Description |
|---|---|---|---|
| body | string | yes | – |
| consent_obtained_at | – | – | – |
| consent_source | – | – | – |
| from_ | – | – | – |
| idempotency_key | – | – | – |
| message_type | string | – | – |
| metadata | – | – | – |
| recipient_consent | boolean | yes | – |
| to | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
submit_verification Submit a verification ~818
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 conversation. Never invent, guess, infer or reuse personal data or documents. Anything else: pass false (the call then sends nothing). ``purpose`` is ``number`` (default) or ``sms``. ``purpose='sms'`` registers the organization to text US numbers from a US number (country_code US, number_type local). The carrier fee is debited from credit at filing. Pass the same ``purpose`` to get_verification_requirements. For ``purpose='number'``, pass the same ``quote_id`` you passed to get_verification_requirements. For ``purpose='sms'``, pass the same ``number_id`` if you passed one. ``subject_type`` is required: ``person`` (the user) or ``business`` (the user's company), the same one passed to get_verification_requirements. Never guess it: ask the user "you or your company?". The verification is named after the person or company in the form (for example the business name, or the first and last name); it comes back as ``subject_name``. The organization may hold several verifications of one subject type (several people or companies). Steps: 1) call get_verification_requirements. 2) Ask the user for each field and file. 3) Call this once. Do not submit twice: a 409 means one with the same subject type and name already exists (see list_verifications). Arguments, as listed by get_verification_requirements: ``fields`` is ``{field name: value}``. ``address`` is ``{"customer_name", "street", "city…
| Name | Type | Req | Description |
|---|---|---|---|
| address | – | – | – |
| attest_authorized | boolean | yes | – |
| country_code | string | yes | – |
| documents | object | yes | – |
| fields | object | yes | – |
| files | array | yes | – |
| number_id | – | – | – |
| number_type | string | yes | – |
| purpose | string | – | – |
| quote_id | – | – | – |
| subject_type | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
update_agent Update an agent ~416
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`` and ``handover_max_duration_seconds`` are 60..3600. ``handover_contacts`` replaces the whole list: ``[{contact_id, note}]`` (max 10) as in ``create_agent``; ``[]`` removes every contact. To remove a value, list its name in ``clear_fields``. Allowed: ``first_message`` (the agent waits for the caller), ``ai_disclosure_line`` (back to the workspace line), ``tools`` (back to all tools), ``max_duration_seconds`` (back to the workspace limit), ``handover_max_duration_seconds`` (back to 1800). Any other name is refused. Do not set and clear one field. Live calls keep the settings they started with. The change applies to the next call or text. Returns the updated ``AgentResponse`` as a dict, or ``{"error": "<message>"}``.
| Name | Type | Req | Description |
|---|---|---|---|
| agent_id | string | yes | – |
| ai_disclosure | – | – | – |
| ai_disclosure_line | – | – | – |
| clear_fields | – | – | – |
| first_message | – | – | – |
| handover_contacts | – | – | – |
| handover_max_duration_seconds | – | – | – |
| language | – | – | – |
| max_duration_seconds | – | – | – |
| name | – | – | – |
| recording_enabled | – | – | – |
| sms_enabled | – | – | – |
| status | – | – | – |
| system_prompt | – | – | – |
| tools | – | – | – |
| voice_enabled | – | – | – |
| voice_id | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
upload_email_attachment Upload an email attachment ~168
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 with the message body and any other attachments, per send) are rejected — host large files externally and link to them in the body instead.
| Name | Type | Req | Description |
|---|---|---|---|
| content_base64 | string | yes | – |
| content_type | string | yes | – |
| filename | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
whoami Who am I ~151
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`` are null when the server runs on a shared operator key (``auth_kind="shared"``) — there is no human behind it, so send without a ``reply_to``. Example: whoami()
Input schema present but exposes no named parameters.
Structured output declared, but exposes no named fields.
No examples provided.
How do I install the Hail MCP server?
Hail is a hosted endpoint at https://mcp.hail.so/, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
remote · mcp.hail.so
claude mcp add --transport http hail-hq-hail-mcp 'https://mcp.hail.so/'
What is the Hail MCP server?
Hail is an MCP server listed in the public MCP registry as io.github.hail-hq/hail-mcp. Phone, SMS and email for AI agents. One remote MCP endpoint, OAuth login, zero install. This page covers its hosted endpoint (https://mcp.hail.so).
Is the Hail MCP server safe to use?
Hail scores 86 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the Hail MCP server expose?
Hail exposes 38 tools: place_call, send_email, upload_email_attachment, get_call, list_calls, and 33 more. Their descriptions and schemas cost roughly 10,379 tokens of context every time the server is loaded.
Does the Hail MCP server require authentication?
Yes. Hail asked us for credentials when we connected, so you will need to authorise it in your MCP client before it can do anything.
Is the Hail MCP server still maintained?
Hail is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.