affine-mcp-server 1.13.0 → 2.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.
@@ -0,0 +1,322 @@
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
+ "compose_database_from_intent",
14
+ "create_collection",
15
+ "create_comment",
16
+ "create_doc",
17
+ "create_doc_from_markdown",
18
+ "create_folder",
19
+ "create_semantic_page",
20
+ "create_tag",
21
+ "create_workspace",
22
+ "create_workspace_blueprint",
23
+ "current_user",
24
+ "delete_blob",
25
+ "delete_block",
26
+ "delete_collection",
27
+ "delete_comment",
28
+ "delete_database_row",
29
+ "delete_doc",
30
+ "delete_folder",
31
+ "delete_organize_link",
32
+ "delete_surface_element",
33
+ "delete_workspace",
34
+ "export_doc_markdown",
35
+ "export_with_fidelity_report",
36
+ "generate_access_token",
37
+ "get_capabilities",
38
+ "get_collection",
39
+ "get_doc",
40
+ "get_edgeless_canvas",
41
+ "get_orphan_docs",
42
+ "get_workspace",
43
+ "inspect_template_structure",
44
+ "instantiate_template_native",
45
+ "list_access_tokens",
46
+ "list_children",
47
+ "list_collections",
48
+ "list_comments",
49
+ "list_docs",
50
+ "list_docs_by_tag",
51
+ "list_histories",
52
+ "list_notifications",
53
+ "list_organize_nodes",
54
+ "list_surface_elements",
55
+ "list_tags",
56
+ "list_workspace_tree",
57
+ "list_workspaces",
58
+ "move_doc",
59
+ "move_organize_node",
60
+ "publish_doc",
61
+ "read_all_notifications",
62
+ "read_database_cells",
63
+ "read_database_columns",
64
+ "read_doc",
65
+ "remove_doc_from_collection",
66
+ "remove_tag_from_doc",
67
+ "rename_folder",
68
+ "replace_doc_with_markdown",
69
+ "resolve_comment",
70
+ "revoke_access_token",
71
+ "revoke_doc",
72
+ "search_docs",
73
+ "sign_in",
74
+ "update_collection",
75
+ "update_collection_rules",
76
+ "update_comment",
77
+ "update_database_row",
78
+ "update_doc_title",
79
+ "update_edgeless_block",
80
+ "update_frame_children",
81
+ "update_profile",
82
+ "update_settings",
83
+ "update_surface_element",
84
+ "update_workspace",
85
+ "upload_blob",
86
+ ];
87
+ const TOOL_GROUPS = {
88
+ add_database_column: ["docs", "docs.database", "docs.write", "write"],
89
+ add_database_row: ["docs", "docs.database", "docs.write", "write"],
90
+ add_doc_to_collection: ["organize", "organize.collections", "organize.write", "write"],
91
+ add_organize_link: ["organize", "organize.folders", "organize.write", "experimental", "write"],
92
+ add_surface_element: ["docs", "docs.edgeless", "docs.surface", "docs.write", "write"],
93
+ add_tag_to_doc: ["docs", "docs.tags", "docs.write", "write"],
94
+ analyze_doc_fidelity: ["docs", "docs.read", "docs.export", "read"],
95
+ append_block: ["docs", "docs.write", "write"],
96
+ append_markdown: ["docs", "docs.markdown", "docs.write", "write"],
97
+ append_semantic_section: ["docs", "docs.semantic", "docs.write", "write"],
98
+ cleanup_blobs: ["blobs", "blobs.write", "cleanup", "destructive", "write"],
99
+ compose_database_from_intent: ["docs", "docs.database", "docs.intent", "docs.write", "write"],
100
+ create_collection: ["organize", "organize.collections", "organize.write", "write"],
101
+ create_comment: ["comments", "comments.write", "write"],
102
+ create_doc: ["docs", "docs.write", "write"],
103
+ create_doc_from_markdown: ["docs", "docs.markdown", "docs.write", "write"],
104
+ create_folder: ["organize", "organize.folders", "organize.write", "experimental", "write"],
105
+ create_semantic_page: ["docs", "docs.semantic", "docs.write", "write"],
106
+ create_tag: ["docs", "docs.tags", "docs.write", "write"],
107
+ create_workspace: ["workspaces", "workspaces.write", "admin", "write"],
108
+ create_workspace_blueprint: ["organize", "organize.folders", "organize.write", "experimental", "write"],
109
+ current_user: ["users", "users.read", "read"],
110
+ delete_blob: ["blobs", "blobs.write", "destructive", "write"],
111
+ delete_block: ["docs", "docs.edgeless", "docs.write", "destructive", "write"],
112
+ delete_collection: ["organize", "organize.collections", "organize.write", "destructive", "write"],
113
+ delete_comment: ["comments", "comments.write", "destructive", "write"],
114
+ delete_database_row: ["docs", "docs.database", "docs.write", "destructive", "write"],
115
+ delete_doc: ["docs", "docs.write", "destructive", "write"],
116
+ delete_folder: ["organize", "organize.folders", "organize.write", "destructive", "experimental", "write"],
117
+ delete_organize_link: ["organize", "organize.folders", "organize.write", "destructive", "experimental", "write"],
118
+ delete_surface_element: ["docs", "docs.edgeless", "docs.surface", "docs.write", "destructive", "write"],
119
+ delete_workspace: ["workspaces", "workspaces.write", "admin", "destructive", "write"],
120
+ export_doc_markdown: ["docs", "docs.export", "docs.markdown", "docs.read", "read"],
121
+ export_with_fidelity_report: ["docs", "docs.export", "docs.markdown", "docs.read", "read"],
122
+ generate_access_token: ["access_tokens", "access_tokens.write", "admin", "write"],
123
+ get_capabilities: ["docs", "docs.read", "read"],
124
+ get_collection: ["organize", "organize.collections", "organize.read", "read"],
125
+ get_doc: ["docs", "docs.read", "read"],
126
+ get_edgeless_canvas: ["docs", "docs.edgeless", "docs.surface", "docs.read", "read"],
127
+ get_orphan_docs: ["docs", "docs.tree", "docs.read", "read"],
128
+ get_workspace: ["workspaces", "workspaces.read", "read"],
129
+ inspect_template_structure: ["docs", "docs.template", "docs.read", "read"],
130
+ instantiate_template_native: ["docs", "docs.template", "docs.write", "write"],
131
+ list_access_tokens: ["access_tokens", "access_tokens.read", "admin", "read"],
132
+ list_children: ["docs", "docs.tree", "docs.read", "read"],
133
+ list_collections: ["organize", "organize.collections", "organize.read", "read"],
134
+ list_comments: ["comments", "comments.read", "read"],
135
+ list_docs: ["docs", "docs.read", "read"],
136
+ list_docs_by_tag: ["docs", "docs.tags", "docs.read", "read"],
137
+ list_histories: ["history", "history.read", "read"],
138
+ list_notifications: ["notifications", "notifications.read", "read"],
139
+ list_organize_nodes: ["organize", "organize.folders", "organize.read", "experimental", "read"],
140
+ list_surface_elements: ["docs", "docs.edgeless", "docs.surface", "docs.read", "read"],
141
+ list_tags: ["docs", "docs.tags", "docs.read", "read"],
142
+ list_workspace_tree: ["docs", "docs.tree", "docs.read", "read"],
143
+ list_workspaces: ["workspaces", "workspaces.read", "read"],
144
+ move_doc: ["docs", "docs.tree", "docs.write", "write"],
145
+ move_organize_node: ["organize", "organize.folders", "organize.write", "experimental", "write"],
146
+ publish_doc: ["docs", "docs.share", "docs.write", "write"],
147
+ read_all_notifications: ["notifications", "notifications.write", "write"],
148
+ read_database_cells: ["docs", "docs.database", "docs.read", "read"],
149
+ read_database_columns: ["docs", "docs.database", "docs.read", "read"],
150
+ read_doc: ["docs", "docs.read", "read"],
151
+ remove_doc_from_collection: ["organize", "organize.collections", "organize.write", "write"],
152
+ remove_tag_from_doc: ["docs", "docs.tags", "docs.write", "write"],
153
+ rename_folder: ["organize", "organize.folders", "organize.write", "experimental", "write"],
154
+ replace_doc_with_markdown: ["docs", "docs.markdown", "docs.write", "write"],
155
+ resolve_comment: ["comments", "comments.write", "write"],
156
+ revoke_access_token: ["access_tokens", "access_tokens.write", "admin", "destructive", "write"],
157
+ revoke_doc: ["docs", "docs.share", "docs.write", "destructive", "write"],
158
+ search_docs: ["docs", "docs.read", "read"],
159
+ sign_in: ["users", "users.auth", "auth", "write"],
160
+ update_collection: ["organize", "organize.collections", "organize.write", "write"],
161
+ update_collection_rules: ["organize", "organize.collections", "organize.write", "write"],
162
+ update_comment: ["comments", "comments.write", "write"],
163
+ update_database_row: ["docs", "docs.database", "docs.write", "write"],
164
+ update_doc_title: ["docs", "docs.write", "write"],
165
+ update_edgeless_block: ["docs", "docs.edgeless", "docs.write", "write"],
166
+ update_frame_children: ["docs", "docs.edgeless", "docs.write", "write"],
167
+ update_profile: ["users", "users.write", "admin", "write"],
168
+ update_settings: ["users", "users.write", "admin", "write"],
169
+ update_surface_element: ["docs", "docs.edgeless", "docs.surface", "docs.write", "write"],
170
+ update_workspace: ["workspaces", "workspaces.write", "admin", "write"],
171
+ upload_blob: ["blobs", "blobs.write", "write"],
172
+ };
173
+ const READ_ONLY_TOOLS = new Set([
174
+ "analyze_doc_fidelity",
175
+ "current_user",
176
+ "export_doc_markdown",
177
+ "export_with_fidelity_report",
178
+ "get_capabilities",
179
+ "get_collection",
180
+ "get_doc",
181
+ "get_edgeless_canvas",
182
+ "get_orphan_docs",
183
+ "get_workspace",
184
+ "inspect_template_structure",
185
+ "list_access_tokens",
186
+ "list_children",
187
+ "list_collections",
188
+ "list_comments",
189
+ "list_docs",
190
+ "list_docs_by_tag",
191
+ "list_histories",
192
+ "list_notifications",
193
+ "list_organize_nodes",
194
+ "list_surface_elements",
195
+ "list_tags",
196
+ "list_workspace_tree",
197
+ "list_workspaces",
198
+ "read_database_cells",
199
+ "read_database_columns",
200
+ "read_doc",
201
+ "search_docs",
202
+ "sign_in",
203
+ ]);
204
+ const CORE_TOOLS = new Set([
205
+ "add_database_column",
206
+ "add_database_row",
207
+ "add_tag_to_doc",
208
+ "append_block",
209
+ "append_markdown",
210
+ "create_doc",
211
+ "create_doc_from_markdown",
212
+ "current_user",
213
+ "export_doc_markdown",
214
+ "get_capabilities",
215
+ "get_doc",
216
+ "get_workspace",
217
+ "list_children",
218
+ "list_docs",
219
+ "list_docs_by_tag",
220
+ "list_tags",
221
+ "list_workspaces",
222
+ "read_database_cells",
223
+ "read_database_columns",
224
+ "read_doc",
225
+ "remove_tag_from_doc",
226
+ "replace_doc_with_markdown",
227
+ "search_docs",
228
+ "sign_in",
229
+ "update_database_row",
230
+ "update_doc_title",
231
+ ]);
232
+ const AUTHORING_EXCLUDED_GROUPS = new Set([
233
+ "admin",
234
+ "cleanup",
235
+ "destructive",
236
+ "experimental",
237
+ ]);
238
+ const KNOWN_PROFILES = new Set(["full", "read_only", "core", "authoring"]);
239
+ const KNOWN_TOOLS = new Set(ALL_TOOLS);
240
+ const KNOWN_GROUPS = new Set(Object.values(TOOL_GROUPS).flat());
241
+ function parseCsv(raw, normalize = true) {
242
+ return (raw || "")
243
+ .split(",")
244
+ .map(value => value.trim())
245
+ .filter(Boolean)
246
+ .map(value => normalize ? value.toLowerCase() : value);
247
+ }
248
+ function normalizeProfile(raw) {
249
+ const value = (raw || "full").trim().toLowerCase();
250
+ if (KNOWN_PROFILES.has(value)) {
251
+ return { profile: value };
252
+ }
253
+ return {
254
+ profile: "full",
255
+ warning: `Unknown AFFINE_TOOL_PROFILE "${raw}". Using "full". Valid profiles: ${[...KNOWN_PROFILES].join(", ")}`,
256
+ };
257
+ }
258
+ function profileAllowsTool(profile, toolName) {
259
+ if (profile === "full") {
260
+ return true;
261
+ }
262
+ if (profile === "read_only") {
263
+ return READ_ONLY_TOOLS.has(toolName);
264
+ }
265
+ if (profile === "core") {
266
+ return CORE_TOOLS.has(toolName);
267
+ }
268
+ const groups = TOOL_GROUPS[toolName];
269
+ return !groups.some(group => AUTHORING_EXCLUDED_GROUPS.has(group));
270
+ }
271
+ export function createToolFilter(env = process.env) {
272
+ const profileResult = normalizeProfile(env.AFFINE_TOOL_PROFILE);
273
+ const disabledGroups = new Set(parseCsv(env.AFFINE_DISABLED_GROUPS));
274
+ const disabledTools = new Set(parseCsv(env.AFFINE_DISABLED_TOOLS));
275
+ const warnings = [];
276
+ if (profileResult.warning) {
277
+ warnings.push(profileResult.warning);
278
+ }
279
+ for (const group of disabledGroups) {
280
+ if (!KNOWN_GROUPS.has(group)) {
281
+ warnings.push(`Unknown group "${group}" in AFFINE_DISABLED_GROUPS. Valid groups: ${[...KNOWN_GROUPS].sort().join(", ")}`);
282
+ }
283
+ }
284
+ for (const tool of disabledTools) {
285
+ if (!KNOWN_TOOLS.has(tool)) {
286
+ warnings.push(`Unknown tool "${tool}" in AFFINE_DISABLED_TOOLS.`);
287
+ }
288
+ }
289
+ function isEnabled(name) {
290
+ const toolName = name;
291
+ if (!KNOWN_TOOLS.has(toolName)) {
292
+ return profileResult.profile === "full" && disabledGroups.size === 0 && disabledTools.size === 0;
293
+ }
294
+ if (disabledTools.has(toolName)) {
295
+ return false;
296
+ }
297
+ const groups = TOOL_GROUPS[toolName];
298
+ if (groups.some(group => disabledGroups.has(group))) {
299
+ return false;
300
+ }
301
+ return profileAllowsTool(profileResult.profile, toolName);
302
+ }
303
+ const enabledTools = ALL_TOOLS.filter(toolName => isEnabled(toolName));
304
+ return {
305
+ profile: profileResult.profile,
306
+ disabledGroups,
307
+ disabledTools,
308
+ warnings,
309
+ enabledTools,
310
+ totalToolCount: ALL_TOOLS.length,
311
+ isEnabled,
312
+ };
313
+ }
314
+ export function toolFilterRequiresRegisterTool(filter) {
315
+ return filter.profile !== "full" || filter.disabledGroups.size > 0 || filter.disabledTools.size > 0;
316
+ }
317
+ export function knownToolSurfaceGroups() {
318
+ return [...KNOWN_GROUPS].sort();
319
+ }
320
+ export function knownToolSurfaceProfiles() {
321
+ return [...KNOWN_PROFILES];
322
+ }
@@ -1,31 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { receipt, text } from "../util/mcp.js";
3
3
  export function registerCommentTools(server, gql, defaults) {
4
- function normalizeCommentNode(node) {
5
- if (!node || typeof node !== "object") {
6
- return null;
7
- }
8
- const replies = Array.isArray(node.replies)
9
- ? node.replies
10
- .filter((reply) => reply && typeof reply === "object")
11
- .map((reply) => ({
12
- id: typeof reply.id === "string" ? reply.id : null,
13
- content: reply.content ?? null,
14
- createdAt: reply.createdAt ?? null,
15
- updatedAt: reply.updatedAt ?? null,
16
- user: reply.user ?? null,
17
- }))
18
- : [];
19
- return {
20
- id: typeof node.id === "string" ? node.id : null,
21
- content: node.content ?? null,
22
- createdAt: node.createdAt ?? null,
23
- updatedAt: node.updatedAt ?? null,
24
- resolved: node.resolved === true,
25
- user: node.user ?? null,
26
- replies,
27
- };
28
- }
29
4
  const listCommentsHandler = async (parsed) => {
30
5
  const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
31
6
  if (!workspaceId)
@@ -45,38 +20,6 @@ export function registerCommentTools(server, gql, defaults) {
45
20
  after: z.string().optional()
46
21
  }
47
22
  }, listCommentsHandler);
