@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,22 @@
|
|
|
1
|
+
import { text } from "../util/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export function registerHistoryTools(server, gql, defaults) {
|
|
4
|
+
const listHistoriesHandler = 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 Histories($workspaceId:String!,$guid:String!,$take:Int,$before:DateTime){ workspace(id:$workspaceId){ histories(guid:$guid, take:$take, before:$before){ id timestamp workspaceId } } }`;
|
|
9
|
+
const data = await gql.request(query, { workspaceId, guid: parsed.guid, take: parsed.take, before: parsed.before });
|
|
10
|
+
return text(data.workspace.histories);
|
|
11
|
+
};
|
|
12
|
+
server.registerTool("list_histories", {
|
|
13
|
+
title: "List Histories",
|
|
14
|
+
description: "List doc histories (timestamps) for a doc.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
workspaceId: z.string().optional(),
|
|
17
|
+
guid: z.string(),
|
|
18
|
+
take: z.number().optional(),
|
|
19
|
+
before: z.string().optional()
|
|
20
|
+
}
|
|
21
|
+
}, listHistoriesHandler);
|
|
22
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { receipt } from "../util/mcp.js";
|
|
3
|
+
import { connectWorkspaceSocket, joinWorkspace, wsUrlFromGraphQLEndpoint, } from "../ws.js";
|
|
4
|
+
import { docIconKey, folderIconKey, getExplorerIcon, normalizeIconInput, setExplorerIcon, } from "../util/explorerIcon.js";
|
|
5
|
+
/**
|
|
6
|
+
* Zod shape for the `icon` parameter shared by both setters. Accepts an emoji
|
|
7
|
+
* shorthand string, a full `{type:"emoji",unicode}` / `{type:"icon",name}`
|
|
8
|
+
* object, or `null` to clear the icon.
|
|
9
|
+
*/
|
|
10
|
+
const iconSchema = z
|
|
11
|
+
.union([
|
|
12
|
+
z.string(),
|
|
13
|
+
z.object({ type: z.literal("emoji"), unicode: z.string() }),
|
|
14
|
+
z.object({ type: z.literal("icon"), name: z.string() }),
|
|
15
|
+
z.null(),
|
|
16
|
+
])
|
|
17
|
+
.describe('Emoji shorthand ("๐งช"), a full object ({type:"emoji",unicode:"๐งช"} or ' +
|
|
18
|
+
'{type:"icon",name:"check"}), or null to remove the icon.');
|
|
19
|
+
/**
|
|
20
|
+
* Registers the explorer-icon tools: set/clear and read the Notion-style
|
|
21
|
+
* sidebar icon on a document or an organize folder. All four share the
|
|
22
|
+
* `explorerIcon` sub-doc helper so the storage model lives in one place.
|
|
23
|
+
*/
|
|
24
|
+
export function registerIconTools(server, gql, defaults) {
|
|
25
|
+
function resolveWorkspaceId(workspaceId) {
|
|
26
|
+
const resolved = workspaceId || defaults.workspaceId;
|
|
27
|
+
if (!resolved) {
|
|
28
|
+
throw new Error("workspaceId is required. Provide it as a parameter or set AFFINE_WORKSPACE_ID.");
|
|
29
|
+
}
|
|
30
|
+
return resolved;
|
|
31
|
+
}
|
|
32
|
+
async function withSocket(workspaceId, fn) {
|
|
33
|
+
const wsUrl = wsUrlFromGraphQLEndpoint(gql.endpoint);
|
|
34
|
+
const socket = await connectWorkspaceSocket(wsUrl, gql.cookie, gql.bearer);
|
|
35
|
+
try {
|
|
36
|
+
await joinWorkspace(socket, workspaceId);
|
|
37
|
+
return await fn(socket);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
socket.disconnect();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// โโโ update_doc_icon โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
44
|
+
const updateDocIconHandler = async (parsed) => {
|
|
45
|
+
const workspaceId = resolveWorkspaceId(parsed.workspaceId);
|
|
46
|
+
const icon = normalizeIconInput(parsed.icon);
|
|
47
|
+
const result = await withSocket(workspaceId, (socket) => setExplorerIcon(socket, workspaceId, docIconKey(parsed.docId), icon));
|
|
48
|
+
return receipt("doc.update_icon", {
|
|
49
|
+
workspaceId,
|
|
50
|
+
docId: parsed.docId,
|
|
51
|
+
icon: result.icon,
|
|
52
|
+
cleared: result.icon === null,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
server.registerTool("update_doc_icon", {
|
|
56
|
+
title: "Update Document Icon",
|
|
57
|
+
description: "Set or clear the sidebar icon (the Notion-style emoji slot) on a document. " +
|
|
58
|
+
"Pass an emoji string, a full icon object, or null to remove it.",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
workspaceId: z.string().optional(),
|
|
61
|
+
docId: z.string().describe("The document whose icon to update."),
|
|
62
|
+
icon: iconSchema,
|
|
63
|
+
},
|
|
64
|
+
}, updateDocIconHandler);
|
|
65
|
+
// โโโ update_folder_icon โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
66
|
+
const updateFolderIconHandler = async (parsed) => {
|
|
67
|
+
const workspaceId = resolveWorkspaceId(parsed.workspaceId);
|
|
68
|
+
const icon = normalizeIconInput(parsed.icon);
|
|
69
|
+
const result = await withSocket(workspaceId, (socket) => setExplorerIcon(socket, workspaceId, folderIconKey(parsed.folderId), icon));
|
|
70
|
+
return receipt("folder.update_icon", {
|
|
71
|
+
workspaceId,
|
|
72
|
+
folderId: parsed.folderId,
|
|
73
|
+
icon: result.icon,
|
|
74
|
+
cleared: result.icon === null,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
server.registerTool("update_folder_icon", {
|
|
78
|
+
title: "Update Folder Icon",
|
|
79
|
+
description: "Set or clear the sidebar icon on an organize folder. " +
|
|
80
|
+
"Pass an emoji string, a full icon object, or null to remove it. Experimental.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
workspaceId: z.string().optional(),
|
|
83
|
+
folderId: z.string().describe("The organize folder whose icon to update."),
|
|
84
|
+
icon: iconSchema,
|
|
85
|
+
},
|
|
86
|
+
}, updateFolderIconHandler);
|
|
87
|
+
// โโโ get_doc_icon โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
88
|
+
const getDocIconHandler = async (parsed) => {
|
|
89
|
+
const workspaceId = resolveWorkspaceId(parsed.workspaceId);
|
|
90
|
+
const result = await withSocket(workspaceId, (socket) => getExplorerIcon(socket, workspaceId, docIconKey(parsed.docId)));
|
|
91
|
+
return receipt("doc.get_icon", {
|
|
92
|
+
workspaceId,
|
|
93
|
+
docId: parsed.docId,
|
|
94
|
+
icon: result.icon,
|
|
95
|
+
hasIcon: result.icon !== null,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
server.registerTool("get_doc_icon", {
|
|
99
|
+
title: "Get Document Icon",
|
|
100
|
+
description: "Read the current sidebar icon of a document. Returns null when none is set.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
workspaceId: z.string().optional(),
|
|
103
|
+
docId: z.string().describe("The document whose icon to read."),
|
|
104
|
+
},
|
|
105
|
+
}, getDocIconHandler);
|
|
106
|
+
// โโโ get_folder_icon โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
107
|
+
const getFolderIconHandler = async (parsed) => {
|
|
108
|
+
const workspaceId = resolveWorkspaceId(parsed.workspaceId);
|
|
109
|
+
const result = await withSocket(workspaceId, (socket) => getExplorerIcon(socket, workspaceId, folderIconKey(parsed.folderId)));
|
|
110
|
+
return receipt("folder.get_icon", {
|
|
111
|
+
workspaceId,
|
|
112
|
+
folderId: parsed.folderId,
|
|
113
|
+
icon: result.icon,
|
|
114
|
+
hasIcon: result.icon !== null,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
server.registerTool("get_folder_icon", {
|
|
118
|
+
title: "Get Folder Icon",
|
|
119
|
+
description: "Read the current sidebar icon of an organize folder. Returns null when none is set. Experimental.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
workspaceId: z.string().optional(),
|
|
122
|
+
folderId: z.string().describe("The organize folder whose icon to read."),
|
|
123
|
+
},
|
|
124
|
+
}, getFolderIconHandler);
|
|
125
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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, offset, after, unreadOnly = false }) => {
|
|
6
|
+
try {
|
|
7
|
+
const query = `
|
|
8
|
+
query GetNotifications($pagination: PaginationInput!) {
|
|
9
|
+
currentUser {
|
|
10
|
+
notifications(pagination: $pagination) {
|
|
11
|
+
edges {
|
|
12
|
+
cursor
|
|
13
|
+
node {
|
|
14
|
+
id
|
|
15
|
+
type
|
|
16
|
+
body
|
|
17
|
+
read
|
|
18
|
+
level
|
|
19
|
+
createdAt
|
|
20
|
+
updatedAt
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
totalCount
|
|
24
|
+
pageInfo {
|
|
25
|
+
hasNextPage
|
|
26
|
+
endCursor
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
const data = await gql.request(query, {
|
|
33
|
+
pagination: {
|
|
34
|
+
first,
|
|
35
|
+
offset,
|
|
36
|
+
after
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
let notifications = (data.currentUser?.notifications?.edges || []).map((edge) => edge.node);
|
|
40
|
+
if (unreadOnly) {
|
|
41
|
+
notifications = notifications.filter((n) => !n.read);
|
|
42
|
+
}
|
|
43
|
+
return text(notifications);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return text({ error: error.message });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
server.registerTool("list_notifications", {
|
|
50
|
+
title: "List Notifications",
|
|
51
|
+
description: "Get user notifications.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
first: z.number().optional().describe("Number of notifications to fetch"),
|
|
54
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
55
|
+
after: z.string().optional().describe("Cursor for pagination"),
|
|
56
|
+
unreadOnly: z.boolean().optional().describe("Show only unread notifications")
|
|
57
|
+
}
|
|
58
|
+
}, listNotificationsHandler);
|
|
59
|
+
// MARK ALL NOTIFICATIONS READ
|
|
60
|
+
const readAllNotificationsHandler = async () => {
|
|
61
|
+
try {
|
|
62
|
+
const mutation = `
|
|
63
|
+
mutation ReadAllNotifications {
|
|
64
|
+
readAllNotifications
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
const data = await gql.request(mutation);
|
|
68
|
+
return text({ success: data.readAllNotifications, message: "All notifications marked as read" });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return text({ error: error.message });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
server.registerTool("read_all_notifications", {
|
|
75
|
+
title: "Mark All Notifications Read",
|
|
76
|
+
description: "Mark all notifications as read.",
|
|
77
|
+
inputSchema: {}
|
|
78
|
+
}, readAllNotificationsHandler);
|
|
79
|
+
}
|