ostradocs Menu

MCP server

The MCP server gives an AI agent your lines, their texts and calls, and a new carrier IP, as 7 tools. Claude Code, Claude Desktop, Cursor and any client that speaks MCP over HTTP can use it with an API key.

Where it is

  • The address is https://mcp.ostranetwork.com/mcp. One endpoint, POST /mcp, over Streamable HTTP: each JSON-RPC message is its own request, answered with one JSON object or a short event stream. A batch is refused, and there are no sessions to keep.
  • It speaks MCP revision 2026-07-28, and the 2025 revisions (2025-03-26, 2025-06-18, 2025-11-25) for clients that still open with initialize.
  • Every request carries Authorization: Bearer, checked afresh each time: a revoked or expired key fails on its very next call. A request with an Origin header from a web page is refused.
  • A key works here exactly as on the REST API: its scopes, its lines, 600 requests a minute.

Connect Claude Code

Make an API key with the scopes your agent needs (for the tools below: lines:read, lines:new_carrier_ip, messages:read, messages:read_content, calls:read, and calls:read_content for what a call carried), then add the server. The syntax is Claude Code’s own, from its MCP documentation:

ShellWrites the key into Claude Code's configuration
claude mcp add --transport http ostra https://mcp.ostranetwork.com/mcp \
  --header "Authorization: Bearer $OSTRA_API_KEY"
claude mcp get ostra

In Claude Code, /mcp lists the tools. Try: “List my Ostra lines”, or “Wait for a code on LAX-SANDBOX-00” while you send the sandbox line a text.

Keep the key in the macOS keychain instead. ostra login (the CLI) stores it there, and so does this command, which asks for the key, then for it again to confirm:

ShellStores the key in the macOS keychain, where ostra login keeps it
security add-generic-password -s com.ostranetwork.cli -a https://api.ostranetwork.com -l "Ostra API key" -U -w

A small script then hands it to Claude Code at each connection, so it is never written to a file. Save this as ~/.local/bin/ostra-mcp-headers and make it executable (chmod 700):

