On this page
The MobileValidate MCP server lets an AI agent check phone numbers and e-mail addresses the way a developer would call the API: normalize, estimate, check, read results. It exposes nine tools, returns structured results, and puts a spend confirmation, a cost ceiling and anti-enumeration rules between the model and your balance. This post shows how to connect it, what to ask, and what the tool calls look like.
Why give an agent phone checks at all?
Agents increasingly do the operational work around customer data: tidy a CRM export, triage sign-ups flagged by a fraud rule, prepare a list before an operations team sends appointment reminders. Each of those tasks has a step where someone asks "is this number real, what kind of line is it, which channel will reach this customer?"
Without a tool, a model guesses from the digits. That fails in predictable ways. It can't know that a US number was ported to a VoIP provider, that +44 7911… belongs to Guernsey, or whether an opted-in customer has a WhatsApp account. With the Model Context Protocol, the agent calls a tool that answers from data and says unknown when it has none. You get answers you can audit, with checked_at timestamps, instead of plausible-sounding guesses.
The same controls that apply to the REST API apply here. The agent gets no more access than a developer would.
Which tools does the server expose?
| Tool | What it does | Spends credit |
|---|---|---|
normalize_numbers | Formats numbers as E.164 locally; flags ambiguous inputs and duplicates | no |
estimate_cost | Free pre-flight: valid, invalid, duplicate and cached counts plus maximum cost | no |
lookup_numbers | Checks up to 100 numbers (optionally with e-mails) against real-time services | yes |
lookup_emails | Checks up to 100 e-mail addresses in real time | yes |
check_spam_reputation | Spam reputation for up to 100 US, CA or DE numbers | yes |
create_lookup_job | Bulk job with up to 50,000 numbers and/or e-mails, including bulk-only services | yes |
get_lookup_job | Job status plus a filtered, paginated page of results | no |
list_services | Services the key can use, with modes, attributes, countries and prices | no |
get_account | Balance, reserved credit, today's usage, limits | no |
Each tool declares a title, annotations such as read-only and idempotent, and input and output schemas. checks takes the same codes and aliases as the API: whatsapp, telegram, carrier, spam, email and so on. Spam reputation is in limited access (internal customers only for now), and live network status (hlr) is coming soon and not offered by the tools yet. The MCP docs have the full reference.
How do you connect a client?
The hosted server speaks Streamable HTTP. Pass an agent key or a test key as a bearer token. In Claude Code:
claude mcp add --transport http mobilevalidate https://mcp.mobilevalidate.com/mcp \
--header "Authorization: Bearer $MOBILEVALIDATE_API_KEY"The equivalent .mcp.json entry, which Cursor and other clients read in the same shape:
{
"mcpServers": {
"mobilevalidate": {
"type": "http",
"url": "https://mcp.mobilevalidate.com/mcp",
"headers": { "Authorization": "Bearer ${MOBILEVALIDATE_API_KEY}" }
}
}
}A local stdio package, @mobilevalidate/mcp, runs the same tools with npx -y @mobilevalidate/mcp. The key is forwarded to the API for each request only and is never stored by the server. Start with a test key: the documented test numbers return fixed answers and nothing is billed.
What can you ask the agent?
Prompts work best when they name the goal and the checks, and leave the mechanics to the tools. Some that map cleanly onto the tools:
- "Normalize these 40 numbers from the event sign-up sheet, assume UK where there's no country code, and tell me which ones are invalid or duplicated." This uses
normalize_numbers, which is free and local. - "What would it cost to check this list for carrier and WhatsApp?" This uses
estimate_cost, which is free. - "For these three new sign-ups, check line type and WhatsApp and flag anything that isn't a mobile." This uses
lookup_numberswithchecks: ["carrier", "whatsapp"]. - "Run a bulk job on the opted-in customer file for WhatsApp, Telegram and RCS, then list the customers with none of them." This uses
create_lookup_job, thenget_lookup_jobwith filters. RCS is bulk only. - "Check the spam reputation of these inbound caller IDs from last night." This uses
check_spam_reputation(US, CA and DE only, where it is enabled for your account).
Prompts that ask the agent to find out who owns a number, or to check a generated range of numbers, fail by design. The tools return no identity data, and ranges are refused.
What does a real tool call return?
Here is lookup_numbers with a test key, checking three test numbers for WhatsApp and carrier. The request the client sends:
{"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {"name": "lookup_numbers",
"arguments": {"numbers": ["+447700900001", "+447700900002", "+447700900003"],
"checks": ["whatsapp", "carrier"]}}}The one-line text summary the model reads first (real output):
3 numbers (0 invalid): whatsapp.registered 1 registered / 1 not / 1 unknown / 0 pending; network.carrier 1 registered / 0 not / 2 unknown / 0 pending. Cost $0 (balance $9.97907).And one item from structuredContent (excerpt):
{"kind": "phone", "e164": "+447700900002", "country": "GB", "number_status": "valid",
"checks": {
"whatsapp.registered": {"status": "completed", "registered": false, "attributes": null,
"confidence": "high", "checked_at": "2026-09-25T16:07:07.013Z",
"cached": false, "billed": false, "reason": null},
"network.carrier": {"status": "unknown", "registered": null, "attributes": null,
"confidence": null, "checked_at": null, "cached": false,
"billed": false, "reason": "NO_DATA"}}}The summary gives the model the answer in one sentence. The structured part gives your code something to check. Unknown answers carry a reason. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). registered: null means "we don't know", and a well-prompted agent should say so rather than read it as "no".
How does spam reputation look to an agent?
check_spam_reputation flattens the result so a model can reason about it without digging through nested attributes. Real test-mode output for three numbers:
3 numbers: 1 high, 0 medium, 0 low, 1 no reports, 1 not conclusive (unknown / pending / unsupported country), 0 invalid. Cost $0. "no reports" does not mean the number is safe.{"input": "+447700900001", "status": "completed", "risk_level": "high", "risk_score": 95,
"reasons": ["regulator", "community"], "voip_range": false, "top_category": "robocall",
"first_seen": "2025-11", "last_seen": "2026-08", "sources": 2, "billed": false, "test": true}The caveat is part of the tool output on purpose. Models tend to turn "no reports" into "safe". Putting the correction in the text the model reads makes that mistake less likely. The spam reputation page explains levels, reasons and coverage.
How is spending kept under control?
OWASP lists excessive agency, giving an LLM more functionality, permissions or autonomy than the task needs, as a top risk for LLM applications (OWASP, 2025). The MCP server limits what an agent can do in layers:
- Keys. Only agent keys (
mv_agent_…, scoped, with a daily spend cap) and test keys are accepted. Live keys are rejected, so an agent never holds an uncapped key. - Free first. Before
lookup_numbersorcreate_lookup_jobspends anything, the server runs the free estimate. - Confirmation. If the maximum cost is above the threshold (default USD 1.00) or the list has more than 100 numbers, the call is refused with
confirmation_required. - Hard ceiling. The confirmed amount is sent to the API as
max_cost, so the request can never cost more than what was shown.
A real refusal, from a 101-number job with a test key:
{"error": {"code": "confirmation_required",
"message": "Confirmation required: the list has 101 numbers/e-mails (more than 100). This will check 101 numbers/e-mails for at most $0 (non-conclusive results are not billed). Show this amount to the USER and ask them to approve it explicitly. Only if they approve, call create_lookup_job again with the same arguments plus confirm_max_cost: \"0\". Do not confirm on the user's behalf.",
"max_cost": {"amount": "0", "currency": "USD"}, "numbers": 101, "confirm_above": "1.00"}}Can the server prove a human approved the spend?
No, and we say so. The confirmation passes through the agent, so a misbehaving agent could repeat the call with confirm_max_cost without asking anyone. That is why the hard limits sit outside the model: the agent key's daily spend cap, its scopes, and max_cost enforced by the API.
The MCP specification puts the rest on the client. It says there SHOULD always be a human in the loop with the ability to deny tool invocations, and that clients should show confirmation prompts for operations (MCP spec, 2025). In practice:
- Keep tool approval on for the four spending tools (
lookup_numbers,lookup_emails,check_spam_reputation,create_lookup_job). The free tools (normalize_numbers,estimate_cost,list_services,get_account,get_lookup_job) are safe to auto-approve. - Give each agent its own key with a small daily cap, and raise it only when a workflow needs it.
- Don't reuse an agent key across projects. A key per agent makes usage and audit logs readable.
The spec also says clients must treat tool annotations as untrusted unless they come from a trusted server. Our annotations describe the tools honestly, but your client's approval settings are what actually protect you.
What else protects the people behind the numbers?
Phone numbers belong to people, and an agent can repeat an action far faster than a person. The server applies the API's rules unchanged:
- Anti-enumeration. 20 or more consecutive numbers in one request are refused (
suspected_enumeration), and so are generated lists of e-mail addresses. Contact-discovery systems have been abused at scale. Researchers showed in 2025 that WhatsApp's contact discovery allowed 3.5 billion accounts to be enumerated before Meta mitigated it (University of Vienna, 2025). We don't want an agent to become another way to do that. - No identity data. Answers are yes/no/unknown, network attributes or reputation attributes. There are no names, photos or profiles.
- No side channels. There is no webhook URL parameter, request metadata is never echoed into tool output, and server logs contain counts and ids, with anything that looks like a number or address masked.
- Opt-outs. Numbers on the suppression list come back as
suppressedand aren't checked.
Use the tools for identifiers you already hold for a legitimate purpose, such as your customers, sign-ups and leads.
How should you design an agent workflow around these tools?
A pattern that works well for list tasks:
normalize_numbersto catch bad formats locally and cheaply.estimate_costso the model can tell the user the worst-case cost in plain words.lookup_numbersfor up to 100 numbers, orcreate_lookup_jobfor anything bigger or bulk-only.get_lookup_jobwith filters, for exampleregistered: truefor one service, rather than pulling every row into the context window.- Report
unknownrows separately and suggest a retry later, rather than treating them as failures.
Keep the model's output to decisions and counts. Your code should read the structured results for anything that changes data. See how to clean a phone number list in bulk for what to do with each result, and OTP fraud prevention for real-time decision tables.
What are the key takeaways?
- The MCP server gives agents nine tools. Five are free; four spend credit.
- Start with a test key: fixed answers, no cost, same request shapes.
- Spending is bounded by the agent key's cap, a confirmation step and
max_cost. Keep client-side approval on for spending tools. unknownmeans no answer and is free.no_reportsin spam reputation is not "safe".- Anti-enumeration and no-identity rules apply to agents exactly as they do to the API.
Sources
- Model Context Protocol specification (2025-06-18): Tools — Model Context Protocol, 2025
- LLM06:2025 Excessive Agency — OWASP Gen AI Security Project, 2025
- Researchers discover security vulnerability in WhatsApp — University of Vienna, 2025
Frequently asked questions
Which MCP clients work with the MobileValidate server?
Any client that supports the Model Context Protocol over Streamable HTTP, such as Claude Code, Claude Desktop through its connector settings, or Cursor. A local stdio package also runs with npx -y @mobilevalidate/mcp.
Can an agent spend money without me noticing?
Only within limits you set. The server accepts agent keys with a daily spend cap, refuses calls above a confirmation threshold (default USD 1.00) or with more than 100 numbers until they are confirmed, and sends the confirmed amount to the API as max_cost.
Can an agent use the MCP server to look up who owns a number?
No. Tools return registered / not registered / unknown, carrier and line type, or spam-reputation attributes. They never return names, photos or profiles, and sequential number ranges are refused.
Can I try it without paying?
Yes. Test keys (mv_test_…) work with the MCP server and return fixed, free answers for the documented test numbers.
Related services and guides
More from the blog
All articlesDeliverability
How to clean a phone number list in bulk, step by step
A step-by-step way to clean a phone list with the MobileValidate jobs API: free estimate, dedupe, invalid rows, capped cost, CSV download and data purge.
7 min read
Fraud prevention
OTP fraud prevention: the checks to run before you send a code
A practical pre-send pipeline for one-time passcodes: normalize, check line type, messenger presence and reputation in one request, then decide.
8 min read
Developers
E.164 regex: why a pattern is not enough to validate phone numbers
The common E.164 regex accepts impossible numbers. Real counterexamples, the shortest valid international numbers, and what to use a regex for instead.
7 min read


