npm package: @mylesiyabor/mddb
Description: Multiplayer markdown database — SQLite-backed, real-time, agent-ready
Author: Gravity
Latest version: 1.5.2

# mddb

Multiplayer markdown database — SQLite-backed, real-time, and agent-ready. Every write broadcasts over WebSocket to all connected browsers.

```bash
npx @mylesiyabor/mddb start
```

Open **http://localhost:7778** in your browser.

## Install

```bash
npm install -g @mylesiyabor/mddb
```

Requires **Node.js 18.14.1+**.

## Usage

```bash
mddb start                        # default port 7778, default DB path
mddb start ./team.db              # custom SQLite file
mddb start --port 8080            # custom port
mddb users create alice           # create a user and print their token
mddb users list
mddb users rotate alice
mddb users delete alice
mddb users promote alice          # mark a user as admin (database flag)
mddb get roadmap ./team.db        # print a document from a specific DB
mddb set roadmap ./notes.md       # write from a file path
mddb set roadmap --by myles < roadmap.md
mddb delete roadmap ./team.db
mddb search "roadmap partner"       # full-text search (FTS5) across all docs
mddb search "keywords" ./team.db  # optional explicit DB path
```

For `mddb get`, `set`, `list`, `history`, `delete`, and `search`, you can pass the DB as either `--db /path/to.db` or a trailing `path/to.db`. `mddb set` accepts content as a literal string, a file path, or stdin.

## Search (full-text)

mddb indexes **title + markdown body** with **SQLite FTS5** (Porter stemmer + **prefix** matching on each token, so `road` matches `roadmap`). This is **keyword search**, not semantic embeddings — good for agents to locate docs before calling `mddb_get`.

- **CLI:** `mddb search "<keywords>"` (optional trailing `path/to.db`)
- **HTTP:** `GET /api/search?q=keywords&limit=20` — JSON with `results[]`: `id`, `title`, `snippet`, `rank`
- **MCP:** `mddb_search` tool
- **Cap:** max hits default **20**, upper bound from **`MDDB_SEARCH_LIMIT`** (default **50**)

Embedding-based search could be added later as an optional mode; FTS stays the default because it has no extra services and stays in sync with your SQLite file.

## Slack notifications (optional)