Shellostra-mcp-headers
#!/bin/sh
# The Authorization header for Claude Code's connection to Ostra,
# read from the key `ostra login` keeps in the macOS keychain.
key=$(security find-generic-password -s com.ostranetwork.cli -a https://api.ostranetwork.com -w) || exit 1
printf '{"Authorization":"Bearer %s"}\n' "$key"
ShellGive the script's absolute path
claude mcp add-json --scope user ostra \
  '{"type":"http","url":"https://mcp.ostranetwork.com/mcp","headersHelper":"/Users/you/.local/bin/ostra-mcp-headers"}'

Claude Code runs the script for each connection and again after a refused call, gives it 10 seconds, and runs a user-scope server’s script from ~/.claude, so give its absolute path (Claude Code’s documentation, read 2026-10-21).

These commands change your own Claude Code settings, so Ostra’s checks do not run them.

Other MCP clients

  • Claude Desktop reaches a remote server as a custom connector, from Anthropic’s cloud, not from your computer: add one with the URL above, “No sign in”, and an Authorization request header of Bearer osk_... (Anthropic’s guide, read 2026-10-21). Menu names change between versions; follow the guide where they differ.
  • Any other client that speaks Streamable HTTP and can send a header: give it the URL and the same bearer header.

Sign in instead of a key

A client can sign in with OAuth (the flow) and use a token issued for this server’s resource, https://mcp.ostranetwork.com/mcp. A token issued for the REST API is refused here, and the reverse. A request with no credential gets 401 with a WWW-Authenticate header naming the protected resource metadata (RFC 9728), at /.well-known/oauth-protected-resource/mcp. A tool call the token lacks a scope for gets 403 with error="insufficient_scope" and the scopes it needs, before the tool runs.

Sign-in works only for a client Ostra registered for this server’s resource; there is no dynamic client registration. Claude Code, Claude Desktop and other MCP clients connect with an API key, as above.

The tools

Each tool calls the same code as its REST route, so the sandbox line, the new carrier IP limits, idempotency, the audit log and every refusal behave exactly as on the API. The list is the same for every caller.

TOOL list_lines

Arguments
None
Scopes
lines:read
Same as
GET /v1/lines
Returns
{lines}

TOOL get_line

Arguments
line_id
Scopes
lines:read
Same as
GET /v1/lines/{line_id} and GET /v1/lines/{line_id}/proxy
Returns
{line, proxy}: the line, its proxy endpoints and its credential list, never a secret.

TOOL new_carrier_ip

Arguments
line_id, idempotency_key optional
Scopes
lines:new_carrier_ip
Same as
POST /v1/lines/{line_id}/new-carrier-ip
Returns
{job, idempotent_replay} at once: a handle to read, never a wait.

TOOL get_new_carrier_ip

Arguments
line_id, job_id
Scopes
lines:new_carrier_ip
Same as
GET /v1/lines/{line_id}/new-carrier-ip/{job_id}
Returns
{job}

TOOL wait_for_code

Arguments
line_id, timeout_seconds optional, cursor optional
Scopes
messages:read and messages:read_content
Same as
GET /v1/lines/{line_id}/messages/wait, then GET /v1/lines/{line_id}/messages/{message_id}
Returns
{line_id, code, message, next_cursor, timed_out}; the message is marked untrusted_content: true, since its words are a third party's, never instructions.. Waits up to 90 seconds (timeout_seconds, default 60)

TOOL list_messages

Arguments
line_id, cursor optional, limit optional, include_content optional
Scopes
messages:read, plus messages:read_content with include_content
Same as
GET /v1/lines/{line_id}/messages
Returns
As the REST list; with content each text is also marked untrusted_content: true, since its body is a third party's, never instructions.

TOOL list_calls

Arguments
line_id, cursor optional, limit optional, include_content optional
Scopes
calls:read, plus calls:read_content with include_content
Same as
GET /v1/lines/{line_id}/calls
Returns
As the REST list; with content each call is also marked untrusted_content: true, since its transcript is a third party's, never instructions.
  • A tool’s answer is the REST answer, as structured content and as the same JSON in text. A refusal is a tool error in the API’s shape, {"error": {"code", "message", ...}}: not_found, new_carrier_ip_refused with next_allowed_at, line_suspended, rate_limited. Arguments that do not fit a tool’s schema are a tool error too, so the agent can correct them.
  • new_carrier_ip returns a job at once; read it with get_new_carrier_ip. Send the same idempotency_key within 24 hours and you get the first answer back, with idempotent_replay: true.
  • wait_for_code waits like the texts long poll, without content and one text at a time, so a text with no code is passed over and never decrypted. The first text with a live code is opened once and written to the audit log. With no code by the deadline it answers code: null, timed_out: true and a next_cursor to wait on from. It waits up to timeout_seconds (1 to 90, default 60), and counts against the 5 waits at once per line.

Every tool’s description says what it is for: testing sign-up, sign-in and verification flows of services you own or are authorized to test.

What is not a tool

On purpose, the server has no tool that creates a line, buys a number, texts or calls out from a line, solves a CAPTCHA, changes a browser’s fingerprint, or creates an account anywhere. A verified organization gets lines by ordering them, from the dashboard or the API, and a line only receives.

Check a key by hand

An MCP client does this for you. To see that a key reaches the server, list the tools with the raw request. OSTRA_MCP_URL is the address above.

Shelltools-list.sh
#!/bin/sh
# Check a key against the MCP server by hand: list its tools.
# MCP clients do this for you; this is the raw request, MCP revision 2026-07-28.
curl -sS -X POST "$OSTRA_MCP_URL" \
  -H "Authorization: Bearer $OSTRA_API_KEY" \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H 'mcp-protocol-version: 2026-07-28' \
  -H 'mcp-method: tools/list' \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}'

The answer is a JSON-RPC result whose tools are the 7 above, in that order, each with its name, title, description and input schema.