affine-mcp-server 1.12.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,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { text } from "../util/mcp.js";
2
+ import { receipt, text } from "../util/mcp.js";
3
3
  export function registerCommentTools(server, gql, defaults) {
4
4
  const listCommentsHandler = async (parsed) => {
5
5
  const workspaceId = parsed.workspaceId || defaults.workspaceId || parsed.workspaceId;
@@ -29,7 +29,14 @@ export function registerCommentTools(server, gql, defaults) {
29
29
  const normalizedContent = typeof parsed.content === 'string' ? { text: parsed.content } : parsed.content;
30
30
  const input = { content: normalizedContent, docId: parsed.docId, workspaceId, docTitle: parsed.docTitle || "", docMode: normalizedDocMode, mentions: parsed.mentions };
31
31
  const data = await gql.request(mutation, { input });
32
- return text(data.createComment);
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
+ });
33
40
  };
34
41
  server.registerTool("create_comment", {
35
42
  title: "Create Comment",
@@ -46,7 +53,11 @@ export function registerCommentTools(server, gql, defaults) {
46
53
  const updateCommentHandler = async (parsed) => {
47
54
  const mutation = `mutation UpdateComment($input: CommentUpdateInput!){ updateComment(input:$input) }`;
48
55
  const data = await gql.request(mutation, { input: { id: parsed.id, content: parsed.content } });
49
- return text({ success: data.updateComment });
56
+ return receipt("comment.update", {
57
+ commentId: parsed.id,
58
+ id: parsed.id,
59
+ success: data.updateComment,
60
+ });
50
61
  };
51
62
  server.registerTool("update_comment", {
52
63
  title: "Update Comment",
@@ -59,7 +70,11 @@ export function registerCommentTools(server, gql, defaults) {
59
70
  const deleteCommentHandler = async (parsed) => {
60
71
  const mutation = `mutation DeleteComment($id:String!){ deleteComment(id:$id) }`;
61
72
  const data = await gql.request(mutation, { id: parsed.id });
62
- return text({ success: data.deleteComment });
73
+ return receipt("comment.delete", {
74
+ commentId: parsed.id,
75
+ id: parsed.id,
76
+ success: data.deleteComment,
77
+ });
63
78
  };
64
79
  server.registerTool("delete_comment", {
65
80
  title: "Delete Comment",
@@ -71,7 +86,12 @@ export function registerCommentTools(server, gql, defaults) {
71
86
  const resolveCommentHandler = async (parsed) => {
72
87
  const mutation = `mutation ResolveComment($input: CommentResolveInput!){ resolveComment(input:$input) }`;
73
88
  const data = await gql.request(mutation, { input: parsed });
74
- return text({ success: data.resolveComment });
89
+ return receipt("comment.resolve", {
90
+ commentId: parsed.id,
91
+ id: parsed.id,
92
+ resolved: parsed.resolved,
93
+ success: data.resolveComment,
94
+ });
75
95
  };
76
96
  server.registerTool("resolve_comment", {
77
97
  title: "Resolve Comment",