notedthat-api-http

HTTP API surface for NotedThat


Keywords
hybrid-search, knowledge-base, markdown, mcp, qdrant, rust, s3, semantic-search, webdav
License
MPL-2.0

Documentation

NotedThat

A markdown-first knowledgebase system exposed as an HTTP API, MCP server, and WebDAV endpoint. NotedThat stores notes as plain Markdown files, indexes them for semantic search via Qdrant, and surfaces them through multiple access protocols so editors, AI agents, and WebDAV clients can all work with the same content.

Status: pre-v1 — under active development. APIs and crate interfaces are unstable.

Documentation

Crate Map

Crate Path Role
notedthat-core crates/notedthat-core Shared domain types, path/range/error/auth primitives, config
notedthat-storage-s3 crates/notedthat-storage-s3 S3 storage adapter
notedthat-storage-fs crates/notedthat-storage-fs Local filesystem storage adapter
notedthat-indexer crates/notedthat-indexer Chunking, embedder client, Qdrant integration
notedthat-write crates/notedthat-write Shared write path (commit(), commit_delete(), MIME sniff, 5 GiB limit) — used by HTTP API + WebDAV surfaces
notedthat-api-http crates/notedthat-api-http HTTP API surface
notedthat-webdav crates/notedthat-webdav WebDAV surface
notedthat-mcp crates/notedthat-mcp MCP tool schemas and HTTP-backed implementation
notedthat-server crates/notedthat-server Server library — HTTP API + WebDAV + remote MCP in one process. Published to ghcr.io/notedthat/server per tagged release.
notedthat-mcp-stdio crates/notedthat-mcp-stdio MCP-over-stdio transport adapter
notedthat crates/notedthat Distribution crate and release facade — owns the published notedthat-server and notedthat-mcp-stdio binaries, the workspace git tag, and the root CHANGELOG. cargo install notedthat installs both.

All 11 crates share a single version via ecosystem-level Semantic Versioning. See RELEASING.md for the versioning policy.

Anonymous public reads

Each knowledge base has its own S3 bucket, and that bucket is the policy boundary. Its .notedthat/manifest.json may add a public_read array with independent discover, browse, content, and search capabilities. Missing or empty means private. This does not create an anonymous write mode: every HTTP and WebDAV mutation remains authenticated, valid credentials retain full access, and supplied invalid credentials receive 401 rather than anonymous access.

The server reads policies once during startup; restart it after changing a manifest. Public-read policy does not apply to MCP authentication, does not support namespace or path-prefix grants, and does not add an application rate limiter. Configure reverse-proxy rate and burst limits before exposing anonymous search. See Configuration for the manifest and operational procedure.

Running locally

NotedThat needs somewhere to keep objects — a local directory or any S3-compatible object store — plus Qdrant and an OpenAI-compatible embedding provider. It does not bundle an embedding model or provider credentials. Use one of the supported flows below and keep provider credentials in ignored local environment files; never commit them.

The default Compose stack stores objects on disk, so it runs no object store at all. Add the S3 overlay when you want the production-shaped setup — that is what CI's integration suite exercises, and what the reference deployment runs.

Compose: objects on disk, search alongside

# Create an ignored local configuration, then replace all four embedding values.
cp .env.example .env
$EDITOR .env
docker compose up --build -d

# Objects live in the `notedthat-data` volume; nothing else is needed to store them.

# Verify HTTP, upload, read, and semantic search.
TOKEN=dev-token-please-change
curl --fail http://127.0.0.1:8080/healthz
printf '# Hello NotedThat\nsemantic search works\n' | curl --fail -X PUT \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: text/markdown' \
  --data-binary @- http://127.0.0.1:8080/api/v1/knowledgebases/notes/hello.md
curl --fail -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8080/api/v1/knowledgebases/notes/hello.md
curl --fail -X POST -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"query":"semantic search"}' \
  http://127.0.0.1:8080/api/v1/knowledgebases/notes/search

The search result is asynchronous: retry the final request until the uploaded document appears. Stop the local stack with docker compose down; add -v only when you intentionally want to delete its object and Qdrant data.

docker-compose.yml fixes its internal listener and Qdrant address, stores objects under /var/lib/notedthat in the notedthat-data volume, and forwards documented size, Qdrant-timeout, and embedding-tuning settings from .env. The bundled Qdrant service is deliberately unauthenticated, so it does not accept a Qdrant API key. A custom NOTEDTHAT_UPLOAD_TMP_DIR also needs an explicit writable mount at that exact path in a custom Compose deployment.

Compose with an S3-compatible store

Adds SeaweedFS and points the server at it, which is the shape of the reference deployment and what CI's integration suite exercises:

