Kajet Turbo
БесплатноНе проверенSerena inspired MCP server for your obsidian-style workspaces
Описание
Serena inspired MCP server for your obsidian-style workspaces
README
[!WARNING] Highly unstable and unsafe. This is a solo hobby project driven by a love of refactoring and design more than of shipping — the git history is full of bugs, some still lurking. No security audit, no stability guarantees. Do not point it at data you can't afford to lose or expose.
[!NOTE] That said, for personal use it's been reasonably solid operating on workspaces with 1k+ files.
MCP-first RAG for markdown notes — somewhere between Serena and Obsidian: from the MCP side you create a workspace (an Obsidian-style vault), then add and edit files on it. The MCP endpoint is OAuth-capable, so it can be exposed and connected to from an MCP client (Claude among others).
The initial prototype was kajet, a
Rust-based predecessor to this project. The "turbo" rewrite happened for
the same kind of reason the original did: more dog walks, wanting to look
up notes from a phone on the go instead of a native macOS binary, and
being tired of a 50GB target/ cache just to rebuild the thing.
Notes live as markdown files in a per-user git repo (workspace). The backend chunks them, computes embeddings, and exposes hybrid search (SQLite FTS5 + vectors) through:
- MCP (
/mcp) — tools to search and edit notes, with OAuth, for plugging into any MCP client; - REST API (
/api) — for the SPA and integrations; - SPA (SvelteKit,
frontend/) — a human-facing note browser.
Three process roles (KAJET_ROLE) share the same SQLite database and the
same on-disk workspaces — see Environment variables
below for topology details.
Stack
Backend:
- Python 3.14 (free-threaded build — no GIL)
- FastAPI + Starlette — REST API and ASGI app
- FastMCP — MCP server, tools, and OAuth
- SQLModel + SQLAlchemy + sqlite-vec — hybrid search: SQLite FTS5 + vector search in the same DB
- Alembic — schema migrations
- Dulwich — pure-Python git, one repo per user workspace
- Loguru — structured JSONL logging
- python-frontmatter + markdown-it-py — note parsing and chunking
- Argon2-cffi — password hashing
- cryptography — token/secret handling
- pydantic-settings — config from env vars
Frontend:
- SvelteKit 5 + Vite + TypeScript
- Orval — generates the typed API client from the backend's OpenAPI schema
- Vitest — unit tests
- ESLint + Prettier + svelte-check — lint, format, type-check
Local development
Backend (Python 3.14t, uv):
uv sync
MCP_BASE_URL=http://localhost:8000 uv run kajet-turbo
Frontend (SvelteKit + Bun, proxies to localhost:8000):
cd frontend
bun install
bun run dev
Common commands:
uv run pytest # backend tests
uv run ruff check --fix . && uv run ruff format .
uv run ty check # type-checking
cd frontend && bun run check && bun run lint
bash scripts/generate-api.sh # regenerate the API client after a backend change
Schema migrations use Alembic, never create_all; details in
CLAUDE.md. More on repo structure, concurrency
rules (free-threaded Python), and code conventions is also in
CLAUDE.md.
Running (production / container)
uv sync
MCP_BASE_URL=http://localhost:8000 kajet-turbo
Environment variables
Required
| Variable | Description |
|---|---|
MCP_BASE_URL |
Public URL of the server (e.g. https://kajet.example.com). Some PaaS deployment targets set this automatically from their own env vars. |
Server
| Variable | Default | Description |
|---|---|---|
MCP_HOST |
0.0.0.0 |
Listen address |
MCP_PORT |
8000 |
Listen port |
KAJET_ROLE |
all |
Process role: all (MCP+API+SPA in one — dev), mcp (/mcp + OAuth only, N workers), api (REST /api + SPA, N workers) |
MCP_WORKERS |
1 |
Worker count for roles mcp and all |
API_WORKERS |
2 |
Worker count for role api |
KAJET_METRICS_SAMPLE_SHARED |
role default | Whether this process owns shared-state sampling (SQLite files, queue snapshots). Default: worker on, api/mcp off, all on only with one process. See Metrics |
KAJET_METRICS_PORT |
9100 |
Metrics listener port of role worker (roles api/mcp/all serve /metrics on their normal port) |
KAJET_WORKER_STALE_AFTER |
300 |
Seconds since a claimed job's last lease renewal before another worker may reclaim it (role worker/all) |
KAJET_GIT_SSH_CONNECT_TIMEOUT |
15 |
Seconds ssh may spend connecting/handshaking with a git push remote before failing (ConnectTimeout) |
KAJET_GIT_SSH_KEEPALIVE_INTERVAL |
15 |
Seconds between ssh keepalive probes on an established git push connection (ServerAliveInterval) |
KAJET_GIT_SSH_KEEPALIVE_COUNT_MAX |
3 |
Missed keepalive probes tolerated before ssh gives up on a stalled git push (ServerAliveCountMax) |
Metrics
Every role exposes Prometheus metrics, and none of them through the ingress:
| Role | Storage | Endpoint |
|---|---|---|
api, mcp |
multiprocess: one directory per container run (PROMETHEUS_MULTIPROC_DIR, default /tmp/kajet-prometheus, cleared once by the supervisor at start) |
GET /metrics on the role's port (kajet-api:8000, kajet-mcp:8000) |
worker |
in-process registry | own listener on KAJET_METRICS_PORT (kajet-worker:9100), started and stopped with the process |
all |
in-process registry | GET /metrics on the normal port |
Scrape these targets directly on the compose network. /metrics is a root route of each
app — never under /mcp or /api, which Caddy proxies — so it matches no ingress
allowlist and is not reachable from outside. Do not publish port 9100 either.
Shared state (SQLite file sizes, WAL, queue snapshots) has exactly one owner per
deployment, selected by KAJET_METRICS_SAMPLE_SHARED and set explicitly in
docker-compose.yml: 1 on kajet-worker, 0 on kajet-api and kajet-mcp. If you
scale kajet-worker past one replica, exactly one replica carries the flag; the others
set it to 0, otherwise every replica reports the same database and the series duplicate.
Worker replicas are separate scrape targets with their own instance label — ordinary
Prometheus, not multiprocess mode.
Sampler health is exported per collector (kajet_sampler_success,
kajet_sampler_last_success_timestamp_seconds, kajet_sampler_duration_seconds,
kajet_sampler_errors_total, kajet_sampler_age_seconds). A reachable /metrics alone
does not prove sampling is healthy: alert on kajet_sampler_age_seconds growing, and note
that when a sample fails the last good snapshot stays visible rather than dropping to zero.
Metric families are catalogued in docs/specs/metrics.md.
Production topology (docker-compose.yml): ingress (Caddy) + kajet-api
(stateless, N workers) + kajet-mcp (stateless, N workers via MCP_WORKERS).
Both roles share the /data (SQLite) and /workspaces (git) volumes on the
same host. The host proxy only routes Host → ingress:8000; path splitting
is done by the Caddyfile.
Development images
CI builds the final Dockerfile targets for linux/amd64 (linux/arm64 is
disabled for now — not deployed anywhere yet):
ghcr.io/jpalczewski/kajet-turbo-appghcr.io/jpalczewski/kajet-turbo-ingress
Pull requests only build, validate, and scan the images. A push to main
publishes images that passed the Trivy scan under the immutable
sha-<commit> tag. After successfully building both targets, CI also moves
the convenient, moving develop tag.
[!IMPORTANT] On first publish, set both packages to
Publicin their GitHub settings. The workflow checks anonymous pull access for both images before movingdevelop, so a private package will block the promotion; after fixing visibility you can safely re-run the same run.
# Latest known-good development build
docker pull ghcr.io/jpalczewski/kajet-turbo-app:develop
docker pull ghcr.io/jpalczewski/kajet-turbo-ingress:develop
# Reproducible pair of images from one commit
docker pull ghcr.io/jpalczewski/kajet-turbo-app:sha-<commit>
docker pull ghcr.io/jpalczewski/kajet-turbo-ingress:sha-<commit>
[!NOTE] For deployments or debugging, use the same
sha-<commit>tag for both images. GHCR does not update two packages transactionally, sodevelopis purely a shortcut for day-to-day work.
Every platform variant has a CycloneDX SBOM and provenance signed by GitHub Actions. The index attestation can be verified like this:
gh attestation verify \
oci://ghcr.io/jpalczewski/kajet-turbo-app:sha-<commit> \
-R jpalczewski/kajet-turbo
The SBOM is pinned to a specific platform manifest (amd64):
gh attestation verify \
oci://ghcr.io/jpalczewski/kajet-turbo-app:sha-<commit>-amd64 \
-R jpalczewski/kajet-turbo \
--predicate-type https://cyclonedx.org/bom
Once a week, CI re-scans the published develop images to catch
vulnerabilities disclosed after publication. CI images are development
artifacts: the workflow does not trigger a deployment. The production
deployment builds both targets from the Dockerfile itself, independent of
these CI-published images.
[!WARNING] Narrow exceptions to the Trivy gate live in
.trivyignore.yaml. Each one is scoped to a specific package or path and has an expiry date; an exception without those constraints should not be added.
Data
| Variable | Default | Description |
|---|---|---|
DB_PATH |
/data/kajet.db |
Path to the SQLite database |
WORKSPACES_DIR |
/workspaces |
Root directory for workspaces |
Vector embeddings are not configured via env vars. Each user sets their own
embedding profile — an OpenAI-compatible endpoint (base_url + model
- dimension, optional API key, stored encrypted) — through the API/SPA. Vector tables are sharded per dimension and created on demand, so nothing needs to be pre-declared. Without an active profile, search still works — it just falls back to FTS5 full-text only.
Initialization
| Variable | Default | Description |
|---|---|---|
KAJET_ADMIN_EMAIL |
— | Admin account email (created on first start) |
KAJET_ADMIN_PASSWORD |
— | Admin account password |
Logging
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
Log level (DEBUG, INFO, WARNING, ERROR) |
LOG_SQL |
— | Set to any value to log SQL queries (LOG_SQL=1) |
Logs are emitted to stderr as JSONL. Examples:
# production — INFO only, no SQL
kajet-turbo
# debug — full application logs
LOG_LEVEL=DEBUG kajet-turbo
# trace SQL queries
LOG_SQL=1 kajet-turbo
# full debug with SQL
LOG_LEVEL=DEBUG LOG_SQL=1 kajet-turbo 2> debug.jsonl
Установить Kajet Turbo в Claude Desktop, Claude Code, Cursor
unyly install kajet-turboСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add kajet-turbo -- uvx --from git+https://github.com/jpalczewski/kajet-turbo kajet-turboПошаговые гайды: как установить Kajet Turbo
FAQ
Kajet Turbo MCP бесплатный?
Да, Kajet Turbo MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Kajet Turbo?
Нет, Kajet Turbo работает без API-ключей и переменных окружения.
Kajet Turbo — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Kajet Turbo в Claude Desktop, Claude Code или Cursor?
Открой Kajet Turbo на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
автор: duxiaohuiSupabase
Database, auth and storage
автор: SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Kajet Turbo with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
