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,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
|
+
}
|