Properties
category: reference tags: [meta, design, prd] last_updated: 2026-03-12 confidence: high
Original PRD Overview
This page is part of the original single-tenant PRD, split across five wiki pages: Design/Original PRD Overview | Design/Original PRD API | Design/Original PRD Semantic Search | Design/Original PRD MCP | Design/Original PRD Note Schema
Overview
Build a research knowledge base system that gives both a human (via web browser) and an AI assistant (via MCP over Claude.ai) full read/write access to a shared wiki. The wiki stores short, atomic, cross-linked markdown notes that serve as a living analytical framework for ongoing research.
The system has three components built on top of an existing Otterwiki fork:
- REST API plugin for Otterwiki — programmatic CRUD on wiki pages
- Chroma semantic search plugin for Otterwiki — vector index maintained in sync with page saves/deletes
- MCP server — exposes wiki operations as MCP tools over SSE, connecting to Claude.ai
All three deploy together via docker-compose alongside Otterwiki itself.
Context
What is Otterwiki?
Otterwiki is a minimalistic, self-hosted wiki powered by Python, Flask, Markdown, and Git. Pages are stored as markdown files in a bare Git repository. It has a plugin system that loads from /plugins and /app-data/plugins.
Why this system?
We currently maintain analytical research documents as PDFs in a Claude.ai project. This is clunky: PDFs are write-once, can't be cross-referenced programmatically, and are expensive to ingest. Decomposing the analysis into atomic wiki notes lets the AI pull only what's relevant per session, and both human and AI can maintain the knowledge base over time.
Key users
- Human researcher — browses, edits, and organizes notes via Otterwiki's web UI
- Claude AI assistant — reads, writes, searches, and cross-references notes via MCP tools in Claude.ai
Architecture
┌─────────────┐ ┌──────────────────────────────────────────────┐
│ Claude.ai │────▶│ MCP Server (SSE) │
│ (iOS/Web) │◀────│ Python / FastMCP │
└─────────────┘ │ Translates MCP tool calls → HTTP API calls │
└──────────────┬───────────────────────────────┘
│ HTTP
┌──────────────▼───────────────────────────────┐
│ Otterwiki (Flask) │
│ ┌─────────────────┐ ┌────────────────────┐ │
│ │ REST API Plugin │ │ Chroma Plugin │ │
│ │ /api/v1/* │ │ Vector index sync │ │
│ │ JSON CRUD │ │ Semantic search │ │
│ └────────┬────────┘ └────────┬───────────┘ │
│ │ │ │
│ ┌────────▼─────────────────────▼───────────┐ │
│ │ Otterwiki Core (Page, PageIndex, etc.) │ │
│ └────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────▼──────┐ ┌───────────────────┐ │
│ │ Git Repo │ │ Chroma DB │ │
│ │ (markdown) │ │ (vector index) │ │
│ └───────────────┘ └───────────────────┘ │
└──────────────────────────────────────────────┘
▲
┌──────────────┘
│ HTTP (browser)
┌─────┴─────┐
│ Human │
│ Browser │
└───────────┘
Deployment
Single docker-compose stack:
otterwiki— Otterwiki with plugins mountedchromadb— Chroma vector database (persistent volume)mcp-server— MCP SSE server
All on the same Docker network. The MCP server and Otterwiki communicate over internal HTTP. Only the MCP server's SSE endpoint and Otterwiki's web UI are exposed externally (behind a reverse proxy with TLS).
Docker Compose
services: otterwiki: image: redimp/otterwiki:2 # OR build from fork if modifying core: # build: ./otterwiki restart: unless-stopped ports: - "8080:80" volumes: - wiki-data:/app-data - ./plugins:/app-data/plugins environment: OTTERWIKI_API_KEY: "${OTTERWIKI_API_KEY}" CHROMADB_HOST: "chromadb" CHROMADB_PORT: "8000" chromadb: image: chromadb/chroma:latest restart: unless-stopped volumes: - chroma-data:/chroma/chroma ports: - "8000:8000" mcp-server: build: ./mcp-server restart: unless-stopped ports: - "8090:8090" environment: OTTERWIKI_API_URL: "http://otterwiki:80" OTTERWIKI_API_KEY: "${OTTERWIKI_API_KEY}" MCP_PORT: "8090" volumes: wiki-data: chroma-data:
Implementation Sequence
Phase 1: Investigate plugin system (30 min)
- Read
otterwiki/plugins.pyin the fork - Read
docs/plugin_examples/ - Determine: Can plugins register Flask blueprints? What hooks are available?
- Decision: plugin path vs. core modification path
Phase 2: REST API (core deliverable)
- Implement the API endpoints (blueprint or plugin, per Phase 1 findings)
- API key authentication middleware
- WikiLink parsing and link graph
- Test with curl
Phase 3: Chroma integration
- Add ChromaDB client to the Otterwiki environment
- Implement index sync (hook-based or periodic, per Phase 1 findings)
- Add semantic search endpoint
- Add
/reindexendpoint for bulk population - Test semantic search
Phase 4: MCP server
- Scaffold FastMCP server with SSE transport
- Implement all eight tools as HTTP wrappers
- Test locally with MCP inspector
- Configure for docker-compose deployment
Phase 5: Docker compose and deployment
- Finalize docker-compose.yaml
- Reverse proxy configuration (Caddy or Nginx)
- TLS setup
- Test end-to-end: Claude.ai → MCP → API → Otterwiki → Git
Success Criteria
- From Claude.ai, I can
read_note("Actors/Iran")and get the page content with frontmatter and links - From Claude.ai, I can
write_note("Events/2026-03-09 Day 10", content)and it appears in Otterwiki's web UI within seconds - From Claude.ai, I can
semantic_search("interceptor depletion strategy")and get relevant notes even if they don't contain those exact words - From the Otterwiki web UI, I can edit a note and the changes are reflected in subsequent MCP tool calls
- All changes are Git-committed with meaningful messages and full version history
- The Chroma index stays in sync with the wiki content regardless of whether edits come from the API or the web UI