{
  "markdown": "# fmsg-mcp\n\n[![Tests](https://github.com/markmnl/fmsg-mcp/actions/workflows/tests.yml/badge.svg)](https://github.com/markmnl/fmsg-mcp/actions/workflows/tests.yml)\n[![npm](https://img.shields.io/npm/v/%40markmnl%2Ffmsg-mcp)](https://www.npmjs.com/package/@markmnl/fmsg-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n\nAn [MCP](https://modelcontextprotocol.io) server that gives any AI agent its own\n[fmsg](https://github.com/markmnl/fmsg) address: send messages, follow threads, react, exchange\nattachments and wait for replies, through a deployed\n[fmsg Web API](https://github.com/markmnl/fmsg-webapi). Works with Claude Code, Claude Desktop,\nCursor, VS Code, claude.ai remote connectors and any other MCP host.\n\n- **stdio** for local hosts: one address per server process, configured by two environment variables.\n- **Streamable HTTP** for shared or remote deployments: one endpoint serving many users, each\n  authenticated by their own fmsg API key.\n- The fmsg Web API client is exported for reuse: `import { FmsgClient } from \"@markmnl/fmsg-mcp/client\"`.\n\n## 1. Get an fmsg address and API key\n\nYou send as an fmsg address, authenticated by an API key (`fmsgk_…`) issued by your fmsg host:\n\n- **No host yet?** Create an account at a public fmsg host such as [fmsg.io](https://fmsg.io) and\n  add an agent (sub-account) to get an API URL and key.\n- **Self-hosting?** Run the stack with [fmsg-docker](https://github.com/markmnl/fmsg-docker) and issue\n  a key with `fmsg-webapi api-key create`.\n\n## 2. Install\n\nRequires Node.js 22 or later.\n\n### Claude Code\n\n```sh\nclaude mcp add fmsg --scope user \\\n  --env FMSG_API_URL=https://api.example.com \\\n  --env FMSG_API_KEY=fmsgk_... \\\n  -- npx -y @markmnl/fmsg-mcp\n```\n\nThen in any session: *\"Send @bob@example.com a note about the release\"*, *\"What's in my fmsg inbox?\"*,\n*\"Wait for Bob's reply and answer it\"*. `/fmsg:chat` and `/fmsg:reply` are available as prompts.\n\n### Claude Desktop, Cursor, VS Code and other stdio hosts\n\nAdd a server entry with the same command; only the config file differs:\n\n```json\n{\n  \"mcpServers\": {\n    \"fmsg\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@markmnl/fmsg-mcp\"],\n      \"env\": { \"FMSG_API_URL\": \"https://api.example.com\", \"FMSG_API_KEY\": \"fmsgk_...\" }\n    }\n  }\n}\n```\n\n(Claude Desktop: `claude_desktop_config.json`; Cursor: `.cursor/mcp.json`; VS Code: `.vscode/mcp.json`\nunder `\"servers\"` with `\"type\": \"stdio\"`.)\n\n### Remote (Streamable HTTP) mode\n\nRun one server for many users. Each client sends **its own** fmsg API key as a bearer token; the\nserver exchanges it at the fmsg host and acts as that address. `FMSG_API_KEY` must not be set.\n\n```sh\nFMSG_API_URL=https://api.example.com npx -y @markmnl/fmsg-mcp --http 0.0.0.0:8765\n# or\ndocker build -t fmsg-mcp . && docker run -e FMSG_API_URL=https://api.example.com -p 8765:8765 fmsg-mcp\n```\n\nThe MCP endpoint is `/mcp`; `/healthz` reports liveness. Point a host at it with\n`Authorization: Bearer fmsgk_...` — for claude.ai, add a custom connector with that URL and header;\nfor Claude Code, `claude mcp add --transport http fmsg https://mcp.example.com/mcp --header \"Authorization: Bearer fmsgk_...\"`.\n\nDeploy behind a TLS-terminating reverse proxy and set `FMSG_MCP_ALLOWED_HOSTS` to the public hostname\nwhen binding to a non-loopback address. `wait_for_message` holds a request open for up to\n`FMSG_MCP_WAIT_MAX_SECONDS` (230), so give the proxy an idle timeout of at least 240 s.\n\n## Tools\n\n| Tool | What it does |\n|---|---|\n| `whoami` | The address this server acts as, the API URL and token expiry |\n| `resolve_address` | Turn a short name into `@user@domain` (directory, then default domain) |\n| `list_messages` | Inbox, newest first, with previews; reactions hidden; optional unread filter |\n| `list_sent` | Sent messages with per-recipient delivery state |\n| `get_message` | One message with headers, full text body, attachments and reactions |\n| `get_thread` | The lineage from the thread root to a message, with gaps for messages you cannot see |\n| `send_message` | Start a new thread; sends immediately (fmsg messages are immutable) |\n| `reply` | Reply into a thread; reply-all by default, refuses terminal and no-reply parents |\n| `add_recipients` | Add recipients to a sent message |\n| `react` | Set or clear your emoji reaction |\n| `mark_read` | Mark received messages read |\n| `download_attachment` | Fetch an attachment inline (base64, images as image blocks) or, over stdio, save it to disk |\n| `delivery_status` | Per-recipient delivery times and host response codes |\n| `wait_for_message` | Block until the next inbound message (WebSocket push), batched per thread, with thread context |\n\nEvery tool returns readable Markdown plus `structuredContent`. Ids are decimal strings. Message\nbodies are labelled as data from other parties, not instructions.\n\nResources `fmsg://message/{id}` and `fmsg://thread/{id}` expose the same content to hosts that\nattach resources; prompts `chat` and `reply` script the wait → reply loop and a guided reply.\n\n## Configuration\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `FMSG_API_URL` | — | Base URL of the fmsg Web API (required) |\n| `FMSG_API_KEY` | — | `fmsgk_…` key; stdio mode only |\n| `FMSG_DEFAULT_DOMAIN` | — | Lets short names resolve: `bob` → `@bob@<domain>` |\n| `FMSG_DIRECTORY` | — | JSON file mapping short names to full addresses |\n| `FMSG_MCP_WAIT_MAX_SECONDS` | `230` | Cap on one `wait_for_message` call |\n| `FMSG_MCP_DOWNLOAD_DIR` | — | Restrict `download_attachment` `save_to` to this directory (stdio) |\n| `FMSG_MCP_HOST` / `FMSG_MCP_PORT` | `127.0.0.1` / `8765` | HTTP bind address (or `--http host:port`) |\n| `FMSG_MCP_ALLOWED_HOSTS` | loopback names | Comma-separated `Host` header allowlist for HTTP mode |\n| `FMSG_MCP_ALLOWED_ORIGINS` | same as hosts | `Origin` allowlist for browser-based callers |\n| `FMSG_MCP_KEY_CACHE_MAX` / `FMSG_MCP_KEY_CACHE_TTL_SECONDS` | `500` / `1800` | HTTP mode per-key client cache |\n\nThe API key is exchanged for a short-lived access token that the server renews automatically.\n\nOver stdio the server also starts with no credentials at all, so hosts and directories can list its tools; every tool call then returns a message naming the missing variables.\n\n## Safety\n\n- Sent messages cannot be edited or recalled; send tools say so in their descriptions and are\n  annotated `destructiveHint` so hosts can ask for confirmation.\n- API keys, tokens and other secret-shaped strings are redacted from outbound bodies, topics and\n  error text; the count of redactions is reported.\n- Nothing about message size or acceptance is assumed: the fmsg host's own responses and delivery\n  codes are surfaced verbatim.\n- See [SECURITY.md](./SECURITY.md).\n\n## Using the client library\n\n```ts\nimport { FmsgClient } from \"@markmnl/fmsg-mcp/client\";\n\nconst client = new FmsgClient(\"https://api.example.com\", process.env.FMSG_API_KEY!);\nconsole.log(await client.address());\nconst inbox = await client.listInbox(10);\nawait client.send({ to: [\"@bob@example.com\"], topic: \"Hi\", body: \"Hello from code\" });\n```\n\n## Development\n\n```sh\nnpm ci\nnpm run typecheck && npm run build && npm test\nnpx @modelcontextprotocol/inspector node dist/index.js          # stdio, with FMSG_API_URL/FMSG_API_KEY set\nbash .github/scripts/run-fmsg-docker-e2e.sh                     # end to end on two real fmsg stacks\n```\n\nSee [AGENTS.md](./AGENTS.md) for layout and conventions.\n\n[MIT licensed](./LICENSE)\n",
  "bytes": 7458,
  "sha": "7217727cec41f62787371c0dfc3ef38ccfde2dfe0a71fa2ec7930d10864e385d",
  "repo_slug": "markmnl/fmsg-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_markmnl_fmsg_mcp_17b512fb/readme"
}