affine-mcp-server 1.2.2 → 1.3.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 +47 -23
- package/bin/affine-mcp +0 -0
- package/dist/index.js +0 -2
- package/dist/tools/accessTokens.js +3 -23
- package/dist/tools/auth.js +0 -8
- package/dist/tools/blobStorage.js +59 -35
- package/dist/tools/comments.js +4 -48
- package/dist/tools/docs.js +215 -204
- package/dist/tools/history.js +0 -36
- package/dist/tools/notifications.js +25 -54
- package/dist/tools/user.js +0 -5
- package/dist/tools/userCRUD.js +20 -152
- package/dist/tools/workspaces.js +35 -106
- package/dist/ws.js +25 -3
- package/package.json +13 -3
- package/dist/tools/updates.js +0 -32
package/dist/tools/docs.js
CHANGED
|
@@ -4,6 +4,29 @@ import { wsUrlFromGraphQLEndpoint, connectWorkspaceSocket, joinWorkspace, loadDo
|
|
|
4
4
|
import * as Y from "yjs";
|
|
5
5
|
const WorkspaceId = z.string().min(1, "workspaceId required");
|
|
6
6
|
const DocId = z.string().min(1, "docId required");
|
|
7
|
+
const APPEND_BLOCK_TYPE_VALUES = [
|
|
8
|
+
"paragraph",
|
|
9
|
+
"heading1",
|
|
10
|
+
"heading2",
|
|
11
|
+
"heading3",
|
|
12
|
+
"quote",
|
|
13
|
+
"bulleted_list",
|
|
14
|
+
"numbered_list",
|
|
15
|
+
"todo",
|
|
16
|
+
"code",
|
|
17
|
+
"divider",
|
|
18
|
+
];
|
|
19
|
+
const AppendBlockType = z.enum(APPEND_BLOCK_TYPE_VALUES);
|
|
20
|
+
function blockVersion(flavour) {
|
|
21
|
+
switch (flavour) {
|
|
22
|
+
case "affine:page":
|
|
23
|
+
return 2;
|
|
24
|
+
case "affine:surface":
|
|
25
|
+
return 5;
|
|
26
|
+
default:
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
7
30
|
export function registerDocTools(server, gql, defaults) {
|
|
8
31
|
// helpers
|
|
9
32
|
function generateId() {
|
|
@@ -19,6 +42,156 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
19
42
|
const cookie = gql.cookie || headers.Cookie || '';
|
|
20
43
|
return { endpoint, cookie };
|
|
21
44
|
}
|
|
45
|
+
function makeText(content) {
|
|
46
|
+
const yText = new Y.Text();
|
|
47
|
+
if (content.length > 0) {
|
|
48
|
+
yText.insert(0, content);
|
|
49
|
+
}
|
|
50
|
+
return yText;
|
|
51
|
+
}
|
|
52
|
+
function setSysFields(block, blockId, flavour) {
|
|
53
|
+
block.set("sys:id", blockId);
|
|
54
|
+
block.set("sys:flavour", flavour);
|
|
55
|
+
block.set("sys:version", blockVersion(flavour));
|
|
56
|
+
}
|
|
57
|
+
function findBlockIdByFlavour(blocks, flavour) {
|
|
58
|
+
for (const [, value] of blocks) {
|
|
59
|
+
const block = value;
|
|
60
|
+
if (block?.get && block.get("sys:flavour") === flavour) {
|
|
61
|
+
return String(block.get("sys:id"));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function ensureNoteBlock(blocks) {
|
|
67
|
+
const existingNoteId = findBlockIdByFlavour(blocks, "affine:note");
|
|
68
|
+
if (existingNoteId) {
|
|
69
|
+
return existingNoteId;
|
|
70
|
+
}
|
|
71
|
+
const pageId = findBlockIdByFlavour(blocks, "affine:page");
|
|
72
|
+
if (!pageId) {
|
|
73
|
+
throw new Error("Document has no page block; unable to insert content.");
|
|
74
|
+
}
|
|
75
|
+
const noteId = generateId();
|
|
76
|
+
const note = new Y.Map();
|
|
77
|
+
setSysFields(note, noteId, "affine:note");
|
|
78
|
+
note.set("sys:parent", pageId);
|
|
79
|
+
note.set("sys:children", new Y.Array());
|
|
80
|
+
note.set("prop:xywh", "[0,0,800,95]");
|
|
81
|
+
note.set("prop:index", "a0");
|
|
82
|
+
note.set("prop:hidden", false);
|
|
83
|
+
note.set("prop:displayMode", "both");
|
|
84
|
+
const background = new Y.Map();
|
|
85
|
+
background.set("light", "#ffffff");
|
|
86
|
+
background.set("dark", "#252525");
|
|
87
|
+
note.set("prop:background", background);
|
|
88
|
+
blocks.set(noteId, note);
|
|
89
|
+
const page = blocks.get(pageId);
|
|
90
|
+
let pageChildren = page.get("sys:children");
|
|
91
|
+
if (!(pageChildren instanceof Y.Array)) {
|
|
92
|
+
pageChildren = new Y.Array();
|
|
93
|
+
page.set("sys:children", pageChildren);
|
|
94
|
+
}
|
|
95
|
+
pageChildren.push([noteId]);
|
|
96
|
+
return noteId;
|
|
97
|
+
}
|
|
98
|
+
function createBlock(noteId, parsed) {
|
|
99
|
+
const blockId = generateId();
|
|
100
|
+
const block = new Y.Map();
|
|
101
|
+
const content = parsed.text ?? "";
|
|
102
|
+
switch (parsed.type) {
|
|
103
|
+
case "paragraph":
|
|
104
|
+
case "heading1":
|
|
105
|
+
case "heading2":
|
|
106
|
+
case "heading3":
|
|
107
|
+
case "quote": {
|
|
108
|
+
setSysFields(block, blockId, "affine:paragraph");
|
|
109
|
+
block.set("sys:parent", noteId);
|
|
110
|
+
block.set("sys:children", new Y.Array());
|
|
111
|
+
const blockType = parsed.type === "heading1"
|
|
112
|
+
? "h1"
|
|
113
|
+
: parsed.type === "heading2"
|
|
114
|
+
? "h2"
|
|
115
|
+
: parsed.type === "heading3"
|
|
116
|
+
? "h3"
|
|
117
|
+
: parsed.type === "quote"
|
|
118
|
+
? "quote"
|
|
119
|
+
: "text";
|
|
120
|
+
block.set("prop:type", blockType);
|
|
121
|
+
block.set("prop:text", makeText(content));
|
|
122
|
+
return { blockId, block, flavour: "affine:paragraph", blockType };
|
|
123
|
+
}
|
|
124
|
+
case "bulleted_list":
|
|
125
|
+
case "numbered_list":
|
|
126
|
+
case "todo": {
|
|
127
|
+
setSysFields(block, blockId, "affine:list");
|
|
128
|
+
block.set("sys:parent", noteId);
|
|
129
|
+
block.set("sys:children", new Y.Array());
|
|
130
|
+
const blockType = parsed.type === "bulleted_list"
|
|
131
|
+
? "bulleted"
|
|
132
|
+
: parsed.type === "numbered_list"
|
|
133
|
+
? "numbered"
|
|
134
|
+
: "todo";
|
|
135
|
+
block.set("prop:type", blockType);
|
|
136
|
+
if (blockType === "todo") {
|
|
137
|
+
block.set("prop:checked", Boolean(parsed.checked));
|
|
138
|
+
}
|
|
139
|
+
block.set("prop:text", makeText(content));
|
|
140
|
+
return { blockId, block, flavour: "affine:list", blockType };
|
|
141
|
+
}
|
|
142
|
+
case "code": {
|
|
143
|
+
setSysFields(block, blockId, "affine:code");
|
|
144
|
+
block.set("sys:parent", noteId);
|
|
145
|
+
block.set("sys:children", new Y.Array());
|
|
146
|
+
block.set("prop:language", (parsed.language || "txt").toLowerCase());
|
|
147
|
+
if (parsed.caption) {
|
|
148
|
+
block.set("prop:caption", parsed.caption);
|
|
149
|
+
}
|
|
150
|
+
block.set("prop:text", makeText(content));
|
|
151
|
+
return { blockId, block, flavour: "affine:code" };
|
|
152
|
+
}
|
|
153
|
+
case "divider": {
|
|
154
|
+
setSysFields(block, blockId, "affine:divider");
|
|
155
|
+
block.set("sys:parent", noteId);
|
|
156
|
+
block.set("sys:children", new Y.Array());
|
|
157
|
+
return { blockId, block, flavour: "affine:divider" };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function appendBlockInternal(parsed) {
|
|
162
|
+
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
163
|
+
if (!workspaceId)
|
|
164
|
+
throw new Error("workspaceId is required");
|
|
165
|
+
const { endpoint, cookie } = await getCookieAndEndpoint();
|
|
166
|
+
const wsUrl = wsUrlFromGraphQLEndpoint(endpoint);
|
|
167
|
+
const socket = await connectWorkspaceSocket(wsUrl, cookie);
|
|
168
|
+
try {
|
|
169
|
+
await joinWorkspace(socket, workspaceId);
|
|
170
|
+
const doc = new Y.Doc();
|
|
171
|
+
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
172
|
+
if (snapshot.missing) {
|
|
173
|
+
Y.applyUpdate(doc, Buffer.from(snapshot.missing, "base64"));
|
|
174
|
+
}
|
|
175
|
+
const prevSV = Y.encodeStateVector(doc);
|
|
176
|
+
const blocks = doc.getMap("blocks");
|
|
177
|
+
const noteId = ensureNoteBlock(blocks);
|
|
178
|
+
const { blockId, block, flavour, blockType } = createBlock(noteId, parsed);
|
|
179
|
+
blocks.set(blockId, block);
|
|
180
|
+
const note = blocks.get(noteId);
|
|
181
|
+
let noteChildren = note.get("sys:children");
|
|
182
|
+
if (!(noteChildren instanceof Y.Array)) {
|
|
183
|
+
noteChildren = new Y.Array();
|
|
184
|
+
note.set("sys:children", noteChildren);
|
|
185
|
+
}
|
|
186
|
+
noteChildren.push([blockId]);
|
|
187
|
+
const delta = Y.encodeStateAsUpdate(doc, prevSV);
|
|
188
|
+
await pushDocUpdate(socket, workspaceId, parsed.docId, Buffer.from(delta).toString("base64"));
|
|
189
|
+
return { appended: true, blockId, flavour, blockType };
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
socket.disconnect();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
22
195
|
const listDocsHandler = async (parsed) => {
|
|
23
196
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
24
197
|
if (!workspaceId) {
|
|
@@ -38,16 +211,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
38
211
|
after: z.string().optional()
|
|
39
212
|
}
|
|
40
213
|
}, listDocsHandler);
|
|
41
|
-
server.registerTool("affine_list_docs", {
|
|
42
|
-
title: "List Documents",
|
|
43
|
-
description: "List documents in a workspace (GraphQL).",
|
|
44
|
-
inputSchema: {
|
|
45
|
-
workspaceId: z.string().describe("Workspace ID (optional if default set).").optional(),
|
|
46
|
-
first: z.number().optional(),
|
|
47
|
-
offset: z.number().optional(),
|
|
48
|
-
after: z.string().optional()
|
|
49
|
-
}
|
|
50
|
-
}, listDocsHandler);
|
|
51
214
|
const getDocHandler = async (parsed) => {
|
|
52
215
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
53
216
|
if (!workspaceId) {
|
|
@@ -65,78 +228,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
65
228
|
docId: DocId
|
|
66
229
|
}
|
|
67
230
|
}, getDocHandler);
|
|
68
|
-
server.registerTool("affine_get_doc", {
|
|
69
|
-
title: "Get Document",
|
|
70
|
-
description: "Get a document by ID (GraphQL metadata).",
|
|
71
|
-
inputSchema: {
|
|
72
|
-
workspaceId: z.string().optional(),
|
|
73
|
-
docId: DocId
|
|
74
|
-
}
|
|
75
|
-
}, getDocHandler);
|
|
76
|
-
const searchDocsHandler = async (parsed) => {
|
|
77
|
-
try {
|
|
78
|
-
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
79
|
-
if (!workspaceId) {
|
|
80
|
-
throw new Error("workspaceId is required. Provide it as a parameter or set AFFINE_WORKSPACE_ID in environment.");
|
|
81
|
-
}
|
|
82
|
-
const query = `query SearchDocs($workspaceId:String!, $keyword:String!, $limit:Int){ workspace(id:$workspaceId){ searchDocs(input:{ keyword:$keyword, limit:$limit }){ docId title highlight createdAt updatedAt } } }`;
|
|
83
|
-
const data = await gql.request(query, { workspaceId, keyword: parsed.keyword, limit: parsed.limit });
|
|
84
|
-
return text(data.workspace?.searchDocs || []);
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
// Return empty array on error (search might not be available)
|
|
88
|
-
console.error("Search docs error:", error.message);
|
|
89
|
-
return text([]);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
server.registerTool("search_docs", {
|
|
93
|
-
title: "Search Documents",
|
|
94
|
-
description: "Search documents in a workspace.",
|
|
95
|
-
inputSchema: {
|
|
96
|
-
workspaceId: z.string().optional(),
|
|
97
|
-
keyword: z.string().min(1),
|
|
98
|
-
limit: z.number().optional()
|
|
99
|
-
}
|
|
100
|
-
}, searchDocsHandler);
|
|
101
|
-
server.registerTool("affine_search_docs", {
|
|
102
|
-
title: "Search Documents",
|
|
103
|
-
description: "Search documents in a workspace.",
|
|
104
|
-
inputSchema: {
|
|
105
|
-
workspaceId: z.string().optional(),
|
|
106
|
-
keyword: z.string().min(1),
|
|
107
|
-
limit: z.number().optional()
|
|
108
|
-
}
|
|
109
|
-
}, searchDocsHandler);
|
|
110
|
-
const recentDocsHandler = async (parsed) => {
|
|
111
|
-
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
112
|
-
if (!workspaceId) {
|
|
113
|
-
throw new Error("workspaceId is required. Provide it as a parameter or set AFFINE_WORKSPACE_ID in environment.");
|
|
114
|
-
}
|
|
115
|
-
// Note: AFFiNE doesn't have a separate 'recentlyUpdatedDocs' field, just use docs
|
|
116
|
-
const query = `query RecentDocs($workspaceId:String!, $first:Int, $offset:Int, $after:String){ workspace(id:$workspaceId){ docs(pagination:{first:$first, offset:$offset, after:$after}){ totalCount pageInfo{ hasNextPage endCursor } edges{ cursor node{ id workspaceId title summary public defaultRole createdAt updatedAt } } } } }`;
|
|
117
|
-
const data = await gql.request(query, { workspaceId, first: parsed.first, offset: parsed.offset, after: parsed.after });
|
|
118
|
-
return text(data.workspace.docs);
|
|
119
|
-
};
|
|
120
|
-
server.registerTool("recent_docs", {
|
|
121
|
-
title: "Recent Documents",
|
|
122
|
-
description: "List recently updated docs in a workspace.",
|
|
123
|
-
inputSchema: {
|
|
124
|
-
workspaceId: z.string().optional(),
|
|
125
|
-
first: z.number().optional(),
|
|
126
|
-
offset: z.number().optional(),
|
|
127
|
-
after: z.string().optional()
|
|
128
|
-
}
|
|
129
|
-
}, recentDocsHandler);
|
|
130
|
-
server.registerTool("affine_recent_docs", {
|
|
131
|
-
title: "Recent Documents",
|
|
132
|
-
description: "List recently updated docs in a workspace.",
|
|
133
|
-
inputSchema: {
|
|
134
|
-
workspaceId: z.string().optional(),
|
|
135
|
-
first: z.number().optional(),
|
|
136
|
-
offset: z.number().optional(),
|
|
137
|
-
after: z.string().optional()
|
|
138
|
-
}
|
|
139
|
-
}, recentDocsHandler);
|
|
140
231
|
const publishDocHandler = async (parsed) => {
|
|
141
232
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
142
233
|
if (!workspaceId) {
|
|
@@ -155,15 +246,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
155
246
|
mode: z.enum(["Page", "Edgeless"]).optional()
|
|
156
247
|
}
|
|
157
248
|
}, publishDocHandler);
|
|
158
|
-
server.registerTool("affine_publish_doc", {
|
|
159
|
-
title: "Publish Document",
|
|
160
|
-
description: "Publish a doc (make public).",
|
|
161
|
-
inputSchema: {
|
|
162
|
-
workspaceId: z.string().optional(),
|
|
163
|
-
docId: z.string(),
|
|
164
|
-
mode: z.enum(["Page", "Edgeless"]).optional()
|
|
165
|
-
}
|
|
166
|
-
}, publishDocHandler);
|
|
167
249
|
const revokeDocHandler = async (parsed) => {
|
|
168
250
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
169
251
|
if (!workspaceId) {
|
|
@@ -181,14 +263,6 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
181
263
|
docId: z.string()
|
|
182
264
|
}
|
|
183
265
|
}, revokeDocHandler);
|
|
184
|
-
server.registerTool("affine_revoke_doc", {
|
|
185
|
-
title: "Revoke Document",
|
|
186
|
-
description: "Revoke a doc's public access.",
|
|
187
|
-
inputSchema: {
|
|
188
|
-
workspaceId: z.string().optional(),
|
|
189
|
-
docId: z.string()
|
|
190
|
-
}
|
|
191
|
-
}, revokeDocHandler);
|
|
192
266
|
// CREATE DOC (high-level)
|
|
193
267
|
const createDocHandler = async (parsed) => {
|
|
194
268
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
@@ -205,8 +279,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
205
279
|
const blocks = ydoc.getMap('blocks');
|
|
206
280
|
const pageId = generateId();
|
|
207
281
|
const page = new Y.Map();
|
|
208
|
-
page
|
|
209
|
-
page.set('sys:flavour', 'affine:page');
|
|
282
|
+
setSysFields(page, pageId, "affine:page");
|
|
210
283
|
const titleText = new Y.Text();
|
|
211
284
|
titleText.insert(0, parsed.title || 'Untitled');
|
|
212
285
|
page.set('prop:title', titleText);
|
|
@@ -215,21 +288,27 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
215
288
|
blocks.set(pageId, page);
|
|
216
289
|
const surfaceId = generateId();
|
|
217
290
|
const surface = new Y.Map();
|
|
218
|
-
surface
|
|
219
|
-
surface.set('sys:flavour', 'affine:surface');
|
|
291
|
+
setSysFields(surface, surfaceId, "affine:surface");
|
|
220
292
|
surface.set('sys:parent', pageId);
|
|
221
293
|
surface.set('sys:children', new Y.Array());
|
|
294
|
+
const elements = new Y.Map();
|
|
295
|
+
elements.set("type", "$blocksuite:internal:native$");
|
|
296
|
+
elements.set("value", new Y.Map());
|
|
297
|
+
surface.set("prop:elements", elements);
|
|
222
298
|
blocks.set(surfaceId, surface);
|
|
223
299
|
children.push([surfaceId]);
|
|
224
300
|
const noteId = generateId();
|
|
225
301
|
const note = new Y.Map();
|
|
226
|
-
note
|
|
227
|
-
note.set('sys:flavour', 'affine:note');
|
|
302
|
+
setSysFields(note, noteId, "affine:note");
|
|
228
303
|
note.set('sys:parent', pageId);
|
|
229
|
-
note.set('prop:displayMode', '
|
|
230
|
-
note.set('prop:xywh', '[0,0,800,
|
|
304
|
+
note.set('prop:displayMode', 'both');
|
|
305
|
+
note.set('prop:xywh', '[0,0,800,95]');
|
|
231
306
|
note.set('prop:index', 'a0');
|
|
232
|
-
note.set('prop:
|
|
307
|
+
note.set('prop:hidden', false);
|
|
308
|
+
const background = new Y.Map();
|
|
309
|
+
background.set("light", "#ffffff");
|
|
310
|
+
background.set("dark", "#252525");
|
|
311
|
+
note.set("prop:background", background);
|
|
233
312
|
const noteChildren = new Y.Array();
|
|
234
313
|
note.set('sys:children', noteChildren);
|
|
235
314
|
blocks.set(noteId, note);
|
|
@@ -237,8 +316,7 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
237
316
|
if (parsed.content) {
|
|
238
317
|
const paraId = generateId();
|
|
239
318
|
const para = new Y.Map();
|
|
240
|
-
para
|
|
241
|
-
para.set('sys:flavour', 'affine:paragraph');
|
|
319
|
+
setSysFields(para, paraId, "affine:paragraph");
|
|
242
320
|
para.set('sys:parent', noteId);
|
|
243
321
|
para.set('sys:children', new Y.Array());
|
|
244
322
|
para.set('prop:type', 'text');
|
|
@@ -293,90 +371,15 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
293
371
|
content: z.string().optional(),
|
|
294
372
|
},
|
|
295
373
|
}, createDocHandler);
|
|
296
|
-
server.registerTool('affine_create_doc', {
|
|
297
|
-
title: 'Create Document',
|
|
298
|
-
description: 'Create a new AFFiNE document with optional content',
|
|
299
|
-
inputSchema: {
|
|
300
|
-
workspaceId: z.string().optional(),
|
|
301
|
-
title: z.string().optional(),
|
|
302
|
-
content: z.string().optional(),
|
|
303
|
-
},
|
|
304
|
-
}, createDocHandler);
|
|
305
374
|
// APPEND PARAGRAPH
|
|
306
375
|
const appendParagraphHandler = async (parsed) => {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await joinWorkspace(socket, workspaceId);
|
|
315
|
-
const doc = new Y.Doc();
|
|
316
|
-
const snapshot = await loadDoc(socket, workspaceId, parsed.docId);
|
|
317
|
-
if (snapshot.missing) {
|
|
318
|
-
Y.applyUpdate(doc, Buffer.from(snapshot.missing, 'base64'));
|
|
319
|
-
}
|
|
320
|
-
const prevSV = Y.encodeStateVector(doc);
|
|
321
|
-
const blocks = doc.getMap('blocks');
|
|
322
|
-
// find a note block
|
|
323
|
-
let noteId = null;
|
|
324
|
-
for (const [key, val] of blocks) {
|
|
325
|
-
const m = val;
|
|
326
|
-
if (m?.get && m.get('sys:flavour') === 'affine:note') {
|
|
327
|
-
noteId = m.get('sys:id');
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (!noteId) {
|
|
332
|
-
// fallback: create a note under existing page
|
|
333
|
-
let pageId = null;
|
|
334
|
-
for (const [key, val] of blocks) {
|
|
335
|
-
const m = val;
|
|
336
|
-
if (m?.get && m.get('sys:flavour') === 'affine:page') {
|
|
337
|
-
pageId = m.get('sys:id');
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (!pageId)
|
|
342
|
-
throw new Error('Doc has no page block');
|
|
343
|
-
const note = new Y.Map();
|
|
344
|
-
noteId = generateId();
|
|
345
|
-
note.set('sys:id', noteId);
|
|
346
|
-
note.set('sys:flavour', 'affine:note');
|
|
347
|
-
note.set('sys:parent', pageId);
|
|
348
|
-
note.set('prop:displayMode', 'DocAndEdgeless');
|
|
349
|
-
note.set('prop:xywh', '[0,0,800,600]');
|
|
350
|
-
note.set('prop:index', 'a0');
|
|
351
|
-
note.set('prop:lockedBySelf', false);
|
|
352
|
-
note.set('sys:children', new Y.Array());
|
|
353
|
-
blocks.set(noteId, note);
|
|
354
|
-
const page = blocks.get(pageId);
|
|
355
|
-
const children = page.get('sys:children');
|
|
356
|
-
children.push([noteId]);
|
|
357
|
-
}
|
|
358
|
-
const paragraphId = generateId();
|
|
359
|
-
const para = new Y.Map();
|
|
360
|
-
para.set('sys:id', paragraphId);
|
|
361
|
-
para.set('sys:flavour', 'affine:paragraph');
|
|
362
|
-
para.set('sys:parent', noteId);
|
|
363
|
-
para.set('sys:children', new Y.Array());
|
|
364
|
-
para.set('prop:type', 'text');
|
|
365
|
-
const ptext = new Y.Text();
|
|
366
|
-
ptext.insert(0, parsed.text);
|
|
367
|
-
para.set('prop:text', ptext);
|
|
368
|
-
blocks.set(paragraphId, para);
|
|
369
|
-
const note = blocks.get(noteId);
|
|
370
|
-
const noteChildren = note.get('sys:children');
|
|
371
|
-
noteChildren.push([paragraphId]);
|
|
372
|
-
const delta = Y.encodeStateAsUpdate(doc, prevSV);
|
|
373
|
-
const deltaB64 = Buffer.from(delta).toString('base64');
|
|
374
|
-
await pushDocUpdate(socket, workspaceId, parsed.docId, deltaB64);
|
|
375
|
-
return text({ appended: true, paragraphId });
|
|
376
|
-
}
|
|
377
|
-
finally {
|
|
378
|
-
socket.disconnect();
|
|
379
|
-
}
|
|
376
|
+
const result = await appendBlockInternal({
|
|
377
|
+
workspaceId: parsed.workspaceId,
|
|
378
|
+
docId: parsed.docId,
|
|
379
|
+
type: "paragraph",
|
|
380
|
+
text: parsed.text,
|
|
381
|
+
});
|
|
382
|
+
return text({ appended: result.appended, paragraphId: result.blockId });
|
|
380
383
|
};
|
|
381
384
|
server.registerTool('append_paragraph', {
|
|
382
385
|
title: 'Append Paragraph',
|
|
@@ -387,15 +390,28 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
387
390
|
text: z.string(),
|
|
388
391
|
},
|
|
389
392
|
}, appendParagraphHandler);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
+
const appendBlockHandler = async (parsed) => {
|
|
394
|
+
const result = await appendBlockInternal(parsed);
|
|
395
|
+
return text({
|
|
396
|
+
appended: result.appended,
|
|
397
|
+
blockId: result.blockId,
|
|
398
|
+
flavour: result.flavour,
|
|
399
|
+
type: result.blockType || null,
|
|
400
|
+
});
|
|
401
|
+
};
|
|
402
|
+
server.registerTool("append_block", {
|
|
403
|
+
title: "Append Block",
|
|
404
|
+
description: "Append a slash-command style block (heading/list/todo/code/divider/quote) to a document.",
|
|
393
405
|
inputSchema: {
|
|
394
|
-
workspaceId:
|
|
395
|
-
docId:
|
|
396
|
-
|
|
406
|
+
workspaceId: WorkspaceId.optional(),
|
|
407
|
+
docId: DocId,
|
|
408
|
+
type: AppendBlockType.describe("Block type to append"),
|
|
409
|
+
text: z.string().optional().describe("Block content text"),
|
|
410
|
+
checked: z.boolean().optional().describe("Todo state when type is todo"),
|
|
411
|
+
language: z.string().optional().describe("Code language when type is code"),
|
|
412
|
+
caption: z.string().optional().describe("Code caption when type is code"),
|
|
397
413
|
},
|
|
398
|
-
},
|
|
414
|
+
}, appendBlockHandler);
|
|
399
415
|
// DELETE DOC
|
|
400
416
|
const deleteDocHandler = async (parsed) => {
|
|
401
417
|
const workspaceId = parsed.workspaceId || defaults.workspaceId;
|
|
@@ -441,9 +457,4 @@ export function registerDocTools(server, gql, defaults) {
|
|
|
441
457
|
description: 'Delete a document and remove from workspace list',
|
|
442
458
|
inputSchema: { workspaceId: z.string().optional(), docId: z.string() },
|
|
443
459
|
}, deleteDocHandler);
|
|
444
|
-
server.registerTool('affine_delete_doc', {
|
|
445
|
-
title: 'Delete Document',
|
|
446
|
-
description: 'Delete a document and remove from workspace list',
|
|
447
|
-
inputSchema: { workspaceId: z.string().optional(), docId: z.string() },
|
|
448
|
-
}, deleteDocHandler);
|
|
449
460
|
}
|
package/dist/tools/history.js
CHANGED
|
@@ -9,16 +9,6 @@ export function registerHistoryTools(server, gql, defaults) {
|
|
|
9
9
|
const data = await gql.request(query, { workspaceId, guid: parsed.guid, take: parsed.take, before: parsed.before });
|
|
10
10
|
return text(data.workspace.histories);
|
|
11
11
|
};
|
|
12
|
-
server.registerTool("affine_list_histories", {
|
|
13
|
-
title: "List Histories",
|
|
14
|
-
description: "List doc histories (timestamps) for a doc.",
|
|
15
|
-
inputSchema: {
|
|
16
|
-
workspaceId: z.string().optional(),
|
|
17
|
-
guid: z.string(),
|
|
18
|
-
take: z.number().optional(),
|
|
19
|
-
before: z.string().optional()
|
|
20
|
-
}
|
|
21
|
-
}, listHistoriesHandler);
|
|
22
12
|
server.registerTool("list_histories", {
|
|
23
13
|
title: "List Histories",
|
|
24
14
|
description: "List doc histories (timestamps) for a doc.",
|
|
@@ -29,30 +19,4 @@ export function registerHistoryTools(server, gql, defaults) {
|
|
|
29
19
|
before: z.string().optional()
|
|
30
20
|
}
|
|
31
21
|
}, listHistoriesHandler);
|
|
32
|
-
const recoverDocHandler = async (parsed) => {
|
|
33
|
-
const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
|
|
34
|
-
if (!workspaceId)
|
|
35
|
-
throw new Error("workspaceId required (or set AFFINE_WORKSPACE_ID)");
|
|
36
|
-
const mutation = `mutation Recover($workspaceId:String!,$guid:String!,$timestamp:DateTime!){ recoverDoc(workspaceId:$workspaceId, guid:$guid, timestamp:$timestamp) }`;
|
|
37
|
-
const data = await gql.request(mutation, { workspaceId, guid: parsed.guid, timestamp: parsed.timestamp });
|
|
38
|
-
return text({ recoveredAt: data.recoverDoc });
|
|
39
|
-
};
|
|
40
|
-
server.registerTool("affine_recover_doc", {
|
|
41
|
-
title: "Recover Document",
|
|
42
|
-
description: "Recover a doc to a previous timestamp.",
|
|
43
|
-
inputSchema: {
|
|
44
|
-
workspaceId: z.string().optional(),
|
|
45
|
-
guid: z.string(),
|
|
46
|
-
timestamp: z.string()
|
|
47
|
-
}
|
|
48
|
-
}, recoverDocHandler);
|
|
49
|
-
server.registerTool("recover_doc", {
|
|
50
|
-
title: "Recover Document",
|
|
51
|
-
description: "Recover a doc to a previous timestamp.",
|
|
52
|
-
inputSchema: {
|
|
53
|
-
workspaceId: z.string().optional(),
|
|
54
|
-
guid: z.string(),
|
|
55
|
-
timestamp: z.string()
|
|
56
|
-
}
|
|
57
|
-
}, recoverDocHandler);
|
|
58
22
|
}
|
|
@@ -2,30 +2,41 @@ import { z } from "zod";
|
|
|
2
2
|
import { text } from "../util/mcp.js";
|
|
3
3
|
export function registerNotificationTools(server, gql) {
|
|
4
4
|
// LIST NOTIFICATIONS
|
|
5
|
-
const listNotificationsHandler = async ({ first = 20, unreadOnly = false }) => {
|
|
5
|
+
const listNotificationsHandler = async ({ first = 20, offset, after, unreadOnly = false }) => {
|
|
6
6
|
try {
|
|
7
7
|
const query = `
|
|
8
|
-
query GetNotifications($
|
|
8
|
+
query GetNotifications($pagination: PaginationInput!) {
|
|
9
9
|
currentUser {
|
|
10
|
-
notifications(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
notifications(pagination: $pagination) {
|
|
11
|
+
edges {
|
|
12
|
+
cursor
|
|
13
|
+
node {
|
|
14
|
+
id
|
|
15
|
+
type
|
|
16
|
+
body
|
|
17
|
+
read
|
|
18
|
+
level
|
|
19
|
+
createdAt
|
|
20
|
+
updatedAt
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
totalCount
|
|
20
24
|
pageInfo {
|
|
21
25
|
hasNextPage
|
|
26
|
+
endCursor
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
`;
|
|
27
|
-
const data = await gql.request(query, {
|
|
28
|
-
|
|
32
|
+
const data = await gql.request(query, {
|
|
33
|
+
pagination: {
|
|
34
|
+
first,
|
|
35
|
+
offset,
|
|
36
|
+
after
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
let notifications = (data.currentUser?.notifications?.edges || []).map((edge) => edge.node);
|
|
29
40
|
if (unreadOnly) {
|
|
30
41
|
notifications = notifications.filter((n) => !n.read);
|
|
31
42
|
}
|
|
@@ -35,51 +46,16 @@ export function registerNotificationTools(server, gql) {
|
|
|
35
46
|
return text({ error: error.message });
|
|
36
47
|
}
|
|
37
48
|
};
|
|
38
|
-
server.registerTool("affine_list_notifications", {
|
|
39
|
-
title: "List Notifications",
|
|
40
|
-
description: "Get user notifications.",
|
|
41
|
-
inputSchema: {
|
|
42
|
-
first: z.number().optional().describe("Number of notifications to fetch"),
|
|
43
|
-
unreadOnly: z.boolean().optional().describe("Show only unread notifications")
|
|
44
|
-
}
|
|
45
|
-
}, listNotificationsHandler);
|
|
46
49
|
server.registerTool("list_notifications", {
|
|
47
50
|
title: "List Notifications",
|
|
48
51
|
description: "Get user notifications.",
|
|
49
52
|
inputSchema: {
|
|
50
53
|
first: z.number().optional().describe("Number of notifications to fetch"),
|
|
54
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
55
|
+
after: z.string().optional().describe("Cursor for pagination"),
|
|
51
56
|
unreadOnly: z.boolean().optional().describe("Show only unread notifications")
|
|
52
57
|
}
|
|
53
58
|
}, listNotificationsHandler);
|
|
54
|
-
// MARK NOTIFICATION AS READ
|
|
55
|
-
const readNotificationHandler = async ({ id }) => {
|
|
56
|
-
try {
|
|
57
|
-
const mutation = `
|
|
58
|
-
mutation ReadNotification($id: String!) {
|
|
59
|
-
readNotification(id: $id)
|
|
60
|
-
}
|
|
61
|
-
`;
|
|
62
|
-
const data = await gql.request(mutation, { id });
|
|
63
|
-
return text({ success: data.readNotification, notificationId: id });
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
return text({ error: error.message });
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
server.registerTool("affine_read_notification", {
|
|
70
|
-
title: "Mark Notification Read",
|
|
71
|
-
description: "Mark a notification as read.",
|
|
72
|
-
inputSchema: {
|
|
73
|
-
id: z.string().describe("Notification ID")
|
|
74
|
-
}
|
|
75
|
-
}, readNotificationHandler);
|
|
76
|
-
server.registerTool("read_notification", {
|
|
77
|
-
title: "Mark Notification Read",
|
|
78
|
-
description: "Mark a notification as read.",
|
|
79
|
-
inputSchema: {
|
|
80
|
-
id: z.string().describe("Notification ID")
|
|
81
|
-
}
|
|
82
|
-
}, readNotificationHandler);
|
|
83
59
|
// MARK ALL NOTIFICATIONS READ
|
|
84
60
|
const readAllNotificationsHandler = async () => {
|
|
85
61
|
try {
|
|
@@ -95,11 +71,6 @@ export function registerNotificationTools(server, gql) {
|
|
|
95
71
|
return text({ error: error.message });
|
|
96
72
|
}
|
|
97
73
|
};
|
|
98
|
-
server.registerTool("affine_read_all_notifications", {
|
|
99
|
-
title: "Mark All Notifications Read",
|
|
100
|
-
description: "Mark all notifications as read.",
|
|
101
|
-
inputSchema: {}
|
|
102
|
-
}, readAllNotificationsHandler);
|
|
103
74
|
server.registerTool("read_all_notifications", {
|
|
104
75
|
title: "Mark All Notifications Read",
|
|
105
76
|
description: "Mark all notifications as read.",
|