@iflow-mcp/dearcloud09-logseq-mcp 1.0.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.
package/README.md ADDED
@@ -0,0 +1,302 @@
1
+ # Logseq MCP Server
2
+
3
+ [![License: Polyform Noncommercial](https://img.shields.io/badge/License-Polyform%20NC-red.svg)](https://polyformproject.org/licenses/noncommercial/1.0.0)
4
+ [![Node.js](https://img.shields.io/badge/Node.js-18%2B-green.svg)](https://nodejs.org/)
5
+ [![MCP](https://img.shields.io/badge/MCP-Compatible-purple.svg)](https://modelcontextprotocol.io/)
6
+
7
+ > **Let AI read and write your Logseq graph directly via MCP**
8
+
9
+ [한국어 README](README.ko.md)
10
+
11
+ Talk to Claude and say "add this to today's journal", "find what I did last week", "show me all pages linked to this one" - and it just works.
12
+
13
+ ---
14
+
15
+ ## Why This?
16
+
17
+ **Problem**: Logseq is a great PKM tool, but integrating with AI assistants requires constant copy-pasting.
18
+
19
+ **Solution**: With this MCP server:
20
+ - Claude **directly** writes to your journal (no copy-paste)
21
+ - **Search and summarize** past entries (maintain context)
22
+ - **Navigate connections** between pages (backlinks, graph)
23
+ - **Auto-generate** daily journals with templates
24
+
25
+ ```
26
+ You: "Summarize today's meeting notes and add them to my journal"
27
+ Claude: [writes directly to Logseq via logseq-mcp]
28
+ "Done! Added to today's journal. Anything else?"
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Is This For You?
34
+
35
+ ### Good fit if you...
36
+
37
+ - Use Logseq as your **primary PKM**
38
+ - Use **Claude Code or Claude Desktop** regularly
39
+ - Want to **delegate** note management to AI
40
+ - Use **local file-based** Logseq (not Logseq Sync)
41
+
42
+ ### Not for you if...
43
+
44
+ - Using **Logseq Sync** (requires local file access)
45
+ - **Obsidian** user (different MCP server needed)
46
+ - Have sensitive info in notes and **uncomfortable with AI access**
47
+ - Use **org-mode** instead of Markdown (not yet supported)
48
+
49
+ ---
50
+
51
+ ## Features
52
+
53
+ | Feature | Description |
54
+ |---------|-------------|
55
+ | **Page CRUD** | Create, read, update, delete pages + property support |
56
+ | **Search** | Full-text search + tag/folder filtering |
57
+ | **Graph Navigation** | Links, backlinks, page relationship traversal |
58
+ | **Journal** | Access today's/specific date journals + templates |
59
+ | **Content Logging** | Log articles, books, movies, exhibitions to journal |
60
+ | **Resources** | Expose graph pages as MCP resources |
61
+
62
+ ---
63
+
64
+ ## Quick Start
65
+
66
+ ### 1. Install
67
+
68
+ ```bash
69
+ git clone https://github.com/dearcloud09/logseq-mcp.git
70
+ cd logseq-mcp
71
+ npm install
72
+ npm run build
73
+ ```
74
+
75
+ ### 2. Configure
76
+
77
+ **Claude Code** (`~/.claude/settings.json`):
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "logseq": {
83
+ "command": "node",
84
+ "args": ["/path/to/logseq-mcp/dist/index.js"],
85
+ "env": {
86
+ "LOGSEQ_GRAPH_PATH": "/path/to/your/logseq/graph"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "logseq": {
99
+ "command": "node",
100
+ "args": ["/path/to/logseq-mcp/dist/index.js"],
101
+ "env": {
102
+ "LOGSEQ_GRAPH_PATH": "/path/to/your/logseq/graph"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### 3. Verify
110
+
111
+ Ask Claude: "Show me my Logseq page list"
112
+
113
+ ---
114
+
115
+ ## Available Tools
116
+
117
+ | Tool | Description |
118
+ |------|-------------|
119
+ | `list_pages` | List all pages with metadata (tags, links, backlinks) |
120
+ | `read_page` | Read page content and metadata |
121
+ | `create_page` | Create new page (with property support) |
122
+ | `update_page` | Update page content |
123
+ | `delete_page` | Delete a page |
124
+ | `append_to_page` | Append content to existing page |
125
+ | `search_pages` | Search by content/title + tag/folder filters |
126
+ | `get_backlinks` | Get pages that reference a specific page |
127
+ | `get_graph` | Get page connection graph data |
128
+ | `get_journal` | Get today's or specific date's journal |
129
+ | `create_journal` | Create journal with optional template |
130
+ | `add_article` | Add article to journal (title, summary, tags, URL, highlights) |
131
+ | `add_book` | Add book to journal (title, author, tags, memo) |
132
+ | `add_movie` | Add movie to journal (title, director, memo) |
133
+ | `add_exhibition` | Add exhibition to journal (title, venue, artist, memo) |
134
+
135
+ ---
136
+
137
+ ## Usage Examples
138
+
139
+ ```
140
+ "Show me today's journal"
141
+ "Add this content to 'Project A' page: ..."
142
+ "Find all pages with #meeting tag"
143
+ "What pages are connected to my Goals page?"
144
+ "Search for TODO items in last week's journals"
145
+ "Create a new page called 'Reading List'"
146
+ "Summarize our conversation and save it as an article in my journal"
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Logseq Graph Structure
152
+
153
+ ```
154
+ your-graph/
155
+ journals/ # Daily journals (2024_01_15.md format)
156
+ pages/ # Regular pages
157
+ logseq/ # Logseq settings
158
+ whiteboards/ # Whiteboards
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Security
164
+
165
+ - Path traversal protection (graph-only access)
166
+ - Symlink/hardlink attack prevention
167
+ - Input validation and sanitization
168
+ - DoS protection (content size limits)
169
+ - Error message sanitization
170
+
171
+ ---
172
+
173
+ ## Troubleshooting
174
+
175
+ ### "LOGSEQ_GRAPH_PATH environment variable is required"
176
+
177
+ Set `LOGSEQ_GRAPH_PATH` in your configuration file.
178
+
179
+ ### MCP server not recognized by Claude
180
+
181
+ 1. Restart Claude Code/Desktop
182
+ 2. Verify path is absolute (`/Users/...` format)
183
+ 3. Ensure `npm run build` was executed
184
+
185
+ ### Pages not showing up
186
+
187
+ - Check if `.md` files exist in `journals/` or `pages/`
188
+ - Verify you're using **local graph** (not Logseq Sync)
189
+
190
+ ### org-mode files not reading
191
+
192
+ Currently **Markdown only**. org-mode support planned for future.
193
+
194
+ ---
195
+
196
+ ## Korean-Specific Features
197
+
198
+ This project includes features optimized for Korean users:
199
+
200
+ ### Daily Automation (Optional)
201
+
202
+ Auto-generate daily journal with weather (Korea only - uses Naver Weather) and diary template.
203
+
204
+ 1. Copy and edit plist file:
205
+ ```bash
206
+ cp com.logseq.daily-automation.plist.example ~/Library/LaunchAgents/com.logseq.daily-automation.plist
207
+ # Edit the file to replace /path/to/ with your actual paths
208
+ ```
209
+
210
+ 2. Load launchd agent:
211
+ ```bash
212
+ launchctl load ~/Library/LaunchAgents/com.logseq.daily-automation.plist
213
+ ```
214
+
215
+ 3. Test manually:
216
+ ```bash
217
+ ./run-daily-automation.sh
218
+ ```
219
+
220
+ Generated template structure:
221
+ ```markdown
222
+ - [[일기]]
223
+ - [[날씨]]
224
+ - {weather info}
225
+ - [[오늘의 일기]]
226
+ - [[행복도]]
227
+ - [[오늘의 행복]]
228
+ - [[오늘의 컨디션]]
229
+ - [[수면]]
230
+ - 취침:
231
+ - 기상:
232
+ - 질: /5
233
+ - [[오늘의 생각]]
234
+ - [[Tasks]]
235
+ - TODO
236
+ - [[오늘 잘 해낸 일]]
237
+ - [[TIL]]
238
+ ```
239
+
240
+ See [Korean README](README.ko.md) for more details.
241
+
242
+ ### Cultural Content Structure
243
+
244
+ `add_book`, `add_movie`, `add_exhibition` tools use Korean wikilink structure (`[[문화]]`). Customize the templates in `src/index.ts` for your language.
245
+
246
+ ---
247
+
248
+ ## Development
249
+
250
+ ```bash
251
+ # Development mode (watch)
252
+ npm run dev
253
+
254
+ # TypeScript build
255
+ npm run build
256
+
257
+ # Production run
258
+ npm start
259
+ ```
260
+
261
+ ### Project Structure
262
+
263
+ ```
264
+ src/
265
+ index.ts # MCP server entry point, tool handlers
266
+ types.ts # TypeScript type definitions
267
+ graph.ts # Graph filesystem operations
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Contributing
273
+
274
+ Issues and PRs welcome!
275
+
276
+ 1. Fork this repo
277
+ 2. Create feature branch (`git checkout -b feature/amazing`)
278
+ 3. Commit changes (`git commit -m 'Add amazing feature'`)
279
+ 4. Push to branch (`git push origin feature/amazing`)
280
+ 5. Open a Pull Request
281
+
282
+ ### Ideas for contribution
283
+
284
+ - [ ] org-mode support
285
+ - [ ] Logseq property search
286
+ - [ ] Whiteboard support
287
+ - [ ] Better graph visualization data
288
+ - [ ] i18n for templates
289
+
290
+ ---
291
+
292
+ ## License
293
+
294
+ [Polyform Noncommercial 1.0.0](LICENSE) - Free for personal and noncommercial use.
295
+
296
+ ---
297
+
298
+ ## Related
299
+
300
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
301
+ - [Logseq](https://logseq.com/)
302
+ - [Claude Code](https://claude.ai/code)
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Logseq Daily Note 자동 생성 스크립트
3
+ * 매일 실행하여 오늘의 일기 템플릿 + 날씨 정보를 자동으로 추가
4
+ */
5
+
6
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
7
+ import { existsSync } from 'node:fs';
8
+ import { join, dirname } from 'node:path';
9
+ import { WeatherScraper } from './dist/weather-scraper.js';
10
+
11
+ // 설정
12
+ const LOGSEQ_GRAPH_PATH = process.env.LOGSEQ_GRAPH_PATH;
13
+ if (!LOGSEQ_GRAPH_PATH) {
14
+ console.error('Error: LOGSEQ_GRAPH_PATH environment variable is required');
15
+ process.exit(1);
16
+ }
17
+ const JOURNALS_PATH = join(LOGSEQ_GRAPH_PATH, 'journals');
18
+
19
+ /**
20
+ * KST 기준 오늘 날짜 가져오기
21
+ */
22
+ function getKSTDate() {
23
+ const now = new Date();
24
+ const kstOffset = 9 * 60; // KST는 UTC+9
25
+ const utcTime = now.getTime() + now.getTimezoneOffset() * 60000;
26
+ return new Date(utcTime + kstOffset * 60000);
27
+ }
28
+
29
+ /**
30
+ * 날짜를 저널 파일명 형식으로 변환 (yyyy_MM_dd.md)
31
+ */
32
+ function formatDateForFilename(date) {
33
+ const year = date.getFullYear();
34
+ const month = String(date.getMonth() + 1).padStart(2, '0');
35
+ const day = String(date.getDate()).padStart(2, '0');
36
+ return `${year}_${month}_${day}.md`;
37
+ }
38
+
39
+ /**
40
+ * 일기 템플릿 생성 (날씨 정보 포함)
41
+ */
42
+ async function generateTemplate(weatherLines) {
43
+ const lines = ['- [[일기]]'];
44
+
45
+ // 날씨 섹션
46
+ lines.push('\t- [[날씨]]');
47
+ if (weatherLines && weatherLines.length > 0) {
48
+ for (const line of weatherLines) {
49
+ lines.push(`\t\t- ${line}`);
50
+ }
51
+ } else {
52
+ lines.push('\t\t- 날씨 정보 없음');
53
+ }
54
+
55
+ // 오늘의 일기 섹션
56
+ lines.push('\t- [[오늘의 일기]]');
57
+ lines.push('\t\t- [[행복도]]');
58
+ lines.push('\t\t\t- [[오늘의 행복]]');
59
+ lines.push('\t\t- [[오늘의 컨디션]]');
60
+ lines.push('\t\t\t- [[수면]]');
61
+ lines.push('\t\t\t\t- 취침:');
62
+ lines.push('\t\t\t\t- 기상:');
63
+ lines.push('\t\t\t\t- 질: /5');
64
+ lines.push('\t\t- [[오늘의 생각]]');
65
+
66
+ // Tasks 섹션
67
+ lines.push('\t- [[Tasks]]');
68
+ lines.push('\t\t- TODO ');
69
+ lines.push('\t\t- [[오늘 잘 해낸 일]]');
70
+
71
+ // TIL 섹션
72
+ lines.push('\t- [[TIL]]');
73
+
74
+ return lines.join('\n');
75
+ }
76
+
77
+ /**
78
+ * 기존 저널에 템플릿이 있는지 확인
79
+ */
80
+ function hasTemplate(content) {
81
+ return content.includes('[[일기]]') || content.includes('[[날씨]]');
82
+ }
83
+
84
+ /**
85
+ * 메인 함수
86
+ */
87
+ async function addTodayDairy() {
88
+ const today = getKSTDate();
89
+ const filename = formatDateForFilename(today);
90
+ const filePath = join(JOURNALS_PATH, filename);
91
+
92
+ console.log(`[${new Date().toISOString()}] Logseq Daily Note 자동 생성 시작`);
93
+ console.log(`대상 파일: ${filePath}`);
94
+
95
+ try {
96
+ // journals 폴더 확인
97
+ if (!existsSync(JOURNALS_PATH)) {
98
+ await mkdir(JOURNALS_PATH, { recursive: true });
99
+ console.log(`journals 폴더 생성: ${JOURNALS_PATH}`);
100
+ }
101
+
102
+ // 기존 파일 내용 확인
103
+ let existingContent = '';
104
+ if (existsSync(filePath)) {
105
+ existingContent = await readFile(filePath, 'utf-8');
106
+
107
+ // 이미 템플릿이 있으면 건너뜀
108
+ if (hasTemplate(existingContent)) {
109
+ console.log('이미 일기 템플릿이 존재합니다. 건너뜁니다.');
110
+ return { success: true, message: '이미 템플릿 존재' };
111
+ }
112
+ }
113
+
114
+ // 날씨 정보 가져오기 (3회 재시도)
115
+ let weatherLines = null;
116
+ const weatherScraper = new WeatherScraper();
117
+
118
+ for (let attempt = 1; attempt <= 3; attempt++) {
119
+ try {
120
+ console.log(`날씨 정보 가져오기 시도 ${attempt}/3...`);
121
+ const weather = await weatherScraper.getWeatherForDongcheon();
122
+ if (weather) {
123
+ weatherLines = weatherScraper.formatWeatherForLogseq(weather);
124
+ console.log('날씨 정보 수집 성공');
125
+ break;
126
+ }
127
+ } catch (error) {
128
+ console.error(`날씨 수집 실패 (시도 ${attempt}):`, error.message);
129
+ if (attempt < 3) {
130
+ const waitMs = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
131
+ await new Promise(resolve => setTimeout(resolve, waitMs));
132
+ }
133
+ }
134
+ }
135
+
136
+ // 템플릿 생성
137
+ const template = await generateTemplate(weatherLines);
138
+
139
+ // 파일 작성
140
+ const newContent = existingContent
141
+ ? existingContent + '\n' + template
142
+ : template;
143
+
144
+ await writeFile(filePath, newContent, 'utf-8');
145
+ console.log('Daily Note 템플릿 생성 완료!');
146
+ console.log(`파일: ${filePath}`);
147
+
148
+ return { success: true, message: '템플릿 생성 완료' };
149
+
150
+ } catch (error) {
151
+ console.error('오류 발생:', error.message);
152
+ return { success: false, message: error.message };
153
+ }
154
+ }
155
+
156
+ // 실행
157
+ addTodayDairy()
158
+ .then(result => {
159
+ console.log('결과:', result);
160
+ process.exit(result.success ? 0 : 1);
161
+ })
162
+ .catch(error => {
163
+ console.error('치명적 오류:', error);
164
+ process.exit(1);
165
+ });
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.logseq.daily-automation</string>
7
+
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string>/bin/bash</string>
11
+ <string>/path/to/logseq-mcp/run-daily-automation.sh</string>
12
+ </array>
13
+
14
+ <key>StartCalendarInterval</key>
15
+ <dict>
16
+ <key>Hour</key>
17
+ <integer>6</integer>
18
+ <key>Minute</key>
19
+ <integer>0</integer>
20
+ </dict>
21
+
22
+ <key>StandardOutPath</key>
23
+ <string>/path/to/logseq-mcp/launchd.log</string>
24
+
25
+ <key>StandardErrorPath</key>
26
+ <string>/path/to/logseq-mcp/launchd.error.log</string>
27
+
28
+ <key>RunAtLoad</key>
29
+ <false/>
30
+
31
+ <key>EnvironmentVariables</key>
32
+ <dict>
33
+ <key>LOGSEQ_GRAPH_PATH</key>
34
+ <string>/path/to/your/logseq/graph</string>
35
+ <key>WEATHER_LOCATION</key>
36
+ <string>서울</string>
37
+ <key>PATH</key>
38
+ <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin</string>
39
+ </dict>
40
+ </dict>
41
+ </plist>
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Logseq Graph Service
3
+ * Handles file system operations for Logseq graphs
4
+ */
5
+ import type { Page, PageMetadata, SearchResult, Graph } from './types.js';
6
+ export declare class GraphService {
7
+ private graphPath;
8
+ private pagesPath;
9
+ private journalsPath;
10
+ constructor(graphPath: string);
11
+ /**
12
+ * List all pages in the graph
13
+ */
14
+ listPages(folder?: string): Promise<PageMetadata[]>;
15
+ private collectAllPages;
16
+ /**
17
+ * Read a page by path or name
18
+ */
19
+ readPage(pathOrName: string): Promise<Page>;
20
+ /**
21
+ * Validate page name (whitelist approach for security)
22
+ */
23
+ private validatePageName;
24
+ /**
25
+ * Create a new page
26
+ */
27
+ createPage(name: string, content: string, properties?: Record<string, unknown>): Promise<Page>;
28
+ /**
29
+ * Update an existing page
30
+ */
31
+ updatePage(pathOrName: string, content: string, properties?: Record<string, unknown>): Promise<Page>;
32
+ /**
33
+ * Delete a page
34
+ */
35
+ deletePage(pathOrName: string): Promise<void>;
36
+ /**
37
+ * Append content to a page
38
+ */
39
+ appendToPage(pathOrName: string, content: string): Promise<Page>;
40
+ /**
41
+ * Search pages
42
+ */
43
+ searchPages(query: string, options?: {
44
+ tags?: string[];
45
+ folder?: string;
46
+ }): Promise<SearchResult[]>;
47
+ /**
48
+ * Get backlinks for a page
49
+ */
50
+ getBacklinks(pathOrName: string): Promise<PageMetadata[]>;
51
+ /**
52
+ * Get graph data
53
+ */
54
+ getGraph(options?: {
55
+ center?: string;
56
+ depth?: number;
57
+ }): Promise<Graph>;
58
+ /**
59
+ * Get journal page for a date
60
+ */
61
+ getJournalPage(date?: string): Promise<Page | null>;
62
+ /**
63
+ * Create journal page
64
+ */
65
+ createJournalPage(date?: string, template?: string): Promise<Page>;
66
+ /**
67
+ * Append content to journal page (creates if doesn't exist)
68
+ */
69
+ appendToJournalPage(date?: string, content?: string): Promise<Page>;
70
+ /**
71
+ * Validate content size (prevents DoS attacks)
72
+ */
73
+ private validateContentSize;
74
+ /**
75
+ * Check if path is a symlink (prevents symlink escape attacks)
76
+ */
77
+ private checkSymlink;
78
+ /**
79
+ * Check if path is a regular file (prevents symlink/hardlink attacks)
80
+ * More strict than checkSymlink - also detects hardlinks to external files
81
+ */
82
+ private checkRegularFile;
83
+ /**
84
+ * Validate that a path is within the graph directory (prevents path traversal attacks)
85
+ */
86
+ private validatePath;
87
+ private resolvePath;
88
+ private extractTags;
89
+ private extractLinks;
90
+ private parseProperties;
91
+ /**
92
+ * Validate and sanitize properties (prevents injection attacks)
93
+ */
94
+ private validateProperties;
95
+ private buildContent;
96
+ private getTodayString;
97
+ }