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.
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { text } from "../util/mcp.js";
3
+ export function registerNotificationTools(server, gql) {
4
+ // LIST NOTIFICATIONS
5
+ const listNotificationsHandler = async ({ first = 20, unreadOnly = false }) => {
6
+ try {
7
+ const query = `
8
+ query GetNotifications($first: Int!) {
9
+ currentUser {
10
+ notifications(first: $first) {
11
+ nodes {
12
+ id
13
+ type
14
+ title
15
+ body
16
+ read
17
+ createdAt
18
+ }
19
+ totalCount
20
+ pageInfo {
21
+ hasNextPage
22
+ }
23
+ }
24
+ }
25
+ }
26
+ `;
27
+ const data = await gql.request(query, { first });
28
+ let notifications = data.currentUser?.notifications?.nodes || [];
29
+ if (unreadOnly) {
30
+ notifications = notifications.filter((n) => !n.read);
31
+ }
32
+ return text(notifications);
33
+ }
34
+ catch (error) {
35
+ return text({ error: error.message });
36
+ }
37
+ };
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
+ server.registerTool("list_notifications", {
47
+ title: "List Notifications",
48
+ description: "Get user notifications.",
49
+ inputSchema: {
50
+ first: z.number().optional().describe("Number of notifications to fetch"),
51
+ unreadOnly: z.boolean().optional().describe("Show only unread notifications")
52
+ }
53
+ }, 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
+ // MARK ALL NOTIFICATIONS READ
84
+ const readAllNotificationsHandler = async () => {
85
+ try {
86
+ const mutation = `
87
+ mutation ReadAllNotifications {
88
+ readAllNotifications
89
+ }
90
+ `;
91
+ const data = await gql.request(mutation);
92
+ return text({ success: data.readAllNotifications, message: "All notifications marked as read" });
93
+ }
94
+ catch (error) {
95
+ return text({ error: error.message });
96
+ }
97
+ };
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
+ server.registerTool("read_all_notifications", {
104
+ title: "Mark All Notifications Read",
105
+ description: "Mark all notifications as read.",
106
+ inputSchema: {}
107
+ }, readAllNotificationsHandler);
108
+ }
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { text } from "../util/mcp.js";
3
+ export function registerUpdateTools(server, gql, defaults) {
4
+ const applyDocUpdatesHandler = 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 Apply($workspaceId:String!,$docId:String!,$op:String!,$updates:String!){ applyDocUpdates(workspaceId:$workspaceId, docId:$docId, op:$op, updates:$updates) }`;
9
+ const data = await gql.request(query, { workspaceId, docId: parsed.docId, op: parsed.op, updates: parsed.updates });
10
+ return text(data.applyDocUpdates);
11
+ };
12
+ server.registerTool("affine_apply_doc_updates", {
13
+ title: "Apply Document Updates",
14
+ description: "Apply CRDT updates to a doc (advanced).",
15
+ inputSchema: {
16
+ workspaceId: z.string().optional(),
17
+ docId: z.string(),
18
+ op: z.string(),
19
+ updates: z.string()
20
+ }
21
+ }, applyDocUpdatesHandler);
22
+ server.registerTool("apply_doc_updates", {
23
+ title: "Apply Document Updates",
24
+ description: "Apply CRDT updates to a doc (advanced).",
25
+ inputSchema: {
26
+ workspaceId: z.string().optional(),
27
+ docId: z.string(),
28
+ op: z.string(),
29
+ updates: z.string()
30
+ }
31
+ }, applyDocUpdatesHandler);
32
+ }
@@ -0,0 +1,18 @@
1
+ import { text } from "../util/mcp.js";
2
+ export function registerUserTools(server, gql) {
3
+ const currentUserHandler = async () => {
4
+ const query = `query Me { currentUser { id name email emailVerified avatarUrl disabled } }`;
5
+ const data = await gql.request(query);
6
+ return text(data.currentUser);
7
+ };
8
+ server.registerTool("affine_current_user", {
9
+ title: "Current User",
10
+ description: "Get current signed-in user.",
11
+ inputSchema: {}
12
+ }, currentUserHandler);
13
+ server.registerTool("current_user", {
14
+ title: "Current User",
15
+ description: "Get current signed-in user.",
16
+ inputSchema: {}
17
+ }, currentUserHandler);
18
+ }
@@ -0,0 +1,209 @@
1
+ import { z } from "zod";
2
+ import { text } from "../util/mcp.js";
3
+ export function registerUserCRUDTools(server, gql) {
4
+ // UPDATE PROFILE
5
+ const updateProfileHandler = async ({ name, avatarUrl }) => {
6
+ try {
7
+ const mutation = `
8
+ mutation UpdateProfile($input: UpdateUserInput!) {
9
+ updateProfile(input: $input) {
10
+ id
11
+ name
12
+ avatarUrl
13
+ email
14
+ }
15
+ }
16
+ `;
17
+ const input = {};
18
+ if (name !== undefined)
19
+ input.name = name;
20
+ if (avatarUrl !== undefined)
21
+ input.avatarUrl = avatarUrl;
22
+ const data = await gql.request(mutation, { input });
23
+ return text(data.updateProfile);
24
+ }
25
+ catch (error) {
26
+ return text({ error: error.message });
27
+ }
28
+ };
29
+ server.registerTool("affine_update_profile", {
30
+ title: "Update Profile",
31
+ description: "Update current user's profile information.",
32
+ inputSchema: {
33
+ name: z.string().optional().describe("Display name"),
34
+ avatarUrl: z.string().optional().describe("Avatar URL")
35
+ }
36
+ }, updateProfileHandler);
37
+ server.registerTool("update_profile", {
38
+ title: "Update Profile",
39
+ description: "Update current user's profile information.",
40
+ inputSchema: {
41
+ name: z.string().optional().describe("Display name"),
42
+ avatarUrl: z.string().optional().describe("Avatar URL")
43
+ }
44
+ }, updateProfileHandler);
45
+ // UPDATE SETTINGS
46
+ const updateSettingsHandler = async ({ settings }) => {
47
+ try {
48
+ const mutation = `
49
+ mutation UpdateSettings($input: UpdateUserSettingsInput!) {
50
+ updateSettings(input: $input) {
51
+ success
52
+ }
53
+ }
54
+ `;
55
+ const data = await gql.request(mutation, {
56
+ input: settings
57
+ });
58
+ return text(data.updateSettings);
59
+ }
60
+ catch (error) {
61
+ return text({ error: error.message });
62
+ }
63
+ };
64
+ server.registerTool("affine_update_settings", {
65
+ title: "Update Settings",
66
+ description: "Update user settings and preferences.",
67
+ inputSchema: {
68
+ settings: z.record(z.any()).describe("Settings object with key-value pairs")
69
+ }
70
+ }, updateSettingsHandler);
71
+ server.registerTool("update_settings", {
72
+ title: "Update Settings",
73
+ description: "Update user settings and preferences.",
74
+ inputSchema: {
75
+ settings: z.record(z.any()).describe("Settings object with key-value pairs")
76
+ }
77
+ }, updateSettingsHandler);
78
+ // SEND VERIFICATION EMAIL
79
+ const sendVerifyEmailHandler = async ({ callbackUrl }) => {
80
+ try {
81
+ const mutation = `
82
+ mutation SendVerifyEmail($callbackUrl: String!) {
83
+ sendVerifyEmail(callbackUrl: $callbackUrl)
84
+ }
85
+ `;
86
+ const data = await gql.request(mutation, {
87
+ callbackUrl: callbackUrl || `${process.env.AFFINE_BASE_URL}/verify`
88
+ });
89
+ return text({ success: data.sendVerifyEmail, message: "Verification email sent" });
90
+ }
91
+ catch (error) {
92
+ return text({ error: error.message });
93
+ }
94
+ };
95
+ server.registerTool("affine_send_verify_email", {
96
+ title: "Send Verification Email",
97
+ description: "Send email verification link.",
98
+ inputSchema: {
99
+ callbackUrl: z.string().optional().describe("Callback URL after verification")
100
+ }
101
+ }, sendVerifyEmailHandler);
102
+ server.registerTool("send_verify_email", {
103
+ title: "Send Verification Email",
104
+ description: "Send email verification link.",
105
+ inputSchema: {
106
+ callbackUrl: z.string().optional().describe("Callback URL after verification")
107
+ }
108
+ }, sendVerifyEmailHandler);
109
+ // CHANGE PASSWORD
110
+ const changePasswordHandler = async ({ token, newPassword, userId }) => {
111
+ try {
112
+ const mutation = `
113
+ mutation ChangePassword($token: String!, $newPassword: String!, $userId: String) {
114
+ changePassword(token: $token, newPassword: $newPassword, userId: $userId)
115
+ }
116
+ `;
117
+ const data = await gql.request(mutation, {
118
+ token,
119
+ newPassword,
120
+ userId
121
+ });
122
+ return text({ success: data.changePassword, message: "Password changed successfully" });
123
+ }
124
+ catch (error) {
125
+ return text({ error: error.message });
126
+ }
127
+ };
128
+ server.registerTool("affine_change_password", {
129
+ title: "Change Password",
130
+ description: "Change user password (requires token from email).",
131
+ inputSchema: {
132
+ token: z.string().describe("Password reset token from email"),
133
+ newPassword: z.string().describe("New password"),
134
+ userId: z.string().optional().describe("User ID")
135
+ }
136
+ }, changePasswordHandler);
137
+ server.registerTool("change_password", {
138
+ title: "Change Password",
139
+ description: "Change user password (requires token from email).",
140
+ inputSchema: {
141
+ token: z.string().describe("Password reset token from email"),
142
+ newPassword: z.string().describe("New password"),
143
+ userId: z.string().optional().describe("User ID")
144
+ }
145
+ }, changePasswordHandler);
146
+ // SEND PASSWORD RESET EMAIL
147
+ const sendPasswordResetHandler = async ({ callbackUrl }) => {
148
+ try {
149
+ const mutation = `
150
+ mutation SendChangePasswordEmail($callbackUrl: String!) {
151
+ sendChangePasswordEmail(callbackUrl: $callbackUrl)
152
+ }
153
+ `;
154
+ const data = await gql.request(mutation, {
155
+ callbackUrl: callbackUrl || `${process.env.AFFINE_BASE_URL}/reset-password`
156
+ });
157
+ return text({ success: data.sendChangePasswordEmail, message: "Password reset email sent" });
158
+ }
159
+ catch (error) {
160
+ return text({ error: error.message });
161
+ }
162
+ };
163
+ server.registerTool("affine_send_password_reset", {
164
+ title: "Send Password Reset",
165
+ description: "Send password reset email.",
166
+ inputSchema: {
167
+ callbackUrl: z.string().optional().describe("Callback URL for password reset")
168
+ }
169
+ }, sendPasswordResetHandler);
170
+ server.registerTool("send_password_reset", {
171
+ title: "Send Password Reset",
172
+ description: "Send password reset email.",
173
+ inputSchema: {
174
+ callbackUrl: z.string().optional().describe("Callback URL for password reset")
175
+ }
176
+ }, sendPasswordResetHandler);
177
+ // DELETE ACCOUNT
178
+ const deleteAccountHandler = async ({ confirm }) => {
179
+ if (!confirm) {
180
+ return text({ error: "Confirmation required. Set confirm: true to delete account." });
181
+ }
182
+ try {
183
+ const mutation = `
184
+ mutation DeleteAccount {
185
+ deleteAccount
186
+ }
187
+ `;
188
+ const data = await gql.request(mutation);
189
+ return text({ success: data.deleteAccount, message: "Account deleted successfully" });
190
+ }
191
+ catch (error) {
192
+ return text({ error: error.message });
193
+ }
194
+ };
195
+ server.registerTool("affine_delete_account", {
196
+ title: "Delete Account",
197
+ description: "Permanently delete user account. WARNING: This cannot be undone!",
198
+ inputSchema: {
199
+ confirm: z.literal(true).describe("Must be true to confirm account deletion")
200
+ }
201
+ }, deleteAccountHandler);
202
+ server.registerTool("delete_account", {
203
+ title: "Delete Account",
204
+ description: "Permanently delete user account. WARNING: This cannot be undone!",
205
+ inputSchema: {
206
+ confirm: z.literal(true).describe("Must be true to confirm account deletion")
207
+ }
208
+ }, deleteAccountHandler);
209
+ }