← All posts

2026-05-04

MCP is the new SDK: shipping @jassra25/inboxr-mcp on day one

We shipped @jassra25/inboxr-mcp the same week we put up the REST docs. That order was deliberate. The single most common thing people actually want to do with disposable email — "sign up to a thing, catch the OTP, click the link" — is exactly the kind of three-step chore that an LLM agent does well and a human does grudgingly.

Treating MCP as a first-class integration changes the design. The REST API is shaped around resources (/v1/inboxes, /v1/messages); the MCP tools are shaped around verbs: create_inbox, wait_for_message, extract_otp, extract_link. An agent doesn't plan a REST call sequence. It picks a verb.

What 16 tools look like

The package exposes 8 email tools and 8 SMS tools. Here's the signup-flow path on the email side:

agent
  ├─ inboxr.create_inbox label="signup-test" → addr
  ├─ playwright.fill(form, addr)
  ├─ inboxr.wait_for_message inbox_id=… timeout=60 → msg
  ├─ inboxr.extract_link msgId=… → url
  └─ playwright.goto(url)

And the SMS-OTP path:

agent
  ├─ inboxr.sms_create_inbox → { id, phoneNumber }
  ├─ inboxr.sms_claim_sender sender_number="+1..."  ← stake the route
  ├─ playwright.fill(otp_request_form, phoneNumber)
  ├─ inboxr.sms_wait_for_message timeout=30 → msg
  └─ inboxr.sms_extract_otp body=msg.body → "498212"

The cost of the agent-first stance

Some REST endpoints look weird in isolation because they're tuned for tool-shaped use: a wait endpoint that long-polls instead of returning Last-Event-Id. A claim endpoint that takes a TTL because agents don't hold state. We accept the asymmetry — the REST API is still complete, just with a few corners that look like they were carved by an LLM. They were.

If you're building any kind of API in 2026, ship the MCP server in the first week. It'll change the verbs you expose, in a good way.