journal_append
Append timestamped entries to a daily journal markdown file. Creates the file automatically on first use and serializes concurrent writes to prevent data loss.
Instructions
Append a timestamped entry to today's (or date's) journal markdown at knowledge/journal/YYYY-MM-DD.md. Creates the file on first use of a date with a # Journal — DATE header; subsequent calls APPEND a new ## HH:MM:SS section without rewriting prior entries. Concurrent calls for the same date are serialised by an internal lock so entries are not lost. SIDE EFFECTS: writes the file (FTS reindex + git commit via the same path as update_file/create_file); on first use, creates with default tag ['journal'] (overridable). date must match YYYY-MM-DD or throws. No external auth or rate limits. Returns {file_id, path, date, time, appended_chars, est_tokens}.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | Entry text (markdown allowed) | |
| date | No | Override the journal date as YYYY-MM-DD (default: today, local time) | |
| tags | No | Tags to apply to the journal file (default: ['journal']) |
Implementation Reference
- apps/mcp/src/index.ts:1916-1989 (handler)The complete handler function for the journal_append MCP tool. Takes text (required), optional date (YYYY-MM-DD), and optional tags. Creates or appends to knowledge/journal/YYYY-MM-DD.md with timestamped entries, using withLock for concurrency safety.
server.tool( "journal_append", "Append a timestamped entry to today's (or `date`'s) journal markdown at `knowledge/journal/YYYY-MM-DD.md`. Creates the file on first use of a date with a `# Journal — DATE` header; subsequent calls APPEND a new `## HH:MM:SS` section without rewriting prior entries. Concurrent calls for the same date are serialised by an internal lock so entries are not lost. SIDE EFFECTS: writes the file (FTS reindex + git commit via the same path as `update_file`/`create_file`); on first use, creates with default tag `['journal']` (overridable). `date` must match `YYYY-MM-DD` or throws. No external auth or rate limits. Returns `{file_id, path, date, time, appended_chars, est_tokens}`.", { text: z.string().min(1).describe("Entry text (markdown allowed)"), date: z.string().optional().describe("Override the journal date as YYYY-MM-DD (default: today, local time)"), tags: z.array(z.string()).optional().describe("Tags to apply to the journal file (default: ['journal'])"), }, async ({ text, date, tags }) => { try { const today = (() => { const d = new Date(); const pad = (n: number) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; })(); const dateStr = date ?? today; if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { throw new Error(`date must be YYYY-MM-DD, got: ${dateStr}`); } const journalPath = join(dataDir, "knowledge", "journal", `${dateStr}.md`); const ts = new Date(); const pad = (n: number) => String(n).padStart(2, "0"); const timeStr = `${pad(ts.getHours())}:${pad(ts.getMinutes())}:${pad(ts.getSeconds())}`; const entry = `\n\n## ${timeStr}\n\n${text.trimEnd()}\n`; const result = await withLock(`journal:${journalPath}`, async () => { const existing = getDatabase() .prepare("SELECT id FROM files WHERE path = ?") .get(journalPath) as { id: number } | undefined; if (existing) { const current = readFile(existing.id); const updated = await updateFile(existing.id, current.content + entry, dataDir); if (tags && tags.length > 0) addTags(existing.id, tags); return updated; } const initial = `# Journal — ${dateStr}${entry}`; return await createFile({ title: dateStr, content: initial, destination: "knowledge", folder: "journal", tags: tags ?? ["journal"], dataDir, }); }); return { content: [ { type: "text", text: JSON.stringify( { file_id: result.id, path: result.path, date: dateStr, time: timeStr, appended_chars: entry.length, est_tokens: estimateTokensFromBuffer(Buffer.from(result.content, "utf8")), }, null, 2 ), }, ], }; } catch (e: any) { return { isError: true, content: [{ type: "text", text: JSON.stringify({ error: e?.message ?? String(e) }, null, 2) }], }; } } ); - apps/mcp/src/index.ts:1919-1923 (schema)Zod schema for journal_append: text (string, min 1), date (optional YYYY-MM-DD string), tags (optional array of strings).
{ text: z.string().min(1).describe("Entry text (markdown allowed)"), date: z.string().optional().describe("Override the journal date as YYYY-MM-DD (default: today, local time)"), tags: z.array(z.string()).optional().describe("Tags to apply to the journal file (default: ['journal'])"), }, - apps/mcp/src/index.ts:1916-1918 (registration)Registration of the journal_append tool via server.tool() with its description, input schema, and handler callback.
server.tool( "journal_append", "Append a timestamped entry to today's (or `date`'s) journal markdown at `knowledge/journal/YYYY-MM-DD.md`. Creates the file on first use of a date with a `# Journal — DATE` header; subsequent calls APPEND a new `## HH:MM:SS` section without rewriting prior entries. Concurrent calls for the same date are serialised by an internal lock so entries are not lost. SIDE EFFECTS: writes the file (FTS reindex + git commit via the same path as `update_file`/`create_file`); on first use, creates with default tag `['journal']` (overridable). `date` must match `YYYY-MM-DD` or throws. No external auth or rate limits. Returns `{file_id, path, date, time, appended_chars, est_tokens}`.", - packages/core/src/db/index.ts:38-38 (helper)Database journal mode set to WAL (related but not directly part of journal_append logic).
globalThis.__ctxnestDb = db; - apps/web/src/lib/mcp-tool-categories.ts:46-46 (registration)Categorizes journal_append under the 'Write' category for the web UI.
journal_append: "Write",