Skip to main content
Glama

cleat-mcp

A local MCP server that lets an assistant read the texts and transcribed calls arriving on your own Cleat US mobile line, including waiting for the next verification code.

Cleat rents ID-verified US mobile numbers that receive SMS/2FA codes and transcripts of incoming calls. It is receive-only: no outbound texts, no outbound calls, no 911. This server exposes four read tools over stdio, so an assistant can finish a sign-in on an account you already hold instead of asking you to read a code off your phone.

Install

Node 20 or newer. This is not on npm; clone it.

git clone https://github.com/davidcleat/cleat-mcp.git
cd cleat-mcp
npm install

Related MCP server: botcall-mcp

Use it

Add this to your MCP client's config, with the absolute path to your clone, and restart the client.

Claude Desktopclaude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\):

{
  "mcpServers": {
    "cleat": {
      "command": "node",
      "args": ["/absolute/path/to/cleat-mcp/bin/cleat-mcp.js"],
      "env": {
        "CLEAT_API_KEY": "clt_your_api_key"
      }
    }
  }
}

Claude Code.mcp.json in the project root, same shape:

{
  "mcpServers": {
    "cleat": {
      "command": "node",
      "args": ["/absolute/path/to/cleat-mcp/bin/cleat-mcp.js"],
      "env": { "CLEAT_API_KEY": "clt_your_api_key" }
    }
  }
}

Check it before you wire it up — this prints the tool list and exits:

printf '%s\n%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  | CLEAT_API_KEY=clt_your_api_key node bin/cleat-mcp.js

Then ask for the code:

Log into my staging account at localhost:3000 with the credentials in .env.test. When it asks for the SMS code, call wait_for_code on my Cleat line first, then submit whatever arrives.

The assistant calls list_lines to find the line, wait_for_code before the step that sends the text, and reads the code out of the result.

The four tools

Tool

Arguments

Returns

list_lines

Every line in the key's workspace, newest first: id, phone, label, status, createdAt. Start here; the rest take a lineId.

list_messages

lineId, limit (1–200, default 20)

Recent texts and call transcripts, newest first. Each one is a Cleat message: id, line, from, body, code, receivedAt, service, contact, label — the same fields the REST API returns.

latest_code

lineId, from?, service?

The most recent code already on the line, or found: false. Returns found: true and the message's fields.

wait_for_code

lineId, timeoutSeconds (1–300, default 30), from?, service?

The next code to arrive, or found: false, timedOut: true.

from is a case-insensitive substring of the sender (a short code, say 32665). service is matched against the service Cleat recognised from the text — facebook, stripe — and against the workspace label.

Why wait_for_code and not polling

wait_for_code reads where the inbox stands before it starts waiting, then only accepts messages that arrive after that point. A code left over from an earlier sign-in is never returned as this one. That baseline is the receivedAt of the newest message the line already has — a server timestamp — so a local clock that is off does not widen or narrow the window.

The order that matters: call wait_for_code, then do the thing that sends the text. It is already listening.

A timeout is an ordinary result (found: false, timedOut: true), not an error, so the assistant can send the code again and retry. Errors the key causes — an expired key, a line the key cannot reach — come back as readable tool errors instead of crashing the server.

Hosted or local

Cleat already runs a hosted MCP server at https://cleat.so/api/mcp, listed in the MCP registry as so.cleat/cleat. It exposes the same four tool names, the same arguments and the same message fields, so a prompt written for one works with the other. Pick on these differences:

