GetSms

GetSms

MCP server that lets AI agents retrieve the latest SMS verification code by keyword, using self-hosted phone forwarding and persistent storage.

Category
访问服务器

README

GetSms

English | 中文

A self-hosted, single-user service that collects the SMS your own phones receive and exposes one MCP tool — get_latest_code — so your AI agent can fetch a verification code by keyword.

You run your own instance. There is no shared or hosted service, no accounts, and no multi-tenant anything. It's you, your phones, your server.

  • One job, done well — an AI agent asks "give me the 淘宝 code" and gets it.
  • No app to install — the device side reuses tools that already exist (iOS Shortcuts today).
  • Tiny & auditable — one Node process, SQLite, no telemetry, no outbound calls.
  • MCP-native — plug it into Claude Code, Cursor, or any MCP client over Streamable HTTP.

Contents

What it is

GetSms stores every SMS message your phones forward to it, persistently, in a local SQLite database. It does not try to guess which messages are verification codes at write time — everything is stored as-is. Filtering happens at read time: your MCP client asks for a code by keyword (the sender/brand, e.g. 淘宝), and GetSms looks for a recent message that contains both that keyword and a verification-code phrase.

The keyword is required by design. An agent must know which service's code it wants — GetSms will never hand back "the latest code from anywhere," so a leaked MCP key can't be used to vacuum up every code you receive.

⚠️ Security warning

Read this before you deploy.

GetSms keeps a persistent, plaintext archive of every SMS message you forward to it. This is not a short-lived relay that forgets messages after they're read — it is a permanent log of your SMS history for as long as the database file exists. That makes it meaningfully more sensitive than a one-shot code-forwarding service: if the server is compromised, an attacker gets your entire SMS history, not just one code.

  • Run it only on a machine you trust and control (your own server, your own VPS, your own home box) — not on shared or untrusted infrastructure.
  • GetSms does not do TLS itself. Put it behind a reverse proxy (Caddy, nginx, Traefik, etc.) with a real certificate before exposing it to the internet, or restrict it to a private network / VPN. See Production deployment.
  • Keep the SQLite database file (GETSMS_DB_PATH) private — restrict its file permissions and don't back it up to somewhere less trusted than the server itself.
  • This is not end-to-end encrypted. The server sees every message body in plaintext, because it has to, in order to extract codes and store your history. There is no protection against a compromised server other than not compromising it.

If this trade-off doesn't work for your threat model, don't deploy GetSms.

How it works

[a phone]                       [your server, one process]        [your agent]
receives a code SMS                                                needs a code
   │                                                                    │
   │ phone automation (iOS Shortcuts)                                   │ MCP: get_latest_code
   │ POST /ingest/{device token}                                        │ (keyword required)
   ▼                                                                    ▼
 ┌──────────────────┐  store as-is, tag device   ┌──────────────┐  query  ┌──────────────┐
 │ Ingest HTTP API  │ ──────────────────────────▶│ SQLite store  │◀────── │  MCP Server  │
 │ /ingest/:token   │                             │ full archive  │        │              │
 └──────────────────┘                             └──────────────┘        └──────────────┘

Incoming SMS are written to SQLite unmodified — no filtering happens at ingest time. All matching (keyword, verification-code detection, time window) happens when the MCP tool is called. Both the HTTP ingest API and the MCP server run in a single Node process.

Why there's no app to install

On purpose. iOS gives third-party apps no API to read SMS at all, so no app — GetSms's or anyone else's — can do it. The only consumer-available path on iOS is Apple's own Shortcuts automation, which can watch for incoming messages and POST them somewhere. That's what GetSms's device side uses for v1: see docs/setup-ios-shortcut.md.

Android support (via SmsForwarder or Tasker, which can read SMS with user consent) is on the roadmap but not implemented yet.

Quick start

Requires Docker, or Node.js >= 20 (see Run without Docker).

  1. Copy the example environment file:
    cp .env.example .env
    
  2. Generate a random MCP API key and a random token for each phone you plan to forward from:
    openssl rand -hex 16   # run once per secret
    
    Put the API key in GETSMS_MCP_API_KEY and one token per device into GETSMS_DEVICES (see Config reference).
  3. Start it:
    docker compose up -d
    

The server listens on GETSMS_PORT (default 3000), mapped to 3000 on the host by docker-compose.yml. Put a TLS-terminating reverse proxy in front of it before exposing it publicly — see below.

Configure your phone

Device-side setup (iOS) is a Shortcuts automation, not an app install. Follow docs/setup-ios-shortcut.md for exact, numbered steps, including the required trigger configuration and a reliability caveat you should read before relying on this for anything time-sensitive.

Connect your agent

Point your MCP client at your server's /mcp endpoint using Streamable HTTP transport, with a bearer header:

https://YOUR_SERVER/mcp
Authorization: Bearer <GETSMS_MCP_API_KEY>

