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.
- SPECIFICATIONS.md — full product and architecture specification
- API reference — HTTP, WebDAV, MCP, and anonymous-read behavior
- Configuration — environment and manifest operations
- DEVELOPMENT.md — developer commands and test conventions
- Open Knowledge Format — concept metadata, search filters, and example bundle
- RELEASING.md — release runbook and Trusted Publishing setup
- LICENSE — Mozilla Public License 2.0
| 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.
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.
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.
# 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/searchThe 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.
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 -dPass 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.
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-serverObjects 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.
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-serverUse the upload, read, and search commands from the Compose flow against this server.
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:latestThe 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 NotedThatWith 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.mdWith the server running, configure your MCP client to launch notedthat-mcp-stdio as a subprocess.
NotedThat also exposes an HTTP MCP endpoint for remote clients that support the MCP HTTP transport:
-
Endpoint:
POST /mcpon 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.
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 | shcargo install (requires a Rust toolchain):
cargo install notedthatOnce 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.xzEdit ~/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.
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"
}
}
}
}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
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.
Mozilla Public License 2.0. See LICENSE for the full text.