Hosted (https://cleat.so/api/mcp)

This package (local, stdio)

Transport

HTTP JSON-RPC

stdio — works with clients that only speak stdio

Install

none

Node 20+ and a clone

Where the key lives

in your client's headers config

in your client's env block, on your machine

Who Cleat sees

your client's requests

your machine's requests

wait_for_code ceiling

55 seconds

300 seconds, subject to your client's own request timeout

How a wait notices a code

the line's own live event, so it answers as the text lands

polls every 2 seconds

A timeout's answer

found: false, timedOut: true

the same, plus waitedSeconds and a reason for the model to read

Code you can read and patch

no

yes

Both read the same REST API and can see exactly what the API key allows. If you have no reason to self-host, the hosted endpoint is less to maintain. Use this one to raise the wait ceiling, to keep the key off a headers config, to run behind your own egress rules, or to change the tools.

Get an API key

  1. Create a Cleat account at cleat.so and subscribe to a line — $24.99/month or $249.90/year.

  2. Verify your identity once, with a government ID. Until the workspace owner has verified, reading a line answers 403 with code verify_first: the line runs and keeps every text, but nobody can read them. list_lines still works, so you can find the line id before verifying.

  3. In workspace settings → API keys, create a key. It starts with clt_ and is shown once.

Scope the key before you hand it to an agent

When you create a key you can narrow it two ways, and both matter here:

  • To named lines. A line outside the key's scope answers 404, exactly as another workspace's line does. Give the key the one line the assistant is meant to read, not the workspace.

  • To an expiry date. After it, the key answers 401 with code key_expired. Set one — an assistant's key should stop working by itself.

That is what makes a key safe to put in a config file an assistant can reach. Revoking a key locks the assistant out on its next call and touches nothing else: no password, no session, no other line.

Only the workspace owner can create keys.

Limits

  • Receive-only. A Cleat line cannot send a text, place a call, or reach 911 or any other emergency number. There is no tool here that tries; the API has no endpoint for it. An incoming call is transcribed and lands like a text, with the transcript in body and the caller in from. Nothing marks a message as having been a call.

  • US numbers. One line, one subscription, no area code choice, no pools.

  • One verified owner per line. Identity is checked once, for the account, by a person. An agent cannot verify, and cannot sign itself up.

  • 120 requests per minute per key. wait_for_code polls once every 2 seconds while it waits, so one wait costs about 30 requests a minute. Over the limit the API answers 429; a wait in progress rides it out and keeps waiting.

  • code is best effort. Cleat extracts what looks like a one-time code, and that field can be null. The full text is always in body — read it when it matters.

  • A line on hold cannot be read. If a subscription lapses, texts are kept but the API answers 402 until it resumes.

  • Cleat cannot tell you that a particular service will accept its numbers. No provider can. Try yours.

What this is for

Accounts you or your company already hold: a shared business login whose 2FA lands on the company line, a staging account an end-to-end suite signs into, a console an on-call engineer needs a code for. It is not for creating extra accounts, signing up in bulk, or working around a platform's limits — that is outside what Cleat sells, and an account used that way is closed.

MIT licensed.

Available Tools

4 tools
latest_codeA

The most recent verification code already on a line, or found: false if none has arrived. Use wait_for_code instead when the code has not been sent yet.

ParametersJSON Schema
NameRequiredDescriptionDefault
fromNoOptional. Only consider senders containing this text, e.g. a short code.
lineIdYesThe line's id, from list_lines.
serviceNoOptional. Only consider messages Cleat attributed to this service, e.g. 'facebook'.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden. It discloses that the tool returns found: false if no code has arrived, implying it does not wait or error, and clarifies that it only considers codes already present. It does not mention side effects, but the semantics strongly imply a read-only lookup; the main behavioral ambiguity is handled.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no waste: the first states the core behavior and fallback, the second routes to the alternative when appropriate. The key behavioral constraint is front-loaded before the alternative reference.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-style tool with three well-documented parameters and no output schema, the description is largely sufficient: it states what is returned, the no-result case, and the correct alternative when the code has not arrived. A precise return shape or example would strengthen it, but nothing critical is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with each parameter already documented including optionality and example semantics. The tool description adds no additional parameter detail, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: it retrieves the most recent verification code already on a line, and explicitly includes the fallback behavior when none has arrived. It also distinguishes itself from the sibling wait_for_code, so an agent can tell them apart immediately.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear usage guidance: use this tool when the code has already arrived, and explicitly directs the agent to wait_for_code when the code has not been sent yet. This directly addresses the main decision point between two sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_linesA

List the US mobile lines in this API key's workspace, newest first. Every other tool takes a lineId from here.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It conveys a read-only listing operation, scopes it to US mobile lines, states the sort order, and implies that returned items include a lineId for use in other tools. It stops short of describing output fields or pagination, but for a zero-parameter list this is sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler. The core behavior is front-loaded, and the second sentence adds the cross-tool usage note. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, read-only listing tool, the description covers scope, ordering, and how the result feeds sibling tools. The lineId mention compensates for the absence of an output schema by giving the agent the most important detail it needs.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool accepts no parameters, so the schema is fully complete and there is nothing for the description to add. The 0-parameter baseline of 4 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a concrete operation ('List'), a specific resource ('US mobile lines'), and the workspace scope ('API key's workspace'), plus the ordering ('newest first'). This is clearly distinguishable from sibling tools about messages and codes, so an agent knows exactly what this tool returns.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The sentence 'Every other tool takes a lineId from here' is an explicit when-to-use instruction: call this first to obtain a lineId before using sibling tools. No alternative listing tool exists among the siblings, so the guidance is complete.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_messagesA

