Published: April 28, 2026 | 12 min read
Already using Claude API for production workloads and want to add SMS verification as a capability? This post ships four production patterns built on the VirtualSMS MCP server — single-shot verification, parallel batch, multi-step reasoning over failures, and a verify-then-persist pipeline. All four share the same MCP connection and differ only in prompt structure.
Anthropic Claude + VirtualSMS MCP — Production Workflow Patterns (2026)
Table of Contents
- Why Claude + MCP for Phone Verification
- Pattern 1: Single-Shot Verification
- Pattern 2: Parallel Batch Verifications
- Pattern 3: Multi-Step Reasoning Over Failures
- Pattern 4: Account-Creation Pipeline
- Error Handling: Timeouts, Rate Limits, Cancellations
- Cost Optimization: HTTP vs stdio Transport
- Claude Tool-Use vs MCP — Which When?
Why Claude + MCP for Phone Verification
For most production phone-verification workloads, the integration code is the bottleneck — you write a tool wrapper, you write a dispatcher, you write retry logic, you write JSON-schema specs, and every change to the underlying API breaks something downstream. Anthropic Claude with MCP collapses all of that into "connect a server URL, prompt Claude." Once the VirtualSMS MCP server is wired into a Claude API call (via mcp_servers), Claude Desktop, or Claude Code, the 18 verification tools become available without any client-side glue.
This post is the production deep-dive for devs already using Claude API for real workloads. The Claude MCP setup guide covers install + tool catalog; this one covers the patterns that emerge once the connection is live: single-shot, batch, multi-step retry, and the verify-then-persist pipeline. All four patterns share the same MCP connection and only differ in prompt structure.
- One MCP connection, four production patterns. No code change between patterns — only the system + user prompt evolve.
- Same 18-tool catalog as Claude Desktop. Whatever you tested in the desktop UI ports verbatim to the API.
- Auto-refund on no-SMS means cost paranoia in retry loops disappears — Claude can branch over failures without tipping budget.
- Bearer-token auth, no KYC, no platform fee. Same key as the REST API; switching paths within a single org is free.
Pattern 1: Single-Shot Verification (Claude API + MCP)
The smallest production unit: one prompt, one number verified, one return. Useful for ad-hoc verifications, lead-capture flows that need a verified contact, or as a building block inside a larger non-Claude pipeline.
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-opus-4-7",
max_tokens=2048,
mcp_servers=[{
"type": "url",
"url": "https://mcp.virtualsms.io/mcp",
"name": "virtualsms",
"authorization_token": os.environ["VIRTUALSMS_API_KEY"],
}],
messages=[{
"role": "user",
"content": "Buy a UK Telegram number, wait for the SMS, return phone + code as JSON.",
}],
)
# Single-turn: Claude orchestrates buy_number → wait_for_code → return.
# No retry boilerplate; if the activation fails, Claude reasons about it and
# either swap_number or cancel_order from the same MCP tool catalog.Notes:
- Single API call, single Claude turn — Claude internally orchestrates
buy_number → wait_for_code → return. - The
authorization_tokenfield is passed straight through to the MCP server as a Bearer token. Get a key in 30 seconds at the API page. - If
buy_numbersucceeds butwait_for_codetimes out, Claude has the choice (within this single turn) of callingswap_numberorcancel_order— depending on prompt phrasing. - Same shape works for any country/service combo VirtualSMS supports — including high-trust pairs like WhatsApp from Germany or Telegram from the UK.
Pattern 2: Parallel Batch Verifications
For multi-account ops, A/B testing, or bulk QA flows, Claude can fan out tool calls within a single API turn. Prompt structure does most of the work; the MCP connection is identical to Pattern 1.
# Pattern 2 — parallel batch via Claude tool-use API.
# Claude can fan out tool calls across an N-element list when prompted
# with a structured request and the right system prompt.
resp = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
mcp_servers=[{"type": "url", "url": "https://mcp.virtualsms.io/mcp",
"name": "virtualsms", "authorization_token": KEY}],
system=("You verify accounts in parallel. For each requested service+country, "
"buy_number then wait_for_code (timeout 300s). Return a JSON array "
"of {service, country, phone, code} or {service, country, error}."),
messages=[{
"role": "user",
"content": "Verify these in parallel: whatsapp/uk, telegram/uk, "
"discord/germany, instagram/france. Return JSON array.",
}],
)
# In practice Claude pipelines 4 buys, then collects 4 codes.
# Activation auto-refunds via cancel_order if any timeout, so cost is bounded.At higher throughput (50+ activations per turn), the REST API is usually a better fit because you can control parallelism explicitly. MCP shines at the 4–20 element batch where the reasoning overhead is worth the lower glue-code cost. The activation pricing is identical; only the orchestration shifts.
💡 Pre-flight tip: call find_cheapest in the system prompt before buy_number on a large batch. Claude pipelines the discovery call, picks the cheapest country per service, and only then commits to activations. Discovery tools are unauthenticated and free — no rate limit pressure.
Pattern 3: Multi-Step Reasoning Over Failures
The pattern that makes Claude + MCP qualitatively different from a REST loop: when verifications fail, Claude reasons about the next move using the full tool catalog rather than a hardcoded retry policy. Useful for strict-detection platforms (WhatsApp, Tinder, banking) where country choice meaningfully affects success rate.
# Pattern 3 — multi-step with Claude reasoning over failure.
# When wait_for_code returns "no SMS", Claude can decide: retry on a different
# country, swap the phone via swap_number, or cancel + report. The tool catalog
# exposes all three; the right move is reasoning, not retry-with-jitter.
system = """You are an account-provisioning agent. For each verification:
1. buy_number(service, country)
2. wait_for_code(order_id, timeout=300)
3. If no code: try swap_number once. If still no code: cancel and try a
different country from the user's preferred list.
4. If 3 distinct countries fail: cancel all, return error with last 3 attempts.
Always cancel orphan orders before returning. Never leave open activations."""
resp = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
mcp_servers=[{"type": "url", "url": "https://mcp.virtualsms.io/mcp",
"name": "virtualsms", "authorization_token": KEY}],
system=system,
messages=[{
"role": "user",
"content": "Verify a fresh Tinder account. Try UK, Germany, then France.",
}],
)The system prompt encodes a small policy: try swap_number once on no-SMS, then move to a different country. Claude's value here is not the retry itself — REST can do that — but the ability to incorporate context (last 3 attempts' outcomes, the user's preferred country list, hints from order_history) into the next decision. This is the pattern that fixes the failure modes unpacked in why AI agents need real phone numbers — the agent stops looping on dead ranges and pivots intelligently.
Pattern 4: Account-Creation Pipeline (Verify → Store → Reuse)
Once you have reliable single-shot verification, the next layer is persistence: store the verified phone + code so it can be reused for password resets, MFA, or 2FA back-fills. The MCP server handles verification; the pipeline handles state.
# Pattern 4 — account-creation pipeline.
# Verify → store → reuse. The MCP server holds the verification half;
# the pipeline owns persistence.
import json, sqlite3
from datetime import datetime
def provision_account(service: str, country: str, db_path="accounts.db") -> dict:
"""Single-call provisioning via Claude + MCP. Stores result for reuse."""
resp = client.messages.create(
model="claude-opus-4-7",
max_tokens=2048,
mcp_servers=[{"type": "url", "url": "https://mcp.virtualsms.io/mcp",
"name": "virtualsms", "authorization_token": KEY}],
messages=[{"role": "user",
"content": f"Buy {country} {service} number, wait for SMS, "
f"return JSON {{phone, code, order_id}}."}],
)
payload = json.loads(_extract_json(resp)) # parse the final assistant turn
conn = sqlite3.connect(db_path)
conn.execute("INSERT INTO accounts(service, country, phone, code, ts) "
"VALUES(?, ?, ?, ?, ?)",
(service, country, payload["phone"], payload["code"],
datetime.utcnow().isoformat()))
conn.commit(); conn.close()
return payloadTwo architectural notes:
- The number stays alive on VirtualSMS via the rental endpoints (separate from activations) if you need to re-receive SMS later. Activation = single SMS; rental = ongoing inbox. Both are exposed via MCP.
- Don't store the order ID alone. If you only persist
order_idand need the phone back later, the order has already been finalised. Persist{phone, code, country, service, ts}at minimum — the rest is recoverable from the dashboard.
Error Handling: Timeouts, Rate Limits, Cancelled Orders
Three error classes show up in production. Each has a clean MCP path:
| Error | How it surfaces | The Claude + MCP fix |
|---|---|---|
| SMS timeout (no code arrives) | wait_for_code returns "no code" | Claude calls swap_number or cancel_order; activation auto-refunds |
| Rate limit (120 rpm) | MCP returns 429 with retry_after | Claude back-off + retry, or contact support for higher limits on production volume |
| Cancelled / orphan order | Disconnected MCP run leaves an order open | cancel_all_orders at the start of the next run; cost is zero on truly-orphaned orders |
| Insufficient balance | buy_number returns "balance too low" | Claude checks get_balance before committing; surfaces a refill request to the user |
The pattern across all four: the MCP catalog already exposes the tool that fixes the error. The system prompt's job is to wire them together with the right policy. Don't write external retry loops — they fight the MCP layer rather than work with it.
Cost Optimization: HTTP vs stdio Transport
VirtualSMS MCP ships in two transport flavors. Both expose the identical tool catalog; the difference is process model and latency:
- StreamableHTTP —
https://mcp.virtualsms.io/mcp. Zero install, hosted by VirtualSMS, points-of-presence in EU + US for low latency. The right default for Claude API workloads, hosted Claude Desktop, and any caller that doesn't want to manage a Node subprocess. Per-call latency: 30-80ms. - stdio —
npx -y virtualsms-mcp. Local Node subprocess, talks via stdin/stdout. Right when you need the MCP server in an air-gapped environment, or when you want to bind to a specific Claude Code session and have observability over every call. Per-call latency: 5-15ms (local).
Activation cost is identical between transports — both call the same upstream API. The optimization is about latency-vs-ops-overhead, not unit cost. Most Claude API users land on StreamableHTTP because the per-call latency is dominated by Claude's own thinking time, not the MCP hop.
Claude Tool-Use vs MCP — Which When?
Both work. The choice is about how much glue code you want to maintain.
| Dimension | Raw Claude tool-use | Claude + MCP |
|---|---|---|
| Glue code | Tool spec + dispatcher (~50-100 lines/tool) | None — connection URL + auth token |
| API key residence | Your environment | Your environment (passed via authorization_token) |
| Tool surface evolution | You update specs when VirtualSMS evolves | Auto-picks-up server-side changes |
| Best for | Custom orchestration outside Claude | Claude-first orchestration |
| Per-call latency | ~5ms (your function call) | ~30-80ms (HTTP) / ~5-15ms (stdio) |
| Setup time | 30-90 min for a clean spec | 5 minutes for first call |
Net: if Claude is doing the orchestration, MCP wins. If Claude is one node in a non-Claude DAG, raw tool-use can be a cleaner fit. The two paths interoperate — nothing stops you from connecting MCP and defining a few extra raw tools in the same Claude API call. The 18-tool catalog covers the verification surface end-to-end; raw tool-use is for the bits that don't.
✅ Production starter checklist: hit get_balance from a Claude prompt to confirm the MCP connection works, then run Pattern 1 against a low-cost service (e.g., Telegram in your home country). Once that lands, you can ship Patterns 2-4 by changing prompt structure alone.
Get a summary or follow-up answer in your favourite AI assistant.
Frequently Asked Questions
How do I use Anthropic Claude with phone verification?
Connect the VirtualSMS MCP server to your Claude API call via the mcp_servers parameter (or to Claude Desktop / Claude Code via the standard MCP install). Once connected, Claude can invoke the 18 SMS-verification tools (buy_number, wait_for_code, swap_number, cancel_order, etc.) directly from natural-language prompts. Single-shot verification ("buy a UK Telegram number") works in one API call; multi-step flows ("retry on different country if first fails") use the same tool catalog with a slightly different system prompt.
Does Claude API support tool use for SMS?
Yes — Claude has tool-use as a first-class API feature, and MCP support extends that with a hosted-server model. The mcp_servers parameter lets you point any Claude API call at https://mcp.virtualsms.io/mcp with a Bearer token, and the 18 verification tools become available to Claude in that conversation. No manual function-spec wrangling, no schema upkeep — when VirtualSMS adds tools or evolves them, Claude picks up the change without redeploying your code.
What's the rate limit for VirtualSMS MCP?
The MCP server inherits the same 120 requests/min default rate limit as the REST API per API key. Production accounts requesting higher throughput (5-figure activations/month) can email [email protected] with expected QPS — limits are lifted on request after a quick conversation. Discovery tools (list_services, list_countries, find_cheapest, check_price) are unauthenticated and don't count against the rate limit, so Claude can plan freely before committing budget.
Can Claude handle bulk phone verification?
Yes — Claude tool-use supports parallel tool calls within a single API turn, and MCP-backed tool calls fan out the same way. For a batch of N verifications, Claude pipelines N buy_number calls, then collects N codes via wait_for_code. The architecture handles 50-element batches cleanly; for 5-figure-per-day workloads, the REST API is usually a better fit because you control parallelism explicitly. The cost difference per activation is zero; the difference is which side does the orchestration.
When should I use Claude tool-use vs MCP for verification?
Use raw Claude tool-use when you have custom orchestration logic that lives outside the verification layer — e.g., feeding verified accounts into a non-Claude downstream pipeline. Use MCP when Claude is the orchestrator and you want zero glue code. The MCP path is dramatically less to maintain (no function-spec JSON, no dispatcher, no schema sync), and the cost delta is zero. Production workloads on Claude Desktop or Claude Code should always use MCP; production workloads on Claude API can go either way, with MCP usually winning on simplicity.
What happens if a verification times out in a Claude workflow?
Claude detects the timeout via the wait_for_code tool returning a "no code received" result, and can branch on it — retry on a different country, swap the phone via swap_number, or cancel and report. The activation auto-refunds if no SMS arrived, so you do not pay for failed verifications. The right pattern (in our experience) is to give Claude a short list of fallback countries in the system prompt and let it reason about which to try next; this beats hardcoded retry loops because Claude can incorporate context (target platform, recent failure patterns, time of day).
Related Articles
Wire Up Claude + MCP in 5 Minutes
Hosted MCP at mcp.virtualsms.io/mcp · 18 SMS-verification tools · Real physical SIM cards · 145+ countries · 120 rpm/key · Activations from $0.05
