dailynote-mcp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ # Memasang DailyNote MCP di Codex
2
+
3
+ MCP lokal ini menyediakan satu tool, `add_journal_entry`. Setiap pengguna memakai
4
+ token dari akun DailyNote sendiri. Token diteruskan melalui environment variable;
5
+ tidak perlu file `.env` atau menyimpan token di konfigurasi Codex.
6
+
7
+ ## 1. Siapkan Node.js dan aplikasi DailyNote
8
+
9
+ Pasang Node.js 24 beserta npm dari [nodejs.org](https://nodejs.org/).
10
+ Periksa dari terminal:
11
+
12
+ ```sh
13
+ node --version
14
+ npx --version
15
+ ```
16
+
17
+ Node harus menunjukkan versi `v24.x`. Pengguna tidak perlu clone proyek,
18
+ memasang pnpm, menjalankan database, atau melakukan migration.
19
+ Aplikasi DailyNote tujuan harus sudah berjalan dan mendukung fitur Token API
20
+ serta endpoint MCP. Pengembang aplikasi menyiapkan backend tersebut.
21
+
22
+ ## 2. Gunakan package npm
23
+
24
+ Konfigurasi berikut memakai `dailynote-mcp@0.1.0` dan berlaku setelah versi
25
+ tersebut tersedia di npm. `npx` akan mengambil package serta dependency ketika
26
+ Codex menjalankan koneksi pertama kali. Akses registry diperlukan untuk
27
+ pengunduhan awal. Tidak perlu menjalankan proses MCP di terminal terpisah.
28
+
29
+ ## 3. Buat token pribadi
30
+
31
+ Login ke DailyNote, buka **Profil → Token API → Buat token**, lalu salin token.
32
+ Token lengkap hanya ditampilkan sekali. Jika perlu mengganti token yang sudah
33
+ ada, pilih **Buat token baru**; token lama akan dinonaktifkan.
34
+
35
+ Jangan kirim token ke chat, Git, atau GitHub Actions. Pengguna lain melakukan
36
+ langkah yang sama memakai akun dan komputernya sendiri.
37
+
38
+ ## 4. Isi environment sesi macOS
39
+
40
+ Contoh berikut memakai terminal **zsh** bawaan macOS. Tempel token ketika diminta;
41
+ input disembunyikan dan token tidak ditulis secara literal dalam riwayat command.
42
+
43
+ ```sh
44
+ export DAILYNOTE_URL="https://alamat-aplikasi-anda.example"
45
+ read -rs 'DAILYNOTE_TOKEN?Token API DailyNote: '
46
+ printf '\n'
47
+ export DAILYNOTE_TOKEN
48
+ launchctl setenv DAILYNOTE_URL "$DAILYNOTE_URL"
49
+ launchctl setenv DAILYNOTE_TOKEN "$DAILYNOTE_TOKEN"
50
+ ```
51
+
52
+ Ganti alamat contoh dengan origin HTTPS aplikasi DailyNote Anda, tanpa path,
53
+ query, atau fragment. Token harus berasal dari akun pada aplikasi tujuan
54
+ tersebut. `http://localhost:3000` hanya untuk development lokal.
55
+
56
+ `export` mengatur terminal saat ini. `launchctl setenv` membuat aplikasi desktop
57
+ yang dibuka setelahnya menerima variabel pada sesi login macOS ini. Variabel
58
+ tersebut harus diisi ulang setelah logout/restart komputer. Jangan menambahkan
59
+ token ke `.zshrc`, `.env`, atau `config.toml` bila ingin tetap tanpa file token.
60
+
61
+ Untuk Codex CLI, jalankan `codex` dari terminal yang sudah menerima `export`.
62
+ Untuk Codex desktop, tutup penuh aplikasi dengan **Quit Codex**, lalu buka lagi
63
+ setelah langkah ini. Aplikasi yang sudah berjalan belum menerima environment baru.
64
+
65
+ ## 5. Daftarkan MCP
66
+
67
+ Buka konfigurasi Codex `~/.codex/config.toml`. Tambahkan blok berikut, atau
68
+ perbarui blok `dailynote-journal` yang sudah ada; pertahankan konfigurasi lainnya.
69
+
70
+ ```toml
71
+ [mcp_servers.dailynote-journal]
72
+ command = "npx"
73
+ args = ["-y", "dailynote-mcp@0.1.0"]
74
+ env_vars = ["DAILYNOTE_URL", "DAILYNOTE_TOKEN"]
75
+ enabled_tools = ["add_journal_entry"]
76
+ ```
77
+
78
+ Jika Codex tidak menemukan `npx`, ganti `command` dengan path absolut hasil
79
+ `command -v npx`. Tidak ada path source code DailyNote dalam konfigurasi ini.
80
+ `env_vars` berisi nama variabel yang diteruskan Codex,
81
+ bukan nilai token. Konfigurasi ini mengikuti
82
+ [dokumentasi resmi OpenAI](https://developers.openai.com/codex/mcp/).
83
+
84
+ Jika sebelumnya memakai source lokal, ganti `command`/`args` dalam blok yang
85
+ sama dengan konfigurasi di atas setelah versi npm tersedia. Pertahankan
86
+ `env_vars`; jangan membuat koneksi kedua karena tool akan muncul ganda.
87
+
88
+ ## 6. Periksa koneksi
89
+
90
+ Dari terminal mana pun:
91
+
92
+ ```sh
93
+ codex mcp get dailynote-journal
94
+ ```
95
+
96
+ Hasil seharusnya menunjukkan `enabled: true`, `transport: stdio`, argumen
97
+ `-y dailynote-mcp@0.1.0`, dan nama kedua environment variable. Token mentah tidak
98
+ perlu tampil.
99
+ Setelah Codex desktop dibuka ulang, buka chat baru dan pastikan tool
100
+ `add_journal_entry` tersedia.
101
+
102
+ ## 7. Coba input jurnal
103
+
104
+ Di chat yang sudah memuat MCP:
105
+
106
+ > Gunakan DailyNote MCP untuk menambahkan "Tes koneksi MCP dari Codex" ke jurnal hari ini.
107
+
108
+ Tindakan ini menambahkan isi jurnal akun pemilik token. Hasil yang diharapkan
109
+ berisi tanggal, status `saved`, dan `requestId`. Periksa hasil di DailyNote;
110
+ tanggal default mengikuti timezone akun.
111
+
112
+ Jika hasil belum pasti karena koneksi terputus, ulangi dengan requestId, teks
113
+ dan tanggal yang sama. Jangan langsung mengirim entri baru dengan ID berbeda.
114
+
115
+ ## Mengganti atau melepas koneksi
116
+
117
+ Untuk mengganti token, buat token baru di profil, ulangi langkah 4 dan buka ulang
118
+ Codex. Untuk mencabut akses, gunakan **Cabut token** di DailyNote.
119
+
120
+ Untuk menghapus pendaftaran MCP dan variabel sesi macOS:
121
+
122
+ ```sh
123
+ codex mcp remove dailynote-journal
124
+ launchctl unsetenv DAILYNOTE_URL
125
+ launchctl unsetenv DAILYNOTE_TOKEN
126
+ unset DAILYNOTE_URL DAILYNOTE_TOKEN
127
+ ```
128
+
129
+ Proses yang sudah berjalan tetap memegang salinan environment lamanya sampai
130
+ ditutup. Menghapus konfigurasi lokal tidak mencabut token atau menghapus jurnal.
131
+
132
+ ## Jika bermasalah
133
+
134
+ - Server tidak bisa dimulai: periksa Node.js 24, path npx, akses registry, dan kedua
135
+ environment variable.
136
+ - `unauthorized`: token dicabut/diganti, salah disalin, atau berasal dari aplikasi
137
+ tujuan lain.
138
+ - Koneksi gagal: pastikan aplikasi tujuan hidup, URL/port tepat, dan gunakan HTTPS
139
+ untuk production.
140
+ - `outcome_unknown`: periksa jurnal atau retry dengan requestId lama.
141
+ - Tool belum terlihat: quit dan buka ulang Codex setelah environment diisi;
142
+ buka chat baru.
143
+
144
+ ## Commands to run
145
+
146
+ Wajib: ikuti langkah 1–6 berurutan. Semua command pemasangan pengguna dapat
147
+ dijalankan dari terminal lokal mana pun, tanpa folder proyek. Pemeriksaan akhir:
148
+
149
+ ```sh
150
+ codex mcp get dailynote-journal
151
+ ```
152
+
153
+ Hasil yang diharapkan: koneksi stdio aktif, versi npm tertentu pada argumen,
154
+ dan satu tool `add_journal_entry` tersedia pada chat baru.
155
+
156
+ Pengguna package tidak perlu menjalankan suite tes atau build aplikasi. Jika
157
+ pemasangan gagal, kirim command, exit status, dan cuplikan error relevan tanpa
158
+ token atau isi jurnal privat.
package/README.md CHANGED
@@ -1,3 +1,83 @@
1
- # Temporary Holding Version
1
+ # DailyNote MCP
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hubungkan AI assistant ke akun DailyNote melalui MCP lokal (stdio). Pengguna
4
+ cukup memasang **Node.js 24**; tidak perlu clone proyek, pnpm, atau database lokal.
5
+ Aplikasi DailyNote tujuan harus sudah mendukung Token API dan endpoint MCP.
6
+
7
+ Konfigurasi npm di bawah memerlukan rilis `dailynote-mcp@0.1.0` tersedia di npm.
8
+
9
+ ## Hubungkan ke Codex
10
+
11
+ 1. Login ke DailyNote, buka **Profil → Token API → Buat token**, lalu salin token.
12
+ 2. Atur `DAILYNOTE_URL` dan `DAILYNOTE_TOKEN` di environment proses Codex.
13
+ Gunakan alamat HTTPS aplikasi DailyNote Anda, tanpa path tambahan. HTTP hanya
14
+ didukung untuk localhost/loopback saat development. Token berasal dari akun
15
+ Anda pada aplikasi tujuan tersebut.
16
+ 3. Gabungkan konfigurasi ini ke `~/.codex/config.toml`:
17
+
18
+ ```toml
19
+ [mcp_servers.dailynote-journal]
20
+ command = "npx"
21
+ args = ["-y", "dailynote-mcp@0.1.0"]
22
+ env_vars = ["DAILYNOTE_URL", "DAILYNOTE_TOKEN"]
23
+ enabled_tools = ["add_journal_entry"]
24
+ ```
25
+
26
+ 4. Tutup penuh dan buka ulang Codex setelah environment diisi. Buka chat baru.
27
+
28
+ `npx` mengunduh package beserta dependency pada penggunaan pertama, kemudian
29
+ menjalankan MCP. Tidak perlu menjalankan server MCP di terminal terpisah.
30
+ Jika Codex tidak menemukan `npx`, gunakan path absolut hasil `command -v npx`.
31
+
32
+ [Panduan pemasangan langkah demi langkah](./INSTALL_CODEX.md), termasuk cara
33
+ mengisi environment sesi macOS tanpa file token. Konfigurasi stdio mengikuti
34
+ [dokumentasi resmi OpenAI](https://developers.openai.com/codex/mcp/).
35
+
36
+ Contoh untuk Claude Desktop ada di `claude-desktop.example.json`. Client harus
37
+ mewarisi kedua environment variable saat dibuka; contoh tidak menyimpan token
38
+ atau mengandalkan interpolasi environment dalam JSON.
39
+
40
+ ## Tool yang tersedia
41
+
42
+ Saat ini ada satu tool: **`add_journal_entry`**.
43
+
44
+ ```json
45
+ {
46
+ "text": "Menyelesaikan pekerjaan dan dokumentasi hari ini.",
47
+ "date": "2026-11-05"
48
+ }
49
+ ```
50
+
51
+ - `text`: teks biasa, maksimum 20.000 unit UTF-16. Baris baru menjadi paragraf;
52
+ HTML dan Markdown disimpan sebagai teks literal.
53
+ - `date`: opsional, format `YYYY-MM-DD`. Default hari ini mengikuti timezone akun.
54
+ - `requestId`: opsional untuk entri baru; gunakan kembali ID yang sama saat retry.
55
+
56
+ Tool membuat jurnal tanggal tersebut bila belum ada, atau menambahkan teks di
57
+ akhir jurnal yang ada. Isi, format, dan media lama tetap dipertahankan. Tidak
58
+ menyediakan pembacaan/penghapusan jurnal, catatan, tugas, atau upload file.
59
+
60
+ Contoh permintaan di Codex:
61
+
62
+ > Gunakan DailyNote MCP untuk menambahkan "Tes koneksi dari Codex" ke jurnal hari ini.
63
+
64
+ Permintaan tersebut menulis jurnal akun pemilik token. Hasil sukses berisi
65
+ tanggal, status `saved`, dan `requestId`; isi jurnal tidak dikembalikan.
66
+
67
+ ## Token dan retry
68
+
69
+ Setiap pengguna memakai token pribadi dari akunnya sendiri. Server membaca
70
+ `DAILYNOTE_URL` dan `DAILYNOTE_TOKEN` dari environment; tidak memuat file `.env`.
71
+ Jangan kirim token ke chat atau memasukkannya ke konfigurasi publik.
72
+
73
+ **Buat token baru** menonaktifkan token lama. Setelah rotasi, perbarui environment
74
+ dan buka ulang client. **Cabut token** di profil menghentikan akses token tersebut.
75
+ Scope token saat ini hanya `journal:append`.
76
+
77
+ Jika hasil `outcome_unknown`, periksa jurnal di aplikasi atau ulangi dengan
78
+ **requestId, text, dan date yang sama**, termasuk tetap tanpa `date` bila sebelumnya
79
+ kosong. Tidak ada retry otomatis. Mengirim payload berbeda dengan ID yang sama
80
+ ditolak; mengganti token tidak menghilangkan deduplikasi akun.
81
+
82
+ Jika proses berhenti sebelum requestId diterima, periksa jurnal lewat aplikasi
83
+ sebelum mengirim ulang.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "dailynote-journal": {
4
+ "command": "npx",
5
+ "args": ["-y", "dailynote-mcp@0.1.0"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,6 @@
1
+ # Merge this block into your existing Codex config; do not replace that file.
2
+ [mcp_servers.dailynote-journal]
3
+ command = "npx"
4
+ args = ["-y", "dailynote-mcp@0.1.0"]
5
+ env_vars = ["DAILYNOTE_URL", "DAILYNOTE_TOKEN"]
6
+ enabled_tools = ["add_journal_entry"]
package/journal.mjs ADDED
@@ -0,0 +1,149 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { McpServer } from "@modelcontextprotocol/server";
4
+ import packageInfo from "./package.json" with { type: "json" };
5
+
6
+ export function readConfig(env) {
7
+ const origin = new URL(env.DAILYNOTE_URL ?? "");
8
+ if (
9
+ origin.username ||
10
+ origin.password ||
11
+ origin.search ||
12
+ origin.hash ||
13
+ origin.pathname !== "/" ||
14
+ !(
15
+ origin.protocol === "https:" ||
16
+ (origin.protocol === "http:" &&
17
+ ["localhost", "127.0.0.1", "[::1]"].includes(origin.hostname))
18
+ )
19
+ )
20
+ throw new Error(
21
+ "DAILYNOTE_URL must be an HTTPS origin or HTTP loopback origin.",
22
+ );
23
+ const token = env.DAILYNOTE_TOKEN ?? "";
24
+ if (!/^[A-Za-z0-9_-]{43,128}$/.test(token) || token.startsWith("replace-"))
25
+ throw new Error(
26
+ "DAILYNOTE_TOKEN must contain a personal API token from your DailyNote profile.",
27
+ );
28
+ return { endpoint: new URL("/api/mcp/journal-entry", origin), token };
29
+ }
30
+
31
+ const validDate = (value) => {
32
+ if (
33
+ typeof value !== "string" ||
34
+ !/^\d{4}-\d{2}-\d{2}$/.test(value) ||
35
+ Number(value.slice(0, 4)) < 1000
36
+ )
37
+ return false;
38
+ const date = new Date(`${value}T00:00:00Z`);
39
+ return (
40
+ Number.isFinite(date.valueOf()) && date.toISOString().slice(0, 10) === value
41
+ );
42
+ };
43
+
44
+ const safeCodes = new Set([
45
+ "unauthorized",
46
+ "forbidden",
47
+ "rate_limited",
48
+ "invalid_input",
49
+ "invalid_content",
50
+ "conflict",
51
+ "idempotency_conflict",
52
+ "outcome_unknown",
53
+ "payload_too_large",
54
+ "request_timeout",
55
+ "unsupported_media_type",
56
+ ]);
57
+
58
+ export function createJournalServer(config) {
59
+ const server = new McpServer({
60
+ name: packageInfo.name,
61
+ version: packageInfo.version,
62
+ });
63
+ server.registerTool(
64
+ "add_journal_entry",
65
+ {
66
+ description:
67
+ "Append plain text to the personal token owner's DailyNote journal. Creates the date if missing; preserves existing text and media. Date defaults to today in the account timezone. Never reads or returns journal content. On an uncertain result, retry identical text/date with the returned requestId; do not generate a new ID.",
68
+ inputSchema: z
69
+ .object({
70
+ text: z
71
+ .string()
72
+ .min(1)
73
+ .max(20_000)
74
+ .refine((text) => text.trim().length > 0)
75
+ .describe(
76
+ "Plain text to append. Line breaks become paragraphs; Markdown/HTML are stored as literal text.",
77
+ ),
78
+ date: z
79
+ .string()
80
+ .refine(validDate)
81
+ .optional()
82
+ .describe(
83
+ "Optional calendar date YYYY-MM-DD in the account timezone.",
84
+ ),
85
+ requestId: z
86
+ .uuid()
87
+ .optional()
88
+ .describe(
89
+ "Leave empty for a new entry. Reuse the returned ID with identical text/date to safely retry an uncertain request.",
90
+ ),
91
+ })
92
+ .strict(),
93
+ annotations: {
94
+ readOnlyHint: false,
95
+ destructiveHint: false,
96
+ idempotentHint: false,
97
+ openWorldHint: true,
98
+ },
99
+ },
100
+ async ({ text, date, requestId = randomUUID() }) => {
101
+ let result;
102
+ let retryAfter;
103
+ try {
104
+ const response = await fetch(config.endpoint, {
105
+ method: "POST",
106
+ redirect: "error",
107
+ signal: AbortSignal.timeout(30_000),
108
+ headers: {
109
+ Authorization: `Bearer ${config.token}`,
110
+ "Content-Type": "application/json",
111
+ },
112
+ body: JSON.stringify({ text, ...(date ? { date } : {}), requestId }),
113
+ });
114
+ const body = await response.json();
115
+ if (
116
+ response.ok &&
117
+ body.ok === true &&
118
+ body.data?.status === "saved" &&
119
+ validDate(body.data.date) &&
120
+ body.data.requestId === requestId
121
+ ) {
122
+ result = { date: body.data.date, status: "saved", requestId };
123
+ } else {
124
+ const code = safeCodes.has(body.error?.code)
125
+ ? body.error.code
126
+ : "outcome_unknown";
127
+ retryAfter = Number(response.headers.get("Retry-After"));
128
+ result = { status: "error", code, requestId };
129
+ }
130
+ } catch {
131
+ // A timed-out or lost response may already have committed. Never retry
132
+ // automatically or include remote diagnostics, URLs, token or input text.
133
+ result = { status: "error", code: "outcome_unknown", requestId };
134
+ }
135
+ if (
136
+ result.code === "rate_limited" &&
137
+ Number.isInteger(retryAfter) &&
138
+ retryAfter > 0
139
+ )
140
+ result.retryAfterSeconds = Math.min(retryAfter, 3600);
141
+ return {
142
+ content: [{ type: "text", text: JSON.stringify(result) }],
143
+ structuredContent: result,
144
+ ...(result.status === "error" ? { isError: true } : {}),
145
+ };
146
+ },
147
+ );
148
+ return server;
149
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "dailynote-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Connect AI assistants to your DailyNote account through MCP.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "bin": {
8
+ "dailynote-mcp": "server.mjs"
9
+ },
10
+ "files": [
11
+ "server.mjs",
12
+ "journal.mjs",
13
+ "INSTALL_CODEX.md",
14
+ "codex.example.toml",
15
+ "claude-desktop.example.json"
16
+ ],
17
+ "keywords": [
18
+ "dailynote",
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "journal"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org/"
26
+ },
27
+ "engines": {
28
+ "node": ">=24 <25"
29
+ },
30
+ "packageManager": "pnpm@11.10.0",
31
+ "scripts": {
32
+ "start": "node server.mjs",
33
+ "test": "node --test server.test.mjs",
34
+ "test:package": "node --test package.test.mjs"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/server": "2.4.0",
38
+ "zod": "4.6.7"
39
+ },
40
+ "devDependencies": {
41
+ "@modelcontextprotocol/client": "2.4.0"
42
+ }
43
+ }
package/server.mjs ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
3
+ import { createJournalServer, readConfig } from "./journal.mjs";
4
+
5
+ try {
6
+ const server = createJournalServer(readConfig(process.env));
7
+ const close = async () => {
8
+ await server.close();
9
+ };
10
+ process.once("SIGINT", close);
11
+ process.once("SIGTERM", close);
12
+ await server.connect(new StdioServerTransport());
13
+ } catch {
14
+ // stdout is exclusively the MCP protocol; diagnostics never include secrets.
15
+ console.error(
16
+ "DailyNote MCP could not start. Set DAILYNOTE_URL and DAILYNOTE_TOKEN in the client environment and check installed dependencies.",
17
+ );
18
+ process.exitCode = 1;
19
+ }