GUIDE

Test OTP in Playwright with MCP.

Two things want to drive the same signup form: an agent with a browser, and a Playwright test in CI. Both need a real inbox to finish an OTP flow. This guide is the wiring for that pair — SMTP-routed addresses over MCP while you (or the agent) explore, then the same flow frozen into a Playwright + SDK test the pipeline runs forever.

Two hands, two layers

Don't put an MCP client inside signup.spec.ts. MCP is for a model that decides what to call next; your CI runner is a script that should not re-negotiate that every commit. MailFixture draws the line the same place the agent guide does:

Same product surface both times: inbox-per-run, long-poll wait, typed OTP. The only question is whether the caller is a model or npx playwright test.

1 · Give the agent an inbox

One keyed remote MCP server. Route the client once; then any prompt that needs email can call it:

terminal
$ claude mcp add --transport http mailfixture https://mailfixture.com/mcp \
    --header "Authorization: Bearer $MAILFIXTURE_API_KEY"

Details and the non-Claude config shape live in the MCP quickstart. Use a separate key labeled for the agent so a leaked laptop key doesn't force a CI secret rotate.

2 · Live OTP run (agent + browser + MCP)

With a browser tool (Playwright MCP, Claude-in-Chrome, your existing agent browser — anything that can type into a form) and MailFixture MCP both connected, an unattended OTP pass is three tools with work in between:

agent transcript · staging signup
> On staging, sign up a fresh user end to end and confirm the email OTP works.
> Use a mailfixture inbox. Leave a Playwright test behind if the flow is green.

create_inbox(ttl_seconds=900)
  → k2x9v04qtr@mxsink.sh
… browser tools fill /signup with that address, click Send code …
wait_for_otp(inbox_id=…, timeout_seconds=45)
  → { best: "482913" }   // server holds until mail lands
… browser types the code, asserts Welcome …
delete_inbox(inbox_id=…)   ✓ teardown

Three properties make this worry-free: the address is real MX (your staging MTA must be able to deliver to it — no local SMTP mock), wait_for_otp is a single tool call that long-polls server-side (the agent never invents a sleep-retry loop or a /\d{6}/ regex over HTML), and a failed agent run can't leave trash behind if you gave the inbox a TTL. Magic links get the same treatment with wait_for_link and optional follow_link if you want the server to GET the verify URL for you.

3 · Freeze it into Playwright

When the agent is done “does this work tonight?”, the leftover should be a normal test. The Playwright quickstart is the source of truth for the SDK API; the shape the agent should leave is exactly this:

signup.spec.ts · the regression
import { test, expect } from '@playwright/test';
import { MailFixture } from 'mailfixture';

const mail = new MailFixture(); // MAILFIXTURE_API_KEY in CI secrets

test('signup sends a one-time code', async ({ page }) => {
  const inbox = await mail.createInbox({ ttlSeconds: 900 });

  await page.goto('/signup');
  await page.fill('#email', inbox.address);
  await page.click('text=Send code');

  const otp = await inbox.waitForOtp({ timeout: 30_000 });
  await page.fill('#otp', otp);

  await expect(page.locator('h1')).toHaveText('Welcome');
});

Why this is the right freeze: OTP waiting stays on the SDK's long-poll path (NOTIFY-backed on the server), each test gets its own inbox so parallel workers can't steal messages, and extraction is the same typed otp.best the MCP tool used — no second pipeline for the test suite. The longer version of inbox-per-test / no-sleep / no-regex is in Testing OTP flows.

RULE Agent key ≠ CI key. Put MAILFIXTURE_API_KEY for Playwright in the pipeline secret store. Label the MCP key mcp-agent (or similar) and revoke it independently when a laptop walks away.

What is whose job

The boundary, stated plainly

This wiring is for your own product's signup, sign-in, and reset flows — staging or production you control. Our terms draw the same line for agents and test runners: automated registration on third-party services you don't operate is prohibited. An agent that farms signups across the web with programmatic inboxes is the abuse case we design against — every address is attributable to an account and an API key, so it is not anonymous.

connect MCP once · leave Playwright tests behind
MCP quickstart Playwright quickstart