Configure a [Slack Incoming Webhook](https://api.slack.com/messaging/webhooks) URL in either place:

1. **Web UI (admins)** — **Settings** in the top bar → **Slack incoming webhook** → **Save**. Stored in SQLite (`app_settings`); leave the field empty to clear the override.
2. **Environment** — set **`MDDB_SLACK_WEBHOOK_URL`** (alias: **`MDDB_SLACK_WEBHOOK`**) on the server process.

**Precedence:** a URL saved in the database from the UI overrides the environment variable. If nothing is stored in the DB, the env URL is used.

When **`auth_required` is false** (no users in the DB and no `MDDB_AUTH_TOKENS` / `MDDB_TOKEN`), the **Settings** button is still shown so you can set the Slack URL without signing in. **GET/PUT `/api/settings`** does not require admin in that mode — only expose mddb on networks you trust, or add users so auth is enforced.

Whenever a **new** document ID is created (first write to that ID), mddb POSTs a short message with doc id, title, and `updated_by` if present. Updates to existing docs do not notify.

Applies to writes that go through **`dbSet`** (HTTP API, Node MCP in local mode, `mddb` server). Raw SQLite writes from other tools bypass this hook.

## Users and admins

### CLI

Create accounts with `mddb users create <name>`, rotate tokens with `mddb users rotate`, remove with `mddb users delete`. Use `mddb users promote <name>` to grant admin in the database.

### Web UI (admins)

In the browser viewer, **Settings** and **Users** are in the **top bar** (after **MCP**; you must be signed in with a token). They only open for **admins**; if you are not an admin yet, hover either button for the server command to promote yourself (`mddb users promote <name>` or `MDDB_ADMIN_NAMES`).

From **Settings**, admins can set the **Slack incoming webhook** URL (see [Slack notifications](#slack-notifications-optional)).

From **Users**, admins can list users and **create** new users (optional **Admin** checkbox). The new user’s token is shown once — copy it for `MDDB_TOKEN`.

**Who counts as an admin**

- Users with `is_admin` set in the database (`mddb users promote` does this), or
- Names listed in **`MDDB_ADMIN_NAMES`** (comma-separated, case-insensitive). On startup, matching users get `is_admin` synced in SQLite so you can bootstrap an admin without extra migrations.

### API (admins)

With a valid bearer token for an admin user:

- `GET /api/users` — list users (no secrets)
- `POST /api/users` — body `{ "name": "...", "is_admin": false }` — create user; response includes the new token once
- `GET /api/settings` — `{ "slack_webhook_url": string | null }` — value stored in DB (not the env fallback); `null` if unset in DB
- `PUT /api/settings` — body `{ "slack_webhook_url": "https://..." }` — save webhook; empty string clears the DB value so the env var applies again

## What it does

- **SQLite** — markdown per document, last **10** versions kept in history
- **WebSocket** — live updates across browsers and sessions
- **Viewer** — EasyMDE editor, wiki links `[[doc-id]]`, slash commands
- **HTTP API** — REST for scripts and integrations
- **MCP** — `mddb_search`, `mddb_get`, `mddb_set`, `mddb_append`, `mddb_upsert_section`, `mddb_delete`, `mddb_read_lines`, `mddb_history`, etc., for Claude Code and other MCP clients
- **Optional auth** — after the first user exists, API and WebSocket require `Authorization: Bearer <token>`

## HTTP API

```
GET    /api/auth/status
GET    /api/auth/validate
GET    /api/me
GET    /api/users              (admin)
POST   /api/users              (admin)   { "name", "is_admin"? }
GET    /api/settings           (admin)
PUT    /api/settings           (admin)   { "slack_webhook_url" }
GET    /api/docs
GET    /api/docs/:id
POST   /api/docs/:id
POST   /api/docs/:id/append
DELETE /api/docs/:id
GET    /api/docs/:id/history
GET    /api/docs/:id/lines?start=1&end=50
GET    /api/search?q=keywords&limit=20   full-text search (FTS5)
WS     /ws
```

## MCP (Claude Code)

Point **`MDDB_HOST`** at wherever the server is reachable (including `http://localhost:7778` for local dev):

```bash
claude mcp add --scope user mddb -e MDDB_HOST=https://your-mddb-host.example.com -- npx -y @mylesiyabor/mddb mcp
```

If the server requires a user token:

```bash
claude mcp add --scope user mddb \
  -e MDDB_HOST=https://your-mddb-host.example.com \
  -e MDDB_TOKEN=<token> \
  -- npx -y @mylesiyabor/mddb mcp
```

Check registration:

```bash
claude mcp get mddb
```

Restart the IDE or start a new session so tools appear.

Example tool usage in an agent session:

```
mddb_list
mddb_get(id: "roadmap")
mddb_set(id: "sprint", content: "# Sprint\n", by: "agent")
```

Bounded reads use **`MDDB_DEFAULT_READ_LINES`** (default 200) unless you pass line ranges or use `mddb_download`.

## Environment variables

| Variable | Meaning |
|----------|---------|
| `MDDB_PATH` | SQLite file path |
| `MDDB_PORT` | Listen port (default `7778`) |
| `MDDB_BASE_PATH` | Optional URL prefix if served behind a reverse proxy subpath (e.g. `/mddb`) |
| `MDDB_TOKEN` / `MDDB_AUTH_TOKENS` | Bootstrap bearer token(s); `MDDB_AUTH_TOKENS` can be `label:token` pairs |
| `MDDB_ADMIN_NAMES` | Comma-separated names treated as admins (merged with DB `is_admin`) |
| `MDDB_DEFAULT_READ_LINES` | Default line window for MCP reads |
| `MDDB_SEARCH_LIMIT` | Max hits per search request (default `50`) |
| `MDDB_SLACK_WEBHOOK_URL` | [Slack Incoming Webhook](https://api.slack.com/messaging/webhooks) URL — posts when a **new** document is created (not on edits). Alias: `MDDB_SLACK_WEBHOOK` |
| `MDDB_HOST` | Used by MCP client (`mcp.js`) to reach the HTTP API |

## Slash commands

Type `/` in the editor:

| Command | Result |
|--------|--------|
| `/h1` `/h2` `/h3` | Headings |
| `/todo` | Task checkbox |
| `/bullet` `/num` | Lists |
| `/code` | Code block |
| `/table` | Table |
| `/quote` `/bold` `/italic` `/link` | Formatting |

## Self-hosting (short)

- Run behind **HTTPS** in production; terminate TLS at your load balancer or reverse proxy.
- Health checks can use **`GET /api/auth/status`** (returns JSON; works without auth when no users exist yet).
- If you expose the app only under a subpath, set **`MDDB_BASE_PATH`** to that path (e.g. `/mddb`) so routes and the viewer match.
- Ensure **`MDDB_HOST`** in MCP config matches the public URL agents use (scheme + host + optional base path).

## Tech

- Node.js HTTP + `ws`
- MCP stdio server: `mcp.js` (`@modelcontextprotocol/sdk`)
- `better-sqlite3`
- EasyMDE / CodeMirror in the bundled viewer