GitHub - jibs/duffel: Local-network markdown workspace with filesystem-backed notes, search, and MCP automation.

6 min read Original article ↗

Duffel is a local-network markdown workspace for people and LLM agents.

It stores notes as ordinary Markdown files, serves them through a small web UI, indexes them with qmd, and exposes an MCP server so coding agents can search, read, write, and append journal entries without custom glue scripts.

Screenshots

Notes with related content

Duffel note view with related recommended content

Journal history

Duffel journal view with populated worklog entries

MCP connector setup

Duffel MCP connector page with local client configuration

Who this is for

Use Duffel when you want:

  • A local notes workspace backed by files you can inspect and version normally.
  • A browser UI for reading, editing, archiving, and searching Markdown notes.
  • An MCP endpoint that lets LLM tools use the same notes as durable project memory.
  • A simple Go + TypeScript app that can run on a laptop, home server, or private network.

Ask an LLM to set it up

Paste this prompt into your coding assistant from a clean machine or server:

Set up Duffel from https://github.com/jibs/duffel.

Requirements:
- Use pnpm, not npm.
- Do not install a global qmd binary; Duffel uses the pinned @tobilu/qmd package from pnpm dependencies.
- Start with a local development run first.
- If this is only a private local demo, run with DUFFEL_AUTH_ENABLED=false.
- If this is a real shared/private-network deployment, keep auth enabled and set DUFFEL_SETUP_TOKEN before first setup.

Steps:
1. Clone the repo and enter it.
2. Run make setup.
3. Create a few sample Markdown files under ./data if the directory is empty.
4. Start the app with make dev for development, or follow ops/deploy/DEPLOY.md for systemd deployment.
5. Open http://localhost:4386.
6. For LLM integration, open http://localhost:4386/#/_mcp and configure the MCP client shown there.
7. Run make ci before proposing any code changes.

Quick start: local demo

Requirements:

  • Go 1.26+
  • Node.js and pnpm
  • make
  • curl
git clone https://github.com/jibs/duffel.git
cd duffel
make setup
mkdir -p data/projects data/research data/journal
cat > data/index.md <<'MD'
---
type: note
---

# Welcome to Duffel

This is a filesystem-backed Markdown workspace.

Try:

- Browse files in the sidebar
- Edit this note
- Search for `MCP`
- Open the MCP Connector page
MD
cat > data/projects/demo.md <<'MD'
---
type: note
---

# Demo project

Duffel keeps project notes in Markdown and exposes them to LLM agents through MCP.

## Tasks

- Capture setup decisions
- Store useful research
- Append journal updates after changes
MD
cat > data/research/mcp.md <<'MD'
---
type: research
---

# MCP notes

Use the MCP connector at `#/_mcp` to configure compatible agents.

Agents should search first, read only relevant files, and append concise journal entries for durable changes.
MD
cat > data/journal/worklog.md <<'MD'
---
type: journal
---

## 2026-01-01 09:00

Demo: created a sample Duffel workspace.
MD
DUFFEL_AUTH_ENABLED=false make dev

Open http://localhost:4386.

For a local demo, auth is disabled in the command above. For anything reachable by other people or machines, keep auth enabled and follow the authentication setup below.

Production/private-network setup

Duffel enables auth by default.

Before the first owner setup, set a one-time setup token:

export DUFFEL_SETUP_TOKEN="$(openssl rand -base64 32)"
make dev

Open http://localhost:4386/oauth/setup, create the owner account, store the returned bootstrap token in your secret manager if you need CLI/MCP bootstrap access, then remove DUFFEL_SETUP_TOKEN from the runtime environment.

For a systemd deployment, use ops/deploy/DEPLOY.md. That guide uses generic hostnames and paths; replace them with your own server values.

LLM and MCP setup

Duffel exposes a streamable HTTP MCP endpoint at /mcp.

In the browser UI:

  1. Open http://localhost:4386/#/_mcp.
  2. Create or paste a bearer token if auth is enabled.
  3. Copy the generated client config for your agent.