For example, with Claude Code:

claude mcp add --transport http getsms https://YOUR_SERVER/mcp \
  --header "Authorization: Bearer <GETSMS_MCP_API_KEY>"

The server exposes exactly one tool, get_latest_code:

get_latest_code({ keyword, device?, since? })
  • keyword — required. The SMS source/brand: the Chinese 【】 signature (e.g. 淘宝, 京东, 12306) or an English service name. Omitting it returns nothing.
  • device — optional, filters to one device's label (from GETSMS_DEVICES).
  • since — optional ISO timestamp; overrides the default recent-message time window (GETSMS_WINDOW_MINUTES).

A message matches when its body contains keyword and contains one of the configured verification-code indicator terms, and was received within the time window. The tool returns the extracted code (if one could be found), the raw message body, the device label, and the received timestamp. Note: the "received" timestamp and the time window are both measured from when the server ingests the forwarded message, not the phone's actual receipt time — any delay in the forwarding automation counts against the window.

Production deployment (HTTPS)

GetSms speaks plain HTTP on GETSMS_PORT. Terminate TLS with a reverse proxy. Caddy is the least-effort option — automatic Let's Encrypt certificates:

# /etc/caddy/Caddyfile
sms.example.com {
    reverse_proxy 127.0.0.1:3000
}

That's the whole config — Caddy obtains and renews the certificate automatically (ports 80 and 443 must be reachable). Then only expose 443 (and 22) at your firewall; keep 3000 bound to localhost.

<details> <summary>nginx equivalent</summary>

server {
    listen 443 ssl;
    server_name sms.example.com;
    ssl_certificate     /etc/letsencrypt/live/sms.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sms.example.com/privkey.pem;
    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

</details>

If your server can't use standard ports 80/443, run the proxy on any high port (e.g. 8443) and include it in the URLs you give your phone and agent — a real certificate can still be obtained via a DNS-01 ACME challenge.

Run without Docker (systemd)

GetSms is plain ESM TypeScript run via tsx — no build step. To run it directly under systemd:

# on the server, as a non-root user (e.g. "getsms")
git clone <your-fork> /opt/getsms && cd /opt/getsms
npm ci --omit=dev
cp .env.example .env && $EDITOR .env
# /etc/systemd/system/getsms.service
[Unit]
Description=GetSms
After=network.target

[Service]
Type=simple
User=getsms
WorkingDirectory=/opt/getsms
EnvironmentFile=/opt/getsms/.env
ExecStart=/usr/bin/node /opt/getsms/node_modules/tsx/dist/cli.mjs src/index.ts
Restart=always
MemoryMax=400M
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now getsms

Config reference

All configuration is via environment variables (see .env.example).

Variable Required Default Description
GETSMS_MCP_API_KEY yes — Bearer token your MCP client must present to call /mcp.
GETSMS_DEVICES yes — JSON array of {"label": "...", "token": "..."}. Each entry is one phone: label identifies it in results, token is the per-device ingest secret used in the /ingest/:token URL.
GETSMS_PORT no 3000 Port the server listens on.
GETSMS_DB_PATH no ./data/getsms.db Path to the SQLite database file.
GETSMS_WINDOW_MINUTES no 10 Default lookback window (minutes) for get_latest_code, overridable per-call via since. Measured from server ingest time, not the phone's actual receipt time.
GETSMS_CODE_INDICATORS no 验证码,verification code,verification,code,OTP,one-time,passcode Comma-separated list of terms that mark a message as containing a verification code.

API reference

  • POST /ingest/:token — webhook the phone automation posts to. Body: { "text": "<sms body>", "sender"?: "..." }. Returns 202 on success, 401 for an unknown token, 400 if text is missing/empty.
  • POST /mcp — MCP Streamable HTTP endpoint, requires Authorization: Bearer <GETSMS_MCP_API_KEY>. GET/DELETE return 405.

Development

npm install
npm test          # Vitest suite (npm run test:watch for watch mode)
npm run dev       # tsx watch, live reload
npm run typecheck # tsc --noEmit

No build step is required — plain ESM TypeScript executed directly via tsx. Requires Node.js >= 20. The test suite covers config parsing, the SQLite store, code extraction/matching, the ingest webhook, the MCP tool logic, and an end-to-end path.

Roadmap

  • Android device side — setup guide for SmsForwarder / Tasker (the server side is already device-agnostic).
  • Optional generic SMS query tool — read non-code messages, off by default.
  • Pluggable storage — Redis/Postgres backend behind the existing store interface for multi-instance setups.

Contributing

Issues and PRs welcome. Please:

  • Keep the single-user, self-hosted, no-telemetry design intact.
  • Follow the existing TDD style — add/adjust tests alongside code; npm test and npm run typecheck must pass.
  • Keep each source file focused (see src/ — config, storage, matching, ingest, MCP, wiring are separate).

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选