affine-mcp-server 1.2.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/LICENSE +22 -0
- package/README.md +228 -0
- package/dist/auth.js +37 -0
- package/dist/config.js +47 -0
- package/dist/graphqlClient.js +45 -0
- package/dist/index.js +68 -0
- package/dist/tools/accessTokens.js +65 -0
- package/dist/tools/auth.js +26 -0
- package/dist/tools/blobStorage.js +112 -0
- package/dist/tools/comments.js +128 -0
- package/dist/tools/docs.js +449 -0
- package/dist/tools/history.js +58 -0
- package/dist/tools/notifications.js +108 -0
- package/dist/tools/updates.js +32 -0
- package/dist/tools/user.js +18 -0
- package/dist/tools/userCRUD.js +209 -0
- package/dist/tools/workspaces.js +373 -0
- package/dist/types.js +1 -0
- package/dist/util/mcp.js +4 -0
- package/dist/ws.js +64 -0
- package/package.json +57 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import fetch from "node-fetch";
|
|
5
|
+
import { io } from "socket.io-client";
|
|
6
|
+
import { text } from "../util/mcp.js";
|
|
7
|
+
// Generate AFFiNE-style document ID
|
|
8
|
+
function generateDocId() {
|
|
9
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
|
|
10
|
+
let id = '';
|
|
11
|
+
for (let i = 0; i < 10; i++) {
|
|
12
|
+
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
13
|
+
}
|
|
14
|
+
return id;
|
|
15
|
+
}
|
|
16
|
+
// Create initial workspace data with a document
|
|
17
|
+
function createInitialWorkspaceData(workspaceName = 'New Workspace') {
|
|
18
|
+
// Create workspace root YDoc
|
|
19
|
+
const rootDoc = new Y.Doc();
|
|
20
|
+
// Set workspace metadata
|
|
21
|
+
const meta = rootDoc.getMap('meta');
|
|
22
|
+
meta.set('name', workspaceName);
|
|
23
|
+
meta.set('avatar', '');
|
|
24
|
+
// Create pages array with initial document
|
|
25
|
+
const pages = new Y.Array();
|
|
26
|
+
const firstDocId = generateDocId();
|
|
27
|
+
// Add first document metadata
|
|
28
|
+
const pageMetadata = new Y.Map();
|
|
29
|
+
pageMetadata.set('id', firstDocId);
|
|
30
|
+
pageMetadata.set('title', 'Welcome to ' + workspaceName);
|
|
31
|
+
pageMetadata.set('createDate', Date.now());
|
|
32
|
+
pageMetadata.set('tags', new Y.Array());
|
|
33
|
+
pages.push([pageMetadata]);
|
|
34
|
+
meta.set('pages', pages);
|
|
35
|
+
// Create settings
|
|
36
|
+
const setting = rootDoc.getMap('setting');
|
|
37
|
+
setting.set('collections', new Y.Array());
|
|
38
|
+
// Encode workspace update
|
|
39
|
+
const workspaceUpdate = Y.encodeStateAsUpdate(rootDoc);
|
|
40
|
+
// Create the actual document
|
|
41
|
+
const docYDoc = new Y.Doc();
|
|
42
|
+
const blocks = docYDoc.getMap('blocks');
|
|
43
|
+
// Create page block with proper structure
|
|
44
|
+
const pageId = generateDocId();
|
|
45
|
+
const pageBlock = new Y.Map();
|
|
46
|
+
pageBlock.set('sys:id', pageId);
|
|
47
|
+
pageBlock.set('sys:flavour', 'affine:page');
|
|
48
|
+
// Title as Y.Text
|
|
49
|
+
const titleText = new Y.Text();
|
|
50
|
+
titleText.insert(0, 'Welcome to ' + workspaceName);
|
|
51
|
+
pageBlock.set('prop:title', titleText);
|
|
52
|
+
// Children
|
|
53
|
+
const pageChildren = new Y.Array();
|
|
54
|
+
pageBlock.set('sys:children', pageChildren);
|
|
55
|
+
blocks.set(pageId, pageBlock);
|
|
56
|
+
// Add surface block (required)
|
|
57
|
+
const surfaceId = generateDocId();
|
|
58
|
+
const surfaceBlock = new Y.Map();
|
|
59
|
+
surfaceBlock.set('sys:id', surfaceId);
|
|
60
|
+
surfaceBlock.set('sys:flavour', 'affine:surface');
|
|
61
|
+
surfaceBlock.set('sys:parent', pageId);
|
|
62
|
+
surfaceBlock.set('sys:children', new Y.Array());
|
|
63
|
+
blocks.set(surfaceId, surfaceBlock);
|
|
64
|
+
pageChildren.push([surfaceId]);
|
|
65
|
+
// Add note block with xywh
|
|
66
|
+
const noteId = generateDocId();
|
|
67
|
+
const noteBlock = new Y.Map();
|
|
68
|
+
noteBlock.set('sys:id', noteId);
|
|
69
|
+
noteBlock.set('sys:flavour', 'affine:note');
|
|
70
|
+
noteBlock.set('sys:parent', pageId);
|
|
71
|
+
noteBlock.set('prop:displayMode', 'DocAndEdgeless');
|
|
72
|
+
noteBlock.set('prop:xywh', '[0,0,800,600]');
|
|
73
|
+
noteBlock.set('prop:index', 'a0');
|
|
74
|
+
noteBlock.set('prop:lockedBySelf', false);
|
|
75
|
+
const noteChildren = new Y.Array();
|
|
76
|
+
noteBlock.set('sys:children', noteChildren);
|
|
77
|
+
blocks.set(noteId, noteBlock);
|
|
78
|
+
pageChildren.push([noteId]);
|
|
79
|
+
// Add initial paragraph
|
|
80
|
+
const paragraphId = generateDocId();
|
|
81
|
+
const paragraphBlock = new Y.Map();
|
|
82
|
+
paragraphBlock.set('sys:id', paragraphId);
|
|
83
|
+
paragraphBlock.set('sys:flavour', 'affine:paragraph');
|
|
84
|
+
paragraphBlock.set('sys:parent', noteId);
|
|
85
|
+
paragraphBlock.set('sys:children', new Y.Array());
|
|
86
|
+
paragraphBlock.set('prop:type', 'text');
|
|
87
|
+
const paragraphText = new Y.Text();
|
|
88
|
+
paragraphText.insert(0, 'This workspace was created by AFFiNE MCP Server');
|
|
89
|
+
paragraphBlock.set('prop:text', paragraphText);
|
|
90
|
+
blocks.set(paragraphId, paragraphBlock);
|
|
91
|
+
noteChildren.push([paragraphId]);
|
|
92
|
+
// Set document metadata
|
|
93
|
+
const docMeta = docYDoc.getMap('meta');
|
|
94
|
+
docMeta.set('id', firstDocId);
|
|
95
|
+
docMeta.set('title', 'Welcome to ' + workspaceName);
|
|
96
|
+
docMeta.set('createDate', Date.now());
|
|
97
|
+
docMeta.set('tags', new Y.Array());
|
|
98
|
+
docMeta.set('version', 1);
|
|
99
|
+
// Encode document update
|
|
100
|
+
const docUpdate = Y.encodeStateAsUpdate(docYDoc);
|
|
101
|
+
return {
|
|
102
|
+
workspaceUpdate,
|
|
103
|
+
firstDocId,
|
|
104
|
+
docUpdate
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function registerWorkspaceTools(server, gql) {
|
|
108
|
+
// LIST WORKSPACES
|
|
109
|
+
const listWorkspacesHandler = async () => {
|
|
110
|
+
try {
|
|
111
|
+
const query = `query { workspaces { id public enableAi createdAt } }`;
|
|
112
|
+
const data = await gql.request(query);
|
|
113
|
+
return text(data.workspaces || []);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return text({ error: error.message });
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
server.registerTool("list_workspaces", {
|
|
120
|
+
title: "List Workspaces",
|
|
121
|
+
description: "List all available AFFiNE workspaces"
|
|
122
|
+
}, listWorkspacesHandler);
|
|
123
|
+
server.registerTool("affine_list_workspaces", {
|
|
124
|
+
title: "List Workspaces",
|
|
125
|
+
description: "List all available AFFiNE workspaces"
|
|
126
|
+
}, listWorkspacesHandler);
|
|
127
|
+
// GET WORKSPACE
|
|
128
|
+
const getWorkspaceHandler = async ({ id }) => {
|
|
129
|
+
try {
|
|
130
|
+
const query = `query GetWorkspace($id: String!) {
|
|
131
|
+
workspace(id: $id) {
|
|
132
|
+
id
|
|
133
|
+
public
|
|
134
|
+
enableAi
|
|
135
|
+
createdAt
|
|
136
|
+
permissions {
|
|
137
|
+
Workspace_Read
|
|
138
|
+
Workspace_CreateDoc
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}`;
|
|
142
|
+
const data = await gql.request(query, { id });
|
|
143
|
+
return text(data.workspace);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return text({ error: error.message });
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
server.registerTool("get_workspace", {
|
|
150
|
+
title: "Get Workspace",
|
|
151
|
+
description: "Get details of a specific workspace",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
id: z.string().describe("Workspace ID")
|
|
154
|
+
}
|
|
155
|
+
}, getWorkspaceHandler);
|
|
156
|
+
server.registerTool("affine_get_workspace", {
|
|
157
|
+
title: "Get Workspace",
|
|
158
|
+
description: "Get details of a specific workspace",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
id: z.string().describe("Workspace ID")
|
|
161
|
+
}
|
|
162
|
+
}, getWorkspaceHandler);
|
|
163
|
+
// CREATE WORKSPACE
|
|
164
|
+
const createWorkspaceHandler = async ({ name, avatar }) => {
|
|
165
|
+
try {
|
|
166
|
+
// Get endpoint and headers from GraphQL client
|
|
167
|
+
const endpoint = gql.endpoint || process.env.AFFINE_BASE_URL + '/graphql';
|
|
168
|
+
const headers = gql.headers || {};
|
|
169
|
+
const cookie = gql.cookie || headers.Cookie || '';
|
|
170
|
+
// Create initial workspace data
|
|
171
|
+
const { workspaceUpdate, firstDocId, docUpdate } = createInitialWorkspaceData(name);
|
|
172
|
+
// Only send workspace update - document will be created separately
|
|
173
|
+
const initData = Buffer.from(workspaceUpdate);
|
|
174
|
+
// Create multipart form
|
|
175
|
+
const form = new FormData();
|
|
176
|
+
// Add GraphQL operation
|
|
177
|
+
form.append('operations', JSON.stringify({
|
|
178
|
+
name: 'createWorkspace',
|
|
179
|
+
query: `mutation createWorkspace($init: Upload!) {
|
|
180
|
+
createWorkspace(init: $init) {
|
|
181
|
+
id
|
|
182
|
+
public
|
|
183
|
+
createdAt
|
|
184
|
+
enableAi
|
|
185
|
+
}
|
|
186
|
+
}`,
|
|
187
|
+
variables: { init: null }
|
|
188
|
+
}));
|
|
189
|
+
// Map file to variable
|
|
190
|
+
form.append('map', JSON.stringify({ '0': ['variables.init'] }));
|
|
191
|
+
// Add workspace init data
|
|
192
|
+
form.append('0', initData, {
|
|
193
|
+
filename: 'init.yjs',
|
|
194
|
+
contentType: 'application/octet-stream'
|
|
195
|
+
});
|
|
196
|
+
// Send request
|
|
197
|
+
const response = await fetch(endpoint, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
...headers,
|
|
201
|
+
'Cookie': cookie,
|
|
202
|
+
...form.getHeaders()
|
|
203
|
+
},
|
|
204
|
+
body: form
|
|
205
|
+
});
|
|
206
|
+
const result = await response.json();
|
|
207
|
+
if (result.errors) {
|
|
208
|
+
throw new Error(result.errors[0].message);
|
|
209
|
+
}
|
|
210
|
+
const workspace = result.data.createWorkspace;
|
|
211
|
+
// Now create the actual document via WebSocket
|
|
212
|
+
const wsUrl = endpoint.replace('https://', 'wss://').replace('http://', 'ws://').replace('/graphql', '');
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
const socket = io(wsUrl, {
|
|
215
|
+
transports: ['websocket'],
|
|
216
|
+
path: '/socket.io/',
|
|
217
|
+
extraHeaders: cookie ? { Cookie: cookie } : undefined
|
|
218
|
+
});
|
|
219
|
+
socket.on('connect', () => {
|
|
220
|
+
// Join the workspace
|
|
221
|
+
socket.emit('space:join', {
|
|
222
|
+
spaceType: 'workspace',
|
|
223
|
+
spaceId: workspace.id
|
|
224
|
+
});
|
|
225
|
+
// Send the document update
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
const docUpdateBase64 = Buffer.from(docUpdate).toString('base64');
|
|
228
|
+
socket.emit('space:push-doc-update', {
|
|
229
|
+
spaceType: 'workspace',
|
|
230
|
+
spaceId: workspace.id,
|
|
231
|
+
docId: firstDocId,
|
|
232
|
+
update: docUpdateBase64
|
|
233
|
+
});
|
|
234
|
+
// Wait longer for sync and disconnect
|
|
235
|
+
setTimeout(() => {
|
|
236
|
+
socket.disconnect();
|
|
237
|
+
resolve(text({
|
|
238
|
+
...workspace,
|
|
239
|
+
name: name,
|
|
240
|
+
avatar: avatar,
|
|
241
|
+
firstDocId: firstDocId,
|
|
242
|
+
status: "success",
|
|
243
|
+
message: "Workspace created successfully",
|
|
244
|
+
url: `${process.env.AFFINE_BASE_URL}/workspace/${workspace.id}`
|
|
245
|
+
}));
|
|
246
|
+
}, 3000);
|
|
247
|
+
}, 1000);
|
|
248
|
+
});
|
|
249
|
+
socket.on('error', () => {
|
|
250
|
+
socket.disconnect();
|
|
251
|
+
// Even if WebSocket fails, workspace was created
|
|
252
|
+
resolve(text({
|
|
253
|
+
...workspace,
|
|
254
|
+
name: name,
|
|
255
|
+
avatar: avatar,
|
|
256
|
+
firstDocId: firstDocId,
|
|
257
|
+
status: "partial",
|
|
258
|
+
message: "Workspace created (document sync may be pending)",
|
|
259
|
+
url: `${process.env.AFFINE_BASE_URL}/workspace/${workspace.id}`
|
|
260
|
+
}));
|
|
261
|
+
});
|
|
262
|
+
// Timeout
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
socket.disconnect();
|
|
265
|
+
resolve(text({
|
|
266
|
+
...workspace,
|
|
267
|
+
name: name,
|
|
268
|
+
avatar: avatar,
|
|
269
|
+
firstDocId: firstDocId,
|
|
270
|
+
status: "success",
|
|
271
|
+
message: "Workspace created",
|
|
272
|
+
url: `${process.env.AFFINE_BASE_URL}/workspace/${workspace.id}`
|
|
273
|
+
}));
|
|
274
|
+
}, 10000);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
return text({ error: error.message, status: "failed" });
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
server.registerTool("create_workspace", {
|
|
282
|
+
title: "Create Workspace",
|
|
283
|
+
description: "Create a new workspace with initial document (accessible in UI)",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
name: z.string().describe("Workspace name"),
|
|
286
|
+
avatar: z.string().optional().describe("Avatar emoji or URL")
|
|
287
|
+
}
|
|
288
|
+
}, createWorkspaceHandler);
|
|
289
|
+
server.registerTool("affine_create_workspace", {
|
|
290
|
+
title: "Create Workspace",
|
|
291
|
+
description: "Create a new workspace with initial document (accessible in UI)",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
name: z.string().describe("Workspace name"),
|
|
294
|
+
avatar: z.string().optional().describe("Avatar emoji or URL")
|
|
295
|
+
}
|
|
296
|
+
}, createWorkspaceHandler);
|
|
297
|
+
server.registerTool("affine_create_workspace_fixed", {
|
|
298
|
+
title: "Create Workspace (Fixed)",
|
|
299
|
+
description: "Create a new workspace with initial document (backward compatible alias)",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
name: z.string().describe("Workspace name"),
|
|
302
|
+
avatar: z.string().optional().describe("Avatar emoji or URL")
|
|
303
|
+
}
|
|
304
|
+
}, createWorkspaceHandler);
|
|
305
|
+
// UPDATE WORKSPACE
|
|
306
|
+
const updateWorkspaceHandler = async ({ id, public: isPublic, enableAi }) => {
|
|
307
|
+
try {
|
|
308
|
+
const mutation = `
|
|
309
|
+
mutation UpdateWorkspace($input: UpdateWorkspaceInput!) {
|
|
310
|
+
updateWorkspace(input: $input) {
|
|
311
|
+
id
|
|
312
|
+
public
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
const input = { id };
|
|
317
|
+
if (isPublic !== undefined)
|
|
318
|
+
input.public = isPublic;
|
|
319
|
+
const data = await gql.request(mutation, { input });
|
|
320
|
+
return text(data.updateWorkspace);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
return text({ error: error.message });
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
server.registerTool("update_workspace", {
|
|
327
|
+
title: "Update Workspace",
|
|
328
|
+
description: "Update workspace settings",
|
|
329
|
+
inputSchema: {
|
|
330
|
+
id: z.string().describe("Workspace ID"),
|
|
331
|
+
public: z.boolean().optional().describe("Make workspace public"),
|
|
332
|
+
enableAi: z.boolean().optional().describe("Enable AI features")
|
|
333
|
+
}
|
|
334
|
+
}, updateWorkspaceHandler);
|
|
335
|
+
server.registerTool("affine_update_workspace", {
|
|
336
|
+
title: "Update Workspace",
|
|
337
|
+
description: "Update workspace settings",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
id: z.string().describe("Workspace ID"),
|
|
340
|
+
public: z.boolean().optional().describe("Make workspace public"),
|
|
341
|
+
enableAi: z.boolean().optional().describe("Enable AI features")
|
|
342
|
+
}
|
|
343
|
+
}, updateWorkspaceHandler);
|
|
344
|
+
// DELETE WORKSPACE
|
|
345
|
+
const deleteWorkspaceHandler = async ({ id }) => {
|
|
346
|
+
try {
|
|
347
|
+
const mutation = `
|
|
348
|
+
mutation DeleteWorkspace($id: String!) {
|
|
349
|
+
deleteWorkspace(id: $id)
|
|
350
|
+
}
|
|
351
|
+
`;
|
|
352
|
+
const data = await gql.request(mutation, { id });
|
|
353
|
+
return text({ success: data.deleteWorkspace, message: "Workspace deleted successfully" });
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
return text({ error: error.message });
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
server.registerTool("delete_workspace", {
|
|
360
|
+
title: "Delete Workspace",
|
|
361
|
+
description: "Delete a workspace permanently",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
id: z.string().describe("Workspace ID")
|
|
364
|
+
}
|
|
365
|
+
}, deleteWorkspaceHandler);
|
|
366
|
+
server.registerTool("affine_delete_workspace", {
|
|
367
|
+
title: "Delete Workspace",
|
|
368
|
+
description: "Delete a workspace permanently",
|
|
369
|
+
inputSchema: {
|
|
370
|
+
id: z.string().describe("Workspace ID")
|
|
371
|
+
}
|
|
372
|
+
}, deleteWorkspaceHandler);
|
|
373
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/util/mcp.js
ADDED
package/dist/ws.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { io } from "socket.io-client";
|
|
2
|
+
export function wsUrlFromGraphQLEndpoint(endpoint) {
|
|
3
|
+
return endpoint
|
|
4
|
+
.replace('https://', 'wss://')
|
|
5
|
+
.replace('http://', 'ws://')
|
|
6
|
+
.replace(/\/graphql\/?$/, '');
|
|
7
|
+
}
|
|
8
|
+
export async function connectWorkspaceSocket(wsUrl, cookie) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const socket = io(wsUrl, {
|
|
11
|
+
transports: ['websocket'],
|
|
12
|
+
path: '/socket.io/',
|
|
13
|
+
extraHeaders: cookie ? { Cookie: cookie } : undefined,
|
|
14
|
+
autoConnect: true
|
|
15
|
+
});
|
|
16
|
+
const onError = (err) => {
|
|
17
|
+
cleanup();
|
|
18
|
+
reject(err);
|
|
19
|
+
};
|
|
20
|
+
const onConnect = () => {
|
|
21
|
+
socket.off('connect_error', onError);
|
|
22
|
+
resolve(socket);
|
|
23
|
+
};
|
|
24
|
+
const cleanup = () => {
|
|
25
|
+
socket.off('connect', onConnect);
|
|
26
|
+
socket.off('connect_error', onError);
|
|
27
|
+
};
|
|
28
|
+
socket.on('connect', onConnect);
|
|
29
|
+
socket.on('connect_error', onError);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export async function joinWorkspace(socket, workspaceId) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
socket.emit('space:join', { spaceType: 'workspace', spaceId: workspaceId, clientVersion: 'mcp' }, (ack) => {
|
|
35
|
+
if (ack?.error)
|
|
36
|
+
return reject(new Error(ack.error.message || 'join failed'));
|
|
37
|
+
resolve();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function loadDoc(socket, workspaceId, docId) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
socket.emit('space:load-doc', { spaceType: 'workspace', spaceId: workspaceId, docId }, (ack) => {
|
|
44
|
+
if (ack?.error) {
|
|
45
|
+
if (ack.error.name === 'DOC_NOT_FOUND')
|
|
46
|
+
return resolve({});
|
|
47
|
+
return reject(new Error(ack.error.message || 'load-doc failed'));
|
|
48
|
+
}
|
|
49
|
+
resolve(ack?.data || {});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export async function pushDocUpdate(socket, workspaceId, docId, updateBase64) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
socket.emit('space:push-doc-update', { spaceType: 'workspace', spaceId: workspaceId, docId, update: updateBase64 }, (ack) => {
|
|
56
|
+
if (ack?.error)
|
|
57
|
+
return reject(new Error(ack.error.message || 'push-doc-update failed'));
|
|
58
|
+
resolve(ack?.data?.timestamp || Date.now());
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export function deleteDoc(socket, workspaceId, docId) {
|
|
63
|
+
socket.emit('space:delete-doc', { spaceType: 'workspace', spaceId: workspaceId, docId });
|
|
64
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "affine-mcp-server",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Model Context Protocol server for AFFiNE - enables AI assistants to interact with AFFiNE workspaces, documents, and collaboration features.",
|
|
7
|
+
"author": "dawncr0w",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/dawncr0w/affine-mcp-server.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"affine",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"ai",
|
|
18
|
+
"claude",
|
|
19
|
+
"knowledge-base"
|
|
20
|
+
],
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"bin": {
|
|
23
|
+
"affine-mcp": "dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"dev": "tsx watch src/index.ts",
|
|
28
|
+
"start": "node dist/index.js",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.17.2",
|
|
44
|
+
"dotenv": "^16.6.1",
|
|
45
|
+
"form-data": "^4.0.4",
|
|
46
|
+
"node-fetch": "^3.3.2",
|
|
47
|
+
"socket.io-client": "^4.8.1",
|
|
48
|
+
"undici": "^6.19.8",
|
|
49
|
+
"yjs": "^13.6.27",
|
|
50
|
+
"zod": "^3.23.8"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^20.14.11",
|
|
54
|
+
"tsx": "^4.16.2",
|
|
55
|
+
"typescript": "^5.5.4"
|
|
56
|
+
}
|
|
57
|
+
}
|