AgentStack
MCP verified MIT Self-run

Vellum

mcp-freema-vellum · by freema

Lightweight self-hosted MCP server over a folder of markdown files. One Go binary, embedded React SPA, no DB.

No reviews yet
0 installs
9 views
0.0% view→install

Install

$ agentstack add mcp-freema-vellum

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Vellum? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

vellum

> a calm window into a folder of markdown

[](https://github.com/freema/vellum/releases) [](https://github.com/freema/vellum/actions/workflows/ci.yml) [](go.mod) [](LICENSE) [](https://github.com/freema/vellum/pkgs/container/vellum)

A lightweight, self-hosted MCP server over a folder of markdown files. One static Go binary with an embedded web UI, one ~24 MB Docker image, ~5 MB idle RAM. docker compose up -d and you're done.

Deliberately light — the smallest stack that just works, for one person or a small team:

  • Your data is flat markdown. Portable, readable in any editor, git or

Obsidian. No lock-in, no database, no embeddings.

  • vellum never calls an LLM itself. The optional curator tools prepare

context; the agent on your side decides.

  • The vault layer is dumb and deterministic. Everything is a file;

config is environment variables.

  • Structure is a feature. inbox/, projects/, archive/ — not a

pile of files. Unfiled notes land in the inbox.

  • Small MCP tool surface (15 tools) — less agent context burned on

tool definitions.

Every note is also a first-class MCP resourcevellum://note/{path} — so your agent can attach a note as context straight from its resource picker, no tool call needed:

Quick start

Pre-built multi-arch images (amd64 + arm64) are published to GitHub Container Registry on every release:

docker pull ghcr.io/freema/vellum:latest

Try it in one command

mkdir -p vault
docker run --rm -p 8080:8080 -v "$PWD/vault:/vault" ghcr.io/freema/vellum

Open http://localhost:8080 — that's your vault, served straight from ./vault. Auth is off by default, which is fine on localhost; enable it before exposing anything (see below).

> Linux bind mounts: the container runs as a non-root user > (uid 65532, distroless), so the mounted directory must be writable by > it: sudo chown 65532 vault. Docker Desktop on macOS/Windows handles > this for you.

Docker Compose (recommended)

Create a docker-compose.yml — this is [the same file](docker-compose.yml) the repo ships (curl -fsSLO https://raw.githubusercontent.com/freema/vellum/main/docker-compose.yml grabs it if you'd rather not paste):

services:
  vellum:
    image: ${VELLUM_IMAGE:-ghcr.io/freema/vellum:latest}
    container_name: vellum
    restart: unless-stopped
    ports:
      - "${VELLUM_PORT:-8080}:8080"
    env_file:
      - path: .env
        required: false
    environment:
      PORT: "8080"
      VELLUM_VAULT_PATH: /vault
    volumes:
      - ./vault:/vault
    # hardening: the binary only ever writes into the mounted vault
    read_only: true
    security_opt:
      - no-new-privileges
    deploy:
      resources:
        limits:
          memory: 256M

Generate a secret and start:

cat > .env `, `TRUST_PROXY=1` and terminate TLS
in the proxy. Details and a smoke-test checklist: [docs/deployment.md](docs/deployment.md).

### One-click PaaS (Railway / Render / Fly.io)

The easiest path if you don't want to run a VPS: deploy the published image
on a platform that gives you TLS and a public URL for free. vellum honours
the `PORT` the platform injects, so the only three things that matter are the
**same everywhere**:

1. **Image**: `ghcr.io/freema/vellum:1.9.0` (pin a version, not `:latest`).
2. **A persistent volume mounted at `/vault`** — this is not optional. vellum
   stores every note as a file; without a persistent disk your vault is wiped
   on the next redeploy. (Serverless hosts like Vercel/Netlify/Cloudflare
   Pages have no persistent writable disk and can't run vellum — use one of
   the platforms below.)
3. **Env** (from the [configuration table](#configuration)):

   ```sh
   AUTH_ENABLED=true
   VELLUM_CLIENT_SECRET=
   VELLUM_ISSUER_URL=https://   # the URL the platform gives you
   TRUST_PROXY=1                                 # you're behind the platform's proxy
   ```

Set `VELLUM_ISSUER_URL` once you know the assigned URL, then redeploy — OAuth
metadata must advertise the real HTTPS host or Claude fails after the consent
step.

- **Railway**: New Project → Deploy from Docker image →
  `ghcr.io/freema/vellum:1.9.0`. Add a Volume mounted at `/vault`, set the env
  above (Railway provides `PORT` and a `*.up.railway.app` domain).
- **Render**: New → Web Service → *Deploy an existing image* →
  `ghcr.io/freema/vellum:1.9.0`. Add a Disk mounted at `/vault`, set the env.
- **Fly.io**: `fly launch --image ghcr.io/freema/vellum:1.9.0`, then
  `fly volume create vault` and mount it at `/vault` in `fly.toml`; set the env
  with `fly secrets set VELLUM_CLIENT_SECRET=… VELLUM_ISSUER_URL=… TRUST_PROXY=1 AUTH_ENABLED=true`.

Then connect Claude to `https:///mcp` (see
[Connect Claude](#connect-claude)).

**Coolify / Traefik**: point Coolify at the repo or image
`ghcr.io/freema/vellum` (pin a version, e.g. `:1.9.0`, not `:latest`),
mount a volume at `/vault`, set the env from the table above. Running raw
compose behind Traefik, uncomment the labels in `docker-compose.yml`:

```yaml
labels:
  - traefik.enable=true
  - traefik.http.routers.vellum.rule=Host(`vellum.example.com`)
  - traefik.http.services.vellum.loadbalancer.server.port=8080

Caddy is two lines:

vellum.example.com {
    reverse_proxy vellum:8080
}

Security posture (read-only container, threat model, what is never logged): [SECURITY.md](SECURITY.md), [docs/threat-model.md](docs/threat-model.md), [docs/logging.md](docs/logging.md).

Development

Requires Go 1.25+, Task, and Node 22 for the SPA.

task build        # Go binary without the SPA (no node needed)
task build-full   # SPA + Go binary with the UI embedded
task test         # go test ./...
task lint         # golangci-lint
task e2e          # compose stack with the fixture vault (docs/e2e.md)

Design decisions kept deliberately simple

  • Search = ranked RAM scan, not bleve. The metadata index narrows by

tag/dir, content is matched from an in-memory cache invalidated precisely by the index (modTime+size), and results are ranked (title > tag > path > body, word starts beat mid-word). Matching is case-, diacritics- and typo-insensitive — "preklapy" finds "překlepy". A query over a 2 000-note vault takes ~0.4 ms warm ([docs/search.md](docs/search.md)). Zero heavy dependencies, no index to corrupt, no warm-up. The Searcher interface exists so bleve can slot in if it ever hurts.

  • Plain CSS custom properties, not Tailwind. The design system is a

fixed token sheet (DESIGN.md); custom properties map to it 1:1 with no build-time dependency.

  • Opaque in-memory tokens, not JWTs. Tokens die with the process and

clients silently re-authorize; no signing keys to manage.

  • No database, ever (v1). Tags, backlinks and task states live in an

in-RAM index rebuilt in ~50 ms at startup.

Why vellum and not …?

  • Obsidian + plugins — vellum doesn't replace your editor; it happily

serves the same vault folder to your agent while you keep editing anywhere. No sync, no plugin sandbox, no Electron on the server.

  • Notion/Anytype-style apps — those own your data. vellum's "database"

is ls and grep-able markdown.

  • Heavier MCP note servers — vellum ships one 24 MB container, no

vector DB, no API keys, and burns minimal agent context (15 tools).

Author

Created by Tomáš Grasl (@freema).

Issues and PRs welcome — the [threat model](docs/threat-model.md) and [SECURITY.md](SECURITY.md) explain the boundaries contributions must keep.

License

[MIT](LICENSE)

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.