anytype-mcp-remote
by bardaxx
README.md
# anytype-mcp-remote
A **remote MCP server** (Streamable HTTP) in front of the local Anytype REST API.
It lets ChatGPT — or any MCP client that supports remote servers — talk to a
self-hosted Anytype space.
```
ChatGPT --HTTPS--> tunnel --> anytype-mcp-remote :8787 --> 127.0.0.1:31012 --> any-sync network
(headless anytype-cli)
```
## Why @anyproto/anytype-mcp is not enough
The official package exposes **only** `StdioServerTransport`: it runs as a child
process of the client, over stdin/stdout. ChatGPT, by contrast, connects to
**remote** MCP servers over HTTPS. This project is the missing piece: the same
API underneath, HTTP transport on top.
## Tools exposed
| Tool | What it does |
|---|---|
| `search` | full-text search, returns id + title + preview |
| `fetch` | one whole object with its properties |
| `list_spaces` | spaces visible to the bot |
| `list_types` | defined types and their properties |
| `list_objects` | every object of one or more types (pagination handled) |
| `list_properties` | properties and, on request, the tag IDs of select fields |
| `create_object` | creates an object — only with `MCP_ALLOW_WRITES=true` |
| `update_object` | edits name/properties — only with `MCP_ALLOW_WRITES=true` |
`search` and `fetch` carry those names on purpose: they are the ones ChatGPT
expects.
Responses go through a `slim()` function that discards the type schema attached
to every object (tens of KB per record). Without it, a list of 100 objects would
swamp the context.
## Configuration
See `.env.example`. In short:
| Variable | Role |
|---|---|
| `ANYTYPE_API_BASE_URL` | `31012` for anytype-cli, `31009` for the desktop app |
| `ANYTYPE_API_KEY` | key generated by the CLI; it never leaves the server |
| `MCP_TOKEN` | **your** secret, the one you hand to the client. Min 24 chars |
| `MCP_ALLOW_WRITES` | `false` by default: the write tools are not registered |
| `MCP_SPACE_ID` | when set, the server is pinned to that space |
| `PUBLIC_BASE_URL` | your domain: it lands in the OpenAPI document for Actions |
| `HOST` | listening interface, `127.0.0.1` by default: it sits behind the proxy |
The environment is validated as a whole at startup (`src/config.ts`): if a
variable is missing or malformed the process dies immediately naming it, rather
than failing later as a 401 or a broken URL in the schema.
## Security
- **The token is the password.** Whoever has the URL has access: treat it as a
credential, keep it out of chats and screenshots. To rotate it, change
`MCP_TOKEN` and restart.
- **The Anytype key never leaves the VPS.** It lives in the environment file;
the client only ever sees your token.
- **Writes are off by default.** With `MCP_ALLOW_WRITES=false` the write tools
are not even registered: they do not appear in `tools/list`, so the model
cannot attempt them. Turn them on only if you truly need them.
- **Pin the space.** With `MCP_SPACE_ID` set, the `space_id` parameter
disappears from the schemas and any attempt to point elsewhere is refused.
- **The bot is revocable.** If something goes wrong, remove it from the space in
the desktop app: it loses access without you touching your personal account.
- The token is compared in constant time and never reaches the logs.
## Development
TypeScript in `strict` mode, compiled with `tsc` into `dist/`. No bundler, no
runtime transpiler: the service runs ordinary JavaScript.
| Command | What it does |
|---|---|
| `npm run typecheck` | `tsc --noEmit` over sources, tests and scripts |
| `npm test` | compiles and runs the unit tests (`node --test`) |
| `npm run build` | emits `dist/` |
| `npm start` | runs `dist/src/server.js` |
| `npm run check` | typecheck plus tests, what CI runs |
Unit tests live in `test/` and never touch the network: the Anytype client takes
an injected `fetch`, so retries, pagination and 401s can be exercised without a
real instance. They cover config validation, object reduction, client retries,
authentication and the shape of the OpenAPI document.
Contributions must be written in English — see [AGENTS.md](AGENTS.md).
### Integration test against a real Anytype
The server behaves identically against the **desktop app** (port 31009): same
`/v1` API.
```
npm install
npm run build
export ANYTYPE_API_BASE_URL=http://127.0.0.1:31009
export ANYTYPE_API_KEY=<app key from the desktop>
export MCP_SPACE_ID=<space id>
export MCP_TOKEN=$(openssl rand -hex 32)
export PORT=8791
npm start &
npm run smoke
```
`scripts/smoke.ts` speaks JSON-RPC like a real MCP client: `initialize`,
`tools/list`, then `search` / `fetch` / `list_objects`, and it checks that a
wrong token gives a 401 and that the write tools stay hidden when not enabled.
## Installing on a VPS
The full runbook, including the privileged steps, is in **`deploy/RUNBOOK.md`**.
Supporting files in `deploy/`: `network.yml` (a **template** for the self-hosted
any-sync network — put your own `networkId` and `peerId` values in it, it is not
usable as-is), `anytype-mcp-remote.service` (systemd unit), `Caddyfile` and
`cloudflared-config.yml` (TLS and reverse proxy, pick one).
## Licence
MIT — see [`LICENSE`](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues