Skip to main content
Glama
private-journal-mcp

private-journal-mcp

by baleen37

private-journal-mcp

An MCP server that stores journal entries in local files and searches them semantically with multilingual embeddings. Search and embedding inference run locally; the embedding model is downloaded and cached once on first use. Optionally, entries can be auto-synced to a Git remote.

Tools

The public tool surface is intentionally minimal: write, search, and read. Compatibility aliases and chronological listing are not provided.

  • write

    • Stores an entry from a required title and content.

    • Optional arg: section (reflections, observations, project_notes, user_context, technical_insights, world_knowledge)

    • section defaults to observations.

    • Returns the compact id of the written entry; the canonical file path stays internal.

  • search

    • Performs semantic search over stored entries and returns compact cards.

    • Automatically favors recent entries using created_at date decay with a 90-day half-life and a 50% score floor.

    • Required arg: query (a string, or an array of 2-5 strings for strict AND search)

    • Optional args: limit (default 10), section, project

    • project is an explicit repository-path filter. If omitted, search covers every project; the current runtime project is never applied automatically.

  • read

    • Reads one or more full Markdown entries by compact ids returned from search.

    • Accepts ids with 1-10 entries and returns { results, missing } in the requested order.

Generated entries use stable j_ ids derived from their date/time filename. Legacy Markdown paths use a reversible j~ fallback. Pass the returned ids to read when the full entry is needed.

Related MCP server: embgrep

Storage Locations

Markdown front matter

New entries use YAML front matter with the caller-provided title and a canonical UTC created_at value. The MCP server also records a Git repository path in project when the client context identifies one, or null when it cannot. Legacy date and timestamp fields are converted by the data revision migration; they are not written for new entries.

---
title: 검색 결과 캐시 오류 수정
created_at: 2026-06-25T12:34:56.789Z
project: baleen37/private-journal-mcp
---

Project attribution is resolved per write. Codex request metadata is preferred, followed by Claude Code's CLAUDE_PROJECT_DIR, then MCP client roots. Only the normalized repository path is stored, without a host, credential, or local absolute path. Ambiguous or unavailable context is stored as project: null and does not block the journal write.

Journal data

Priority order:

  1. PRIVATE_JOURNAL_PATH

  2. $XDG_DATA_HOME/private-journal

  3. ~/.local/share/private-journal

Model cache

Priority order:

  1. $XDG_CACHE_HOME/private-journal/models

  2. ~/.cache/private-journal/models

The default embedding model is Xenova/multilingual-e5-small.

Install / Build

npm install
npm run build

Run locally:

node dist/index.js

Index migrations are selected by the stored schema_revision and run in consecutive order by the migration runner. Each migration creates a temporary SQLite vector index, verifies it, and replaces the old index only after success. Run the migration command before starting sessions that use this journal:

node dist/index.js migrate-index

Without a Git remote, the sync subcommand still runs local data migrations and incrementally indexes changed Markdown files, but does not perform Git operations.

node dist/index.js sync

This repo is a plugin for both Claude Code (.claude-plugin/plugin.json) and Codex (.codex-plugin/plugin.json). Installing it registers the MCP server and the SessionStart sync hook in one step — no manual settings.json/config.toml edits. The MCP server is declared inline in each manifest's mcpServers field (not a root .mcp.json, which would auto-load as a project-scope server). It resolves the plugin's own install path via ${CLAUDE_PLUGIN_ROOT} (Claude Code) / a ./bin relative path with cwd (Codex). The bundled hooks/hooks.json resolves paths the same way.

Build first so dist/ exists, then install:

npm install && npm run build

Claude Code:

/plugin install /absolute/path/to/private-journal-mcp

When Claude Code enables the plugin, it asks for an optional Git remote. Enter a remote URL to enable Git sync, or leave it blank for local-only storage. To change it later, open the plugin configuration dialog and select private-journal-mcp.

Codex:

codex plugin install /absolute/path/to/private-journal-mcp

OpenCode:

Published package, in opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["private-journal-mcp"]
}

Local checkout:

npm install && npm run build
mkdir -p .opencode/plugins
ln -sfn "$(pwd)/opencode-plugin.mjs" .opencode/plugins/private-journal-mcp.js

The symlink keeps the plugin's relative dist/ import rooted at the checkout. OpenCode automatically loads plugins from the project .opencode/plugins/ directory; use ~/.config/opencode/plugins/ for a global local plugin instead.

The plugin exposes write, search, and read as native OpenCode tools. It uses the same local data path and Git remote environment variables as the MCP server. Set PRIVATE_JOURNAL_GIT_REMOTE for Git sync; leave it unset for local-only storage.

Manual MCP registration (without the plugin)

claude mcp add private-journal -- node /absolute/path/to/private-journal-mcp/dist/index.js

Git Sync (optional)

Claude Code passes this setting to the plugin as CLAUDE_PLUGIN_OPTION_GIT_REMOTE. Existing Codex and manual MCP setups can continue to use PRIVATE_JOURNAL_GIT_REMOTE:

export PRIVATE_JOURNAL_GIT_REMOTE="git@github.com:youruser/my-journal.git"

Recommended prerequisites:

  • You must already be authenticated for that remote via gh auth login or equivalent Git credentials.

  • Do not put credentials or tokens in the remote URL. Use SSH or a Git credential helper instead.

Setting up the remote

Create a private repo and point the server at it:

gh repo create <your-account>/private-journal-vault --private
export PRIVATE_JOURNAL_GIT_REMOTE=git@github.com:<your-account>/private-journal-vault.git

If the remote is empty, the data directory is initialized in place (and stays silent — no error — on the first sync). If the remote already has entries, it is cloned and merged with whatever is already local.