Recent texts and transcribed calls received by one line, newest first. Read body as well as code: code is a best-effort extraction and can be null.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many messages to return. 1 to 200.
lineIdYesThe line's id, from list_lines.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden, and it adds useful context beyond the schema by disclosing order (newest first) and the note that code is a best-effort extraction and can be null. It also explicitly tells the agent to read body, which is genuinely useful behavioral guidance. It omits broader details like pagination or full return shape, but covers the key warning.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, front-loaded with the core behavior, and adds a targeted warning in a second sentence. Every element earns its place and nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description gives enough for an agent to invoke correctly by identifying resource, ordering, key fields, and the nullable code field. Given the tool's modest complexity, the key missing pieces are minor since the schema is simple and output schema is absent but largely inferred.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and parameters are described in the schema with descriptions. The tool description contributes no extra parameter semantics beyond clarifying that one line is involved, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies what is returned: recent texts and transcribed calls for a single line, ordered newest first. This distinguishes it from siblings like list_lines and code-focused tools such as latest_code. The resource and scope are unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the main use case: fetching recent messages or transcribed calls for a line. However, it never explicitly states when to prefer this tool instead of latest_code or wait_for_code. Guidance is implied rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

wait_for_codeA

Wait for the NEXT code to arrive on a line and return it. A code that was already in the inbox when the call started is never returned. Call this immediately before the step that sends the text, then complete that step while it waits.

ParametersJSON Schema
NameRequiredDescriptionDefault
fromNoOptional. Only accept senders containing this text.
lineIdYesThe line's id, from list_lines.
serviceNoOptional. Only accept messages Cleat attributed to this service.
timeoutSecondsNoHow long to wait. 1 to 300 seconds.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It discloses the waiting behavior, the boundary that it never returns a pre-existing code, and the recommended call pattern. It does not describe timeout/error behavior or side effects, but these are partly encoded in the timeoutSeconds parameter and the operation is conceptually read-only. Overall, the core behavior is transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, each contributes distinct information: the operation, the exclusion rule, and the usage timing. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no output schema and no annotations, the description covers the essential selection criteria (what it returns, when to call it, and what it ignores). The schema fully documents parameters, and sibling context (latest_code) clarifies the distinction. Minor gaps like timeout behavior are delegated to the schema's timeoutSeconds description, so overall it is complete enough for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% – every parameter (from, lineId, service, timeoutSeconds) has a description in the input schema. The description only mentions 'line' in passing and adds no semantic detail beyond the schema, so a baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description states a specific verb ('Wait... return') and resource ('a code on a line'), and explicitly contrasts with existing inbox codes ('A code that was already in the inbox... is never returned'), distinguishing it from the sibling latest_code. The purpose is unambiguous and does not require reading the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit timing advice ('Call this immediately before the step that sends the text, then complete that step while it waits') and describes what it will not do ('a code that was already in the inbox is never returned'), which is an implicit when-not. It does not name the alternative latest_code directly, but the context is clear enough for an agent to choose correctly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedlatest_code
    • First observedlist_lines
    • First observedlist_messages
    • First observedwait_for_code

TDQS

A4.3/5.0

Scored across 4 tools

Disambiguation4/5

list_lines and list_messages are clearly distinct, and latest_code versus wait_for_code is resolved by existing-code versus next-code semantics. There is mild overlap in code retrieval, but the descriptions make the intended usage clear.

Naming Consistency4/5

list_lines and list_messages follow a consistent verb_noun pattern, while latest_code and wait_for_code deviate stylistically. The names are still readable and semantically grouped, though not perfectly uniform.

Tool Count5/5

Four tools is well-scoped for a focused SMS verification code server: line discovery, message browsing, and two code-retrieval modes. Each tool earns its place without unnecessary bloat.

Completeness5/5

The tool set covers the full OTP retrieval workflow: find a line, read messages, retrieve an already-received code, or wait for the next incoming code. No critical operation appears to be missing for its stated purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI-powered SMS messaging through Twilio with automatic conversation threading, message status tracking, and webhook support for receiving inbound messages.
    79 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to purchase virtual phone numbers, retrieve SMS verification codes, and manage activations through natural language by wrapping the VirtualSMS Consumer API.
    9 npm
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables agents to read Android SMS verification codes via ADB and use them to complete web-based SMS login flows.
    -