48
- const listUnresolvedThreadsHandler = async (parsed) => {
49
- const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
50
- if (!workspaceId)
51
- throw new Error("workspaceId required (or set AFFINE_WORKSPACE_ID)");
52
- 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 } } } } } } }`;
53
- const data = await gql.request(query, { workspaceId, docId: parsed.docId, first: parsed.first, offset: parsed.offset, after: parsed.after });
54
- const comments = data.workspace.comments;
55
- const unresolvedThreads = Array.isArray(comments?.edges)
56
- ? comments.edges
57
- .map((edge) => normalizeCommentNode(edge?.node))
58
- .filter((node) => node && node.resolved !== true)
59
- : [];
60
- return text({
61
- workspaceId,
62
- docId: parsed.docId,
63
- totalComments: comments?.totalCount ?? unresolvedThreads.length,
64
- unresolvedThreadCount: unresolvedThreads.length,
65
- threads: unresolvedThreads,
66
- pageInfo: comments?.pageInfo ?? null,
67
- });
68
- };
69
- server.registerTool("list_unresolved_threads", {
70
- title: "List Unresolved Threads",
71
- description: "List unresolved comment threads for a document. Replies are included read-only under each root comment.",
72
- inputSchema: {
73
- workspaceId: z.string().optional(),
74
- docId: z.string(),
75
- first: z.number().optional(),
76
- offset: z.number().optional(),
77
- after: z.string().optional()
78
- }
79
- }, listUnresolvedThreadsHandler);
80
23
  const createCommentHandler = async (parsed) => {
81
24
  const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
82
25
  if (!workspaceId)