private-journal-mcp
The private-journal-mcp server lets you store, search, and retrieve private journal entries locally, with optional Git sync.
write_journal: Write a new journal entry with content, organized into one of six sections:reflections,observations(default),project_notes,user_context,technical_insights, orworld_knowledge.search_journal: Perform semantic search over entries using natural language queries (via multilingual embeddings), with optional filters for section, result limit, and minimum relevance score.read_journal: Retrieve the full Markdown content of a specific journal entry by its file path.list_journal: Browse recent entries chronologically, filterable by number of days or result limit, returning paths, dates, and sections.Local-first storage: All entries and embedding inference run locally with no external API calls required.
Optional Git sync: Automatically push/pull journal changes to a private Git remote, with conflict resolution, retry logic, and automatic commits on write.
SessionStart hook: When installed as a plugin, automatically syncs with the configured Git remote at the start of each session, enabling multi-device use.
Enables automatic syncing of journal entries to a Git remote, with commit, pull --rebase, and push on write, as well as a session-start sync hook.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@private-journal-mcpwrite a reflection on today's team meeting"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
writeStores an entry from a required
titleandcontent.Optional arg:
section(reflections,observations,project_notes,user_context,technical_insights,world_knowledge)sectiondefaults toobservations.Returns the compact id of the written entry; the canonical file path stays internal.
searchPerforms semantic search over stored entries and returns compact cards.
Automatically favors recent entries using
created_atdate 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(default10),section,projectprojectis an explicit repository-path filter. If omitted, search covers every project; the current runtime project is never applied automatically.
readReads one or more full Markdown entries by compact ids returned from
search.Accepts
idswith 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: mesh-memory
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:
PRIVATE_JOURNAL_PATH$XDG_DATA_HOME/private-journal~/.local/share/private-journal
Model cache
Priority order:
$XDG_CACHE_HOME/private-journal/models~/.cache/private-journal/models
The default embedding model is Xenova/multilingual-e5-small.
Install / Build
npm install
npm run buildRun locally:
node dist/index.jsIndex 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-indexWithout 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 syncInstall as a Plugin (recommended)
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 buildClaude Code:
/plugin install /absolute/path/to/private-journal-mcpWhen 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-mcpOpenCode:
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.jsThe 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.jsGit 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 loginor 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.gitIf 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
writesave 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 viaPRIVATE_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.lockfile 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 syncpulls 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_atwins.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
.embeddingfiles are not part of runtime conflict handling; runmigrate-indexto convert and remove them.
Available Tools
4 toolslist_journalA
List recent journal entries with paths, dates, sections, and projects for chronological review before reading full entries.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | ||
| limit | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It correctly conveys that this is a read-only listing operation returning metadata rather than full content, and it reveals the output fields. It does not mention default time windows, sorting direction, or pagination behavior, but for a simple list tool it gives sufficient behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-front-loaded sentence. It states the action, scope, included fields, and purpose without wasted words, making it easy for an agent to parse quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description lists the return fields and an intended use case, which is helpful given there is no output schema. However, it omits important details like the default value for 'days,' how 'limit' affects results, and the exact chronological ordering (oldest-first vs newest-first). These gaps are meaningful for a listing tool, though the overall complexity is low.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides no parameter descriptions (0% coverage), and the tool description does not compensate by explaining 'days' or 'limit.' The word 'recent' hints at the days parameter and 'limit' is self-explanatory to a degree, but the tool description adds no explicit semantics, defaults, or interactions between the parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and resource ('recent journal entries'), and enumerates the returned fields: paths, dates, sections, and projects. It also states the purpose as 'chronological review before reading full entries,' which clearly distinguishes it from read_journal, write_journal, and search_journal.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'before reading full entries' gives clear context for when to use this tool: as a lightweight overview before invoking read_journal. It does not explicitly name alternatives or state when not to use it, so it stops short of a 5, but the intended workflow is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_journalA
Read the full content of a single journal entry by file path returned from search_journal or list_journal.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It correctly implies a read-only, non-destructive operation but lacks details on permissions, rate limits, or side effects. Adequate but minimal.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient sentence with no wasted words, front-loading the key information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema, the description lacks details about the return value (just 'full content') and assumes the user knows the format. It is functional but could be more complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and the description only says 'by file path' without adding format, constraints, or examples. It adds little beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool reads the full content of a single journal entry by file path, and specifies that the path is obtained from sibling tools search_journal or list_journal, effectively distinguishing it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides clear context for when to use the tool: after obtaining a file path from search_journal or list_journal. However, it does not explicitly state when not to use it or mention alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| project | No | ||
| section | No | ||
| minScore | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full responsibility for behavioral disclosure, and it excels: it reveals the scoring model (multilingual-e5 cosine similarity), the narrow score band, the unreliability of high scores, the need to judge relevance from excerpts, and the created_at date decay with 90-day half-life and 50% floor. This is far beyond what the schema alone conveys.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and return format, then layers scoring caveats and parameter guidance in a natural order. It is somewhat long, but nearly every sentence adds operational value, and the separation into paragraphs makes the three key ideas easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a semantic search tool with five parameters and no output schema or annotations, the description covers everything needed to call it correctly: what results look like, how to interpret scores, how ordering works, and how to use section and project. No critical operational gap remains.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it does for the non-obvious parameters: section's recall role, project's exact filtering semantics, and minScore's relationship to semantic similarity rather than raw relevance. Limit and query are not explicitly explained, but their meanings are nearly self-evident from the schema and context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: search private journal entries semantically and return LLM-readable markdown snippets. It also enumerates the returned fields (source paths, sections, projects, scores, excerpts), making the tool's function explicit and distinct from static read/list operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives concrete when-to-use guidance for parameters: narrow with section when intent is known, omit section for broad discovery, omit project to search across all projects, and use project only when an explicit repository filter is needed. It also clarifies that the runtime project is never applied automatically, preventing a likely misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | ||
| content | Yes | ||
| section | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full disclosure burden. It discloses that entries are durable and private, mentions the default section, and states the return value as a JSON path. However, it does not explain whether entries with the same title are overwritten, how file paths are generated, or any side effects like appending rather than creating new files. This is a moderate level of transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured: a single-sentence summary upfront, a bulleted list for section options, and a final return note. There is no redundancy, and every sentence adds value. It is appropriately sized for the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the core purpose, sections, and return value, which is adequate for a write tool with no output schema. It omits potential overwrite behavior and file naming details, but the existence of sibling read/search tools implies a structured journal system, making these omissions less critical. Overall, it is nearly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It fully explains the 'section' parameter by detailing each enum value and the default behavior. It also suggests 'meaningful title' for the title parameter, but content is left to common sense. The compensation is partial, hence the score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb and resource: 'Write a durable private journal entry with a meaningful title.' It also distinguishes the tool from siblings (list, read, search) by focusing on the write operation. The section list adds detail but does not obscure the core purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides thorough guidance on selecting the appropriate section based on note content, which is a key usage decision. It does not explicitly contrast with sibling tools, but the verb 'write' makes the primary use case clear. It could improve by stating when to use this tool rather than read/list/search, but the context is sufficient.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v1.7.0- Changed
search_journal1 field changed- added
Input schema / properties / projectAdded value: +{ + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ] +}
1 tool update
v1.6.0- Changed
write_journal2 fields changed- added
Input schema / properties / titleAdded value: +{ + "minLength": 1, + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "content" -]New value: +[ + "title", + "content" +]
2 tool updates
v1.4.4- Changed
list_journal6 fields changed- added
Input schema / properties / days / exclusiveMinimumAdded value: +0 - added
Input schema / properties / days / maximumAdded value: +3650 - changed
Input schema / properties / days / typePrevious value: -"number"New value: +"integer" - added
Input schema / properties / limit / exclusiveMinimumAdded value: +0 - added
Input schema / properties / limit / maximumAdded value: +50 - changed
Input schema / properties / limit / typePrevious value: -"number"New value: +"integer"
- Changed
search_journal4 fields changed- added
Input schema / properties / limit / exclusiveMinimumAdded value: +0 - added
Input schema / properties / limit / maximumAdded value: +50 - changed
Input schema / properties / limit / typePrevious value: -"number"New value: +"integer" - added
Input schema / properties / minScoreAdded value: +{ + "maximum": 1, + "minimum": 0, + "type": "number" +}
4 tool updates
v1.3.0- First observed
list_journal - First observed
read_journal - First observed
search_journal - First observed
write_journal
TDQS
Scored across 4 tools
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.
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.
Four tools is a reasonable, focused surface for a journal server. Each tool covers a distinct core operation without redundancy or feature bloat.
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
Related MCP Connectors
Persistent memory for AI agents. Search, store, and recall across sessions.
Persistent memory for AI agents. Semantic search, memory graph, W3C DID identity.
Mem0-compatible persistent memory for AI agents: write facts once, recall them semantically.
Universal persistent memory and knowledge retrieval layer for AI agents and LLMs.
Related MCP Servers
- AlicenseAqualityAmaintenancePersistent memory for AI agents with sectioned entries (project/user/context/etc), semantic search, CLI, and per-project scope. Local JSON, zero config, no server required.12108 PyPI4MIT
- AlicenseNot gradedqualityDmaintenanceSelf-hosted semantic memory for AI agents. Save worklogs, decisions, and notes via MCP, then recall them across sessions by meaning rather than keyword. Backed by Postgres + pgvector with local embeddings (multilingual-e5-base).1MIT
- AlicenseNot gradedqualityDmaintenanceA project-scoped private journaling and semantic search server for Claude, using local AI embeddings to capture technical insights, user context, and work-in-progress notes.GPL 3.0
- AlicenseNot gradedqualityDmaintenanceProvides local semantic search over files using embeddings, enabling directory indexing and natural language queries without external services.26 PyPIMIT