Behavior:

  • A write save returns after Markdown and the SQLite index are durable. Git pull/commit/rebase/push runs in a detached background process.

  • Push is retried up to 5 times (PUSH_RETRY_LIMIT) with exponential backoff (100/200/400/800ms), which lets several machines writing at once converge without losing entries.

  • Network commands (fetch, push, ls-remote, clone) time out after 10s, tunable via PRIVATE_JOURNAL_GIT_TIMEOUT_MS. Local rebase is never interrupted — cutting a rebase short would leave the repo unable to commit.

  • If a previous run left an interrupted rebase, the next sync resolves it, or aborts it, or as a last resort force-cleans unreadable rebase state. Local commits are preserved either way.

  • Within one machine, sync is serialized by a .private-journal-sync.lock file in the data directory. If another session already holds it, this run is skipped (not queued); the next run picks up whatever is pending. Locks older than 120s are considered stale and stolen.

  • All Claude Code and Codex sessions for one OS user share one embedding worker and one active model inference. Query embeddings have priority over queued passage backfill. SQLite WAL allows concurrent index readers and short writes.

  • Markdown and Git remain canonical. SQLite is disposable derived state at .private-journal-index.sqlite; its WAL/SHM files are excluded from Git.

  • A missing or incomplete SQLite index is backfilled from Markdown once. Once it is complete, startup processes only Git-reported changed paths.

  • Reads (search, read) do not pull. A session sees the snapshot from when it started, plus anything it wrote itself. Changes from other machines arrive at the next session start or the next write.

  • node dist/index.js sync pulls and pushes any pending commits before a session starts.

Git commit identity

Automatic sync commits use journal <journal@localhost> by default. Override the identity for the MCP process with GIT_NAME and GIT_EMAIL:

{
  "env": {
    "GIT_NAME": "your-name",
    "GIT_EMAIL": "your-github-email@example.com"
  }
}

The same values are used for both the Git author and committer. Use an email linked to your GitHub account if these commits should count toward your contribution graph.

Data-format compatibility

When the same journal is used on multiple computers, an app that upgrades the journal data format records the new version in the journal. Older app versions then stop before reading or writing and tell you to update, instead of risking an incompatible change.

The 1 -> 2 data migration rewrites legacy YAML front matter in a staging directory and preserves the original data if any entry cannot be converted.

SessionStart sync hook

When installed as a plugin, the SessionStart sync hook is registered automatically (see hooks/hooks.json) — nothing to configure. Without a configured remote it still runs local data migrations and incremental index backfill.

The hook uses sync --background, so SessionStart returns immediately while the existing sync process continues in the background. To run the same sync in the foreground, use node dist/index.js sync.

To wire it up manually instead, add to ~/.claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node /absolute/path/to/private-journal-mcp/dist/index.js sync --background"
          }
        ]
      }
    ]
  }
}

Conflict Handling

  • Distinct entries mostly coexist automatically because filenames include a microsecond suffix.

  • When two entries share a filename, the one with the larger timestamp derived from frontmatter created_at wins.

  • If the derived timestamp is identical, the local version takes precedence.

  • The SQLite row for the adopted Markdown is regenerated from the source file.

  • Legacy .embedding files are not part of runtime conflict handling; run migrate-index to convert and remove them.

Available Tools

4 tools
list_journalA

List recent journal entries with paths, dates, sections, and projects for chronological review before reading full entries.

Inputs: days, limit.

read_journalA

Read the full content of a single journal entry by file path returned from search_journal or list_journal.

Inputs: path (required).

search_journalA

Search private journal entries semantically and return LLM-readable markdown snippets with source paths, sections, projects, scores, and excerpts.

Use section to narrow recall when the intent is known; omit section for broad discovery.

Scores are cosine similarities from a multilingual-e5 model and cluster in a narrow band (~0.80-0.89), so a high score alone does not mean an entry is relevant. Always judge relevance from the excerpt text, and treat small score gaps as noise. Results are ordered with an automatic created_at date decay (90-day half-life, 50% floor); score and minScore remain semantic similarity values.

Omit project to search across all projects. Use project only when an explicit repository filter is needed; the current runtime project is never applied automatically.

Inputs: query (required), limit, project, section, +1 more.

write_journalA

Write a durable private journal entry with a meaningful title. section defaults to observations.

Pick the section by what the note is about:

  • project_notes: current repo/task state, decisions, and where work stands.

  • technical_insights: reusable fixes, root causes, and gotchas worth recalling later.

  • user_context: stable preferences and working style of the person you assist.

  • observations: raw findings from this session that are not yet generalized.

  • reflections: retrospectives on how the work went and what to change next time.

  • world_knowledge: durable facts about systems or the world outside this repo.

Returns a JSON object with the written file path.

Inputs: title (required), content (required), section.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool has a distinct job: writing entries, listing entries chronologically, searching semantically, and reading a specific entry by path. The retrieval tools are separated by mode (recent/chronological vs. semantic), so an agent should not misselect among them.

    Naming Consistency5/5

    All tool names follow a consistent verb_journal pattern: read_journal, list_journal, write_journal, search_journal. Naming is uniform, predictable, and clearly conveys each action.

    Tool Count5/5

    Four tools is a reasonable, focused surface for a journal server. Each tool covers a distinct core operation without redundancy or feature bloat.

    Completeness5/5

    The toolset covers writing, browsing, searching, and reading entries, which are the core workflows for a journal system. The absence of update/delete is acceptable for a durable, append-friendly journal design.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Provides local semantic search over files using embeddings, enabling directory indexing and natural language queries without external services.
      23 PyPI
      MIT