# MCP server

> Give Claude Code and other AI agents Nexafon's docs and examples, and safe test actions with a test key, through the Nexafon MCP server.

Source: https://docs.nexafone.so/v1/ai/mcp/

The Nexafon MCP server (`@nexafon/mcp`) lets an AI coding agent read these docs and the example apps while it writes
your integration, and, with a **test** secret key, send a test message and look at your webhook deliveries and recent
errors. It speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdio, so it works in Claude Code
and any other MCP client that starts local servers.

It pairs with the [Nexafon agent skill](/v1/tools/ai-agents/), which teaches the agent the integration steps and rules,
and the [CLI](/v1/tools/cli/), which scaffolds the integration and checks it with `nexafon doctor`.

## Tools

| Tool | Needs | What it does |
| --- | --- | --- |
| `search_docs(query, limit?)` | | Searches these docs; each result has the page title, its URL and a snippet |
| `get_page(path)` | | Returns a page as Markdown, the same text as its `.md` twin (`/v1/quickstart/` or `/v1/quickstart.md`) |
| `get_example(name, file?)` | | Lists the files of an example app (`react`, `next`, `partner`, …) with its README, or returns one file |
| `send_test_message(to, text)` | test key | Sends a text to a customer through [`sendMessage`](/v1/api/inbox/) in test mode |
| `list_webhook_deliveries(limit?, endpointId?, status?)` | test key | The latest delivery attempts across your [webhook endpoints](/v1/webhooks/), the newest first |
| `recent_errors(limit?)` | test key | Failed webhook deliveries, failing or turned-off endpoints, and outbound messages WhatsApp did not accept |

The docs and the examples are bundled into the package when it is built, so the first three tools work offline and
match the version you installed. Links in their answers point at `NEXAFON_DOCS_URL`.

- **There is no separate test-send operation.** `send_test_message` calls the documented send-message operation with
  your test key, so [test mode](/v1/concepts/#test-mode) applies: a real WhatsApp number answers
  [`test_mode_live`](/v1/errors/test_mode_live/) unless the workspace has a WhatsApp test number. The
  customer must have written within the last 24 hours; a new conversation starts with a template.
- **`recent_errors`** reports what the public API exposes. The API keeps no log of failed requests: those come back
  to your own code as [Problem Details](/v1/errors/).
- `get_example` serves only the files committed under `examples/<name>`, and never a `.env` file (the `.env.example`
  templates are included).

## Settings

The server reads its settings from its environment:

| Variable | |
| --- | --- |
| `NEXAFON_DOCS_URL` | Required. The docs site, `https://docs.nexafone.so`, for the links in results |
| `NEXAFON_API_URL` | The API, `https://api.nexafone.so`; needed by the test-key tools. https, or http only to localhost, 127.0.0.1 or a `*.localhost` name, since the key is sent there. Against a local Nexafon stack use its `API_URL` unchanged: the server sends `*.localhost` names to 127.0.0.1 itself (macOS does not resolve them for Node), with the Host header the stack routes by |
| `NEXAFON_SECRET_KEY` | A **test** secret key (`sk_test_…`) with the `inbox:reply` and `developers:manage` scopes; needed by the test-key tools |
| `NEXAFON_WORKSPACE_ID` | The key's workspace id: the `{workspaceId}` in API paths, also sent as `workspaceId` in every webhook delivery |
| `NEXAFON_APP_URL`, `NEXAFON_EMBED_CDN_URL` | Optional: fill in the dashboard and SDK loader URLs in the pages the server returns |

:::caution[Test keys only]
The server refuses a live key (`sk_live_…`) and any other kind of key, so an agent never acts on real customers with
it. The key never appears in a tool result, an error or the server's log. Keep it out of files you commit: let the
MCP config read it from your environment, as below.
:::

## Claude Code

:::note[Not published yet]
The Nexafon packages are not on npm, Packagist or PyPI yet, and `go get` finds the Go module only once the Nexafon
repository is public and tagged, so installing them by name fails for now. Until then,
[install them from a checkout of the repo](/v1/quickstart/#install-from-a-checkout). The install commands shown here
are the ones to use after release.
:::

`@nexafon/mcp` is not on npm yet, so `npx -y @nexafon/mcp@0.1.0` fails for now. Until it is published, build it in a
checkout of the repo (`pnpm install`, then `pnpm --filter @nexafon/mcp build`) and start it with `node`. Add the server
to a project, so everyone working on it gets the tools, with the key and workspace id read from each developer's own
environment:

```sh
claude mcp add nexafon --scope project \
  --env NEXAFON_DOCS_URL=https://docs.nexafone.so \
  --env NEXAFON_API_URL=https://api.nexafone.so \
  --env 'NEXAFON_WORKSPACE_ID=${NEXAFON_WORKSPACE_ID:-}' \
  --env 'NEXAFON_SECRET_KEY=${NEXAFON_SECRET_KEY:-}' \
  -- node /path/to/nexafon/mcp/dist/bin.js
```

That writes `.mcp.json` at the project root. Claude Code fills in `${…}` from the environment it starts in; a variable
that is not set becomes empty (`:-`), which leaves the test-key tools off and the docs tools working:

```json
{
  "mcpServers": {
    "nexafon": {
      "command": "node",
      "args": ["/path/to/nexafon/mcp/dist/bin.js"],
      "env": {
        "NEXAFON_DOCS_URL": "https://docs.nexafone.so",
        "NEXAFON_API_URL": "https://api.nexafone.so",
        "NEXAFON_WORKSPACE_ID": "${NEXAFON_WORKSPACE_ID:-}",
        "NEXAFON_SECRET_KEY": "${NEXAFON_SECRET_KEY:-}"
      }
    }
  }
}
```

After the release, replace `node /path/to/nexafon/mcp/dist/bin.js` with `npx -y @nexafon/mcp@0.1.0` (in the JSON,
`"command": "npx"` and `"args": ["-y", "@nexafon/mcp@0.1.0"]`). Run `/mcp` in Claude Code to see the server and its
tools.

## Other MCP clients

Any client that starts stdio servers runs it the same way: the command `node /path/to/nexafon/mcp/dist/bin.js` (after
the release, `npx -y @nexafon/mcp@0.1.0`) with the variables above in its environment. Cursor reads the same
`mcpServers` object from `.cursor/mcp.json`, and Claude Desktop from its `claude_desktop_config.json`. Where a client
cannot read a variable from your environment, put the test key in its user-level config, never in a file you commit.
