mermaid-mcp
by anas-alward
README.md
# mermaid-mcp
An [MCP](https://modelcontextprotocol.io) server that gives an AI agent full access to your
[Mermaid](https://mermaid.alward.dev) diagrams and collections — list, read, create, update and
delete over the Mermaid REST API.
- **Two transports.** `stdio` for local agents (Claude Code, opencode), stateless
Streamable HTTP for remote clients and shared deployments.
- **No database, no session state.** It is a thin, well-behaved proxy in front of the Mermaid API:
whatever the backend can do, the agent can do.
- **Per-caller credentials.** Each HTTP request carries its own Mermaid API key, so one deployment
can serve many users without leaking anyone's diagrams.
---
## Tools
| Tool | What it does |
| --- | --- |
| `list_diagrams` | List diagrams. Optional `search` (name substring), `page`, `limit` (max 100). |
| `get_diagram` | Fetch one diagram by id, including its full Mermaid source. |
| `create_diagram` | Create a diagram from a name and Mermaid source, optionally in a collection. |
| `update_diagram` | Change a diagram's `name`, `content` and/or `collectionId` (`null` = unfiled). |
| `delete_diagram` | Permanently delete a diagram. |
| `list_collections` | List collections (diagram folders). |
| `create_collection` | Create a collection. |
| `delete_collection` | Delete a collection. Its diagrams survive and become unfiled. |
> `list_diagrams` returns the **full source** of every diagram in the page, so responses get large
> fast. Give the agent a `search` term or a small `limit` when it only needs an overview.
---
## Quick start
### Local agent (stdio)
Create an API key in the Mermaid dashboard (avatar menu → **API keys**), then point your client at
this server:
```bash
npm install
MERMAID_API_KEY=ek_... npm run start:stdio
```
**Claude Code**
```bash
claude mcp add mermaid --env MERMAID_API_KEY=ek_... -- node /path/to/mermaid-mcp/src/index.js --stdio
```
**opencode** — add to `opencode.json`:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"mermaid": {
"type": "local",
"command": ["node", "/path/to/mermaid-mcp/src/index.js", "--stdio"],
"environment": { "MERMAID_API_KEY": "ek_..." },
"enabled": true
}
}
}
```
### Remote client (HTTP)
```bash
MERMAID_BASE_URL=https://mermaid.alward.dev PORT=8120 node src/index.js
```
The endpoint is `POST http://<host>:8120/mcp`, stateless. Send the caller's key on every request:
```bash
curl -X POST http://localhost:8120/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H "authorization: Bearer $MERMAID_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
`accept` must include **both** `application/json` and `text/event-stream`, as the MCP Streamable
HTTP spec requires.
**opencode / other remote MCP clients:**
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"mermaid": {
"type": "remote",
"url": "https://mermaid.example.com/mcp",
"enabled": true,
"headers": { "authorization": "Bearer ek_..." }
}
}
}
```
### Docker
```bash
docker build -t mermaid-mcp .
docker run -d -p 8120:8120 -e MERMAID_BASE_URL=https://mermaid.alward.dev mermaid-mcp
```
The image runs as the unprivileged `node` user and ships a `HEALTHCHECK` against `/healthz`, so
`docker run` reports `healthy` on its own.
### Deploying to a server
The repo is the source of truth. Clone it on the host and build from the checkout:
```bash
git clone https://github.com/anas-alward/mermaid-mcp.git mcp-server
cd mcp-server && npm ci # only needed to run the suite on the host
```
To deploy an update:
```bash
cd mcp-server && git pull
cd .. && docker compose build mcp && docker compose up -d mcp
docker compose ps # wait for (healthy)
curl -s localhost:8120/healthz # version confirms which build is live
```
Two things to know when the server directory is a git checkout:
- `.env` is gitignored, so it never travels with the clone. Create it on the host; the compose file
declares it `required: false`, and the server only reads it for `--stdio`.
- `.dockerignore` excludes `.git`, so clone history stays out of the build context and the image.
Without git on the host, rsync the tree instead — the deploy directory only needs to match the
repository, plus `.env`.
---
## Authentication
The server holds no credentials of its own. In **HTTP mode the key must arrive on the request**,
as either header:
```
authorization: Bearer ek_...
x-mermaid-api-key: ek_...
```
A request without one gets `401` and a JSON-RPC error explaining how to fix it. `MERMAID_API_KEY`
is only consulted in `--stdio` mode, where there are no HTTP headers to read it from.
> **Changed in 1.0.0.** 0.2.0 silently fell back to the server's `MERMAID_API_KEY` when a request
> carried no key — which meant a publicly reachable deployment would hand one account's diagrams to
> anonymous callers. HTTP mode is now strict.
### Upgrading from 0.2.x
- Entry point moved: `node server.mjs` → `node src/index.js` (add `--stdio` for stdio mode).
Container `CMD` and client configs need the new path.
- HTTP requests must carry their own key; `MERMAID_API_KEY` is now stdio-only.
- `MERMAID_HOST_HEADER` is gone. It never worked — `fetch` will not let you override `Host` — so if
you were reaching a backend directly, point `MERMAID_BASE_URL` at the address that routes to it.
- Tool names, parameters and responses are unchanged.
---
## Configuration
All configuration is environment variables, validated at boot: a bad value stops the process with a
message naming the variable, rather than failing later on the first tool call.
| Variable | Default | Purpose |
| --- | --- | --- |
| `MERMAID_BASE_URL` | `https://mermaid.alward.dev` | Origin of the Mermaid deployment. No trailing slash, no `/api/v1`. |
| `MERMAID_API_KEY` | — | Dashboard API key. **stdio mode only**; ignored in HTTP mode. |
| `HOST` | `0.0.0.0` | HTTP bind address. Use `127.0.0.1` to keep it local. |
| `PORT` | `8120` | HTTP port. `0` picks a free port (used by tests). |
| `MCP_PATH` | `/mcp` | Path the MCP endpoint is served from. |
| `REQUEST_TIMEOUT_MS` | `15000` | Abort an upstream call that takes longer. |
| `MAX_BODY_SIZE` | `2mb` | Largest accepted MCP request body. |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error`, `silent`. |
| `CORS_ORIGINS` | `*` | Comma-separated browser origins, or `*`. |
| `ALLOWED_HOSTS` | — | Comma-separated `Host` allow-list. Unset disables the check. |
Copy `.env.example` as a starting point. The process does not read `.env` files itself — use your
process manager, `docker run -e`, or `--env-file`.
---
## HTTP endpoints
| Route | Purpose |
| --- | --- |
| `POST /mcp` | MCP JSON-RPC. Stateless; no session id is issued. |
| `GET /healthz` | Liveness/readiness for load balancers and the Docker healthcheck. |
| `GET /` | Service name, version and where things live. |
| `GET`/`DELETE /mcp` | `405` with a JSON-RPC error — this endpoint is POST-only. |
Errors are always JSON-RPC shaped: `-32700` for a malformed body, `-32600` for a bad request or
missing key, `-32603` for an internal fault. Upstream failures (bad key, missing diagram, timeout)
come back as **tool results with `isError: true`**, so the agent can read what went wrong and retry
instead of seeing a transport crash.
**Security knobs.** Auth is a bearer token, not a cookie, so there is no CSRF surface; `CORS_ORIGINS`
only matters for browser clients. Set `ALLOWED_HOSTS` when the server is reachable from a browser to
close off DNS-rebinding. Terminate TLS in front of it — the server speaks plain HTTP.
**Operations.** Logs go to stderr, one line per event, with credential-shaped fields redacted. Set
`LOG_LEVEL=debug` to also see rejected requests. `SIGINT`/`SIGTERM` drain in-flight requests and
exit 0, with a 10s backstop.
---
## Development
```bash
npm install
npm test # node:test — no network, no API key needed
npm run dev # HTTP mode with --watch
```
The suite runs against an in-process mock of the Mermaid API, so it is fast and hermetic. It covers
the API client (auth, error mapping, timeouts, id encoding), all eight tools through a real MCP
client, and the HTTP layer (auth, CORS, host validation, body limits, JSON-RPC error shapes).
### Layout
```
src/
index.js entry point: transport selection, shutdown, fatal error handling
config.js environment parsing and validation
logger.js levelled stderr logging with redaction
api.js Mermaid REST client: auth, timeout, error translation
tools.js the eight MCP tools
http.js Express app, stateless Streamable HTTP, health, CORS, host checks
test/ node:test suites + mock backend
```
Adding a tool means adding one `server.registerTool(...)` block in `src/tools.js` and a test in
`test/tools.test.js`. Nothing else needs to know about it.
---
## Before you publish
`git init`, then push — CI runs the suite on Node 20 and 22.
## License
MIT © 2026 Anas Alward. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues