Content
# 🔍 ContextWiki
[](https://github.com/eunhwa99/MCPContentSearch/actions/workflows/ci.yml)
**A private knowledge retrieval MCP server for LLM clients.**
Syncs Notion · Tistory · GitHub · Obsidian into vector + metadata stores and returns citation-backed context.
---
## 🏗️ Architecture
```text
[ Sources ]
Notion / Tistory / GitHub / Obsidian
|
v
[ Ingestion Service ]
/ \
v v
[ Chroma ] [ SQLite ]
semantic search metadata gate
\ /
v v
[ Verified Context ]
```
---
## 🛠️ MCP Tools
| Tool | Description |
|------|-------------|
| `list_sources()` | List configured sources |
| `sync_source(source_id)` | Sync a specific source |
| `sync_all()` | Sync all sources at once |
| `get_sync_status(source_id="")` | Get source and sync job status |
| `search_context(query, ...)` | Semantic search with SQLite validation |
| `search_documents(query, ...)` | Search results grouped by document |
| `fetch_context(document_id="", chunk_id="")` | Fetch a specific document or chunk directly |
> 💡 In production, use `search_context` / `search_documents` to gather grounded evidence, then let a downstream LLM generate the final answer.
---
## ⚡ Quick Start
**Prerequisites:** Python `3.13`, [`uv`](https://docs.astral.sh/uv/)
```bash
uv sync --locked
cp .env.example .env
uv run --locked python main.py
```
**Docker:**
```bash
docker build -t contextwiki .
cp .env.example .env
docker run --rm -i \
--env-file .env \
-v contextwiki_data:/home/appuser/.mcp_content_search \
contextwiki
```
> For Obsidian in Docker, add `-v "/path/to/vault:/vault:ro"` and set `CONTEXTWIKI_OBSIDIAN_VAULT_PATH=/vault`.
---
## ⚙️ Configuration
### Source Activation
| Source | Source ID | Env var to enable |
|--------|-----------|-------------------|
| Notion | `source_notion` | `NOTION_API_KEY` |
| Tistory | `source_tistory` | `TISTORY_BLOG_NAME` |
| GitHub | `source_github` | `CONTEXTWIKI_GITHUB_REPOSITORIES` |
| Obsidian | `source_obsidian` | `CONTEXTWIKI_OBSIDIAN_VAULT_PATH` |
Set only the sources you plan to use. A source stays disabled when its enabling
config is missing or empty. Bad credentials can still fail later during refresh
or sync. Some invalid target values can fail earlier during startup or source
refresh; for example, a malformed `CONTEXTWIKI_GITHUB_REPOSITORIES` value can
prevent the server from starting cleanly.
Source-specific env vars only register and enable each source. With the default
embedding setup, successful sync/indexing still also requires `OPENAI_API_KEY`
unless you reconfigure embeddings.
### `.env` Example
```bash
OPENAI_API_KEY=... # required for default embeddings/indexing; also used by optional rewrite
NOTION_API_KEY=...
TISTORY_BLOG_NAME=devlog # subdomain only, not the full URL
CONTEXTWIKI_GITHUB_REPOSITORIES=eunhwa99/MCPContentSearch@main
GITHUB_TOKEN=... # needed for private repos or higher rate limits
CONTEXTWIKI_OBSIDIAN_VAULT_PATH=/absolute/path/to/vault
```
Important GitHub notes:
- Use `owner/repo` or `owner/repo@ref`.
- Do not wrap `CONTEXTWIKI_GITHUB_REPOSITORIES` in quotes in `.env`.
- `GITHUB_TOKEN` is optional, but private repositories and higher rate limits
usually need it.
**Notion example**
```bash
NOTION_API_KEY=...
```
Set this only if you want to enable the Notion source.
**Tistory example**
```bash
TISTORY_BLOG_NAME=devlog
```
Use the blog subdomain only, not the full URL. For example, use `devlog`, not
`https://devlog.tistory.com`.
**Obsidian example**
```bash
CONTEXTWIKI_OBSIDIAN_VAULT_PATH=/absolute/path/to/your/vault
CONTEXTWIKI_OBSIDIAN_MAX_FILES=2000
CONTEXTWIKI_OBSIDIAN_MAX_FILE_BYTES=512000
```
Use the vault root path, not an individual `.md` file path.
### Search Query Rewrite (Optional)
```bash
CONTEXTWIKI_SEARCH_LLM_ENABLED=true
CONTEXTWIKI_SEARCH_LLM_PROVIDER=openai
CONTEXTWIKI_SEARCH_LLM_MODEL=gpt-4.1-mini
```
---
## 🚀 Usage
### Claude Desktop — local uv (Recommended)
This is the easiest setup path on macOS because:
- Claude Desktop can spawn the server directly.
- ContextWiki loads the repository-local `.env` at startup, so Claude Desktop
does not need plaintext env entries in `claude_desktop_config.json`.
- Obsidian can use your real host vault path directly.
- You do not need Docker mounts for the vault.
Important:
- Repository `.env` values do not override env vars already set by Claude
Desktop or your shell.
- If you previously set `OPENAI_API_KEY`, `NOTION_API_KEY`, `GITHUB_TOKEN`, or
other source env vars in `claude_desktop_config.json` or a parent shell, clear
the stale values there too.
Do this in order:
1. Create `.env` in your repo root.
2. Put your real values there.
3. Add the MCP entry below to Claude Desktop.
4. Fully restart Claude Desktop.
5. In a fresh Claude Desktop chat, ask it to call `list_sources()`.
Example `.env`:
```bash
# Required for default sync/indexing because embeddings use OpenAI by default
OPENAI_API_KEY=...
# Optional: enable Notion source
NOTION_API_KEY=...
# Optional: enable Tistory source
TISTORY_BLOG_NAME=devlog
# Optional: enable GitHub source
CONTEXTWIKI_GITHUB_REPOSITORIES=eunhwa99/MCPContentSearch
CONTEXTWIKI_GITHUB_DEFAULT_REF=main
# Optional for private repos or higher GitHub API limits
GITHUB_TOKEN=...
# Optional: enable Obsidian source
CONTEXTWIKI_OBSIDIAN_VAULT_PATH=/absolute/path/to/your/vault
```
On macOS, add this to
`~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"content-search-server": {
"command": "/absolute/path/to/uv",
"args": [
"--directory", "/absolute/path/to/MCPContentSearch",
"run", "--python", "3.13", "python", "main.py"
]
}
}
}
```
> Run `which uv` to find the path. **Fully restart Claude Desktop** after any config change.
### Claude Desktop — Docker
```json
{
"mcpServers": {
"content-search-server": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/to/MCPContentSearch/.env",
"-v", "contextwiki_data:/home/appuser/.mcp_content_search",
"contextwiki:latest"
]
}
}
}
```
### Cursor
Add the same local uv config above to `.cursor/mcp.json`.
### After Connecting
1. Call `sync_all()` or `sync_source("source_notion")`
2. Search with `search_context()` or `search_documents()`
**Example prompt:**
```text
find my projects about DynamoDB and organize it with STAR method. Answer in English
```

---
## 🔧 Troubleshooting
| Symptom | Fix |
|---------|-----|
| MCP server not discovered | Recheck config path and fully restart the client |
| Only works after manual start | Run `command` + `args` directly in terminal to see errors |
| `Invalid GitHub repository spec` | Remove quotes from `.env`; use `owner/repo`, not a full URL |
| Obsidian not working in Docker | Set both the volume mount and `CONTEXTWIKI_OBSIDIAN_VAULT_PATH=/vault` |
| Source still disabled after config change | Fully restart the MCP client — a chat refresh is not enough |
| `sync_all()` too slow | Sync individually: `sync_source("source_github")`, etc. |
---
## ✅ Verification
```bash
./scripts/demo.sh # Quick flow check with sample vault (no credentials needed)
./scripts/verify_all.sh # Full verification after code changes
./scripts/demo.sh --query "your question" # Custom query
```
- `./scripts/demo.sh` is the default reviewer path. It uses the bundled sample vault, needs no credentials, and keeps retrieval plus helper preview on the same input by default.
---
## 📁 Project Structure
```text
main.py FastMCP server entry point
api/ MCP tool handlers
core/ Shared models, exceptions, utilities
environments/ Env var and secret loading
fetching/ Source connectors (Notion, Tistory, GitHub, Obsidian)
indexing/ Chunking, deduplication, Chroma indexing
search/ Search, ranking, metadata gate, citation answer support
storage/ SQLite lifecycle management
tests/, scripts/ Verification harnesses and utilities
```
---
## 📖 Additional Docs
- [Architecture](.agents/docs/architecture.md)