SMS MCP server for verification codes
The VerifyWith MCP server lets an AI client buy a private US phone number, wait for a text and read the verification code, inside spending limits the account owner sets. The endpoint is https://api.verifywith.io/mcp, over Streamable HTTP. There are two ways in: browser sign-in, which Claude, ChatGPT and Cursor start when you add the address, or an API key sent as a bearer token. Numbers receive texts only, from $3.99 a day.
Checked by VerifyWith on

The tools
The server has nine tools. Each one needs a level, and the levels include each other: a connection that can get numbers can also read and manage them.
| Tool | What it does | Level it needs |
|---|---|---|
| list_prices | The four durations and prices, this connection’s level and spending limits. Buys nothing. | Any |
| buy_number | Buys a number. Arguments: duration, auto_renew, nickname, buy_another. | Get and extend numbers |
| wait_for_code | Waits for the next text on a number and returns it with the code. timeout_seconds 5 to 55, default 45. | Read |
| list_numbers | The person’s numbers, newest first, with the latest code. | Read |
| get_number_details | One number: status, end date, texts received, and whether it can be cancelled. | Read |
| read_messages | The texts on a number, newest first, with the code found in each. | Read |
| extend_number | Adds time, charged to the saved card at once. | Get and extend numbers |
| set_auto_renew | Turns automatic renewal on or off. | Manage |
| cancel_number | Cancels a number with no texts, within 24 hours, money back when our provider accepts. | Manage |
There is no tool for choosing an area code or a specific number: buy_number assigns one.
How each client connects
Every client uses the same address, https://api.verifywith.io/mcp. The server name in the examples is verifywith. Click-by-click steps for Claude, Claude Code and Cursor are in give Claude a phone number.
| Client | Where you add it | How it signs in |
|---|---|---|
| Claude (web and desktop) | Customize, Connectors, Add custom connector | Browser sign-in |
| ChatGPT | Plugins, Add custom MCP server, with OAuth | Browser sign-in |
| Claude Code | claude mcp add --transport http verifywith https://api.verifywith.io/mcp | /mcp or claude mcp login verifywith, or an API key header |
| Cursor | ~/.cursor/mcp.json or .cursor/mcp.json | Browser sign-in from Cursor’s MCP settings |
| Your own code | Any MCP client over Streamable HTTP | API key, or the SDK’s OAuth support |
Browser sign-in or an API key
Browser sign-in is OAuth 2.1, and it is what an AI app does on its own when you add the address. An unauthenticated request gets a 401 that points at the server’s metadata; the app opens VerifyWith, the owner signs in (email code, passkey, Google or Apple) and lands on a consent page that names the app and its domain. There the owner picks the level and the spending limits and presses Allow. The approval then appears on the Developers page beside any API keys, where it can be changed or disconnected.
Under the hood the server supports PKCE (S256 only), Client ID Metadata Documents and dynamic client registration, and every client is a public client. Access tokens last 30 minutes and are bound to the /mcp address; refresh tokens rotate on every use. The server re-reads the approval on every request, so disconnecting stops the app at once.
An API key is for your own code, or any client that can’t sign in. Create one on the Developers page and send it as Authorization: Bearer sk_…. The same key works on the REST API at https://api.verifywith.io/v1. With the official TypeScript SDK:
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(
new StreamableHTTPClientTransport(new URL("https://api.verifywith.io/mcp"), {
requestInit: { headers: { Authorization: `Bearer ${process.env.VERIFYWITH_API_KEY}` } },
}),
);
const bought = await client.callTool({
name: "buy_number",
arguments: { duration: "day", nickname: "Acme sign-up" },
});
// Type number.type_into_forms into the site, ask it to send the code, then:
const text = await client.callTool({
name: "wait_for_code",
arguments: { number_id: bought.structuredContent.number.number_id },
});
Access levels and spending limits
The owner gives each connection or key one of three levels. Only the top level can spend money, and it always comes with two limits the owner sets.
| Level | What it allows | Can spend |
|---|---|---|
| Read (“See your numbers and texts”) | List numbers, read texts and codes, wait for a code | No |
| Manage (“See and manage them”) | Also cancel numbers and switch auto-renew | No |
| Get and extend numbers (“Full access” for a key) | Also buy numbers and add time | Yes, within the two limits |
The two limits are the most the connection may spend on one number (extensions included) and the most in any 24 hours. A purchase over either is refused with over_spending_limit, naming the limit and its amount, and nothing is charged. Renewals the owner turns on do not count toward them.
What an agent can never do
- Send a text or make a call. The numbers receive texts only. A site that offers to call with the code can’t reach them.
- Spend past the limits, or do more than the level the owner chose. A call outside the level answers
not_allowedand says which level it needs. - Skip the owner’s bank. When the bank wants the card holder to confirm a payment, the agent gets
needs_bank_confirmationand a link for the owner, valid about an hour. The purchase waits until the owner confirms. - Touch the account itself. Cards, keys, passkeys and account settings are managed only by the signed-in owner. With no saved card the agent gets
needs_cardand a link for the owner. - See card details or payment ids. Results carry VerifyWith’s own ids only.
Our acceptable use policy applies to agents too: an agent verifies accounts that belong to its owner, not accounts in bulk or for other people.
How the tools behave
- Buying is safe to retry without an idempotency key. Repeating a
buy_numbercall with the same arguments within 10 minutes returns the same purchase withalready_bought: trueand charges nothing new. After the owner confirms with their bank, the same call completes the purchase. To buy a second identical number on purpose, passbuy_another: true.extend_numberworks the same way withextend_again. wait_for_codeblocks for at most 55 seconds, because most clients cut a tool call off at about a minute. It returns a text that arrived up to 15 minutes before the call if this tool hasn’t returned it yet. On timeout it answersno_text_yet; texts are stored as they arrive, so calling again misses nothing. It sends progress notifications every 10 seconds when the client asks for them.- Every result says what to do next. Each carries an
outcome, a sentence, the facts and anext_step, repeated as text for clients that pass only text to the model. Results where nothing was done are errors; results waiting for the owner are not, and carry alink. - Rate limits. Purchases are capped at 20 an hour per key or connection and 30 per person, shared with the REST API. At most 5 waits can be open at once per connection.
The numbers and what they cost
Each number is a non-VoIP US (+1) number that receives texts from any sender for as long as it is rented. Line type matters because many services look a number up and refuse VoIP numbers (see why apps reject VoIP numbers). Services also refuse numbers for other reasons, so no particular service is promised. If no text comes after a resend, the tools tell the agent to stop and tell the owner.
Numbers are rented for 1 day $3.99, 1 week $7.99, 1 month $12.99, 1 year $34.99, the same prices as on the website. The MCP server itself costs nothing, and list_prices is free to call. The price is held on the owner’s saved card and taken when the first text arrives or after 24 hours. A number that has received no texts can be cancelled from 5 minutes to 24 hours after purchase; the money comes back when our provider accepts the cancellation (see the refund policy).
For a one-time sign-up code a day is enough. A longer rental suits an account that will text the number again at login.
Questions people ask
- Is there an MCP server that can receive SMS verification codes?
- Yes. VerifyWith runs one at https://api.verifywith.io/mcp. An AI client connected to it can buy a private US number, wait for the text and read the code, charged to the owner’s card within limits the owner sets.
- Does the MCP server need to be installed or run locally?
- No. It is a remote server over Streamable HTTP, so a client connects to its address. There is nothing to install or keep running on your machine.
- Which MCP protocol versions does it support?
- Both eras from one endpoint: 2026-07-28 clients, which send one stateless request at a time, and 2025-era clients that start with initialize.
- Can the agent choose the area code?
- No. buy_number assigns a US number; there is no choice of area code or of a specific number.
- What happens if the agent calls buy_number twice by mistake?
- A second call with the same arguments within 10 minutes returns the number the first one bought and charges nothing. Only buy_another: true buys an additional number.