docker compose -f docker-compose.yml -f docker-compose.s3.yml up --build -d

Pass both files to every subsequent docker compose command in that stack, including down. An overlay rather than a Compose profile because the two backends need different environment variables on the same service, and setting both at once is a startup error — see Configuration.

Native server: objects on disk, no object store

The smallest way to run NotedThat from a checkout. It needs Qdrant and an embedding provider, and nothing else:

docker compose up -d qdrant
set -a; . ./.env; set +a
export NOTEDTHAT_LISTEN_ADDR=127.0.0.1:8080
export NOTEDTHAT_STORAGE_BACKEND=fs
export NOTEDTHAT_FS_ROOT="$PWD/.notedthat-data"
mkdir -p "$NOTEDTHAT_FS_ROOT"
export NOTEDTHAT_QDRANT_URL=http://127.0.0.1:6334
cargo run -p notedthat-server

Objects are files under $NOTEDTHAT_FS_ROOT, at their key paths — ls -R it, open it in an editor, back it up with any file-level tool. Edits made that way are picked up: the tree is watched, so a note changed outside NotedThat is re-indexed and searchable shortly afterwards (configuration). .env sets NOTEDTHAT_S3_*, which the fs backend refuses to start alongside, so unset those three or comment them out first.

Native server: Compose-managed dependencies

Start SeaweedFS and Qdrant, then export host-facing settings before running the server natively. Reuse your local embedding values from .env without committing it.

docker compose -f docker-compose.yml -f docker-compose.s3.yml up -d seaweedfs qdrant
set -a; . ./.env; set +a
export NOTEDTHAT_LISTEN_ADDR=127.0.0.1:8080
export NOTEDTHAT_S3_ENDPOINT_URL=http://127.0.0.1:8333
export NOTEDTHAT_S3_FORCE_PATH_STYLE=true
export NOTEDTHAT_QDRANT_URL=http://127.0.0.1:6334
cargo run --bin notedthat-server

Use the upload, read, and search commands from the Compose flow against this server.

Docker image

Once the first tagged release exists, the server image is published to GHCR at ghcr.io/notedthat/server. Every published image is cosign-signed (keyless via Sigstore/Fulcio) and carries a SLSA L2 build provenance attestation.

docker pull ghcr.io/notedthat/server:latest

# Start the storage/index dependencies first. On Linux, use --add-host below.
docker compose up -d seaweedfs qdrant
set -a; . ./.env; set +a
docker run --rm --stop-timeout 45 --add-host=host.docker.internal:host-gateway \
  -p 8080:8080 \
  -e NOTEDTHAT_API_TOKEN -e NOTEDTHAT_KBS \
  -e NOTEDTHAT_S3_REGION -e NOTEDTHAT_S3_ACCESS_KEY_ID -e NOTEDTHAT_S3_SECRET_ACCESS_KEY \
  -e NOTEDTHAT_WEBDAV_USERNAME -e NOTEDTHAT_WEBDAV_PASSWORD \
  -e EMBEDDING_ENDPOINT_URL -e EMBEDDING_MODEL -e EMBEDDING_API_KEY -e EMBEDDING_DIMENSIONS \
  -e NOTEDTHAT_S3_ENDPOINT_URL=http://host.docker.internal:8333 \
  -e NOTEDTHAT_S3_FORCE_PATH_STYLE=true \
  -e NOTEDTHAT_QDRANT_URL=http://host.docker.internal:6334 \
  ghcr.io/notedthat/server:latest

The image publishes one HTTP port, 8080: the API is under /api/v1, WebDAV is under /webdav, and streamable MCP is at POST /mcp. Public deployments should terminate TLS once at a reverse proxy and forward the complete path space to this upstream.

The default temporary directory stages uploads and index snapshots. For production-sized uploads, ensure its backing filesystem has at least 5 GiB for every concurrent maximum-size upload, plus index snapshots and backend/build working space; configure NOTEDTHAT_UPLOAD_TMP_DIR when a specific writable location is needed. Do not use tmpfs, which consumes RAM. See configuration for cleanup behavior and sizing details.

Verify the signature + provenance before running in production:

gh attestation verify oci://ghcr.io/notedthat/server:0.2.0 --owner NotedThat

WebDAV

With the server running, configure a WebDAV client with the credentials from your local .env:

export NOTEDTHAT_WEBDAV_USERNAME=webdav-user-please-change
export NOTEDTHAT_WEBDAV_PASSWORD=webdav-pass-please-change
# 7. PROPFIND root (list KBs)
curl -X PROPFIND -u "$NOTEDTHAT_WEBDAV_USERNAME:$NOTEDTHAT_WEBDAV_PASSWORD" \
  -H 'Depth: 1' http://127.0.0.1:8080/webdav/

