@mseep/affine-mcp-server 2.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/LICENSE +22 -0
- package/README.md +270 -0
- package/bin/affine-mcp +5 -0
- package/dist/auth.js +61 -0
- package/dist/cli.js +726 -0
- package/dist/config.js +178 -0
- package/dist/edgeless/layout.js +222 -0
- package/dist/graphqlClient.js +116 -0
- package/dist/httpAuth.js +147 -0
- package/dist/httpDiagnostics.js +38 -0
- package/dist/index.js +209 -0
- package/dist/markdown/parse.js +559 -0
- package/dist/markdown/render.js +227 -0
- package/dist/markdown/types.js +1 -0
- package/dist/oauth.js +154 -0
- package/dist/sse.js +261 -0
- package/dist/toolSurface.js +349 -0
- package/dist/tools/accessTokens.js +45 -0
- package/dist/tools/auth.js +18 -0
- package/dist/tools/blobStorage.js +136 -0
- package/dist/tools/comments.js +104 -0
- package/dist/tools/docs.js +7478 -0
- package/dist/tools/history.js +22 -0
- package/dist/tools/icons.js +125 -0
- package/dist/tools/notifications.js +79 -0
- package/dist/tools/organize.js +1145 -0
- package/dist/tools/properties.js +426 -0
- package/dist/tools/user.js +13 -0
- package/dist/tools/userCRUD.js +77 -0
- package/dist/tools/workspaces.js +322 -0
- package/dist/util/explorerIcon.js +95 -0
- package/dist/util/mcp.js +28 -0
- package/dist/ws.js +113 -0
- package/docs/assets/edgeless-canvas-demo-advanced-dark.png +0 -0
- package/docs/assets/edgeless-canvas-demo-advanced-light.png +0 -0
- package/docs/client-setup.md +174 -0
- package/docs/configuration-and-deployment.md +265 -0
- package/docs/edgeless-canvas-cookbook.md +226 -0
- package/docs/getting-started.md +229 -0
- package/docs/tool-reference.md +200 -0
- package/docs/workflow-recipes.md +147 -0
- package/package.json +118 -0
- package/tool-manifest.json +99 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
const ALL_TOOLS = [
|
|
2
|
+
"add_database_column",
|
|
3
|
+
"add_database_row",
|
|
4
|
+
"add_doc_to_collection",
|
|
5
|
+
"add_organize_link",
|
|
6
|
+
"add_surface_element",
|
|
7
|
+
"add_tag_to_doc",
|
|
8
|
+
"analyze_doc_fidelity",
|
|
9
|
+
"append_block",
|
|
10
|
+
"append_markdown",
|
|
11
|
+
"append_semantic_section",
|
|
12
|
+
"cleanup_blobs",
|
|
13
|
+
"clear_doc_property",
|
|
14
|
+
"compose_database_from_intent",
|
|
15
|
+
"create_collection",
|
|
16
|
+
"create_comment",
|
|
17
|
+
"create_custom_property",
|
|
18
|
+
"create_doc",
|
|
19
|
+
"create_doc_from_markdown",
|
|
20
|
+
"create_folder",
|
|
21
|
+
"create_semantic_page",
|
|
22
|
+
"create_tag",
|
|
23
|
+
"create_workspace",
|
|
24
|
+
"create_workspace_blueprint",
|
|
25
|
+
"current_user",
|
|
26
|
+
"delete_blob",
|
|
27
|
+
"delete_block",
|
|
28
|
+
"delete_collection",
|
|
29
|
+
"delete_comment",
|
|
30
|
+
"delete_custom_property",
|
|
31
|
+
"delete_database_row",
|
|
32
|
+
"delete_doc",
|
|
33
|
+
"delete_folder",
|
|
34
|
+
"delete_organize_link",
|
|
35
|
+
"delete_surface_element",
|
|
36
|
+
"delete_workspace",
|
|
37
|
+
"export_doc_markdown",
|
|
38
|
+
"export_with_fidelity_report",
|
|
39
|
+
"find_doc_by_title",
|
|
40
|
+
"generate_access_token",
|
|
41
|
+
"get_capabilities",
|
|
42
|
+
"get_collection",
|
|
43
|
+
"get_doc",
|
|
44
|
+
"get_doc_icon",
|
|
45
|
+
"get_edgeless_canvas",
|
|
46
|
+
"get_folder_icon",
|
|
47
|
+
"get_orphan_docs",
|
|
48
|
+
"get_workspace",
|
|
49
|
+
"inspect_template_structure",
|
|
50
|
+
"instantiate_template_native",
|
|
51
|
+
"list_access_tokens",
|
|
52
|
+
"list_children",
|
|
53
|
+
"list_collections",
|
|
54
|
+
"list_comments",
|
|
55
|
+
"list_doc_properties",
|
|
56
|
+
"list_docs",
|
|
57
|
+
"list_docs_by_tag",
|
|
58
|
+
"list_histories",
|
|
59
|
+
"list_notifications",
|
|
60
|
+
"list_organize_nodes",
|
|
61
|
+
"list_surface_elements",
|
|
62
|
+
"list_tags",
|
|
63
|
+
"list_workspace_tree",
|
|
64
|
+
"list_workspaces",
|
|
65
|
+
"move_doc",
|
|
66
|
+
"move_organize_node",
|
|
67
|
+
"publish_doc",
|
|
68
|
+
"read_all_notifications",
|
|
69
|
+
"read_database_cells",
|
|
70
|
+
"read_database_columns",
|
|
71
|
+
"read_doc",
|
|
72
|
+
"remove_doc_from_collection",
|
|
73
|
+
"remove_tag_from_doc",
|
|
74
|
+
"rename_folder",
|
|
75
|
+
"replace_doc_with_markdown",
|
|
76
|
+
"resolve_comment",
|
|
77
|
+
"revoke_access_token",
|
|
78
|
+
"revoke_doc",
|
|
79
|
+
"search_docs",
|
|
80
|
+
"set_doc_property",
|
|
81
|
+
"sign_in",
|
|
82
|
+
"update_collection",
|
|
83
|
+
"update_collection_rules",
|
|
84
|
+
"update_comment",
|
|
85
|
+
"update_database_row",
|
|
86
|
+
"update_doc_icon",
|
|
87
|
+
"update_doc_title",
|
|
88
|
+
"update_edgeless_block",
|
|
89
|
+
"update_folder_icon",
|
|
90
|
+
"update_frame_children",
|
|
91
|
+
"update_profile",
|
|
92
|
+
"update_settings",
|
|
93
|
+
"update_surface_element",
|
|
94
|
+
"update_workspace",
|
|
95
|
+
"upload_blob",
|
|
96
|
+
];
|
|
97
|
+
const TOOL_GROUPS = {
|
|
98
|
+
add_database_column: ["docs", "docs.database", "docs.write", "write"],
|
|
99
|
+
add_database_row: ["docs", "docs.database", "docs.write", "write"],
|
|
100
|
+
add_doc_to_collection: ["organize", "organize.collections", "organize.write", "write"],
|
|
101
|
+
add_organize_link: ["organize", "organize.folders", "organize.write", "experimental", "write"],
|
|
102
|
+
add_surface_element: ["docs", "docs.edgeless", "docs.surface", "docs.write", "write"],
|
|
103
|
+
add_tag_to_doc: ["docs", "docs.tags", "docs.write", "write"],
|
|
104
|
+
analyze_doc_fidelity: ["docs", "docs.read", "docs.export", "read"],
|
|
105
|
+
append_block: ["docs", "docs.write", "write"],
|
|
106
|
+
append_markdown: ["docs", "docs.markdown", "docs.write", "write"],
|
|
107
|
+
append_semantic_section: ["docs", "docs.semantic", "docs.write", "write"],
|
|
108
|
+
cleanup_blobs: ["blobs", "blobs.write", "cleanup", "destructive", "write"],
|
|
109
|
+
clear_doc_property: ["docs", "docs.properties", "docs.write", "write"],
|
|
110
|
+
compose_database_from_intent: ["docs", "docs.database", "docs.intent", "docs.write", "write"],
|
|
111
|
+
create_collection: ["organize", "organize.collections", "organize.write", "write"],
|
|
112
|
+
create_comment: ["comments", "comments.write", "write"],
|
|
113
|
+
create_custom_property: ["docs", "docs.properties", "docs.write", "write"],
|
|
114
|
+
create_doc: ["docs", "docs.write", "write"],
|
|
115
|
+
create_doc_from_markdown: ["docs", "docs.markdown", "docs.write", "write"],
|
|
116
|
+
create_folder: ["organize", "organize.folders", "organize.write", "experimental", "write"],
|
|
117
|
+
create_semantic_page: ["docs", "docs.semantic", "docs.write", "write"],
|
|
118
|
+
create_tag: ["docs", "docs.tags", "docs.write", "write"],
|
|
119
|
+
create_workspace: ["workspaces", "workspaces.write", "admin", "write"],
|
|
120
|
+
create_workspace_blueprint: ["organize", "organize.folders", "organize.write", "experimental", "write"],
|
|
121
|
+
current_user: ["users", "users.read", "read"],
|
|
122
|
+
delete_blob: ["blobs", "blobs.write", "destructive", "write"],
|
|
123
|
+
delete_block: ["docs", "docs.edgeless", "docs.write", "destructive", "write"],
|
|
124
|
+
delete_collection: ["organize", "organize.collections", "organize.write", "destructive", "write"],
|
|
125
|
+
delete_comment: ["comments", "comments.write", "destructive", "write"],
|
|
126
|
+
delete_custom_property: ["docs", "docs.properties", "docs.write", "destructive", "write"],
|
|
127
|
+
delete_database_row: ["docs", "docs.database", "docs.write", "destructive", "write"],
|
|
128
|
+
delete_doc: ["docs", "docs.write", "destructive", "write"],
|
|
129
|
+
delete_folder: ["organize", "organize.folders", "organize.write", "destructive", "experimental", "write"],
|
|
130
|
+
delete_organize_link: ["organize", "organize.folders", "organize.write", "destructive", "experimental", "write"],
|
|
131
|
+
delete_surface_element: ["docs", "docs.edgeless", "docs.surface", "docs.write", "destructive", "write"],
|
|
132
|
+
delete_workspace: ["workspaces", "workspaces.write", "admin", "destructive", "write"],
|
|
133
|
+
export_doc_markdown: ["docs", "docs.export", "docs.markdown", "docs.read", "read"],
|
|
134
|
+
export_with_fidelity_report: ["docs", "docs.export", "docs.markdown", "docs.read", "read"],
|
|
135
|
+
find_doc_by_title: ["docs", "docs.read", "read"],
|
|
136
|
+
generate_access_token: ["access_tokens", "access_tokens.write", "admin", "write"],
|
|
137
|
+
get_capabilities: ["docs", "docs.read", "read"],
|
|
138
|
+
get_collection: ["organize", "organize.collections", "organize.read", "read"],
|
|
139
|
+
get_doc: ["docs", "docs.read", "read"],
|
|
140
|
+
get_doc_icon: ["docs", "docs.read", "read"],
|
|
141
|
+
get_edgeless_canvas: ["docs", "docs.edgeless", "docs.surface", "docs.read", "read"],
|
|
142
|
+
get_folder_icon: ["organize", "organize.folders", "organize.read", "experimental", "read"],
|
|
143
|
+
get_orphan_docs: ["docs", "docs.tree", "docs.read", "read"],
|
|
144
|
+
get_workspace: ["workspaces", "workspaces.read", "read"],
|
|
145
|
+
inspect_template_structure: ["docs", "docs.template", "docs.read", "read"],
|
|
146
|
+
instantiate_template_native: ["docs", "docs.template", "docs.write", "write"],
|
|
147
|
+
list_access_tokens: ["access_tokens", "access_tokens.read", "admin", "read"],
|
|
148
|
+
list_children: ["docs", "docs.tree", "docs.read", "read"],
|
|
149
|
+
list_collections: ["organize", "organize.collections", "organize.read", "read"],
|
|
150
|
+
list_comments: ["comments", "comments.read", "read"],
|
|
151
|
+
list_doc_properties: ["docs", "docs.properties", "docs.read", "read"],
|
|
152
|
+
list_docs: ["docs", "docs.read", "read"],
|
|
153
|
+
list_docs_by_tag: ["docs", "docs.tags", "docs.read", "read"],
|
|
154
|
+
list_histories: ["history", "history.read", "read"],
|
|
155
|
+
list_notifications: ["notifications", "notifications.read", "read"],
|
|
156
|
+
list_organize_nodes: ["organize", "organize.folders", "organize.read", "experimental", "read"],
|
|
157
|
+
list_surface_elements: ["docs", "docs.edgeless", "docs.surface", "docs.read", "read"],
|
|
158
|
+
list_tags: ["docs", "docs.tags", "docs.read", "read"],
|
|
159
|
+
list_workspace_tree: ["docs", "docs.tree", "docs.read", "read"],
|
|
160
|
+
list_workspaces: ["workspaces", "workspaces.read", "read"],
|
|
161
|
+
move_doc: ["docs", "docs.tree", "docs.write", "write"],
|
|
162
|
+
move_organize_node: ["organize", "organize.folders", "organize.write", "experimental", "write"],
|
|
163
|
+
publish_doc: ["docs", "docs.share", "docs.write", "write"],
|
|
164
|
+
read_all_notifications: ["notifications", "notifications.write", "write"],
|
|
165
|
+
read_database_cells: ["docs", "docs.database", "docs.read", "read"],
|
|
166
|
+
read_database_columns: ["docs", "docs.database", "docs.read", "read"],
|
|
167
|
+
read_doc: ["docs", "docs.read", "read"],
|
|
168
|
+
remove_doc_from_collection: ["organize", "organize.collections", "organize.write", "write"],
|
|
169
|
+
remove_tag_from_doc: ["docs", "docs.tags", "docs.write", "write"],
|
|
170
|
+
rename_folder: ["organize", "organize.folders", "organize.write", "experimental", "write"],
|
|
171
|
+
replace_doc_with_markdown: ["docs", "docs.markdown", "docs.write", "write"],
|
|
172
|
+
resolve_comment: ["comments", "comments.write", "write"],
|
|
173
|
+
revoke_access_token: ["access_tokens", "access_tokens.write", "admin", "destructive", "write"],
|
|
174
|
+
revoke_doc: ["docs", "docs.share", "docs.write", "destructive", "write"],
|
|
175
|
+
search_docs: ["docs", "docs.read", "read"],
|
|
176
|
+
set_doc_property: ["docs", "docs.properties", "docs.write", "write"],
|
|
177
|
+
sign_in: ["users", "users.auth", "auth", "write"],
|
|
178
|
+
update_collection: ["organize", "organize.collections", "organize.write", "write"],
|
|
179
|
+
update_collection_rules: ["organize", "organize.collections", "organize.write", "write"],
|
|
180
|
+
update_comment: ["comments", "comments.write", "write"],
|
|
181
|
+
update_database_row: ["docs", "docs.database", "docs.write", "write"],
|
|
182
|
+
update_doc_icon: ["docs", "docs.write", "write"],
|
|
183
|
+
update_doc_title: ["docs", "docs.write", "write"],
|
|
184
|
+
update_edgeless_block: ["docs", "docs.edgeless", "docs.write", "write"],
|
|
185
|
+
update_folder_icon: ["organize", "organize.folders", "organize.write", "experimental", "write"],
|
|
186
|
+
update_frame_children: ["docs", "docs.edgeless", "docs.write", "write"],
|
|
187
|
+
update_profile: ["users", "users.write", "admin", "write"],
|
|
188
|
+
update_settings: ["users", "users.write", "admin", "write"],
|
|
189
|
+
update_surface_element: ["docs", "docs.edgeless", "docs.surface", "docs.write", "write"],
|
|
190
|
+
update_workspace: ["workspaces", "workspaces.write", "admin", "write"],
|
|
191
|
+
upload_blob: ["blobs", "blobs.write", "write"],
|
|
192
|
+
};
|
|
193
|
+
const READ_ONLY_TOOLS = new Set([
|
|
194
|
+
"analyze_doc_fidelity",
|
|
195
|
+
"current_user",
|
|
196
|
+
"export_doc_markdown",
|
|
197
|
+
"export_with_fidelity_report",
|
|
198
|
+
"find_doc_by_title",
|
|
199
|
+
"get_capabilities",
|
|
200
|
+
"get_collection",
|
|
201
|
+
"get_doc",
|
|
202
|
+
"get_doc_icon",
|
|
203
|
+
"get_edgeless_canvas",
|
|
204
|
+
"get_folder_icon",
|
|
205
|
+
"get_orphan_docs",
|
|
206
|
+
"get_workspace",
|
|
207
|
+
"inspect_template_structure",
|
|
208
|
+
"list_access_tokens",
|
|
209
|
+
"list_children",
|
|
210
|
+
"list_collections",
|
|
211
|
+
"list_comments",
|
|
212
|
+
"list_doc_properties",
|
|
213
|
+
"list_docs",
|
|
214
|
+
"list_docs_by_tag",
|
|
215
|
+
"list_histories",
|
|
216
|
+
"list_notifications",
|
|
217
|
+
"list_organize_nodes",
|
|
218
|
+
"list_surface_elements",
|
|
219
|
+
"list_tags",
|
|
220
|
+
"list_workspace_tree",
|
|
221
|
+
"list_workspaces",
|
|
222
|
+
"read_database_cells",
|
|
223
|
+
"read_database_columns",
|
|
224
|
+
"read_doc",
|
|
225
|
+
"search_docs",
|
|
226
|
+
"sign_in",
|
|
227
|
+
]);
|
|
228
|
+
const CORE_TOOLS = new Set([
|
|
229
|
+
"add_database_column",
|
|
230
|
+
"add_database_row",
|
|
231
|
+
"add_tag_to_doc",
|
|
232
|
+
"append_block",
|
|
233
|
+
"append_markdown",
|
|
234
|
+
"create_doc",
|
|
235
|
+
"create_doc_from_markdown",
|
|
236
|
+
"current_user",
|
|
237
|
+
"export_doc_markdown",
|
|
238
|
+
"find_doc_by_title",
|
|
239
|
+
"get_capabilities",
|
|
240
|
+
"get_doc",
|
|
241
|
+
"get_doc_icon",
|
|
242
|
+
"get_workspace",
|
|
243
|
+
"list_children",
|
|
244
|
+
"list_docs",
|
|
245
|
+
"list_docs_by_tag",
|
|
246
|
+
"list_tags",
|
|
247
|
+
"list_workspaces",
|
|
248
|
+
"read_database_cells",
|
|
249
|
+
"read_database_columns",
|
|
250
|
+
"read_doc",
|
|
251
|
+
"remove_tag_from_doc",
|
|
252
|
+
"replace_doc_with_markdown",
|
|
253
|
+
"search_docs",
|
|
254
|
+
"sign_in",
|
|
255
|
+
"update_database_row",
|
|
256
|
+
"update_doc_icon",
|
|
257
|
+
"update_doc_title",
|
|
258
|
+
]);
|
|
259
|
+
const AUTHORING_EXCLUDED_GROUPS = new Set([
|
|
260
|
+
"admin",
|
|
261
|
+
"cleanup",
|
|
262
|
+
"destructive",
|
|
263
|
+
"experimental",
|
|
264
|
+
]);
|
|
265
|
+
const KNOWN_PROFILES = new Set(["full", "read_only", "core", "authoring"]);
|
|
266
|
+
const KNOWN_TOOLS = new Set(ALL_TOOLS);
|
|
267
|
+
const KNOWN_GROUPS = new Set(Object.values(TOOL_GROUPS).flat());
|
|
268
|
+
function parseCsv(raw, normalize = true) {
|
|
269
|
+
return (raw || "")
|
|
270
|
+
.split(",")
|
|
271
|
+
.map(value => value.trim())
|
|
272
|
+
.filter(Boolean)
|
|
273
|
+
.map(value => normalize ? value.toLowerCase() : value);
|
|
274
|
+
}
|
|
275
|
+
function normalizeProfile(raw) {
|
|
276
|
+
const value = (raw || "full").trim().toLowerCase();
|
|
277
|
+
if (KNOWN_PROFILES.has(value)) {
|
|
278
|
+
return { profile: value };
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
profile: "full",
|
|
282
|
+
warning: `Unknown AFFINE_TOOL_PROFILE "${raw}". Using "full". Valid profiles: ${[...KNOWN_PROFILES].join(", ")}`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function profileAllowsTool(profile, toolName) {
|
|
286
|
+
if (profile === "full") {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (profile === "read_only") {
|
|
290
|
+
return READ_ONLY_TOOLS.has(toolName);
|
|
291
|
+
}
|
|
292
|
+
if (profile === "core") {
|
|
293
|
+
return CORE_TOOLS.has(toolName);
|
|
294
|
+
}
|
|
295
|
+
const groups = TOOL_GROUPS[toolName];
|
|
296
|
+
return !groups.some(group => AUTHORING_EXCLUDED_GROUPS.has(group));
|
|
297
|
+
}
|
|
298
|
+
export function createToolFilter(env = process.env) {
|
|
299
|
+
const profileResult = normalizeProfile(env.AFFINE_TOOL_PROFILE);
|
|
300
|
+
const disabledGroups = new Set(parseCsv(env.AFFINE_DISABLED_GROUPS));
|
|
301
|
+
const disabledTools = new Set(parseCsv(env.AFFINE_DISABLED_TOOLS));
|
|
302
|
+
const warnings = [];
|
|
303
|
+
if (profileResult.warning) {
|
|
304
|
+
warnings.push(profileResult.warning);
|
|
305
|
+
}
|
|
306
|
+
for (const group of disabledGroups) {
|
|
307
|
+
if (!KNOWN_GROUPS.has(group)) {
|
|
308
|
+
warnings.push(`Unknown group "${group}" in AFFINE_DISABLED_GROUPS. Valid groups: ${[...KNOWN_GROUPS].sort().join(", ")}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (const tool of disabledTools) {
|
|
312
|
+
if (!KNOWN_TOOLS.has(tool)) {
|
|
313
|
+
warnings.push(`Unknown tool "${tool}" in AFFINE_DISABLED_TOOLS.`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function isEnabled(name) {
|
|
317
|
+
const toolName = name;
|
|
318
|
+
if (!KNOWN_TOOLS.has(toolName)) {
|
|
319
|
+
return profileResult.profile === "full" && disabledGroups.size === 0 && disabledTools.size === 0;
|
|
320
|
+
}
|
|
321
|
+
if (disabledTools.has(toolName)) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
const groups = TOOL_GROUPS[toolName];
|
|
325
|
+
if (groups.some(group => disabledGroups.has(group))) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return profileAllowsTool(profileResult.profile, toolName);
|
|
329
|
+
}
|
|
330
|
+
const enabledTools = ALL_TOOLS.filter(toolName => isEnabled(toolName));
|
|
331
|
+
return {
|
|
332
|
+
profile: profileResult.profile,
|
|
333
|
+
disabledGroups,
|
|
334
|
+
disabledTools,
|
|
335
|
+
warnings,
|
|
336
|
+
enabledTools,
|
|
337
|
+
totalToolCount: ALL_TOOLS.length,
|
|
338
|
+
isEnabled,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
export function toolFilterRequiresRegisterTool(filter) {
|
|
342
|
+
return filter.profile !== "full" || filter.disabledGroups.size > 0 || filter.disabledTools.size > 0;
|
|
343
|
+
}
|
|
344
|
+
export function knownToolSurfaceGroups() {
|
|
345
|
+
return [...KNOWN_GROUPS].sort();
|
|
346
|
+
}
|
|
347
|
+
export function knownToolSurfaceProfiles() {
|
|
348
|
+
return [...KNOWN_PROFILES];
|
|
349
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { text } from "../util/mcp.js";
|
|
3
|
+
export function registerAccessTokenTools(server, gql) {
|
|
4
|
+
const listAccessTokensHandler = async () => {
|
|
5
|
+
try {
|
|
6
|
+
const query = `query { currentUser { accessTokens { id name createdAt expiresAt } } }`;
|
|
7
|
+
const data = await gql.request(query);
|
|
8
|
+
return text(data.currentUser?.accessTokens || []);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
console.error("List access tokens error:", error.message);
|
|
12
|
+
return text({ error: error.message });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
server.registerTool("list_access_tokens", {
|
|
16
|
+
title: "List Access Tokens",
|
|
17
|
+
description: "List personal access tokens (metadata).",
|
|
18
|
+
inputSchema: {}
|
|
19
|
+
}, listAccessTokensHandler);
|
|
20
|
+
const generateAccessTokenHandler = async (parsed) => {
|
|
21
|
+
const mutation = `mutation($input: GenerateAccessTokenInput!){ generateUserAccessToken(input:$input){ id name createdAt expiresAt token } }`;
|
|
22
|
+
const data = await gql.request(mutation, { input: { name: parsed.name, expiresAt: parsed.expiresAt ?? null } });
|
|
23
|
+
return text(data.generateUserAccessToken);
|
|
24
|
+
};
|
|
25
|
+
server.registerTool("generate_access_token", {
|
|
26
|
+
title: "Generate Access Token",
|
|
27
|
+
description: "Generate a personal access token (returns token).",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
name: z.string(),
|
|
30
|
+
expiresAt: z.string().optional()
|
|
31
|
+
}
|
|
32
|
+
}, generateAccessTokenHandler);
|
|
33
|
+
const revokeAccessTokenHandler = async (parsed) => {
|
|
34
|
+
const mutation = `mutation($id:String!){ revokeUserAccessToken(id:$id) }`;
|
|
35
|
+
const data = await gql.request(mutation, { id: parsed.id });
|
|
36
|
+
return text({ success: data.revokeUserAccessToken });
|
|
37
|
+
};
|
|
38
|
+
server.registerTool("revoke_access_token", {
|
|
39
|
+
title: "Revoke Access Token",
|
|
40
|
+
description: "Revoke a personal access token by id.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
id: z.string()
|
|
43
|
+
}
|
|
44
|
+
}, revokeAccessTokenHandler);
|
|
45
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { loginWithPassword } from "../auth.js";
|
|
3
|
+
import { text } from "../util/mcp.js";
|
|
4
|
+
export function registerAuthTools(server, gql, baseUrl) {
|
|
5
|
+
const signInHandler = async (parsed) => {
|
|
6
|
+
const { cookieHeader } = await loginWithPassword(baseUrl, parsed.email, parsed.password);
|
|
7
|
+
gql.setCookie(cookieHeader);
|
|
8
|
+
return text({ signedIn: true });
|
|
9
|
+
};
|
|
10
|
+
server.registerTool("sign_in", {
|
|
11
|
+
title: "Sign In",
|
|
12
|
+
description: "Sign in to AFFiNE using email and password; sets session cookies for subsequent calls.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
email: z.string().email(),
|
|
15
|
+
password: z.string().min(1)
|
|
16
|
+
}
|
|
17
|
+
}, signInHandler);
|
|
18
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { text } from "../util/mcp.js";
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import fetch from "node-fetch";
|
|
5
|
+
function decodeBlobContent(content) {
|
|
6
|
+
const normalized = content.trim().replace(/\s+/g, "");
|
|
7
|
+
const base64Like = normalized.length > 0 && normalized.length % 4 === 0 && /^[A-Za-z0-9+/=]+$/.test(normalized);
|
|
8
|
+
if (base64Like) {
|
|
9
|
+
try {
|
|
10
|
+
const decoded = Buffer.from(normalized, "base64");
|
|
11
|
+
if (decoded.length > 0) {
|
|
12
|
+
return decoded;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Fallback to UTF-8 text below.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return Buffer.from(content, "utf8");
|
|
20
|
+
}
|
|
21
|
+
export function registerBlobTools(server, gql) {
|
|
22
|
+
// UPLOAD BLOB/FILE
|
|
23
|
+
const uploadBlobHandler = async ({ workspaceId, content, filename, contentType }) => {
|
|
24
|
+
try {
|
|
25
|
+
const endpoint = gql.endpoint;
|
|
26
|
+
const headers = gql.headers;
|
|
27
|
+
const cookie = gql.cookie;
|
|
28
|
+
const payload = decodeBlobContent(content);
|
|
29
|
+
const safeFilename = filename || `blob-${Date.now()}.bin`;
|
|
30
|
+
const mime = contentType || "application/octet-stream";
|
|
31
|
+
const form = new FormData();
|
|
32
|
+
form.append("operations", JSON.stringify({
|
|
33
|
+
query: `mutation SetBlob($workspaceId: String!, $blob: Upload!) {
|
|
34
|
+
setBlob(workspaceId: $workspaceId, blob: $blob)
|
|
35
|
+
}`,
|
|
36
|
+
variables: {
|
|
37
|
+
workspaceId,
|
|
38
|
+
blob: null
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
form.append("map", JSON.stringify({ "0": ["variables.blob"] }));
|
|
42
|
+
form.append("0", payload, { filename: safeFilename, contentType: mime });
|
|
43
|
+
const response = await fetch(endpoint, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
...headers,
|
|
47
|
+
Cookie: cookie,
|
|
48
|
+
...form.getHeaders(),
|
|
49
|
+
},
|
|
50
|
+
body: form,
|
|
51
|
+
});
|
|
52
|
+
const result = await response.json();
|
|
53
|
+
if (result.errors?.length) {
|
|
54
|
+
throw new Error(result.errors[0].message);
|
|
55
|
+
}
|
|
56
|
+
const blobKey = result.data?.setBlob;
|
|
57
|
+
if (!blobKey) {
|
|
58
|
+
throw new Error("Upload succeeded but no blob key was returned.");
|
|
59
|
+
}
|
|
60
|
+
return text({
|
|
61
|
+
id: blobKey,
|
|
62
|
+
key: blobKey,
|
|
63
|
+
workspaceId,
|
|
64
|
+
filename: safeFilename,
|
|
65
|
+
contentType: mime,
|
|
66
|
+
size: payload.length,
|
|
67
|
+
uploadedAt: new Date().toISOString()
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return text({ error: error.message });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
server.registerTool("upload_blob", {
|
|
75
|
+
title: "Upload Blob",
|
|
76
|
+
description: "Upload a file or blob to workspace storage.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
workspaceId: z.string().describe("Workspace ID"),
|
|
79
|
+
content: z.string().describe("Base64 encoded content or text"),
|
|
80
|
+
filename: z.string().optional().describe("Filename"),
|
|
81
|
+
contentType: z.string().optional().describe("MIME type")
|
|
82
|
+
}
|
|
83
|
+
}, uploadBlobHandler);
|
|
84
|
+
// DELETE BLOB
|
|
85
|
+
const deleteBlobHandler = async ({ workspaceId, key, permanently = false }) => {
|
|
86
|
+
try {
|
|
87
|
+
const mutation = `
|
|
88
|
+
mutation DeleteBlob($workspaceId: String!, $key: String!, $permanently: Boolean) {
|
|
89
|
+
deleteBlob(workspaceId: $workspaceId, key: $key, permanently: $permanently)
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
const data = await gql.request(mutation, {
|
|
93
|
+
workspaceId,
|
|
94
|
+
key,
|
|
95
|
+
permanently
|
|
96
|
+
});
|
|
97
|
+
return text({ success: data.deleteBlob, key, workspaceId, permanently });
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return text({ error: error.message });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
server.registerTool("delete_blob", {
|
|
104
|
+
title: "Delete Blob",
|
|
105
|
+
description: "Delete a blob/file from workspace storage.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
workspaceId: z.string().describe("Workspace ID"),
|
|
108
|
+
key: z.string().describe("Blob key/ID to delete"),
|
|
109
|
+
permanently: z.boolean().optional().describe("Delete permanently")
|
|
110
|
+
}
|
|
111
|
+
}, deleteBlobHandler);
|
|
112
|
+
// RELEASE DELETED BLOBS
|
|
113
|
+
const cleanupBlobsHandler = async ({ workspaceId }) => {
|
|
114
|
+
try {
|
|
115
|
+
const mutation = `
|
|
116
|
+
mutation ReleaseDeletedBlobs($workspaceId: String!) {
|
|
117
|
+
releaseDeletedBlobs(workspaceId: $workspaceId)
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
const data = await gql.request(mutation, {
|
|
121
|
+
workspaceId
|
|
122
|
+
});
|
|
123
|
+
return text({ success: true, workspaceId, blobsReleased: data.releaseDeletedBlobs });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return text({ error: error.message });
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
server.registerTool("cleanup_blobs", {
|
|
130
|
+
title: "Cleanup Deleted Blobs",
|
|
131
|
+
description: "Permanently remove deleted blobs to free up storage.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
workspaceId: z.string().describe("Workspace ID")
|
|
134
|
+
}
|
|
135
|
+
}, cleanupBlobsHandler);
|
|
136
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { receipt, text } from "../util/mcp.js";
|
|
3
|
+
export function registerCommentTools(server, gql, defaults) {
|
|
4
|
+
const listCommentsHandler = async (parsed) => {
|
|
5
|
+
const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
|
|
6
|
+
if (!workspaceId)
|
|
7
|
+
throw new Error("workspaceId required (or set AFFINE_WORKSPACE_ID)");
|
|
8
|
+
const query = `query ListComments($workspaceId:String!,$docId:String!,$first:Int,$offset:Int,$after:String){ workspace(id:$workspaceId){ comments(docId:$docId, pagination:{first:$first, offset:$offset, after:$after}){ totalCount pageInfo{ hasNextPage endCursor } edges{ cursor node{ id content createdAt updatedAt resolved user{ id name avatarUrl } replies{ id content createdAt updatedAt user{ id name avatarUrl } } } } } } }`;
|
|
9
|
+
const data = await gql.request(query, { workspaceId, docId: parsed.docId, first: parsed.first, offset: parsed.offset, after: parsed.after });
|
|
10
|
+
return text(data.workspace.comments);
|
|
11
|
+
};
|
|
12
|
+
server.registerTool("list_comments", {
|
|
13
|
+
title: "List Comments",
|
|
14
|
+
description: "List comments of a doc (with replies).",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
workspaceId: z.string().optional(),
|
|
17
|
+
docId: z.string(),
|
|
18
|
+
first: z.number().optional(),
|
|
19
|
+
offset: z.number().optional(),
|
|
20
|
+
after: z.string().optional()
|
|
21
|
+
}
|
|
22
|
+
}, listCommentsHandler);
|
|
23
|
+
const createCommentHandler = async (parsed) => {
|
|
24
|
+
const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
|
|
25
|
+
if (!workspaceId)
|
|
26
|
+
throw new Error("workspaceId required (or set AFFINE_WORKSPACE_ID)");
|
|
27
|
+
const mutation = `mutation CreateComment($input: CommentCreateInput!){ createComment(input:$input){ id content createdAt updatedAt resolved } }`;
|
|
28
|
+
const normalizedDocMode = (parsed.docMode || 'page').toLowerCase() === 'edgeless' ? 'edgeless' : 'page';
|
|
29
|
+
const normalizedContent = typeof parsed.content === 'string' ? { text: parsed.content } : parsed.content;
|
|
30
|
+
const input = { content: normalizedContent, docId: parsed.docId, workspaceId, docTitle: parsed.docTitle || "", docMode: normalizedDocMode, mentions: parsed.mentions };
|
|
31
|
+
const data = await gql.request(mutation, { input });
|
|
32
|
+
return receipt("comment.create", {
|
|
33
|
+
workspaceId,
|
|
34
|
+
docId: parsed.docId,
|
|
35
|
+
commentId: data.createComment.id,
|
|
36
|
+
id: data.createComment.id,
|
|
37
|
+
...data.createComment,
|
|
38
|
+
comment: data.createComment,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
server.registerTool("create_comment", {
|
|
42
|
+
title: "Create Comment",
|
|
43
|
+
description: "Create a comment on a doc.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
workspaceId: z.string().optional(),
|
|
46
|
+
docId: z.string(),
|
|
47
|
+
docTitle: z.string().optional(),
|
|
48
|
+
docMode: z.enum(["Page", "Edgeless", "page", "edgeless"]).optional(),
|
|
49
|
+
content: z.any(),
|
|
50
|
+
mentions: z.array(z.string()).optional()
|
|
51
|
+
}
|
|
52
|
+
}, createCommentHandler);
|
|
53
|
+
const updateCommentHandler = async (parsed) => {
|
|
54
|
+
const mutation = `mutation UpdateComment($input: CommentUpdateInput!){ updateComment(input:$input) }`;
|
|
55
|
+
const data = await gql.request(mutation, { input: { id: parsed.id, content: parsed.content } });
|
|
56
|
+
return receipt("comment.update", {
|
|
57
|
+
commentId: parsed.id,
|
|
58
|
+
id: parsed.id,
|
|
59
|
+
success: data.updateComment,
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
server.registerTool("update_comment", {
|
|
63
|
+
title: "Update Comment",
|
|
64
|
+
description: "Update a comment content.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
id: z.string(),
|
|
67
|
+
content: z.any()
|
|
68
|
+
}
|
|
69
|
+
}, updateCommentHandler);
|
|
70
|
+
const deleteCommentHandler = async (parsed) => {
|
|
71
|
+
const mutation = `mutation DeleteComment($id:String!){ deleteComment(id:$id) }`;
|
|
72
|
+
const data = await gql.request(mutation, { id: parsed.id });
|
|
73
|
+
return receipt("comment.delete", {
|
|
74
|
+
commentId: parsed.id,
|
|
75
|
+
id: parsed.id,
|
|
76
|
+
success: data.deleteComment,
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
server.registerTool("delete_comment", {
|
|
80
|
+
title: "Delete Comment",
|
|
81
|
+
description: "Delete a comment by id.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
id: z.string()
|
|
84
|
+
}
|
|
85
|
+
}, deleteCommentHandler);
|
|
86
|
+
const resolveCommentHandler = async (parsed) => {
|
|
87
|
+
const mutation = `mutation ResolveComment($input: CommentResolveInput!){ resolveComment(input:$input) }`;
|
|
88
|
+
const data = await gql.request(mutation, { input: parsed });
|
|
89
|
+
return receipt("comment.resolve", {
|
|
90
|
+
commentId: parsed.id,
|
|
91
|
+
id: parsed.id,
|
|
92
|
+
resolved: parsed.resolved,
|
|
93
|
+
success: data.resolveComment,
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
server.registerTool("resolve_comment", {
|
|
97
|
+
title: "Resolve Comment",
|
|
98
|
+
description: "Resolve or unresolve a comment.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
id: z.string(),
|
|
101
|
+
resolved: z.boolean()
|
|
102
|
+
}
|
|
103
|
+
}, resolveCommentHandler);
|
|
104
|
+
}
|