Doc Manager
The Doc Manager server is a zero-knowledge document vault with an MCP interface for managing plaintext and encrypted documents (e.g., via Claude). Encrypted documents are never decrypted server-side.
Document Operations:
List all documents — ID, title, encryption status, attachment info, and content size
Get a plaintext document by ID (refuses encrypted ones)
Search case-insensitively across titles and plaintext content, returning snippets
Create a new plaintext document (returns new UUID)
Update content and optionally title of a plaintext document
Delete a document and all its blobs irreversibly (works on encrypted too)
Attachment Operations:
Get attachment from a plaintext document — returns filename, size, base64, and UTF-8 text if applicable
Add/replace attachment (UTF-8 text or base64 binary) on a plaintext document
Delete attachment from any document, leaving the document intact
Access & Configuration:
OAuth 2.1 + PKCE (GitHub) authentication, restricting access to specified users
Read-only mode (
MCP_READ_ONLY) disables create/update toolsAccessible via Streamable-HTTP or stdio
Doc Manager
A zero-knowledge document vault backed by Azure Blob Storage, with a built-in MCP server so you can add, read and search your docs straight from a Claude conversation.
Zero-knowledge by design — documents and attachments are encrypted in the browser with AES-256-GCM (PBKDF2-SHA256, 600k iterations); the password never leaves the client.
Talk to your docs — Claude (web, desktop, Claude Code) can list, fetch, search, create and update plaintext documents through the MCP endpoint.
One Rust binary — Rocket backend + React 19 / Vite 8 frontend, deployable to Azure Container Apps, plain Docker, or
containerd+ Kaniko in a homelab.
Talking to your docs through Claude
The same Rust process exposes a Streamable-HTTP [Model Context Protocol]
(https://modelcontextprotocol.io) server at /mcp, gated by an OAuth 2.1
flow against an upstream identity provider (currently GitHub —
Auth0 reserved for a future round, the code is provider-shaped).
Encrypted documents stay private (their content is never returned to
Claude); plaintext documents become first-class citizens in the chat.
Tools
Tool | What it does | Available in |
| List every doc with | yes |
| Fetch one plaintext document by id; refuses encrypted ones | yes |
| Case-insensitive substring search over titles (always) and plaintext content | yes |
| Fetch the file attached to a doc (filename, bytes, base64, UTF-8 if applicable) | yes |
| Create a new plaintext document; returns the new id | no |
| Replace content (and optionally title) of a plaintext document | no |
| Attach a file (text or base64-encoded binary) to a doc; replaces any existing | no |
| Delete a document and all its blobs (main, title, attachment); irreversible | no |
| Delete only the attachment of a document, leaving the document itself intact | no |
Encrypted documents are surfaced to Claude with encrypted: true so it can
discover them by title, but their contents and writes are refused server-side.
That keeps the zero-knowledge guarantee intact: encrypted = "private to me",
plaintext = "shareable with Claude".
Add it to Claude
Claude.ai's custom-connector advanced settings accept an OAuth Client ID + Client Secret (only those two fields), so Claude.ai runs the full OAuth 2.1 + PKCE flow itself and our server only needs to (a) advertise the IdP via well-known metadata and (b) validate the resulting bearer.
Register a GitHub OAuth App. GitHub → Settings → Developer settings → OAuth Apps → New OAuth App. Set:
Authorization callback URL:
https://claude.ai/api/mcp/auth_callbackCopy the Client ID and generate a Client Secret.
Configure the server (env vars on the container app):
export OAUTH_PROVIDER=github export OAUTH_PUBLIC_BASE_URL=https://doc-manager.giuliohome.com export OAUTH_ALLOWED_USERS=giuliohome # comma-separated GitHub logins # optional — disables create/update tools: export MCP_READ_ONLY=trueOAUTH_ALLOWED_USERSis required — an empty allowlist refuses to start, so a misconfiguration cannot silently let any GitHub user in.Add the connector in Claude.ai — Settings → Connectors → Add custom connector, URL
https://doc-manager.giuliohome.com/mcp, then Advanced settings → paste the GitHub OAuth App's Client ID and Client Secret. The first request triggers the OAuth dance: Claude.ai sees the401+WWW-Authenticatefrom/mcp, fetches our well-known metadata, walks you throughgithub.com/login/oauth/authorize, and forwards the resulting bearer on every subsequent call. The server validates each request by callinghttps://api.github.com/user(5-min cache, SHA-256-hashed token key).
If OAUTH_PROVIDER is unset the endpoint replies 503 Service Unavailable,
so the MCP surface is fully opt-in.
Related MCP server: @bitatlas/mcp-server
Building the frontend
cd frontend
npm i
npm run build
cd ..Running with Docker
docker build -t giuliohome/doc-manager:latest .
export AZURE_STORAGE_ACCOUNT=youraccount
export AZURE_STORAGE_ACCESS_KEY=yourkey
export RUST_ROCKET_EXACT_ORIGIN=http://localhost:8080
# optional — enables the MCP endpoint:
export OAUTH_PROVIDER=github
export OAUTH_PUBLIC_BASE_URL=http://localhost:8080
export OAUTH_ALLOWED_USERS=your-github-login
docker run -p 8080:8080 \
-e AZURE_STORAGE_ACCOUNT \
-e AZURE_STORAGE_ACCESS_KEY \
-e RUST_ROCKET_EXACT_ORIGIN \
-e OAUTH_PROVIDER \
-e OAUTH_PUBLIC_BASE_URL \
-e OAUTH_ALLOWED_USERS \
giuliohome/doc-manager:latestTL;DR — containerd + Kaniko (homelab)
sudo mkdir /kcache
sudo ctr i pull gcr.io/kaniko-project/warmer:latest
sudo ctr run --net-host --rm --mount type=bind,src=$(pwd),dst=/workspace,options=rbind:rw --mount type=bind,src=/kcache,dst=/cache,options=rbind:rw gcr.io/kaniko-project/warmer:latest kaniko-warmer /kaniko/warmer --cache-dir=/cache --image=docker.io/rust:1-slim-bookworm --skip-tls-verify-registry index.docker.io --dockerfile=/workspace/Dockerfile
sudo ctr i pull gcr.io/kaniko-project/executor:latest
sudo ctr run --net-host --rm --mount type=bind,src=$(pwd),dst=/workspace,options=rbind:rw --mount type=bind,src=/kcache,dst=/cache,options=rbind:rw gcr.io/kaniko-project/executor:latest kaniko-executor /kaniko/executor -cache-dir=/cache --dockerfile=/workspace/Dockerfile --context=/workspace --no-push --skip-tls-verify --build-arg pkg=docs-app --tarPath=/workspace/doc-manager-latest.tar --destination=giuliohome/doc-manager:latest --cache=true --cache-repo=giuliohome/doc-manager:latest --no-push-cache
sudo ctr image import doc-manager-latest.tar
sudo ctr c create --net-host docker.io/giuliohome/doc-manager:latest doc-manager
sudo ctr t start doc-managerConfiguration reference
Env var | Required | Purpose |
| yes | Azure Storage account name (container |
| yes | Storage access key |
| yes | CORS origin for the React frontend |
| no (enables MCP) |
|
| with | Public base URL of this server, e.g. |
| with | Comma-separated GitHub logins; must list at least one user — empty refuses to start |
| no (default false) | When |
| no (auto) | Opens |
| no (default false) | When |
End-to-end tests
See the e2e repo.
Available Tools
9 toolsadd_attachmentA
Attach a file to an existing plaintext document. Replaces any pre-existing attachment for that document (one attachment per doc by design). Refuses if the document is encrypted client-side. Provide either 'content' (UTF-8 text, e.g. markdown) or 'content_base64' (binary, base64-encoded).
Inputs: filename (required), id (required), content, content_base64.
create_documentA
Create a new plaintext document. Returns the new id. Cannot create encrypted documents.
Inputs: content (required), title (required).
delete_attachmentA
Delete the file attached to a document, leaving the document itself intact. No-op (with status: 'no_attachment') if the document has no attachment. Works on encrypted attachments too.
Inputs: id (required).
delete_documentA
Delete a document and all of its associated blobs (main content, title, and any attachment). Works on encrypted documents too — deletion is destructive but does not disclose contents. Irreversible.
Inputs: id (required).
get_attachmentA
Fetch the file attached to a plaintext document. Returns filename, byte size, base64-encoded content, and a UTF-8 text rendition when the bytes are valid UTF-8 (e.g. .md, .txt, .json). Refuses if the attachment is encrypted client-side (named '_dmencblob').
Inputs: id (required).
get_documentA
Fetch a single plaintext document by id. Refuses encrypted documents (their content is unreadable server-side).
Inputs: id (required).
list_documentsA
List every document in the vault with its id, title, encryption status, attachment status, and content size.
Inputs: no named parameters.
search_documentsA
Case-insensitive substring search across titles (always) and plaintext content. Returns id, title, encrypted flag, what matched, and a short snippet.
Inputs: query (required).
update_documentA
Replace the content (and optionally the title) of an existing plaintext document. Refuses if the document is encrypted.
Inputs: content (required), id (required), title.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool has a distinct purpose (create, read, update, delete for documents and attachments, plus search). No two tools overlap in functionality, and descriptions clarify edge cases (e.g., add_attachment replaces existing attachment).
All tools follow a consistent verb_noun pattern (e.g., create_document, delete_attachment, search_documents). No mixing of conventions like camelCase or inconsistent verb forms.
With 9 tools, the server covers all core operations for document and attachment management. The count is well-scoped—neither too few (would miss functionality) nor too many (would overwhelm).
The toolset provides full CRUD for plaintext documents and attachments. However, encrypted documents are only deletable; they cannot be read or updated via the server. This is a deliberate design but limits utility if encrypted documents are present.
Maintenance
Related MCP Connectors
Persistent memory for AI assistants — one shared, OAuth-secured vault for every MCP client.
Notes and memory assistant: one Markdown vault that Claude, ChatGPT and any MCP client can share.
Private persistent memory for Claude, ChatGPT & Gemini via MCP - semantic search, zero-code setup.
Sovereign E2E cloud storage for AI agents. Zero-knowledge, RGPD-compliant.
Related MCP Servers
- AlicenseAqualityDmaintenanceSecrets vault for Claude Code. Encrypt API keys, tokens and passwords with AES-256. Full audit logs, MCP access rules, and zero-knowledge mode. Secrets never appear in chat.1757 npm1MIT
- AlicenseAqualityBmaintenanceZero-Knowledge Cloud Drive for Humans and Agents. Client-side AES-256-GCM encryption with 7 MCP tools for secure file vault management — the server never sees plaintext data.7247 npm2MIT
- AlicenseNot gradedqualityBmaintenanceLocal-first markdown vault with a built-in MCP server (streamable HTTP). 16 tools and 2 resources for Claude Code / Desktop / Cursor: read/write/search plus context_for_query, find_orphans, weekly_digest, compare_notes, semantic_outline. Per-folder agent permissions, LanceDB vectors, local Xenova ONNX embedder swappable to Ollama. Single Bun binary. AGPL.36AGPL 3.0

LoreDocsofficial
AlicenseAqualityBmaintenanceKnowledge vaults for AI projects: specs, guides and architecture decisions organized per project, full-text searchable and version-tracked with restore. Import from Obsidian and Notion, or ingest files directly. Local SQLite storage and 45+ MCP tools for Claude Code, Cowork, Codex, Cursor and Hermes Agent. Free: 3 vaults. Pro ($9/mo): semantic search and auto-discovered document relationships.49204 PyPI4Business Source 1.1