Connect LlamaIndex agents to a NZ phone number, SMS and email

Checked · Markdown

Short answer

Install llama-index-tools-mcp, create a BasicMCPClient for https://agentline.nz/mcp with your Agentline NZ API key in an Authorization: Bearer header, and pass McpToolSpec(client=client).to_tool_list_async() to a FunctionAgent. The agent can then text from a New Zealand 028 number, use an @agentline.nz inbox and wait for one-time codes.

Before you start

  • An Agentline NZ API key from Dashboard → API keys, exported as AGENTLINE_KEY. Start free with an email inbox (no card), or take a 7-day trial of Starter or Pro to get a number.
  • Your 028 number is set up within a few days of signup. Your email inbox works straight away.
  • An LLM for the agent. The example uses OpenAI, with OPENAI_API_KEY set; any LlamaIndex LLM that supports tool calling works.

Steps

  1. Install the MCP tools package. llama-index-tools-mcp brings in llama-index-core. Install the LLM package you want as well.
    pip install llama-index-tools-mcp llama-index-llms-openai
  2. Connect and build the agent. A URL that doesn't end in /sse selects Streamable HTTP. headers is sent with every request, and timeout is in seconds.
    import asyncio, os
    from llama_index.core.agent.workflow import FunctionAgent
    from llama_index.llms.openai import OpenAI
    from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
    
    async def main():
        client = BasicMCPClient(
            "https://agentline.nz/mcp",
            headers={"Authorization": f"Bearer {os.environ['AGENTLINE_KEY']}"},
            timeout=60,
        )
        tools = await McpToolSpec(client=client).to_tool_list_async()
        agent = FunctionAgent(
            name="Assistant",
            description="Texts, emails and reads one-time codes with Agentline NZ.",
            tools=tools,
            llm=OpenAI(model="gpt-4o"),
            system_prompt="Use Agentline NZ to text, email and wait for one-time codes.",
        )
        print(await agent.run("What's my Agentline NZ number?"))
    
    asyncio.run(main())
  3. Run it. Run the script. The agent should call get_my_number and print your number and inbox.

Limit the agent to some tools

Pass allowed_tools to keep an agent read-only, for example:

spec = McpToolSpec(
    client=client,
    allowed_tools=["get_my_number", "list_messages", "list_emails", "read_email", "wait_for_code"],
)

The sending tools are send_sms and send_email. wait_for_code doesn't send anything, but it isn't strictly read-only: it consumes each code it returns, so the code isn't returned again.

Using the REST API instead

If you'd rather not use MCP, wrap a few REST calls as FunctionTools. The API is at https://agentline.nz/api/v1 with the same Authorization: Bearer header:

import os, httpx
from llama_index.core.tools import FunctionTool

API = "https://agentline.nz/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['AGENTLINE_KEY']}"}

def send_sms(to: str, body: str) -> dict:
    """Text a New Zealand mobile number, such as 021 123 4567, from the Agentline NZ 028 number."""
    return httpx.post(f"{API}/sms", headers=HEADERS, json={"to": to, "body": body}).json()

def list_inbound_sms(limit: int = 20) -> dict:
    """List received texts, newest first."""
    return httpx.get(f"{API}/sms", headers=HEADERS, params={"direction": "in", "limit": limit}).json()

def wait_for_code(channel: str = "any", timeout_seconds: int = 45) -> dict:
    """Wait for a one-time code to arrive by SMS or email. channel is sms, email or any."""
    return httpx.post(
        f"{API}/wait-for-code",
        headers=HEADERS,
        json={"channel": channel, "timeout_seconds": timeout_seconds},
        timeout=60,
    ).json()

tools = [FunctionTool.from_defaults(fn) for fn in (send_sms, list_inbound_sms, wait_for_code)]

POST /sms returns {"message": {…}}, GET /sms returns {"messages": [...]}, and POST /wait-for-code returns {"found": true, "code": "…", …} or {"found": false}. Errors come back as {"error": {"code": "…", "message": "…"}}, which the agent can read. The docs list every endpoint.

Good to know

  • Keep timeout at 60 seconds or more. wait_for_code can wait up to 55 seconds, and BasicMCPClient's default is 30.
  • Until you're on a paid plan, your agent can only email or text your own verified email address and mobile number. Anything else returns recipient_not_allowed.
  • Standard NZ mobile numbers only (020/021/022/027/028/029). No landlines, short codes, premium or international numbers.
  • BasicMCPClient opens a new session for each call, so you don't need to close it.

FAQ

Can I use aget_tools_from_mcp_url instead?

Yes. Pass the same BasicMCPClient as client, for example await aget_tools_from_mcp_url('https://agentline.nz/mcp', client=client). It returns the same tool list as McpToolSpec and also takes allowed_tools.

What does Agentline NZ cost?

Starter is NZ$29/month and Pro is NZ$79/month, billed in NZ dollars. Start free with an email inbox (no card), or take a 7-day trial of Starter or Pro to get a number. Agentline NZ is not GST-registered, so no GST is charged.

How long does it take to get the 028 number?

Your 028 number is set up within a few days of signup. Your email inbox works straight away. Full refund if your number hasn't arrived within 30 days of your first payment.

Can my assistant send marketing or bulk texts?

No. Transactional and assistant messages only: replies, bookings, confirmations, reminders people asked for, and codes. Marketing, promotions, cold texts and bulk texts are blocked. See What we don't allow.

Start free · Start free with an email inbox (no card), or take a 7-day trial of Starter or Pro to get a number.