# 8. PUT a markdown file via WebDAV
echo "# Hello WebDAV" | curl -X PUT \
  -u "$NOTEDTHAT_WEBDAV_USERNAME:$NOTEDTHAT_WEBDAV_PASSWORD" \
  --data-binary @- \
  http://127.0.0.1:8080/webdav/notes/hello-webdav.md

# 9. GET it back
curl -u "$NOTEDTHAT_WEBDAV_USERNAME:$NOTEDTHAT_WEBDAV_PASSWORD" \
  http://127.0.0.1:8080/webdav/notes/hello-webdav.md

# 10. DELETE it
curl -X DELETE -u "$NOTEDTHAT_WEBDAV_USERNAME:$NOTEDTHAT_WEBDAV_PASSWORD" \
  http://127.0.0.1:8080/webdav/notes/hello-webdav.md

MCP (Claude Desktop, Cursor, Zed)

With the server running, configure your MCP client to launch notedthat-mcp-stdio as a subprocess.

Remote MCP hosting

NotedThat also exposes an HTTP MCP endpoint for remote clients that support the MCP HTTP transport:

  • Endpoint: POST /mcp on the same listener as the API and WebDAV
  • Auth: Authorization: Bearer <NOTEDTHAT_API_TOKEN> (same token as the HTTP API)
  • Note: public deployments require HTTPS termination at a reverse proxy before exposing this listener

See docs/API.md for the full MCP transport and Resources protocol docs.

Install options

Three ways to get notedthat-server and notedthat-mcp-stdio onto your PATH — all equivalent, pick whichever fits your setup. Both binaries ship from the single notedthat crate, so every route below installs the pair. Installer scripts become available after the first tagged release.

Build or extract locally:

make mcp-stdio                         # build from this checkout
make mcp-stdio-from-image              # extract from notedthat-server:local
# Override PREFIX=/some/dir or IMAGE=ghcr.io/notedthat/server:tag as needed.

Shell installer (macOS / Linux). Prebuilt binaries are not published for Windows at the moment — on Windows use cargo install notedthat below:

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/NotedThat/NotedThat/releases/latest/download/notedthat-installer.sh | sh

cargo install (requires a Rust toolchain):

cargo install notedthat

Once installed, wire it into your MCP client:

{
  "mcpServers": {
    "notedthat": {
      "command": "notedthat-mcp-stdio",
      "env": {
        "NOTEDTHAT_URL": "http://localhost:8080",
        "NOTEDTHAT_TOKEN": "your-token-here"
      }
    }
  }
}

Every prebuilt binary is cosign-signed with a SLSA L2 build provenance attestation — verify before running:

cosign verify-blob \
  --bundle notedthat-x86_64-unknown-linux-gnu.tar.xz.bundle \
  --certificate-identity-regexp 'https://github.com/NotedThat/NotedThat/.+' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  notedthat-x86_64-unknown-linux-gnu.tar.xz

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "notedthat": {
      "command": "notedthat-mcp-stdio",
      "env": {
        "NOTEDTHAT_URL": "http://localhost:8080",
        "NOTEDTHAT_TOKEN": "your-token-here"
      }
    }
  }
}

Restart Claude Desktop after saving.

Cursor

Add to Cursor's settings (.cursor/mcp.json or via Settings → MCP):

{
  "mcpServers": {
    "notedthat": {
      "command": "notedthat-mcp-stdio",
      "env": {
        "NOTEDTHAT_URL": "http://localhost:8080",
        "NOTEDTHAT_TOKEN": "your-token-here"
      }
    }
  }
}

Zed

Add to Zed settings (~/.config/zed/settings.json):

{
  "assistant": {
    "mcp_servers": {
      "notedthat": {
        "command": {
          "path": "notedthat-mcp-stdio",
          "env": {
            "NOTEDTHAT_URL": "http://localhost:8080",
            "NOTEDTHAT_TOKEN": "your-token-here"
          }
        }
      }
    }
  }
}

Full configuration reference — every setting as an environment variable or as the flag that overrides it: docs/CONFIGURATION.md

Full API documentation: docs/API.md

Contributing

See CONTRIBUTING.md for the contribution process — PR workflow, commit conventions (Conventional Commits + signed + DCO), testing requirements, and AI-assistance disclosure. Build, test, and run commands live in DEVELOPMENT.md. The project is pre-v1, so interfaces change frequently — check open issues before starting significant work.

License

Mozilla Public License 2.0. See LICENSE for the full text.