Generic MCP config shape:

{
  "mcpServers": {
    "duffel": {
      "type": "streamable-http",
      "url": "http://localhost:4386/mcp",
      "headers": {
        "Authorization": "Bearer ${DUFFEL_MCP_TOKEN}"
      }
    }
  }
}

If you started Duffel with DUFFEL_AUTH_ENABLED=false, omit the headers block.

Recommended agent behavior:

  • Search before reading broad folders.
  • Start with small result limits.
  • Read only the files needed for the task.
  • Use journal files for concise change history.
  • Keep research artifacts under a clear folder such as research/.
  • Do not commit secrets, local note data, .env, generated tokens, or runtime logs.

Configuration

Environment variables:

Variable Default Purpose
DUFFEL_HOST empty Bind address. Empty means all interfaces.
DUFFEL_PORT 4386 HTTP port.
DUFFEL_DATA_DIR ./data Markdown workspace root.
DUFFEL_FRONTEND_DIR ./src/frontend Static frontend directory.
DUFFEL_AUTH_ENABLED true Require OAuth/Bearer auth for API and MCP.
DUFFEL_SETUP_TOKEN empty Required for first owner setup when auth is enabled.

qmd is provided by the pinned npm dependency @tobilu/qmd@2.1.0; do not install or depend on a global qmd binary.

API overview

All data APIs are under /api. OAuth and MCP discovery routes are at the root. Responses are JSON except agent helper script/snippet/version routes and OAuth HTML forms.

Main routes:

  • GET|PUT|POST|DELETE /api/fs/* for filesystem-backed notes and directories.
  • POST /api/archive/* and POST /api/unarchive/* for archive moves.
  • POST /api/journal/* and POST /api/journal/*/append for journal files.
  • GET /api/search?q=<query> for workspace search.
  • GET|POST|DELETE /api/mcp/tokens for MCP bearer token management.
  • GET|POST /mcp for the MCP server.
  • GET|POST /oauth/setup, /oauth/authorize, /oauth/token, and /oauth/register for auth.

Duffel Lib

Duffel's low-level knowledge workspace capabilities are available as Go packages under pkg/duffellib.

Use duffellib.Open when another project needs the same safe disk-backed document behavior as Duffel:

  • root-confined path resolution with symlink escape checks
  • validated text writes, verified read-after-write, and allowlisted binary image writes
  • journal creation and locked journal append
  • archive/unarchive operations
  • first-class workspace search through Workspace.Search

Search is part of the library API. The concrete search backend is a Duffel implementation detail; callers should use duffellib.Workspace and duffellib/search.SearchOptions rather than shelling out to indexing tools.

Development

Common commands:

make setup          # install dependencies
make dev            # build frontend JS and run the Go server
make build          # build the backend binary
make test           # run unit, integration, and e2e tests
make lint           # run Go and TypeScript linters
make fmt            # format Go and TypeScript
make release-audit  # scan tracked files for privacy/secret risks
make ci             # full local check suite

Before publishing changes, run:

The CI target includes formatting checks, linting, Go vet, TypeScript checks, frontend build, backend tests, e2e tests, and the release audit.

Security model

  • Auth is enabled by default.
  • First owner setup requires DUFFEL_SETUP_TOKEN.
  • API data routes and /mcp require Bearer auth when auth is enabled.
  • OAuth discovery/setup routes and agent helper routes remain public for bootstrap.
  • Storage canonicalizes paths and keeps operations under the configured data root.
  • Same-origin CORS defaults and mutating cross-origin protections are enforced.

For public or internet-facing deployment, put Duffel behind HTTPS/TLS and normal network controls. Duffel is primarily designed for trusted local or private network use.

Repository hygiene

Tracked source should not include personal note data, generated tokens, .env, runtime logs, or local machine paths.

Run these before publishing:

make release-audit
trufflehog filesystem --no-update --only-verified .

More docs