@iflow-mcp/h30190_trilium_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/.env +2 -0
- package/.env.example +2 -0
- package/README.md +205 -0
- package/dist/index.js +187 -0
- package/dist/trilium-client.js +170 -0
- package/dist/types.js +1 -0
- package/package.json +30 -0
- package/src/index.ts +214 -0
- package/src/trilium-client.ts +184 -0
- package/src/types.ts +45 -0
- package/trilium_mcp_process.log +26 -0
- package/tsconfig.json +15 -0
package/.env
ADDED
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Trilium MCP Server
|
|
2
|
+
|
|
3
|
+
[English](#english) | [繁體中文](#traditional-chinese)
|
|
4
|
+
|
|
5
|
+
<a name="english"></a>
|
|
6
|
+
|
|
7
|
+
## English
|
|
8
|
+
|
|
9
|
+
> [!WARNING]
|
|
10
|
+
> **Disclaimer & Backup Warning**
|
|
11
|
+
>
|
|
12
|
+
> This tool (MCP Server) has permissions to **write, modify** your Trilium notes.
|
|
13
|
+
> **Please ensure you backup your Trilium library before performing any operations with this tool.**
|
|
14
|
+
>
|
|
15
|
+
> The author is not responsible for any data loss, corruption, or unexpected consequences resulting from the use of this tool. Use at your own risk.
|
|
16
|
+
|
|
17
|
+
This is a Model Context Protocol (MCP) Server designed for [Trilium Notes](https://github.com/zadam/trilium). It allows AI agents (such as Claude Desktop, Gemini CLI, etc.) to interact directly with your Trilium notes library to search, read, write, and organize notes.
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- **Search Notes (`search_notes`)**: Search notes by keywords.
|
|
22
|
+
- **Read Note (`read_note`)**: Read note content and metadata.
|
|
23
|
+
- **Create Note (`create_note`)**: Create new notes (supports text, code, etc.).
|
|
24
|
+
- **Update Note (`update_note`)**: Modify existing note content or attributes.
|
|
25
|
+
- **Move Note (`move_note`)**: Move notes to a new parent.
|
|
26
|
+
- **Manage Attributes (`manage_attributes`)**: Add, modify, or delete note attributes.
|
|
27
|
+
|
|
28
|
+
### Installation & Setup
|
|
29
|
+
|
|
30
|
+
#### 1. Install Dependencies
|
|
31
|
+
|
|
32
|
+
Ensure you have Node.js installed (v16+ recommended).
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### 2. Configure Environment Variables
|
|
39
|
+
|
|
40
|
+
Create a `.env` file in the project root (see `.env.example`):
|
|
41
|
+
|
|
42
|
+
```env
|
|
43
|
+
TRILIUM_ETAPI_URL=http://localhost:8080/etapi
|
|
44
|
+
TRILIUM_ETAPI_TOKEN=your_token_here
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `TRILIUM_ETAPI_URL`: Your Trilium ETAPI URL. Usually your Trilium URL appended with `/etapi`.
|
|
48
|
+
- `TRILIUM_ETAPI_TOKEN`: The token created in Trilium's `Options` -> `ETAPI`.
|
|
49
|
+
|
|
50
|
+
#### 3. Build Project
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Usage
|
|
57
|
+
|
|
58
|
+
#### Configuration for MCP Client
|
|
59
|
+
|
|
60
|
+
For Claude Desktop or Gemini CLI, add the following to your configuration file (e.g., `claude_desktop_config.json` or `settings.json`):
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"trilium": {
|
|
66
|
+
"command": "node",
|
|
67
|
+
"args": ["/path/to/your/Trilium_MCP/dist/index.js"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Note**:
|
|
74
|
+
- Replace `/path/to/your/Trilium_MCP/dist/index.js` with the actual absolute path on your machine.
|
|
75
|
+
- The program automatically reads the `.env` file from the project directory, so you don't need to repeat environment variables in the config file.
|
|
76
|
+
|
|
77
|
+
### Development
|
|
78
|
+
|
|
79
|
+
- **Build**: `npm run build`
|
|
80
|
+
- **Test Connection**: `npx ts-node test-trilium.ts` (Requires `.env` setup)
|
|
81
|
+
|
|
82
|
+
### License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
85
|
+
|
|
86
|
+
### About the Author
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
- Author: HJPLUS.DESIGN HAO.H
|
|
91
|
+
- Website: [HJPLUS.DESIGN](https://www.hjplusdesign.com/)
|
|
92
|
+
- Facebook Page: [HJPLUS.DESIGN](https://www.facebook.com/HJPLUS.DESIGN/)
|
|
93
|
+
- Email: info@hjplusdesign.com
|
|
94
|
+
|
|
95
|
+
We are external R&D partners for the design, architecture, and manufacturing industries. We specialize in helping companies without internal technical teams to implement digital workflows, tools, and AI, automating your knowledge and operational processes to supplement your team's skill upgrades.
|
|
96
|
+
We specialize in solving the following situations:
|
|
97
|
+
- Companies with design and manufacturing capabilities but lacking tools and processes for technical integration.
|
|
98
|
+
- Teams without technical personnel or teams that need automation or data integration.
|
|
99
|
+
- Complex projects with multiple data formats but lacking integration experience.
|
|
100
|
+
- Wanting to implement AI or BIM but not knowing where to start.
|
|
101
|
+
- Needing project-based digital consulting or tool development support.
|
|
102
|
+
- Needing complex geometric analysis modeling or BIM project consulting.
|
|
103
|
+
For more digital transformation consulting and service details, please contact us.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
<a name="traditional-chinese"></a>
|
|
108
|
+
|
|
109
|
+
## 繁體中文 (Traditional Chinese)
|
|
110
|
+
|
|
111
|
+
> [!WARNING]
|
|
112
|
+
> **免責聲明與備份提醒**
|
|
113
|
+
>
|
|
114
|
+
> 本工具 (MCP Server) 具有對您的 Trilium 筆記庫進行**寫入、修改**的權限。
|
|
115
|
+
> **在使用本工具進行任何操作之前,請務必備份您的 Trilium 筆記庫。**
|
|
116
|
+
>
|
|
117
|
+
> 作者不對因使用本工具而導致的任何資料遺失、損壞或不可預期的後果負責。使用者需自行承擔使用風險。
|
|
118
|
+
|
|
119
|
+
這是一個為 [Trilium Notes](https://github.com/zadam/trilium) 設計的 Model Context Protocol (MCP) Server。它允許 AI 代理 (如 Claude Desktop, Gemini CLI 等) 直接與您的 Trilium 筆記庫互動,執行搜尋、讀取、寫入和整理筆記等操作。
|
|
120
|
+
|
|
121
|
+
### 功能
|
|
122
|
+
|
|
123
|
+
- **搜尋筆記 (`search_notes`)**: 透過關鍵字搜尋筆記。
|
|
124
|
+
- **讀取筆記 (`read_note`)**: 讀取筆記的內容與屬性 (Metadata)。
|
|
125
|
+
- **建立筆記 (`create_note`)**: 建立新的筆記 (支援文字、程式碼等各種類型)。
|
|
126
|
+
- **更新筆記 (`update_note`)**: 修改現有筆記的內容或屬性。
|
|
127
|
+
- **移動筆記 (`move_note`)**: 將筆記移動到新的父節點下。
|
|
128
|
+
- **管理屬性 (`manage_attributes`)**: 新增、修改或刪除筆記的屬性 (Attributes)。
|
|
129
|
+
|
|
130
|
+
### 安裝與設定
|
|
131
|
+
|
|
132
|
+
#### 1. 安裝依賴
|
|
133
|
+
|
|
134
|
+
確保您已安裝 Node.js (建議 v16 以上)。
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm install
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### 2. 設定環境變數
|
|
141
|
+
|
|
142
|
+
在專案根目錄建立 `.env` 檔案 (可參考 `.env.example`):
|
|
143
|
+
|
|
144
|
+
```env
|
|
145
|
+
TRILIUM_ETAPI_URL=http://localhost:8080/etapi
|
|
146
|
+
TRILIUM_ETAPI_TOKEN=your_token_here
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `TRILIUM_ETAPI_URL`: 您的 Trilium ETAPI 網址。通常是您的 Trilium 網址加上 `/etapi`。
|
|
150
|
+
- `TRILIUM_ETAPI_TOKEN`: 在 Trilium 的 `Options` -> `ETAPI` 中建立的 Token。
|
|
151
|
+
|
|
152
|
+
#### 3. 編譯專案
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm run build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 使用方法
|
|
159
|
+
|
|
160
|
+
#### 在 MCP Client 中設定
|
|
161
|
+
|
|
162
|
+
以 Claude Desktop 或 Gemini CLI 為例,請在設定檔 (如 `claude_desktop_config.json` 或 `settings.json`) 中加入以下設定:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"mcpServers": {
|
|
167
|
+
"trilium": {
|
|
168
|
+
"command": "node",
|
|
169
|
+
"args": ["/path/to/your/Trilium_MCP/dist/index.js"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**注意**:
|
|
176
|
+
- 請將 `/path/to/your/Trilium_MCP/dist/index.js` 替換為您實際的檔案路徑。
|
|
177
|
+
- 程式會自動讀取專案目錄下的 `.env` 檔案,因此不需要在設定檔中重複填寫環境變數。
|
|
178
|
+
|
|
179
|
+
### 開發
|
|
180
|
+
|
|
181
|
+
- **編譯**: `npm run build`
|
|
182
|
+
- **測試連線**: `npx ts-node test-trilium.ts` (需先設定好 `.env`)
|
|
183
|
+
|
|
184
|
+
### 授權
|
|
185
|
+
|
|
186
|
+
MIT
|
|
187
|
+
|
|
188
|
+
### 關於作者
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
- 作者: 加號設計數位工程有限公司 HAO.H
|
|
193
|
+
- 網站: [加號設計數位工程有限公司](https://www.hjplusdesign.com/)
|
|
194
|
+
- 粉絲專頁: [加號設計數位工程有限公司](https://www.facebook.com/HJPLUS.DESIGN/)
|
|
195
|
+
- 電子郵件: info@hjplusdesign.com
|
|
196
|
+
|
|
197
|
+
我們是設計、建築與製造產業的外部研發夥伴,專門協助缺乏內部技術團隊的公司導入數位工作流程、工具與 AI,自動化你的知識與作業流程,以補足團隊技能升級的能量。
|
|
198
|
+
我們專門解決以下情況:
|
|
199
|
+
- 公司有設計與製造能量,但缺乏技術整合的工具與流程
|
|
200
|
+
- 團隊中沒技術人員或團隊,卻需要自動化或資料串接
|
|
201
|
+
- 專案複雜、資料格式多,但缺乏整合經驗
|
|
202
|
+
- 想導入 AI 或 BIM,但不知道從哪開始
|
|
203
|
+
- 需要專案型的數位顧問或工具開發支援
|
|
204
|
+
- 需要處理複雜幾何分析建模或BIM專案顧問
|
|
205
|
+
更多數位轉型諮詢與服務內容歡迎與我們聯絡。
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
7
|
+
import dotenv from "dotenv";
|
|
8
|
+
import { TriliumClient } from "./trilium-client.js";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
// Load .env from the project root (assuming dist/index.js is one level deep)
|
|
14
|
+
dotenv.config({ path: path.resolve(__dirname, "../.env") });
|
|
15
|
+
const TRILIUM_ETAPI_URL = process.env.TRILIUM_ETAPI_URL;
|
|
16
|
+
const TRILIUM_ETAPI_TOKEN = process.env.TRILIUM_ETAPI_TOKEN;
|
|
17
|
+
const TEST_MODE = process.env.TEST_MODE === "true";
|
|
18
|
+
if (!TRILIUM_ETAPI_URL || !TRILIUM_ETAPI_TOKEN) {
|
|
19
|
+
console.error("Error: TRILIUM_ETAPI_URL and TRILIUM_ETAPI_TOKEN must be set in .env");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const trilium = new TriliumClient(TRILIUM_ETAPI_URL, TRILIUM_ETAPI_TOKEN, TEST_MODE);
|
|
23
|
+
const server = new Server({
|
|
24
|
+
name: "trilium-mcp",
|
|
25
|
+
version: "V1.0.0(251124_HJPLUS.DESIGN)",
|
|
26
|
+
}, {
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
// Tool Schemas
|
|
32
|
+
const searchNotesSchema = z.object({
|
|
33
|
+
query: z.string().describe("The search query string"),
|
|
34
|
+
});
|
|
35
|
+
const readNoteSchema = z.object({
|
|
36
|
+
noteId: z.string().describe("The ID of the note to read"),
|
|
37
|
+
});
|
|
38
|
+
const createNoteSchema = z.object({
|
|
39
|
+
parentNoteId: z.string().describe("The ID of the parent note"),
|
|
40
|
+
title: z.string().describe("The title of the new note"),
|
|
41
|
+
type: z.string().default("text").describe("The type of the note (e.g., text, code)"),
|
|
42
|
+
content: z.string().optional().describe("The initial content of the note"),
|
|
43
|
+
mime: z.string().optional().describe("MIME type for the note"),
|
|
44
|
+
});
|
|
45
|
+
const updateNoteSchema = z.object({
|
|
46
|
+
noteId: z.string().describe("The ID of the note to update"),
|
|
47
|
+
title: z.string().optional().describe("New title"),
|
|
48
|
+
type: z.string().optional().describe("New type"),
|
|
49
|
+
content: z.string().optional().describe("New content"),
|
|
50
|
+
mime: z.string().optional().describe("New MIME type"),
|
|
51
|
+
});
|
|
52
|
+
const moveNoteSchema = z.object({
|
|
53
|
+
noteId: z.string().describe("The ID of the note to move"),
|
|
54
|
+
parentNoteId: z.string().describe("The ID of the new parent note"),
|
|
55
|
+
});
|
|
56
|
+
const manageAttributesSchema = z.object({
|
|
57
|
+
action: z.enum(["create", "update", "delete"]).describe("The action to perform"),
|
|
58
|
+
noteId: z.string().optional().describe("The ID of the note (required for create)"),
|
|
59
|
+
attributeId: z.string().optional().describe("The ID of the attribute (required for update/delete)"),
|
|
60
|
+
type: z.enum(["label", "relation"]).optional().describe("Type of attribute (required for create)"),
|
|
61
|
+
name: z.string().optional().describe("Name of the attribute (required for create)"),
|
|
62
|
+
value: z.string().optional().describe("Value of the attribute"),
|
|
63
|
+
isInheritable: z.boolean().optional().describe("Whether the attribute is inheritable"),
|
|
64
|
+
});
|
|
65
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
66
|
+
return {
|
|
67
|
+
tools: [
|
|
68
|
+
{
|
|
69
|
+
name: "search_notes",
|
|
70
|
+
description: "Search for notes in Trilium",
|
|
71
|
+
inputSchema: zodToJsonSchema(searchNotesSchema),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "read_note",
|
|
75
|
+
description: "Read a note's metadata and content",
|
|
76
|
+
inputSchema: zodToJsonSchema(readNoteSchema),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "create_note",
|
|
80
|
+
description: "Create a new note",
|
|
81
|
+
inputSchema: zodToJsonSchema(createNoteSchema),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "update_note",
|
|
85
|
+
description: "Update an existing note",
|
|
86
|
+
inputSchema: zodToJsonSchema(updateNoteSchema),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "move_note",
|
|
90
|
+
description: "Move a note to a new parent",
|
|
91
|
+
inputSchema: zodToJsonSchema(moveNoteSchema),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "manage_attributes",
|
|
95
|
+
description: "Manage note attributes (create, update, delete)",
|
|
96
|
+
inputSchema: zodToJsonSchema(manageAttributesSchema),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
102
|
+
try {
|
|
103
|
+
const { name, arguments: args } = request.params;
|
|
104
|
+
switch (name) {
|
|
105
|
+
case "search_notes": {
|
|
106
|
+
const { query } = searchNotesSchema.parse(args);
|
|
107
|
+
const results = await trilium.searchNotes(query);
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
case "read_note": {
|
|
113
|
+
const { noteId } = readNoteSchema.parse(args);
|
|
114
|
+
const note = await trilium.getNote(noteId);
|
|
115
|
+
const content = await trilium.getNoteContent(noteId);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{ type: "text", text: `Metadata:\n${JSON.stringify(note, null, 2)}\n\nContent:\n${content}` },
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
case "create_note": {
|
|
123
|
+
const params = createNoteSchema.parse(args);
|
|
124
|
+
const note = await trilium.createNote(params);
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
case "update_note": {
|
|
130
|
+
const { noteId, ...params } = updateNoteSchema.parse(args);
|
|
131
|
+
const note = await trilium.updateNote(noteId, params);
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
case "move_note": {
|
|
137
|
+
const { noteId, parentNoteId } = moveNoteSchema.parse(args);
|
|
138
|
+
const note = await trilium.moveNote(noteId, parentNoteId);
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
case "manage_attributes": {
|
|
144
|
+
const { action, noteId, attributeId, type, name, value, isInheritable } = manageAttributesSchema.parse(args);
|
|
145
|
+
if (action === "create") {
|
|
146
|
+
if (!noteId || !type || !name || value === undefined) {
|
|
147
|
+
throw new Error("Missing required parameters for create attribute");
|
|
148
|
+
}
|
|
149
|
+
const attr = await trilium.createAttribute(noteId, type, name, value, isInheritable);
|
|
150
|
+
return { content: [{ type: "text", text: JSON.stringify(attr, null, 2) }] };
|
|
151
|
+
}
|
|
152
|
+
else if (action === "update") {
|
|
153
|
+
if (!attributeId || value === undefined) {
|
|
154
|
+
throw new Error("Missing required parameters for update attribute");
|
|
155
|
+
}
|
|
156
|
+
await trilium.updateAttribute(attributeId, value, isInheritable);
|
|
157
|
+
return { content: [{ type: "text", text: "Attribute updated" }] };
|
|
158
|
+
}
|
|
159
|
+
else if (action === "delete") {
|
|
160
|
+
if (!attributeId) {
|
|
161
|
+
throw new Error("Missing attributeId for delete attribute");
|
|
162
|
+
}
|
|
163
|
+
await trilium.deleteAttribute(attributeId);
|
|
164
|
+
return { content: [{ type: "text", text: "Attribute deleted" }] };
|
|
165
|
+
}
|
|
166
|
+
return { content: [{ type: "text", text: "Invalid action" }] };
|
|
167
|
+
}
|
|
168
|
+
default:
|
|
169
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
176
|
+
isError: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
async function main() {
|
|
181
|
+
const transport = new StdioServerTransport();
|
|
182
|
+
await server.connect(transport);
|
|
183
|
+
}
|
|
184
|
+
main().catch((error) => {
|
|
185
|
+
console.error("Server error:", error);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
export class TriliumClient {
|
|
3
|
+
client;
|
|
4
|
+
testMode;
|
|
5
|
+
constructor(baseUrl, token, testMode = false) {
|
|
6
|
+
this.testMode = testMode;
|
|
7
|
+
this.client = axios.create({
|
|
8
|
+
baseURL: baseUrl,
|
|
9
|
+
headers: {
|
|
10
|
+
'Authorization': token,
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async searchNotes(query) {
|
|
16
|
+
if (this.testMode) {
|
|
17
|
+
return {
|
|
18
|
+
results: [
|
|
19
|
+
{
|
|
20
|
+
noteId: "test-note-1",
|
|
21
|
+
title: `Test Note: ${query}`,
|
|
22
|
+
type: "text",
|
|
23
|
+
mime: "text/html",
|
|
24
|
+
isProtected: false,
|
|
25
|
+
dateCreated: new Date().toISOString(),
|
|
26
|
+
dateModified: new Date().toISOString(),
|
|
27
|
+
parentNoteIds: ["root"],
|
|
28
|
+
childNoteIds: []
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const response = await this.client.get('/notes', {
|
|
34
|
+
params: { search: query },
|
|
35
|
+
});
|
|
36
|
+
return response.data;
|
|
37
|
+
}
|
|
38
|
+
async getNote(noteId) {
|
|
39
|
+
if (this.testMode) {
|
|
40
|
+
return {
|
|
41
|
+
noteId: noteId,
|
|
42
|
+
title: "Test Note",
|
|
43
|
+
type: "text",
|
|
44
|
+
mime: "text/html",
|
|
45
|
+
isProtected: false,
|
|
46
|
+
dateCreated: new Date().toISOString(),
|
|
47
|
+
dateModified: new Date().toISOString(),
|
|
48
|
+
parentNoteIds: ["root"],
|
|
49
|
+
childNoteIds: []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const response = await this.client.get(`/notes/${noteId}`);
|
|
53
|
+
return response.data;
|
|
54
|
+
}
|
|
55
|
+
async getNoteContent(noteId) {
|
|
56
|
+
if (this.testMode) {
|
|
57
|
+
return `<p>This is test content for note ${noteId}</p>`;
|
|
58
|
+
}
|
|
59
|
+
const response = await this.client.get(`/notes/${noteId}/content`, {
|
|
60
|
+
responseType: 'text',
|
|
61
|
+
});
|
|
62
|
+
return response.data;
|
|
63
|
+
}
|
|
64
|
+
async createNote(params) {
|
|
65
|
+
if (this.testMode) {
|
|
66
|
+
return {
|
|
67
|
+
noteId: "new-test-note",
|
|
68
|
+
title: params.title,
|
|
69
|
+
type: params.type,
|
|
70
|
+
mime: params.mime || "text/html",
|
|
71
|
+
isProtected: false,
|
|
72
|
+
dateCreated: new Date().toISOString(),
|
|
73
|
+
dateModified: new Date().toISOString(),
|
|
74
|
+
parentNoteIds: [params.parentNoteId],
|
|
75
|
+
childNoteIds: []
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const response = await this.client.post('/create-note', params);
|
|
79
|
+
return response.data;
|
|
80
|
+
}
|
|
81
|
+
async updateNote(noteId, params) {
|
|
82
|
+
if (this.testMode) {
|
|
83
|
+
return {
|
|
84
|
+
noteId: noteId,
|
|
85
|
+
title: params.title || "Updated Test Note",
|
|
86
|
+
type: params.type || "text",
|
|
87
|
+
mime: params.mime || "text/html",
|
|
88
|
+
isProtected: false,
|
|
89
|
+
dateCreated: new Date().toISOString(),
|
|
90
|
+
dateModified: new Date().toISOString(),
|
|
91
|
+
parentNoteIds: ["root"],
|
|
92
|
+
childNoteIds: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Update metadata
|
|
96
|
+
if (params.title || params.type || params.mime) {
|
|
97
|
+
await this.client.patch(`/notes/${noteId}`, {
|
|
98
|
+
title: params.title,
|
|
99
|
+
type: params.type,
|
|
100
|
+
mime: params.mime
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Update content if provided
|
|
104
|
+
if (params.content !== undefined) {
|
|
105
|
+
await this.client.put(`/notes/${noteId}/content`, params.content, {
|
|
106
|
+
headers: { 'Content-Type': 'text/plain' } // Assuming text content for now
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return this.getNote(noteId);
|
|
110
|
+
}
|
|
111
|
+
async moveNote(noteId, parentNoteId) {
|
|
112
|
+
if (this.testMode) {
|
|
113
|
+
return {
|
|
114
|
+
noteId: noteId,
|
|
115
|
+
title: "Moved Test Note",
|
|
116
|
+
type: "text",
|
|
117
|
+
mime: "text/html",
|
|
118
|
+
isProtected: false,
|
|
119
|
+
dateCreated: new Date().toISOString(),
|
|
120
|
+
dateModified: new Date().toISOString(),
|
|
121
|
+
parentNoteIds: [parentNoteId],
|
|
122
|
+
childNoteIds: []
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// In Trilium, moving a note usually means changing its parent.
|
|
126
|
+
// We can use PATCH to update parentNoteIds.
|
|
127
|
+
// WARNING: This replaces all parents. If a note is cloned, this might unclone it from other locations.
|
|
128
|
+
// For simple "move", this is acceptable.
|
|
129
|
+
await this.client.patch(`/notes/${noteId}`, {
|
|
130
|
+
parentNoteIds: [parentNoteId],
|
|
131
|
+
});
|
|
132
|
+
return this.getNote(noteId);
|
|
133
|
+
}
|
|
134
|
+
// Attribute Management
|
|
135
|
+
async createAttribute(noteId, type, name, value, isInheritable = false) {
|
|
136
|
+
if (this.testMode) {
|
|
137
|
+
return {
|
|
138
|
+
attributeId: "test-attr-1",
|
|
139
|
+
noteId: noteId,
|
|
140
|
+
type: type,
|
|
141
|
+
name: name,
|
|
142
|
+
value: value,
|
|
143
|
+
isInheritable: isInheritable || false
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const response = await this.client.post('/attributes', {
|
|
147
|
+
noteId,
|
|
148
|
+
type,
|
|
149
|
+
name,
|
|
150
|
+
value,
|
|
151
|
+
isInheritable,
|
|
152
|
+
});
|
|
153
|
+
return response.data;
|
|
154
|
+
}
|
|
155
|
+
async updateAttribute(attributeId, value, isInheritable) {
|
|
156
|
+
if (this.testMode) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await this.client.patch(`/attributes/${attributeId}`, {
|
|
160
|
+
value,
|
|
161
|
+
isInheritable
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async deleteAttribute(attributeId) {
|
|
165
|
+
if (this.testMode) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await this.client.delete(`/attributes/${attributeId}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iflow-mcp/h30190_trilium_mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"trilium-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "http://192.168.0.125:3004/HJPLUS/Trilium_MCP.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
23
|
+
"@types/node": "^24.10.1",
|
|
24
|
+
"axios": "^1.13.2",
|
|
25
|
+
"dotenv": "^17.2.3",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"zod": "^3.23.8",
|
|
28
|
+
"zod-to-json-schema": "^3.23.5"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
10
|
+
import dotenv from "dotenv";
|
|
11
|
+
import { TriliumClient } from "./trilium-client.js";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Load .env from the project root (assuming dist/index.js is one level deep)
|
|
19
|
+
dotenv.config({ path: path.resolve(__dirname, "../.env") });
|
|
20
|
+
|
|
21
|
+
const TRILIUM_ETAPI_URL = process.env.TRILIUM_ETAPI_URL;
|
|
22
|
+
const TRILIUM_ETAPI_TOKEN = process.env.TRILIUM_ETAPI_TOKEN;
|
|
23
|
+
const TEST_MODE = process.env.TEST_MODE === "true";
|
|
24
|
+
|
|
25
|
+
if (!TRILIUM_ETAPI_URL || !TRILIUM_ETAPI_TOKEN) {
|
|
26
|
+
console.error("Error: TRILIUM_ETAPI_URL and TRILIUM_ETAPI_TOKEN must be set in .env");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const trilium = new TriliumClient(TRILIUM_ETAPI_URL, TRILIUM_ETAPI_TOKEN, TEST_MODE);
|
|
31
|
+
|
|
32
|
+
const server = new Server(
|
|
33
|
+
{
|
|
34
|
+
name: "trilium-mcp",
|
|
35
|
+
version: "V1.0.0(251124_HJPLUS.DESIGN)",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
capabilities: {
|
|
39
|
+
tools: {},
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Tool Schemas
|
|
45
|
+
const searchNotesSchema = z.object({
|
|
46
|
+
query: z.string().describe("The search query string"),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const readNoteSchema = z.object({
|
|
50
|
+
noteId: z.string().describe("The ID of the note to read"),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const createNoteSchema = z.object({
|
|
54
|
+
parentNoteId: z.string().describe("The ID of the parent note"),
|
|
55
|
+
title: z.string().describe("The title of the new note"),
|
|
56
|
+
type: z.string().default("text").describe("The type of the note (e.g., text, code)"),
|
|
57
|
+
content: z.string().optional().describe("The initial content of the note"),
|
|
58
|
+
mime: z.string().optional().describe("MIME type for the note"),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const updateNoteSchema = z.object({
|
|
62
|
+
noteId: z.string().describe("The ID of the note to update"),
|
|
63
|
+
title: z.string().optional().describe("New title"),
|
|
64
|
+
type: z.string().optional().describe("New type"),
|
|
65
|
+
content: z.string().optional().describe("New content"),
|
|
66
|
+
mime: z.string().optional().describe("New MIME type"),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const moveNoteSchema = z.object({
|
|
70
|
+
noteId: z.string().describe("The ID of the note to move"),
|
|
71
|
+
parentNoteId: z.string().describe("The ID of the new parent note"),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const manageAttributesSchema = z.object({
|
|
75
|
+
action: z.enum(["create", "update", "delete"]).describe("The action to perform"),
|
|
76
|
+
noteId: z.string().optional().describe("The ID of the note (required for create)"),
|
|
77
|
+
attributeId: z.string().optional().describe("The ID of the attribute (required for update/delete)"),
|
|
78
|
+
type: z.enum(["label", "relation"]).optional().describe("Type of attribute (required for create)"),
|
|
79
|
+
name: z.string().optional().describe("Name of the attribute (required for create)"),
|
|
80
|
+
value: z.string().optional().describe("Value of the attribute"),
|
|
81
|
+
isInheritable: z.boolean().optional().describe("Whether the attribute is inheritable"),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
85
|
+
return {
|
|
86
|
+
tools: [
|
|
87
|
+
{
|
|
88
|
+
name: "search_notes",
|
|
89
|
+
description: "Search for notes in Trilium",
|
|
90
|
+
inputSchema: zodToJsonSchema(searchNotesSchema as any),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "read_note",
|
|
94
|
+
description: "Read a note's metadata and content",
|
|
95
|
+
inputSchema: zodToJsonSchema(readNoteSchema as any),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "create_note",
|
|
99
|
+
description: "Create a new note",
|
|
100
|
+
inputSchema: zodToJsonSchema(createNoteSchema as any),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "update_note",
|
|
104
|
+
description: "Update an existing note",
|
|
105
|
+
inputSchema: zodToJsonSchema(updateNoteSchema as any),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "move_note",
|
|
109
|
+
description: "Move a note to a new parent",
|
|
110
|
+
inputSchema: zodToJsonSchema(moveNoteSchema as any),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "manage_attributes",
|
|
114
|
+
description: "Manage note attributes (create, update, delete)",
|
|
115
|
+
inputSchema: zodToJsonSchema(manageAttributesSchema as any),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
|
|
122
|
+
try {
|
|
123
|
+
const { name, arguments: args } = request.params;
|
|
124
|
+
|
|
125
|
+
switch (name) {
|
|
126
|
+
case "search_notes": {
|
|
127
|
+
const { query } = searchNotesSchema.parse(args);
|
|
128
|
+
const results = await trilium.searchNotes(query);
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case "read_note": {
|
|
135
|
+
const { noteId } = readNoteSchema.parse(args);
|
|
136
|
+
const note = await trilium.getNote(noteId);
|
|
137
|
+
const content = await trilium.getNoteContent(noteId);
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{ type: "text", text: `Metadata:\n${JSON.stringify(note, null, 2)}\n\nContent:\n${content}` },
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case "create_note": {
|
|
146
|
+
const params = createNoteSchema.parse(args);
|
|
147
|
+
const note = await trilium.createNote(params);
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "update_note": {
|
|
154
|
+
const { noteId, ...params } = updateNoteSchema.parse(args);
|
|
155
|
+
const note = await trilium.updateNote(noteId, params);
|
|
156
|
+
return {
|
|
157
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case "move_note": {
|
|
162
|
+
const { noteId, parentNoteId } = moveNoteSchema.parse(args);
|
|
163
|
+
const note = await trilium.moveNote(noteId, parentNoteId);
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case "manage_attributes": {
|
|
170
|
+
const { action, noteId, attributeId, type, name, value, isInheritable } = manageAttributesSchema.parse(args);
|
|
171
|
+
|
|
172
|
+
if (action === "create") {
|
|
173
|
+
if (!noteId || !type || !name || value === undefined) {
|
|
174
|
+
throw new Error("Missing required parameters for create attribute");
|
|
175
|
+
}
|
|
176
|
+
const attr = await trilium.createAttribute(noteId, type, name, value, isInheritable);
|
|
177
|
+
return { content: [{ type: "text", text: JSON.stringify(attr, null, 2) }] };
|
|
178
|
+
} else if (action === "update") {
|
|
179
|
+
if (!attributeId || value === undefined) {
|
|
180
|
+
throw new Error("Missing required parameters for update attribute");
|
|
181
|
+
}
|
|
182
|
+
await trilium.updateAttribute(attributeId, value, isInheritable);
|
|
183
|
+
return { content: [{ type: "text", text: "Attribute updated" }] };
|
|
184
|
+
} else if (action === "delete") {
|
|
185
|
+
if (!attributeId) {
|
|
186
|
+
throw new Error("Missing attributeId for delete attribute");
|
|
187
|
+
}
|
|
188
|
+
await trilium.deleteAttribute(attributeId);
|
|
189
|
+
return { content: [{ type: "text", text: "Attribute deleted" }] };
|
|
190
|
+
}
|
|
191
|
+
return { content: [{ type: "text", text: "Invalid action" }] };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
default:
|
|
195
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
201
|
+
isError: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
async function main() {
|
|
207
|
+
const transport = new StdioServerTransport();
|
|
208
|
+
await server.connect(transport);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
main().catch((error) => {
|
|
212
|
+
console.error("Server error:", error);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import { Note, NoteContent, CreateNoteParams, UpdateNoteParams, Attribute, SearchResult } from './types.js';
|
|
3
|
+
|
|
4
|
+
export class TriliumClient {
|
|
5
|
+
private client: AxiosInstance;
|
|
6
|
+
private testMode: boolean;
|
|
7
|
+
|
|
8
|
+
constructor(baseUrl: string, token: string, testMode: boolean = false) {
|
|
9
|
+
this.testMode = testMode;
|
|
10
|
+
this.client = axios.create({
|
|
11
|
+
baseURL: baseUrl,
|
|
12
|
+
headers: {
|
|
13
|
+
'Authorization': token,
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async searchNotes(query: string): Promise<SearchResult> {
|
|
20
|
+
if (this.testMode) {
|
|
21
|
+
return {
|
|
22
|
+
results: [
|
|
23
|
+
{
|
|
24
|
+
noteId: "test-note-1",
|
|
25
|
+
title: `Test Note: ${query}`,
|
|
26
|
+
type: "text",
|
|
27
|
+
mime: "text/html",
|
|
28
|
+
isProtected: false,
|
|
29
|
+
dateCreated: new Date().toISOString(),
|
|
30
|
+
dateModified: new Date().toISOString(),
|
|
31
|
+
parentNoteIds: ["root"],
|
|
32
|
+
childNoteIds: []
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const response = await this.client.get<SearchResult>('/notes', {
|
|
38
|
+
params: { search: query },
|
|
39
|
+
});
|
|
40
|
+
return response.data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getNote(noteId: string): Promise<Note> {
|
|
44
|
+
if (this.testMode) {
|
|
45
|
+
return {
|
|
46
|
+
noteId: noteId,
|
|
47
|
+
title: "Test Note",
|
|
48
|
+
type: "text",
|
|
49
|
+
mime: "text/html",
|
|
50
|
+
isProtected: false,
|
|
51
|
+
dateCreated: new Date().toISOString(),
|
|
52
|
+
dateModified: new Date().toISOString(),
|
|
53
|
+
parentNoteIds: ["root"],
|
|
54
|
+
childNoteIds: []
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const response = await this.client.get<Note>(`/notes/${noteId}`);
|
|
58
|
+
return response.data;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getNoteContent(noteId: string): Promise<string> {
|
|
62
|
+
if (this.testMode) {
|
|
63
|
+
return `<p>This is test content for note ${noteId}</p>`;
|
|
64
|
+
}
|
|
65
|
+
const response = await this.client.get(`/notes/${noteId}/content`, {
|
|
66
|
+
responseType: 'text',
|
|
67
|
+
});
|
|
68
|
+
return response.data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async createNote(params: CreateNoteParams): Promise<Note> {
|
|
72
|
+
if (this.testMode) {
|
|
73
|
+
return {
|
|
74
|
+
noteId: "new-test-note",
|
|
75
|
+
title: params.title,
|
|
76
|
+
type: params.type,
|
|
77
|
+
mime: params.mime || "text/html",
|
|
78
|
+
isProtected: false,
|
|
79
|
+
dateCreated: new Date().toISOString(),
|
|
80
|
+
dateModified: new Date().toISOString(),
|
|
81
|
+
parentNoteIds: [params.parentNoteId],
|
|
82
|
+
childNoteIds: []
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const response = await this.client.post<Note>('/create-note', params);
|
|
86
|
+
return response.data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async updateNote(noteId: string, params: UpdateNoteParams): Promise<Note> {
|
|
90
|
+
if (this.testMode) {
|
|
91
|
+
return {
|
|
92
|
+
noteId: noteId,
|
|
93
|
+
title: params.title || "Updated Test Note",
|
|
94
|
+
type: params.type || "text",
|
|
95
|
+
mime: params.mime || "text/html",
|
|
96
|
+
isProtected: false,
|
|
97
|
+
dateCreated: new Date().toISOString(),
|
|
98
|
+
dateModified: new Date().toISOString(),
|
|
99
|
+
parentNoteIds: ["root"],
|
|
100
|
+
childNoteIds: []
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Update metadata
|
|
104
|
+
if (params.title || params.type || params.mime) {
|
|
105
|
+
await this.client.patch(`/notes/${noteId}`, {
|
|
106
|
+
title: params.title,
|
|
107
|
+
type: params.type,
|
|
108
|
+
mime: params.mime
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Update content if provided
|
|
113
|
+
if (params.content !== undefined) {
|
|
114
|
+
await this.client.put(`/notes/${noteId}/content`, params.content, {
|
|
115
|
+
headers: { 'Content-Type': 'text/plain' } // Assuming text content for now
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return this.getNote(noteId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async moveNote(noteId: string, parentNoteId: string): Promise<Note> {
|
|
123
|
+
if (this.testMode) {
|
|
124
|
+
return {
|
|
125
|
+
noteId: noteId,
|
|
126
|
+
title: "Moved Test Note",
|
|
127
|
+
type: "text",
|
|
128
|
+
mime: "text/html",
|
|
129
|
+
isProtected: false,
|
|
130
|
+
dateCreated: new Date().toISOString(),
|
|
131
|
+
dateModified: new Date().toISOString(),
|
|
132
|
+
parentNoteIds: [parentNoteId],
|
|
133
|
+
childNoteIds: []
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// In Trilium, moving a note usually means changing its parent.
|
|
137
|
+
// We can use PATCH to update parentNoteIds.
|
|
138
|
+
// WARNING: This replaces all parents. If a note is cloned, this might unclone it from other locations.
|
|
139
|
+
// For simple "move", this is acceptable.
|
|
140
|
+
await this.client.patch(`/notes/${noteId}`, {
|
|
141
|
+
parentNoteIds: [parentNoteId],
|
|
142
|
+
});
|
|
143
|
+
return this.getNote(noteId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Attribute Management
|
|
147
|
+
async createAttribute(noteId: string, type: 'label' | 'relation', name: string, value: string, isInheritable: boolean = false): Promise<Attribute> {
|
|
148
|
+
if (this.testMode) {
|
|
149
|
+
return {
|
|
150
|
+
attributeId: "test-attr-1",
|
|
151
|
+
noteId: noteId,
|
|
152
|
+
type: type,
|
|
153
|
+
name: name,
|
|
154
|
+
value: value,
|
|
155
|
+
isInheritable: isInheritable || false
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const response = await this.client.post<Attribute>('/attributes', {
|
|
159
|
+
noteId,
|
|
160
|
+
type,
|
|
161
|
+
name,
|
|
162
|
+
value,
|
|
163
|
+
isInheritable,
|
|
164
|
+
});
|
|
165
|
+
return response.data;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async updateAttribute(attributeId: string, value: string, isInheritable?: boolean): Promise<void> {
|
|
169
|
+
if (this.testMode) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await this.client.patch(`/attributes/${attributeId}`, {
|
|
173
|
+
value,
|
|
174
|
+
isInheritable
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async deleteAttribute(attributeId: string): Promise<void> {
|
|
179
|
+
if (this.testMode) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
await this.client.delete(`/attributes/${attributeId}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface Note {
|
|
2
|
+
noteId: string;
|
|
3
|
+
title: string;
|
|
4
|
+
type: string;
|
|
5
|
+
mime: string;
|
|
6
|
+
isProtected: boolean;
|
|
7
|
+
dateCreated: string;
|
|
8
|
+
dateModified: string;
|
|
9
|
+
parentNoteIds: string[];
|
|
10
|
+
childNoteIds: string[];
|
|
11
|
+
attributes?: Attribute[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Attribute {
|
|
15
|
+
attributeId: string;
|
|
16
|
+
noteId: string;
|
|
17
|
+
type: 'label' | 'relation';
|
|
18
|
+
name: string;
|
|
19
|
+
value: string;
|
|
20
|
+
isInheritable: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NoteContent {
|
|
24
|
+
noteId: string;
|
|
25
|
+
content: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SearchResult {
|
|
29
|
+
results: Note[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CreateNoteParams {
|
|
33
|
+
parentNoteId: string;
|
|
34
|
+
title: string;
|
|
35
|
+
type: string;
|
|
36
|
+
content?: string;
|
|
37
|
+
mime?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface UpdateNoteParams {
|
|
41
|
+
title?: string;
|
|
42
|
+
type?: string;
|
|
43
|
+
mime?: string;
|
|
44
|
+
content?: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[2025-12-29] Trilium MCP 处理日志
|
|
2
|
+
|
|
3
|
+
项目信息:
|
|
4
|
+
- 原始仓库: https://github.com/h30190/Trilium_MCP
|
|
5
|
+
- Fork地址: https://github.com/iflow-mcp/trilium_mcp
|
|
6
|
+
- 本地路径: /Users/jiashuowang/MCPs/提示词项目/iflow_auto_import/data/trilium_mcp
|
|
7
|
+
- 项目类型: Node.js
|
|
8
|
+
|
|
9
|
+
[2025-12-29] [✅] 获取项目信息: GitHub地址和项目名称
|
|
10
|
+
|
|
11
|
+
[2025-12-29] [✅] 阅读代码:
|
|
12
|
+
- 项目为Node.js TypeScript项目
|
|
13
|
+
- 入口文件: src/index.ts
|
|
14
|
+
- 构建输出: dist/index.js
|
|
15
|
+
- 依赖: @modelcontextprotocol/sdk, axios, dotenv, zod
|
|
16
|
+
- 工具列表:
|
|
17
|
+
1. search_notes - 搜索笔记
|
|
18
|
+
2. read_note - 读取笔记
|
|
19
|
+
3. create_note - 创建笔记
|
|
20
|
+
4. update_note - 更新笔记
|
|
21
|
+
5. move_note - 移动笔记
|
|
22
|
+
6. manage_attributes - 管理属性
|
|
23
|
+
- 传输协议: stdio
|
|
24
|
+
- 需要环境变量: TRILIUM_ETAPI_URL, TRILIUM_ETAPI_TOKEN
|
|
25
|
+
|
|
26
|
+
[2025-12-29] [✅] 本地测试: 成功获取到6个工具,测试通过
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules"]
|
|
15
|
+
}
|