# Add OTP Verification to an AI Agent with MCP

> Point any MCP client at the otp.com server and your AI agent can send, verify, resend, and check one-time passwords using only your API key.

- Category: Engineering
- Author: otp.com Team
- Published: 2026-07-14
- Tags: mcp, ai
- Canonical: https://otp.com/blog/engineering/otp-verification-in-an-ai-agent-with-mcp

---

Verification is the kind of code nobody wants to own: send a code, wait, check what the user typed,
fall back a channel if it fails. It's glue, and someone has to write and maintain it.

An AI agent can do that wiring for you. The otp.com **MCP server** exposes verification as agent
tools over the [Model Context Protocol](https://modelcontextprotocol.io), so an assistant in Claude
Desktop, Claude Code, Cursor, or your own app can run the whole flow from a single prompt.

## No backend, one credential

The server is a thin, self-contained client of the same public OTP API. There's nothing to deploy
and no secret baked into source: you pass your API key at runtime through an environment variable,
and that key is the only credential.

## Setup

1. Create an API key in the panel under **API Keys**. An `otp_live_…` key sends for real; an
   `otp_test_…` key runs in the sandbox.
2. Point your MCP client at the server:

```json
{
  "mcpServers": {
    "otp": {
      "command": "npx",
      "args": ["-y", "@otp.com/mcp"],
      "env": { "OTP_API_KEY": "otp_live_your_key_here" }
    }
  }
}
```

The client launches the server with `npx`; there's nothing to install globally. One optional env
var, `OTP_API_BASE_URL`, overrides the default `https://api.otp.com/api/v1` when you want to point
the agent at staging or a self-hosted environment.

## The tools

One tool per API action:

| Tool | Does | Input |
|------|------|-------|
| `send_otp` | Send a code (routing picks the channel) | `recipient`, `locale?` |
| `verify_otp` | Verify what the user entered | `otp_id`, `code` |
| `resend_otp` | Resend on the next channel | `otp_id` |
| `get_otp_status` | Check an OTP's status | `otp_id` |

Same rule as the REST API: the code is never returned. The agent verifies against the `otp_id` from
`send_otp`, so a leaked transcript never leaks a usable code.

## Start in the sandbox

Before you wire a live key into an agent that can act on its own, use a test key. With an
`otp_test_…` key nothing is sent and nothing is charged, and the accepted code is always `123456`.
That means you can rehearse the entire conversation, including the fallback and error branches,
without touching a real phone or your balance. Once the flow behaves, swap in the `otp_live_…` key.

## What a run looks like

With the tools in place, the agent orchestrates the state machine on its own. A typical exchange:

1. The agent calls `send_otp` with the user's number and gets back an `otp_id` and the `channel`
   routing chose (say, `sms`).
2. It asks the user for the code and calls `verify_otp` with the `otp_id` and what they typed.
3. If the user says nothing arrived, the agent calls `resend_otp`, which advances to the next
   channel rather than resending the same SMS. If that next channel is a code channel (Telegram,
   email), it asks for the new code and verifies again. If it is WhatsApp, the response carries an
   `action_url` (a `wa.me` link) that the agent surfaces so the user opens it, receives the code over
   chat, and reads it back for `verify_otp`, exactly as on the other channels. If it calls resend too
   soon it gets a `429` cooldown and simply waits; if the chain is exhausted it gets a `409` and
   tells the user to try another method.
4. It can call `get_otp_status` at any point to see whether the OTP is `pending`, `approved`,
   `failed`, or `expired`, and narrate the right next step.

"Verify this user before checkout" becomes a prompt, not a sprint. There is no integration branch to
write, because the branching lives in the agent's reasoning over four small tools.

## Keep the key where it belongs

The API key is a real credential: an `otp_live_…` key sends real messages against your balance.
Treat the MCP config the way you treat any secret. Keep it in the client's environment, not in a
shared repo or a prompt, and prefer a test key for anything you are still iterating on. Because the
server holds the key and the code is never returned, the agent can drive verification without ever
seeing a secret it could leak.

## When to reach for MCP

MCP is the fastest path when an assistant is already in the loop: an AI-built product, an internal
agent that onboards users, or a prototype you want working in one prompt. For a fixed server-to-server
backend where no agent is involved, the [official SDKs](/docs/sdks) or the raw REST endpoints are the
better fit; they cover the same four actions with less moving machinery. Pick MCP when the agent is
the integration, and the SDK when your own code is.

Prefer to call the API yourself? The [REST reference](/docs/api/send) and the
[official SDKs](/docs/sdks) cover the same four actions.
