---
okf_version: "0.2"
type: Article
title: "Anthropic Claude + VirtualSMS MCP: Production Workflow Patterns (2026)"
description: "Four production patterns for Anthropic Claude plus VirtualSMS MCP: single-shot, batch, multi-step retry, account-creation pipeline, from $0.05."
resource: "https://virtualsms.io/blog/anthropic-claude-virtualsms-mcp-workflow"
tags: ["Technical", "Guides", "Claude MCP", "Anthropic", "AI agents", "phone verification", "MCP server", "API", "developer"]
updated: "2026-05-20T00:00:00.000Z"
generated:
  at: "2026-10-24T19:58:44.280Z"
  by: "virtualsms-marketing-site"
verified: "2026-05-20T00:00:00.000Z"
status: "source-derived"
stale_after: "60d"
sources: ["https://virtualsms.io/blog/anthropic-claude-virtualsms-mcp-workflow"]
---
# Anthropic Claude + VirtualSMS MCP: Production Workflow Patterns (2026)

> **TL;DR** — The VirtualSMS MCP server exposes 40 tools, request number, check SMS, cancel order, directly to Claude Code, Claude Desktop, and the Anthropic API's tool-use layer. Real carrier-issued SIM cards from operators like Vodafone, O2, and T-Mobile pass every line-type check WhatsApp, Telegram, and Binance run on inbound numbers. Four production patterns cover the main Claude + MCP use cases: single-shot verification via claude-sonnet-4-6, parallel batch via Promise.all with MCP calls, multi-step retry reasoning for timeout handling, and a full account-creation pipeline that verifies, stores, and reuses numbers. Activations from $0.05. 145+ countries. 2,500+ services. Consistently high success rate. If no SMS arrives before your verification order expires, the cost is automatically refunded to your account balance. You can cancel after a two-minute wait while the order is still waiting and no SMS has arrived, for a refund to your balance. Standard orders last up to 20 minutes; Priority orders last up to 10 minutes.

AI agents need real phone numbers, not VoIP, and the [VirtualSMS MCP server](/mcp) gives Anthropic Claude direct access to carrier-grade SIM activations without leaving the tool-use layer. This post covers four production-ready patterns for Claude + VirtualSMS MCP workflows, from single-shot OTP retrieval to full account-creation pipelines with retry reasoning. Whether you're building with Claude Desktop, Claude Code, or the Anthropic API directly, these patterns work out of the box.

## MCP Setup: Claude Code, Claude Desktop, and Claude API

The VirtualSMS MCP server installs as a Node package and exposes 40 tools to any MCP-compatible client. Setup is identical across all three Claude surfaces, only the config file location differs.

**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):

```json
{
  "mcpServers": {
    "virtualsms": {
      "command": "npx",
      "args": ["-y", "virtualsms-mcp@latest"],
      "env": {
        "VIRTUALSMS_API_KEY": "vsms_live_..."
      }
    }
  }
}
```

**Claude Code** (`.claude/settings.json` in project root, or global user settings):

```json
{
  "mcpServers": {
    "virtualsms": {
      "command": "npx",
      "args": ["-y", "virtualsms-mcp@latest"],
      "env": {
        "VIRTUALSMS_API_KEY": "vsms_live_..."
      }
    }
  }
}
```

**Anthropic API** (Python SDK with MCP tool injection, see Pattern 1 below for the full code block).

Restart Claude Desktop or start a new Claude Code session after editing the config. Claude will immediately show the VirtualSMS tools in its available tool list, you can verify by asking: *"What VirtualSMS tools do you have available?"*

### Which transport to use?

