affine-mcp-server 1.2.2 → 1.4.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/README.md +99 -23
- package/bin/affine-mcp +0 -0
- package/dist/index.js +1 -3
- package/dist/tools/accessTokens.js +3 -23
- package/dist/tools/auth.js +0 -8
- package/dist/tools/blobStorage.js +59 -35
- package/dist/tools/comments.js +4 -48
- package/dist/tools/docs.js +331 -197
- package/dist/tools/history.js +0 -36
- package/dist/tools/notifications.js +25 -54
- package/dist/tools/user.js +0 -5
- package/dist/tools/userCRUD.js +20 -152
- package/dist/tools/workspaces.js +35 -106
- package/dist/ws.js +25 -3
- package/package.json +15 -5
- package/dist/tools/updates.js +0 -32
package/README.md
CHANGED
|
@@ -2,28 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
A Model Context Protocol (MCP) server that integrates with AFFiNE (self‑hosted or cloud). It exposes AFFiNE workspaces and documents to AI assistants over stdio.
|
|
4
4
|
|
|
5
|
-
[](https://github.com/dawncr0w/affine-mcp-server/releases)
|
|
6
6
|
[](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
7
|
+
[](https://github.com/dawncr0w/affine-mcp-server/actions/workflows/ci.yml)
|
|
7
8
|
[](LICENSE)
|
|
8
9
|
|
|
10
|
+
<a href="https://glama.ai/mcp/servers/@DAWNCR0W/affine-mcp-server">
|
|
11
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@DAWNCR0W/affine-mcp-server/badge" alt="AFFiNE Server MCP server" />
|
|
12
|
+
</a>
|
|
13
|
+
|
|
9
14
|
## Overview
|
|
10
15
|
|
|
11
16
|
- Purpose: Manage AFFiNE workspaces and documents through MCP
|
|
12
17
|
- Transport: stdio only (Claude Desktop / Codex compatible)
|
|
13
18
|
- Auth: Token, Cookie, or Email/Password (priority order)
|
|
14
|
-
- Tools:
|
|
15
|
-
- Status:
|
|
19
|
+
- Tools: 32 focused tools with WebSocket-based document editing
|
|
20
|
+
- Status: Active
|
|
16
21
|
|
|
17
|
-
> New in v1.
|
|
22
|
+
> New in v1.4.0: Added `read_doc` for document content snapshots (blocks + plain text), plus Cursor setup/troubleshooting guidance.
|
|
18
23
|
|
|
19
24
|
## Features
|
|
20
25
|
|
|
21
26
|
- Workspace: create (with initial doc), read, update, delete
|
|
22
|
-
- Documents: list/get/
|
|
27
|
+
- Documents: list/get/read/publish/revoke + create/append paragraph/delete (WebSocket‑based)
|
|
23
28
|
- Comments: full CRUD and resolve
|
|
24
|
-
- Version History: list
|
|
25
|
-
- Users & Tokens: profile/settings and personal access tokens
|
|
29
|
+
- Version History: list
|
|
30
|
+
- Users & Tokens: current user, sign in, profile/settings, and personal access tokens
|
|
26
31
|
- Notifications: list and mark as read
|
|
32
|
+
- Blob storage: upload/delete/cleanup
|
|
27
33
|
|
|
28
34
|
## Requirements
|
|
29
35
|
|
|
@@ -43,7 +49,7 @@ npx -y -p affine-mcp-server affine-mcp -- --version
|
|
|
43
49
|
|
|
44
50
|
The package installs a CLI named `affine-mcp` that runs the MCP server over stdio.
|
|
45
51
|
|
|
46
|
-
Note: From v1.2.2 the CLI wrapper (`bin/affine-mcp`) ensures Node runs the ESM entrypoint, preventing shell from misinterpreting JS.
|
|
52
|
+
Note: From v1.2.2+ the CLI wrapper (`bin/affine-mcp`) ensures Node runs the ESM entrypoint, preventing shell from misinterpreting JS.
|
|
47
53
|
|
|
48
54
|
## Configuration
|
|
49
55
|
|
|
@@ -106,6 +112,43 @@ Notes
|
|
|
106
112
|
- Command: `affine-mcp`
|
|
107
113
|
- Environment: `AFFINE_BASE_URL` + one auth method (`AFFINE_API_TOKEN` | `AFFINE_COOKIE` | `AFFINE_EMAIL`/`AFFINE_PASSWORD`)
|
|
108
114
|
|
|
115
|
+
### Cursor
|
|
116
|
+
|
|
117
|
+
Cursor also supports MCP over stdio with `mcp.json`.
|
|
118
|
+
|
|
119
|
+
Project-local (`.cursor/mcp.json`) example:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"affine": {
|
|
125
|
+
"command": "affine-mcp",
|
|
126
|
+
"env": {
|
|
127
|
+
"AFFINE_BASE_URL": "https://your-affine-instance.com",
|
|
128
|
+
"AFFINE_API_TOKEN": "apt_xxx"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If you prefer `npx`:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"affine": {
|
|
141
|
+
"command": "npx",
|
|
142
|
+
"args": ["-y", "-p", "affine-mcp-server", "affine-mcp"],
|
|
143
|
+
"env": {
|
|
144
|
+
"AFFINE_BASE_URL": "https://your-affine-instance.com",
|
|
145
|
+
"AFFINE_API_TOKEN": "apt_xxx"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
109
152
|
## Available Tools
|
|
110
153
|
|
|
111
154
|
### Workspace
|
|
@@ -118,33 +161,30 @@ Notes
|
|
|
118
161
|
### Documents
|
|
119
162
|
- `list_docs` – list documents with pagination
|
|
120
163
|
- `get_doc` – get document metadata
|
|
121
|
-
- `
|
|
122
|
-
- `recent_docs` – list recently updated documents
|
|
164
|
+
- `read_doc` – read document block content and plain text snapshot (WebSocket)
|
|
123
165
|
- `publish_doc` – make document public
|
|
124
166
|
- `revoke_doc` – revoke public access
|
|
125
167
|
- `create_doc` – create a new document (WebSocket)
|
|
126
168
|
- `append_paragraph` – append a paragraph block (WebSocket)
|
|
169
|
+
- `append_block` – append slash-command style blocks (`heading/list/todo/code/divider/quote`)
|
|
127
170
|
- `delete_doc` – delete a document (WebSocket)
|
|
128
171
|
|
|
129
172
|
### Comments
|
|
130
173
|
- `list_comments`, `create_comment`, `update_comment`, `delete_comment`, `resolve_comment`
|
|
131
174
|
|
|
132
175
|
### Version History
|
|
133
|
-
- `list_histories
|
|
176
|
+
- `list_histories`
|
|
134
177
|
|
|
135
178
|
### Users & Tokens
|
|
136
179
|
- `current_user`, `sign_in`, `update_profile`, `update_settings`
|
|
137
180
|
- `list_access_tokens`, `generate_access_token`, `revoke_access_token`
|
|
138
181
|
|
|
139
182
|
### Notifications
|
|
140
|
-
- `list_notifications`, `
|
|
183
|
+
- `list_notifications`, `read_all_notifications`
|
|
141
184
|
|
|
142
185
|
### Blob Storage
|
|
143
186
|
- `upload_blob`, `delete_blob`, `cleanup_blobs`
|
|
144
187
|
|
|
145
|
-
### Advanced
|
|
146
|
-
- `apply_doc_updates` – apply CRDT updates to documents
|
|
147
|
-
|
|
148
188
|
## Use Locally (clone)
|
|
149
189
|
|
|
150
190
|
```bash
|
|
@@ -160,19 +200,39 @@ npm link
|
|
|
160
200
|
# Now use `affine-mcp` like a global binary
|
|
161
201
|
```
|
|
162
202
|
|
|
203
|
+
## Quality Gates
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm run build
|
|
207
|
+
npm run test:tool-manifest
|
|
208
|
+
npm run pack:check
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- `tool-manifest.json` is the source of truth for publicly exposed tool names.
|
|
212
|
+
- CI validates that `registerTool(...)` declarations match the manifest exactly.
|
|
213
|
+
|
|
163
214
|
## Troubleshooting
|
|
164
215
|
|
|
165
216
|
Authentication
|
|
166
217
|
- Email/Password: ensure your instance allows password auth and credentials are valid
|
|
167
218
|
- Cookie: copy cookies (e.g., `affine_session`, `affine_csrf`) from the browser DevTools after login
|
|
168
|
-
- Token: generate a personal access token; verify it hasn
|
|
169
|
-
- Startup timeouts: v1.2.2 includes a CLI wrapper fix and
|
|
219
|
+
- Token: generate a personal access token; verify it hasn't expired
|
|
220
|
+
- Startup timeouts: v1.2.2+ includes a CLI wrapper fix and default async login to avoid blocking the MCP handshake. Set `AFFINE_LOGIN_AT_START=sync` only if needed.
|
|
170
221
|
|
|
171
222
|
Connection
|
|
172
223
|
- Confirm `AFFINE_BASE_URL` is reachable
|
|
173
224
|
- GraphQL endpoint default is `/graphql`
|
|
174
225
|
- Check firewall/proxy rules; verify CORS if self‑hosted
|
|
175
226
|
|
|
227
|
+
Method not found
|
|
228
|
+
- MCP tool names (for example `list_workspaces`) are not JSON-RPC top-level method names.
|
|
229
|
+
- Use an MCP client (`tools/list`, `tools/call`) instead of sending direct JSON-RPC calls like `{\"method\":\"list_workspaces\"}`.
|
|
230
|
+
- From v1.3.0, only canonical tool names are exposed (legacy `affine_*` aliases were removed).
|
|
231
|
+
|
|
232
|
+
Workspace visibility
|
|
233
|
+
- This MCP server can access server-backed workspaces only (AFFiNE cloud/self-hosted).
|
|
234
|
+
- Browser local-storage workspaces are client-side data, so they are not visible via server GraphQL/WebSocket APIs.
|
|
235
|
+
|
|
176
236
|
## Security Considerations
|
|
177
237
|
|
|
178
238
|
- Never commit `.env` with secrets
|
|
@@ -183,6 +243,17 @@ Connection
|
|
|
183
243
|
|
|
184
244
|
## Version History
|
|
185
245
|
|
|
246
|
+
### 1.4.0 (2026‑02‑13)
|
|
247
|
+
- Added `read_doc` for reading document block snapshot + plain text
|
|
248
|
+
- Added Cursor setup examples and troubleshooting notes for JSON-RPC method usage
|
|
249
|
+
- Added explicit local-storage workspace limitation notes
|
|
250
|
+
|
|
251
|
+
### 1.3.0 (2026‑02‑13)
|
|
252
|
+
- Added `append_block` for slash-command style editing (`heading/list/todo/code/divider/quote`)
|
|
253
|
+
- Tool surface simplified to 31 canonical tools (duplicate aliases removed)
|
|
254
|
+
- Added CI + manifest parity verification (`npm run test:tool-manifest`, `npm run ci`)
|
|
255
|
+
- Added open-source community health docs and issue/PR templates
|
|
256
|
+
|
|
186
257
|
### 1.2.2 (2025‑09‑18)
|
|
187
258
|
- CLI wrapper added to ensure Node runs ESM entry (`bin/affine-mcp`), preventing shell mis-execution
|
|
188
259
|
- Docs cleaned: use env vars via shell/app config; `.env` file no longer recommended
|
|
@@ -195,7 +266,7 @@ Connection
|
|
|
195
266
|
|
|
196
267
|
### 1.2.0 (2025‑09‑16)
|
|
197
268
|
- WebSocket-based document tools: `create_doc`, `append_paragraph`, `delete_doc` (create/edit/delete now supported)
|
|
198
|
-
- Tool aliases
|
|
269
|
+
- Tool aliases introduced at the time (`affine_*` + non-prefixed names). They were removed later to reduce duplication.
|
|
199
270
|
- ESM resolution: NodeNext; improved build stability
|
|
200
271
|
- CLI binary: `affine-mcp` for easy `npm i -g` usage
|
|
201
272
|
|
|
@@ -212,11 +283,16 @@ Connection
|
|
|
212
283
|
## Contributing
|
|
213
284
|
|
|
214
285
|
Contributions are welcome!
|
|
215
|
-
1.
|
|
216
|
-
2.
|
|
217
|
-
3.
|
|
218
|
-
4.
|
|
219
|
-
|
|
286
|
+
1. Read `CONTRIBUTING.md`
|
|
287
|
+
2. Run `npm run ci` locally before opening PR
|
|
288
|
+
3. Keep tool changes synced with `tool-manifest.json`
|
|
289
|
+
4. Use issue/PR templates in `.github/`
|
|
290
|
+
|
|
291
|
+
## Community Health
|
|
292
|
+
|
|
293
|
+
- Code of Conduct: `CODE_OF_CONDUCT.md`
|
|
294
|
+
- Security policy: `SECURITY.md`
|
|
295
|
+
- Contributing guide: `CONTRIBUTING.md`
|
|
220
296
|
|
|
221
297
|
## License
|
|
222
298
|
|
package/bin/affine-mcp
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import { registerCommentTools } from "./tools/comments.js";
|
|
|
8
8
|
import { registerHistoryTools } from "./tools/history.js";
|
|
9
9
|
import { registerUserTools } from "./tools/user.js";
|
|
10
10
|
import { registerUserCRUDTools } from "./tools/userCRUD.js";
|
|
11
|
-
import { registerUpdateTools } from "./tools/updates.js";
|
|
12
11
|
import { registerAccessTokenTools } from "./tools/accessTokens.js";
|
|
13
12
|
import { registerBlobTools } from "./tools/blobStorage.js";
|
|
14
13
|
import { registerNotificationTools } from "./tools/notifications.js";
|
|
@@ -16,7 +15,7 @@ import { loginWithPassword } from "./auth.js";
|
|
|
16
15
|
import { registerAuthTools } from "./tools/auth.js";
|
|
17
16
|
const config = loadConfig();
|
|
18
17
|
async function buildServer() {
|
|
19
|
-
const server = new McpServer({ name: "affine-mcp", version: "1.
|
|
18
|
+
const server = new McpServer({ name: "affine-mcp", version: "1.4.0" });
|
|
20
19
|
// Initialize GraphQL client with authentication
|
|
21
20
|
const gql = new GraphQLClient({
|
|
22
21
|
endpoint: `${config.baseUrl}${config.graphqlPath}`,
|
|
@@ -65,7 +64,6 @@ async function buildServer() {
|
|
|
65
64
|
registerHistoryTools(server, gql, { workspaceId: config.defaultWorkspaceId });
|
|
66
65
|
registerUserTools(server, gql);
|
|
67
66
|
registerUserCRUDTools(server, gql);
|
|
68
|
-
registerUpdateTools(server, gql, { workspaceId: config.defaultWorkspaceId });
|
|
69
67
|
registerAccessTokenTools(server, gql);
|
|
70
68
|
registerBlobTools(server, gql);
|
|
71
69
|
registerNotificationTools(server, gql);
|
|
@@ -3,20 +3,15 @@ import { text } from "../util/mcp.js";
|
|
|
3
3
|
export function registerAccessTokenTools(server, gql) {
|
|
4
4
|
const listAccessTokensHandler = async () => {
|
|
5
5
|
try {
|
|
6
|
-
const query = `query { accessTokens { id name createdAt expiresAt } }`;
|
|
6
|
+
const query = `query { currentUser { accessTokens { id name createdAt expiresAt } } }`;
|
|
7
7
|
const data = await gql.request(query);
|
|
8
|
-
return text(data.accessTokens || []);
|
|
8
|
+
return text(data.currentUser?.accessTokens || []);
|
|
9
9
|
}
|
|
10
10
|
catch (error) {
|
|
11
11
|
console.error("List access tokens error:", error.message);
|
|
12
|
-
return text(
|
|
12
|
+
return text({ error: error.message });
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
-
server.registerTool("affine_list_access_tokens", {
|
|
16
|
-
title: "List Access Tokens",
|
|
17
|
-
description: "List personal access tokens (metadata).",
|
|
18
|
-
inputSchema: {}
|
|
19
|
-
}, listAccessTokensHandler);
|
|
20
15
|
server.registerTool("list_access_tokens", {
|
|
21
16
|
title: "List Access Tokens",
|
|
22
17
|
description: "List personal access tokens (metadata).",
|
|
@@ -27,14 +22,6 @@ export function registerAccessTokenTools(server, gql) {
|
|
|
27
22
|
const data = await gql.request(mutation, { input: { name: parsed.name, expiresAt: parsed.expiresAt ?? null } });
|
|
28
23
|
return text(data.generateUserAccessToken);
|
|
29
24
|
};
|
|
30
|
-
server.registerTool("affine_generate_access_token", {
|
|
31
|
-
title: "Generate Access Token",
|
|
32
|
-
description: "Generate a personal access token (returns token).",
|
|
33
|
-
inputSchema: {
|
|
34
|
-
name: z.string(),
|
|
35
|
-
expiresAt: z.string().optional()
|
|
36
|
-
}
|
|
37
|
-
}, generateAccessTokenHandler);
|
|
38
25
|
server.registerTool("generate_access_token", {
|
|
39
26
|
title: "Generate Access Token",
|
|
40
27
|
description: "Generate a personal access token (returns token).",
|
|
@@ -48,13 +35,6 @@ export function registerAccessTokenTools(server, gql) {
|
|
|
48
35
|
const data = await gql.request(mutation, { id: parsed.id });
|
|
49
36
|
return text({ success: data.revokeUserAccessToken });
|
|
50
37
|
};
|
|
51
|
-
server.registerTool("affine_revoke_access_token", {
|
|
52
|
-
title: "Revoke Access Token",
|
|
53
|
-
description: "Revoke a personal access token by id.",
|
|
54
|
-
inputSchema: {
|
|
55
|
-
id: z.string()
|
|
56
|
-
}
|
|
57
|
-
}, revokeAccessTokenHandler);
|
|
58
38
|
server.registerTool("revoke_access_token", {
|
|
59
39
|
title: "Revoke Access Token",
|
|
60
40
|
description: "Revoke a personal access token by id.",
|
package/dist/tools/auth.js
CHANGED
|
@@ -7,14 +7,6 @@ export function registerAuthTools(server, gql, baseUrl) {
|
|
|
7
7
|
gql.setCookie(cookieHeader);
|
|
8
8
|
return text({ signedIn: true });
|
|
9
9
|
};
|
|
10
|
-
server.registerTool("affine_sign_in", {
|
|
11
|
-
title: "Sign In",
|
|
12
|
-
description: "Sign in to AFFiNE using email and password; sets session cookies for subsequent calls.",
|
|
13
|
-
inputSchema: {
|
|
14
|
-
email: z.string().email(),
|
|
15
|
-
password: z.string().min(1)
|
|
16
|
-
}
|
|
17
|
-
}, signInHandler);
|
|
18
10
|
server.registerTool("sign_in", {
|
|
19
11
|
title: "Sign In",
|
|
20
12
|
description: "Sign in to AFFiNE using email and password; sets session cookies for subsequent calls.",
|
|
@@ -1,36 +1,76 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { text } from "../util/mcp.js";
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import fetch from "node-fetch";
|
|
5
|
+
function decodeBlobContent(content) {
|
|
6
|
+
const normalized = content.trim().replace(/\s+/g, "");
|
|
7
|
+
const base64Like = normalized.length > 0 && normalized.length % 4 === 0 && /^[A-Za-z0-9+/=]+$/.test(normalized);
|
|
8
|
+
if (base64Like) {
|
|
9
|
+
try {
|
|
10
|
+
const decoded = Buffer.from(normalized, "base64");
|
|
11
|
+
if (decoded.length > 0) {
|
|
12
|
+
return decoded;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Fallback to UTF-8 text below.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return Buffer.from(content, "utf8");
|
|
20
|
+
}
|
|
3
21
|
export function registerBlobTools(server, gql) {
|
|
4
22
|
// UPLOAD BLOB/FILE
|
|
5
23
|
const uploadBlobHandler = async ({ workspaceId, content, filename, contentType }) => {
|
|
6
24
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
25
|
+
const endpoint = gql.endpoint || `${process.env.AFFINE_BASE_URL || "http://localhost:3010"}/graphql`;
|
|
26
|
+
const headers = gql.headers || {};
|
|
27
|
+
const cookie = gql.cookie || headers.Cookie || "";
|
|
28
|
+
const payload = decodeBlobContent(content);
|
|
29
|
+
const safeFilename = filename || `blob-${Date.now()}.bin`;
|
|
30
|
+
const mime = contentType || "application/octet-stream";
|
|
31
|
+
const form = new FormData();
|
|
32
|
+
form.append("operations", JSON.stringify({
|
|
33
|
+
query: `mutation SetBlob($workspaceId: String!, $blob: Upload!) {
|
|
34
|
+
setBlob(workspaceId: $workspaceId, blob: $blob)
|
|
35
|
+
}`,
|
|
36
|
+
variables: {
|
|
37
|
+
workspaceId,
|
|
38
|
+
blob: null
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
form.append("map", JSON.stringify({ "0": ["variables.blob"] }));
|
|
42
|
+
form.append("0", payload, { filename: safeFilename, contentType: mime });
|
|
43
|
+
const response = await fetch(endpoint, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
...headers,
|
|
47
|
+
Cookie: cookie,
|
|
48
|
+
...form.getHeaders(),
|
|
49
|
+
},
|
|
50
|
+
body: form,
|
|
51
|
+
});
|
|
52
|
+
const result = await response.json();
|
|
53
|
+
if (result.errors?.length) {
|
|
54
|
+
throw new Error(result.errors[0].message);
|
|
55
|
+
}
|
|
56
|
+
const blobKey = result.data?.setBlob;
|
|
57
|
+
if (!blobKey) {
|
|
58
|
+
throw new Error("Upload succeeded but no blob key was returned.");
|
|
59
|
+
}
|
|
10
60
|
return text({
|
|
11
|
-
id:
|
|
61
|
+
id: blobKey,
|
|
62
|
+
key: blobKey,
|
|
12
63
|
workspaceId,
|
|
13
|
-
filename:
|
|
14
|
-
contentType:
|
|
15
|
-
size:
|
|
16
|
-
uploadedAt: new Date().toISOString()
|
|
17
|
-
note: "Blob metadata created. Use AFFiNE UI for actual file upload."
|
|
64
|
+
filename: safeFilename,
|
|
65
|
+
contentType: mime,
|
|
66
|
+
size: payload.length,
|
|
67
|
+
uploadedAt: new Date().toISOString()
|
|
18
68
|
});
|
|
19
69
|
}
|
|
20
70
|
catch (error) {
|
|
21
71
|
return text({ error: error.message });
|
|
22
72
|
}
|
|
23
73
|
};
|
|
24
|
-
server.registerTool("affine_upload_blob", {
|
|
25
|
-
title: "Upload Blob",
|
|
26
|
-
description: "Upload a file or blob to workspace storage.",
|
|
27
|
-
inputSchema: {
|
|
28
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
29
|
-
content: z.string().describe("Base64 encoded content or text"),
|
|
30
|
-
filename: z.string().optional().describe("Filename"),
|
|
31
|
-
contentType: z.string().optional().describe("MIME type")
|
|
32
|
-
}
|
|
33
|
-
}, uploadBlobHandler);
|
|
34
74
|
server.registerTool("upload_blob", {
|
|
35
75
|
title: "Upload Blob",
|
|
36
76
|
description: "Upload a file or blob to workspace storage.",
|
|
@@ -60,15 +100,6 @@ export function registerBlobTools(server, gql) {
|
|
|
60
100
|
return text({ error: error.message });
|
|
61
101
|
}
|
|
62
102
|
};
|
|
63
|
-
server.registerTool("affine_delete_blob", {
|
|
64
|
-
title: "Delete Blob",
|
|
65
|
-
description: "Delete a blob/file from workspace storage.",
|
|
66
|
-
inputSchema: {
|
|
67
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
68
|
-
key: z.string().describe("Blob key/ID to delete"),
|
|
69
|
-
permanently: z.boolean().optional().describe("Delete permanently")
|
|
70
|
-
}
|
|
71
|
-
}, deleteBlobHandler);
|
|
72
103
|
server.registerTool("delete_blob", {
|
|
73
104
|
title: "Delete Blob",
|
|
74
105
|
description: "Delete a blob/file from workspace storage.",
|
|
@@ -95,13 +126,6 @@ export function registerBlobTools(server, gql) {
|
|
|
95
126
|
return text({ error: error.message });
|
|
96
127
|
}
|
|
97
128
|
};
|
|
98
|
-
server.registerTool("affine_cleanup_blobs", {
|
|
99
|
-
title: "Cleanup Deleted Blobs",
|
|
100
|
-
description: "Permanently remove deleted blobs to free up storage.",
|
|
101
|
-
inputSchema: {
|
|
102
|
-
workspaceId: z.string().describe("Workspace ID")
|
|
103
|
-
}
|
|
104
|
-
}, cleanupBlobsHandler);
|
|
105
129
|
server.registerTool("cleanup_blobs", {
|
|
106
130
|
title: "Cleanup Deleted Blobs",
|
|
107
131
|
description: "Permanently remove deleted blobs to free up storage.",
|
package/dist/tools/comments.js
CHANGED
|
@@ -9,17 +9,6 @@ export function registerCommentTools(server, gql, defaults) {
|
|
|
9
9
|
const data = await gql.request(query, { workspaceId, docId: parsed.docId, first: parsed.first, offset: parsed.offset, after: parsed.after });
|
|
10
10
|
return text(data.workspace.comments);
|
|
11
11
|
};
|
|
12
|
-
server.registerTool("affine_list_comments", {
|
|
13
|
-
title: "List Comments",
|
|
14
|
-
description: "List comments of a doc (with replies).",
|
|
15
|
-
inputSchema: {
|
|
16
|
-
workspaceId: z.string().optional(),
|
|
17
|
-
docId: z.string(),
|
|
18
|
-
first: z.number().optional(),
|
|
19
|
-
offset: z.number().optional(),
|
|
20
|
-
after: z.string().optional()
|
|
21
|
-
}
|
|
22
|
-
}, listCommentsHandler);
|
|
23
12
|
server.registerTool("list_comments", {
|
|
24
13
|
title: "List Comments",
|
|
25
14
|
description: "List comments of a doc (with replies).",
|
|
@@ -36,22 +25,12 @@ export function registerCommentTools(server, gql, defaults) {
|
|
|
36
25
|
if (!workspaceId)
|
|
37
26
|
throw new Error("workspaceId required (or set AFFINE_WORKSPACE_ID)");
|
|
38
27
|
const mutation = `mutation CreateComment($input: CommentCreateInput!){ createComment(input:$input){ id content createdAt updatedAt resolved } }`;
|
|
39
|
-
const
|
|
28
|
+
const normalizedDocMode = (parsed.docMode || 'page').toLowerCase() === 'edgeless' ? 'edgeless' : 'page';
|
|
29
|
+
const normalizedContent = typeof parsed.content === 'string' ? { text: parsed.content } : parsed.content;
|
|
30
|
+
const input = { content: normalizedContent, docId: parsed.docId, workspaceId, docTitle: parsed.docTitle || "", docMode: normalizedDocMode, mentions: parsed.mentions };
|
|
40
31
|
const data = await gql.request(mutation, { input });
|
|
41
32
|
return text(data.createComment);
|
|
42
33
|
};
|
|
43
|
-
server.registerTool("affine_create_comment", {
|
|
44
|
-
title: "Create Comment",
|
|
45
|
-
description: "Create a comment on a doc.",
|
|
46
|
-
inputSchema: {
|
|
47
|
-
workspaceId: z.string().optional(),
|
|
48
|
-
docId: z.string(),
|
|
49
|
-
docTitle: z.string().optional(),
|
|
50
|
-
docMode: z.enum(["Page", "Edgeless"]).optional(),
|
|
51
|
-
content: z.any(),
|
|
52
|
-
mentions: z.array(z.string()).optional()
|
|
53
|
-
}
|
|
54
|
-
}, createCommentHandler);
|
|
55
34
|
server.registerTool("create_comment", {
|
|
56
35
|
title: "Create Comment",
|
|
57
36
|
description: "Create a comment on a doc.",
|
|
@@ -59,7 +38,7 @@ export function registerCommentTools(server, gql, defaults) {
|
|
|
59
38
|
workspaceId: z.string().optional(),
|
|
60
39
|
docId: z.string(),
|
|
61
40
|
docTitle: z.string().optional(),
|
|
62
|
-
docMode: z.enum(["Page", "Edgeless"]).optional(),
|
|
41
|
+
docMode: z.enum(["Page", "Edgeless", "page", "edgeless"]).optional(),
|
|
63
42
|
content: z.any(),
|
|
64
43
|
mentions: z.array(z.string()).optional()
|
|
65
44
|
}
|
|
@@ -69,14 +48,6 @@ export function registerCommentTools(server, gql, defaults) {
|
|
|
69
48
|
const data = await gql.request(mutation, { input: { id: parsed.id, content: parsed.content } });
|
|
70
49
|
return text({ success: data.updateComment });
|
|
71
50
|
};
|
|
72
|
-
server.registerTool("affine_update_comment", {
|
|
73
|
-
title: "Update Comment",
|
|
74
|
-
description: "Update a comment content.",
|
|
75
|
-
inputSchema: {
|
|
76
|
-
id: z.string(),
|
|
77
|
-
content: z.any()
|
|
78
|
-
}
|
|
79
|
-
}, updateCommentHandler);
|
|
80
51
|
server.registerTool("update_comment", {
|
|
81
52
|
title: "Update Comment",
|
|
82
53
|
description: "Update a comment content.",
|
|
@@ -90,13 +61,6 @@ export function registerCommentTools(server, gql, defaults) {
|
|
|
90
61
|
const data = await gql.request(mutation, { id: parsed.id });
|
|
91
62
|
return text({ success: data.deleteComment });
|
|
92
63
|
};
|
|
93
|
-
server.registerTool("affine_delete_comment", {
|
|
94
|
-
title: "Delete Comment",
|
|
95
|
-
description: "Delete a comment by id.",
|
|
96
|
-
inputSchema: {
|
|
97
|
-
id: z.string()
|
|
98
|
-
}
|
|
99
|
-
}, deleteCommentHandler);
|
|
100
64
|
server.registerTool("delete_comment", {
|
|
101
65
|
title: "Delete Comment",
|
|
102
66
|
description: "Delete a comment by id.",
|
|
@@ -109,14 +73,6 @@ export function registerCommentTools(server, gql, defaults) {
|
|
|
109
73
|
const data = await gql.request(mutation, { input: parsed });
|
|
110
74
|
return text({ success: data.resolveComment });
|
|
111
75
|
};
|
|
112
|
-
server.registerTool("affine_resolve_comment", {
|
|
113
|
-
title: "Resolve Comment",
|
|
114
|
-
description: "Resolve or unresolve a comment.",
|
|
115
|
-
inputSchema: {
|
|
116
|
-
id: z.string(),
|
|
117
|
-
resolved: z.boolean()
|
|
118
|
-
}
|
|
119
|
-
}, resolveCommentHandler);
|
|
120
76
|
server.registerTool("resolve_comment", {
|
|
121
77
|
title: "Resolve Comment",
|
|
122
78
|
description: "Resolve or unresolve a comment.",
|