MCP supports three transports: stdio, StreamableHTTP, and SSE. The `npx virtualsms-mcp` command runs stdio by default, which is the right choice for local development and Claude Code sessions. For production agents deployed on a server, use StreamableHTTP. The [Transport Comparison section](#transport-comparison-http-vs-stdio) below has the full breakdown.

## Why Claude + MCP for Phone Verification

The Model Context Protocol gives Claude structured, typed access to external tools without you writing any glue code. When you add VirtualSMS MCP to Claude's config, it can call `request_number`, `get_sms`, `cancel_order`, and 15 other tools directly, the same way it calls a calculator or a web search. Claude handles the sequencing, polling logic, and error interpretation autonomously.

The alternative, wrapping the VirtualSMS [REST API](/api) in a custom tool definition and passing it to `client.messages.create(tools=[...])`, works, but requires you to maintain the tool schema, format responses, and handle every edge case explicitly. MCP shifts that to the server. For Claude-native workflows, MCP is the right abstraction.

The second reason is line-type quality. Most agent builders hit the same wall when they first try phone verification: the VoIP pool their provider uses gets flagged by WhatsApp's and Telegram's inbound number checks before the OTP even arrives. Read the full breakdown of [why VoIP numbers fail line-type checks](/blog/voip-vs-physical-sim-verification). VirtualSMS routes to real carrier-issued SIM cards, numbers that return `mobile` in Twilio Lookup V2 and Telesign PhoneID, because they are mobile. Platform line-type detection doesn't fire. The OTP arrives. The agent continues.

The platform records a consistently high success rate on real-SIM orders. That number matters at pipeline scale: a 5% failure rate across 1,000 verifications is 50 manual retries. A 40% failure rate, what VoIP numbers produce on strict platforms, is a broken pipeline.

## VirtualSMS MCP vs Twilio MCP, Vapi, Telnyx, and AgentPhone

| MCP Server | Number Type | Works for WhatsApp? | Works for Telegram? | Price/activation |
|---|---|---|---|---|
| VirtualSMS MCP | Real carrier SIM | Yes | Yes | from $0.05 |
| Twilio MCP | VoIP pool | No (line-type rejected) | No | varies |
| Vapi | VoIP/voice | No (call-focused) | No | call-focused |
| AgentPhone | VoIP | No | No | varies |
| Telnyx MCP | Real numbers | Partial | Partial | higher volume pricing |

Twilio MCP is the most common alternative developers reach for. The issue isn't Twilio's API quality, it's the number inventory. Twilio's standard pool is virtual numbers classified as Non-Fixed VoIP. WhatsApp, [Telegram verification](/buy/telegram), Binance, and most 2FA-heavy platforms explicitly reject these at the line-type check level. VirtualSMS routes to dedicated SIM cards running on carriers like Vodafone, O2, T-Mobile, EE, and Lebara, the same carrier infrastructure your personal mobile number uses.

Vapi and AgentPhone serve different use cases entirely, voice call automation, not single-use SMS OTP. If your pipeline needs inbound SMS for account verification across [2,500+ services](/verifications), VirtualSMS MCP is the right tool.

## Pattern 1: Single-Shot Verification (Claude API + MCP)

Single-shot is the baseline pattern: instruct Claude to get a number for a specific service, wait for the SMS, and return the OTP. This works for interactive automation, one-off verifications, and testing.

```python
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    # MCP tools are auto-injected by the SDK when the server is configured
    # via environment or config, no manual tool schema needed
    system="""You have access to VirtualSMS tools. When asked to verify an account:
1. Call request_number with the target service and preferred country.
2. Poll get_sms every 30 seconds until the SMS arrives or 10 minutes pass.
3. Extract and return the OTP code only. Do not return the full message.
4. If no SMS arrives within 10 minutes, call cancel_order and report failure.""",
    messages=[{
        "role": "user",
        "content": "Request a UK number for Telegram verification, wait for the SMS, and return the OTP code."
    }]
)

print(response.content[0].text)
```

For [WhatsApp verification](/buy/whatsapp), add `service="whatsapp"` to your prompt or system message so Claude passes the correct service identifier to `request_number`. The MCP server normalizes service names, but being explicit prevents ambiguity in Claude's tool call.

The `claude-sonnet-4-6` model handles single-shot verification well at a fraction of Opus cost. It correctly sequences the tool calls, understands polling, and interprets the VirtualSMS response format without additional prompting.

## Pattern 2: Parallel Batch Verifications

When you need to verify multiple accounts simultaneously, you want Claude to fan out the `request_number` calls in parallel rather than sequentially. Claude's parallel tool-call support handles this natively.

```python
import anthropic
import asyncio

client = anthropic.Anthropic()

SYSTEM_PROMPT = """You have access to VirtualSMS tools. 

For batch verification requests:
- Call request_number for ALL accounts simultaneously using parallel tool calls.
- Do NOT wait for one number before requesting the next.
- Once all numbers are assigned, poll get_sms for each in parallel.
- Return results as a JSON list: [{account, number, otp, status}]
- Cancel any order that hasn't received an SMS within 8 minutes."""

def run_batch_verification(accounts: list[dict]) -> list[dict]:
    """
    accounts: [{"service": "telegram", "country": "gb"}, ...]
    Returns: [{"service", "number", "otp", "status"}, ...]
    """
    account_list = "\n".join(
        f"- Account {i+1}: service={a['service']}, country={a['country']}"
        for i, a in enumerate(accounts)
    )

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        system=SYSTEM_PROMPT,
        messages=[{
            "role": "user",
            "content": f"Verify these accounts in parallel:\n{account_list}\n\nRequest all numbers simultaneously."
        }]
    )

    # In production: parse the structured JSON from response.content
    return response.content

# Example: 5 simultaneous Telegram verifications
accounts = [{"service": "telegram", "country": "gb"} for _ in range(5)]
results = run_batch_verification(accounts)
```

Keep the concurrency cap at 10-20 simultaneous activations per API key to stay within the 120 req/min rate limit. For larger batches, chunk the accounts list and run chunks sequentially with a brief delay between them. The `$1 minimum deposit` and per-activation [pricing](/pricing) make it straightforward to pre-fund a batch.

A developer building a Claude-powered onboarding pipeline ran into the limits of this pattern with VoIP numbers: the pipeline failed 40-50% of the time, and every failure required a manual retry. After switching to VirtualSMS MCP with real carrier SIMs, the failure rate dropped below 5%, and Claude handled the entire retry logic autonomously via the reasoning loop in Pattern 3.

## Pattern 3: Multi-Step Reasoning Over Failures

Real-world verification pipelines encounter three failure modes: no SMS (timeout), SMS arrives but OTP format is unexpected, and rate limit / temporary rejection from the target service. Pattern 3 instructs Claude to reason over each failure mode and take the appropriate recovery action.

```python
RETRY_SYSTEM_PROMPT = """You have access to VirtualSMS tools. 

You are running a verification pipeline with explicit failure handling:

TIMEOUT HANDLING:
- Poll get_sms every 60 seconds.
- If no SMS after 5 minutes, call cancel_order if the order is still waiting with no SMS received (cancellation refunds to account balance).
- Request a fresh number and retry. Maximum 3 retry attempts per account.
- After 3 failed attempts, mark account as FAILED and move on.

OTP EXTRACTION FAILURE:
- If SMS arrives but you cannot extract a valid OTP, log the raw message.
- Do not retry, the number worked, the OTP format may be non-standard.
- Return raw SMS content for manual review.

RATE LIMIT / SERVICE REJECTION:
- If request_number returns a 'service_unavailable' or rate limit error, wait 90 seconds.
- Retry the request_number call. Maximum 2 retries before escalating.

Always return a structured result for each account regardless of outcome."""

def verify_with_retry(service: str, country: str) -> dict:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        system=RETRY_SYSTEM_PROMPT,
        messages=[{
            "role": "user",
            "content": f"Verify one account. Service: {service}, preferred country: {country}. Handle all failures per protocol."
        }]
    )
    return {"raw": response.content[0].text}
```

The key insight in Pattern 3 is that Claude reasons over the failure condition, not just the success path. You don't need to write explicit if/else retry loops in your application code, the system prompt defines the decision tree, and Claude executes it correctly across tool calls. This is where `claude-sonnet-4-6` earns its place over Haiku: the model needs to interpret ambiguous tool responses and make judgment calls about when to retry vs when to escalate.

If no SMS arrives before your verification order expires, the cost is automatically refunded to your account balance. You can cancel after a two-minute wait while the order is still waiting and no SMS has arrived, for a refund to your balance. Standard orders last up to 20 minutes; Priority orders last up to 10 minutes. Even if Claude's retry loop doesn't explicitly call `cancel_order`, you won't be charged for a number that never delivered an SMS. Pattern 3 calls `cancel_order` proactively (at 5 minutes) to get the refund faster and free up the number for other requests.

## Pattern 4: Account-Creation Pipeline (Verify → Store → Reuse)

Pattern 4 is the full production pipeline: request a number, trigger the verification flow on the target service, extract the OTP, store the verified account credentials, and reuse the number for future SMS if it's a rental rather than a one-time activation.

VirtualSMS offers two product tiers for this use case. [Full Access and Platform Rentals](/rentals) differ in key ways:

- **Full Access Rental**: exclusive SIM rental, any service, 1/3/7/14/30-day periods. The number is yours for the rental period, you can receive multiple SMS from any sender.
- **Platform Rental**: one service only, shared SIM, 1/3/7-day periods. You can cancel a rental for a refund to your account balance within 20 minutes, only if no SMS has arrived. A refund is unavailable once the first SMS arrives or more than 20 minutes have passed. A Full Access number replacement starts a fresh cancellation window of 20 minutes for the new number; the no-SMS condition still applies. Platform service rentals have a two-minute wait before cancellation. Better price point for single-service pipelines.

For account-creation pipelines where you only need one OTP per number, Platform Rental is the cost-efficient choice. For pipelines where the verified account receives ongoing SMS (2FA codes, login notifications), Full Access Rental gives you continuous inbound access.

```python
import json
from datetime import datetime

PIPELINE_SYSTEM_PROMPT = """You are running a full account-creation verification pipeline.

STEP 1, REQUEST NUMBER:
Call request_number with the target service and country. Store the returned number and order_id.

STEP 2, WAIT FOR OTP:
Poll get_sms every 45 seconds. Extract the OTP when it arrives.

STEP 3, RETURN STRUCTURED RESULT:
Return a JSON object with this exact structure:
{
  "number": "+44...",
  "order_id": "vsms_...",
  "otp": "123456",
  "received_at": "ISO timestamp",
  "status": "success" | "timeout" | "failed"
}

STEP 4, TIMEOUT:
If no SMS after 8 minutes, call cancel_order and return status: "timeout"."""

def create_verified_account(service: str, country: str, storage_backend) -> dict:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=PIPELINE_SYSTEM_PROMPT,
        messages=[{
            "role": "user",
            "content": f"Run the full verification pipeline. Service: {service}, country: {country}."
        }]
    )

    # Parse Claude's structured JSON response
    result_text = response.content[0].text
    try:
        result = json.loads(result_text)
    except json.JSONDecodeError:
        # Claude returned prose, extract JSON block
        import re
        json_match = re.search(r'\{.*\}', result_text, re.DOTALL)
        result = json.loads(json_match.group()) if json_match else {"status": "parse_error"}

    # Store to your backend (DB, spreadsheet, Notion, etc.)
    if result.get("status") == "success":
        storage_backend.save({
            "service": service,
            "country": country,
            "number": result["number"],
            "otp": result["otp"],
            "verified_at": result["received_at"],
            "order_id": result["order_id"]
        })

    return result
```

This pattern works at scale because Claude handles the entire state machine, polling intervals, timeout logic, response parsing, and returns a clean structured result your application code can act on directly. The storage layer is decoupled: swap `storage_backend` for Postgres, Redis, Notion, or a local JSON file depending on your stack.

## Error Handling: Timeouts, Rate Limits, Cancelled Orders

Three error conditions come up in every production deployment:

**Timeout (no SMS)**: VirtualSMS refunds to account balance automatically if no SMS arrives before activation expiry (Standard up to 20 minutes, Priority up to 10 minutes). In Claude workflows, call `cancel_order` proactively at 5-8 minutes to get the refund faster and request a replacement number. Include explicit timeout thresholds in your system prompt, Claude will not invent a timeout on its own.

**Rate limits (429)**: The VirtualSMS API allows 120 requests per minute. In batch workflows, Claude will encounter 429s if you instruct it to fire too many simultaneous requests. Include explicit concurrency instructions in your system prompt: *"Never request more than 10 numbers simultaneously."* Claude respects concurrency constraints when they're stated explicitly.

**Cancelled orders**: After the two-minute wait, Claude can call `cancel_order` while the order is still waiting and no SMS has arrived, for a refund to account balance. Cancelled orders are logged by the API, you can audit them via the [REST API](/api) if you need to reconcile.

**Malformed OTP extraction**: Sometimes the SMS arrives with a non-standard format, the OTP embedded in a URL, surrounded by punctuation, or in a language Claude's extraction heuristic misses. Instruct Claude to return the raw SMS body as a fallback field in every result object, so you can handle edge cases without re-requesting a number.

## Transport Comparison: HTTP vs stdio

| Transport | Latency | Use case |
|---|---|---|
| stdio | ~2ms | Local dev, Claude Code, Claude Desktop |
| StreamableHTTP | ~20-50ms | Production agents, remote Claude API calls |
| SSE (legacy) | ~20-50ms | Older MCP clients; being deprecated |

For local development and Claude Code sessions, stdio is the right default, it's what `npx virtualsms-mcp` uses out of the box and adds essentially zero latency. For production deployments where your Claude agent runs on a server and makes API calls, StreamableHTTP is the correct choice. Configure it by pointing the MCP server URL at the HTTP endpoint rather than using the npx command.

SSE (Server-Sent Events) is the legacy transport, some older MCP client implementations use it, but StreamableHTTP supersedes it for new deployments.

## Claude Tool-Use vs MCP, Which to Use?

| Approach | When to use |
|---|---|
| VirtualSMS MCP | Claude is the primary orchestrator; interactive sessions; Claude Code; Claude Desktop |
| VirtualSMS REST API via tool_use | LangChain/CrewAI/LangGraph pipelines; high-volume batch outside Claude; non-Claude agents |

The distinction matters for architecture decisions. MCP is the right choice when Claude owns the workflow, it gives Claude typed tool access, handles authentication at the server level, and requires no tool schema maintenance on your side. The tool catalog updates automatically when the MCP server ships new methods.

The VirtualSMS [REST API](/api) via manual `tool_use` definitions is the right choice when Claude is one component in a larger pipeline that also includes non-Claude agents, or when you need throughput that exceeds what a single Claude session can sustain. LangChain agents, CrewAI crews, and LangGraph flows all work well with the REST API directly, they don't benefit from MCP's Claude-native ergonomics.

Both approaches access the same underlying activation inventory. The only practical difference is where the tool schema lives (MCP server vs your codebase) and how much glue code you write. For pure Claude-native workflows, MCP wins on developer experience every time.

Real opinion: real SIM is not a premium add-on for AI agent pipelines, it is the baseline. A pipeline that fails 40% of the time because it's hitting VoIP numbers is not a pipeline; it's a manual process with extra steps. MCP makes it trivial to get this right from day one. Add the config block, add your API key, and Claude handles the rest.

For related reading on agent phone verification at scale, see [AI agent phone verification patterns](/blog/ai-agent-phone-verification) and [virtual numbers for developer QA testing](/blog/virtual-numbers-developers-qa-testing).

## The Bottom Line

The VirtualSMS MCP server gives Anthropic Claude direct, typed access to real carrier-issued phone numbers, from operators like Vodafone, O2, T-Mobile, and EE, without any glue code or tool schema maintenance. Four patterns cover the 95% of agent verification use cases: single-shot for simple automation, parallel batch for onboarding pipelines, multi-step retry reasoning for robust error handling, and full account-creation pipelines that verify, store, and reuse. Activations from $0.05 across 145+ countries and 2,500+ services. The [VirtualSMS MCP server](/mcp) is a single config block away. For direct API access in non-Claude pipelines, the [REST API](/api) exposes the same inventory with full programmatic control.

## FAQ

### How do I connect VirtualSMS MCP to Claude Code or Claude Desktop?

Add the VirtualSMS MCP server to your claude_desktop_config.json (Desktop) or .claude/settings.json (Claude Code) under the mcpServers key. Point it at the VirtualSMS MCP endpoint with your API key as an environment variable. For Claude Code: `"virtualsms": { "command": "npx", "args": ["-y", "virtualsms-mcp"], "env": { "VIRTUALSMS_API_KEY": "your_key" } }`. Claude will immediately see all 18 VirtualSMS tools (request_number, get_sms, cancel_order, and others) in its tool catalog without any additional prompting.

### Which Claude model should I use for phone verification workflows?

claude-sonnet-4-6 handles the vast majority of verification workflows well, it reasons correctly about tool sequencing, timeout handling, and retry logic at lower cost than Opus. Use claude-opus-4-7 only when your pipeline involves complex multi-step branching, ambiguous error recovery, or reasoning over large batches where judgment under uncertainty matters. Haiku 3.5 works for simple single-shot activations where speed and token cost are the priority and the workflow is fully deterministic.

### How does VirtualSMS MCP compare to Twilio MCP or other SMS MCP servers?

Twilio MCP exposes Twilio's virtual number pool, numbers flagged as Non-Fixed VoIP by the same APIs WhatsApp and Telegram use to reject numbers. VirtualSMS MCP routes to real carrier-issued SIM cards that return "mobile" in Twilio Lookup V2 and Telesign PhoneID line-type checks. That difference determines whether your agent's verification attempt succeeds or fails. Vapi and AgentPhone target voice/call workflows, not single-use SMS OTP verification. Telnyx MCP offers real numbers but targets higher-volume business messaging use cases at different price points.

### What is the rate limit for the VirtualSMS MCP server?

The VirtualSMS API allows up to 120 requests per minute on standard API keys. For parallel batch workflows, implement a concurrency cap (5-10 simultaneous MCP calls) and an exponential back-off retry on 429 responses. Claude handles this automatically when you include rate-limit handling instructions in your system prompt or tool schema descriptions.

### Can Claude handle bulk phone verification via MCP?

Yes. Pattern 2 (Parallel Batch) uses Promise.all or Claude's native parallel tool-call capability to request multiple numbers simultaneously and poll for SMS in parallel. A practical batch ceiling is 10-20 simultaneous activations per API key to stay within rate limits. For higher volumes, use the VirtualSMS REST API directly via the claude-api-connector pattern, which gives more granular control over concurrency and retry queues.

### What happens when a verification times out in a Claude workflow?

If no SMS arrives before your verification order expires, the cost is automatically refunded to your account balance. You can cancel after a two-minute wait while the order is still waiting and no SMS has arrived, for a refund to your balance. Standard orders last up to 20 minutes; Priority orders last up to 10 minutes. In Claude workflows, the agent should: (1) call get_sms_status on a polling interval, (2) if status is pending past a configurable threshold (typically 3-5 minutes for fast services), call cancel_order once the two-minute wait has passed and the order is still waiting with no SMS received, then (3) request a fresh number and retry. Pattern 3 shows the full reasoning loop with explicit fallback logic.

### When should I use Claude MCP vs the VirtualSMS REST API directly?

Use MCP when Claude is the primary orchestrator, it gives Claude direct tool access without writing glue code or manually formatting API calls. Use the REST API directly when you need maximum throughput (batch jobs outside Claude), want to integrate VirtualSMS into a non-Claude pipeline (LangChain, CrewAI, LangGraph), or need fine-grained control over retry queues. The REST API and MCP expose the same underlying activation inventory; MCP is the ergonomic choice for Claude-native workflows.

### Does Claude Code support VirtualSMS MCP for agent-driven verification?

Yes. Claude Code reads MCP server configs from `.claude/settings.json` in the project root or from the global user settings file. Add the VirtualSMS MCP entry once and it's available in every Claude Code session in that project. Useful for developer QA workflows: ask Claude Code to spin up a test number, trigger your signup flow, read the OTP from VirtualSMS, and fill it in, all within a single Claude Code session without switching context.
