affine-mcp-server 1.5.0 → 1.7.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 +214 -33
- package/dist/auth.js +31 -7
- package/dist/cli.js +288 -0
- package/dist/config.js +124 -37
- package/dist/graphqlClient.js +86 -15
- package/dist/index.js +63 -10
- package/dist/markdown/parse.js +465 -0
- package/dist/markdown/render.js +202 -0
- package/dist/sse.js +284 -0
- package/dist/tools/blobStorage.js +3 -3
- package/dist/tools/docs.js +1496 -112
- package/dist/tools/workspaces.js +8 -7
- package/dist/ws.js +62 -35
- package/package.json +13 -1
- /package/dist/{types.js → markdown/types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# AFFiNE MCP Server
|
|
2
2
|
|
|
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.
|
|
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 (default) or HTTP (`/mcp`).
|
|
4
4
|
|
|
5
|
-
[](https://github.com/dawncr0w/affine-mcp-server/releases)
|
|
6
6
|
[](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
7
7
|
[](https://github.com/dawncr0w/affine-mcp-server/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
@@ -14,17 +14,18 @@ A Model Context Protocol (MCP) server that integrates with AFFiNE (self‑hosted
|
|
|
14
14
|
## Overview
|
|
15
15
|
|
|
16
16
|
- Purpose: Manage AFFiNE workspaces and documents through MCP
|
|
17
|
-
- Transport: stdio
|
|
17
|
+
- Transport: stdio (default) and optional HTTP (`/mcp`) for remote MCP deployments
|
|
18
18
|
- Auth: Token, Cookie, or Email/Password (priority order)
|
|
19
|
-
- Tools:
|
|
19
|
+
- Tools: 43 focused tools with WebSocket-based document editing
|
|
20
20
|
- Status: Active
|
|
21
21
|
|
|
22
|
-
> New in v1.
|
|
22
|
+
> New in v1.7.0: Added remote HTTP MCP support (`/mcp`) with token/CORS controls, while retaining legacy SSE compatibility (`/sse`, `/messages`) for older clients.
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
26
26
|
- Workspace: create (with initial doc), read, update, delete
|
|
27
|
-
- Documents: list/get/read/publish/revoke + create/append
|
|
27
|
+
- Documents: list/get/read/publish/revoke + create/append/replace/delete + markdown import/export + tags (WebSocket‑based)
|
|
28
|
+
- Database workflows: create database blocks, then add columns/rows via MCP tools
|
|
28
29
|
- Comments: full CRUD and resolve
|
|
29
30
|
- Version History: list
|
|
30
31
|
- Users & Tokens: current user, sign in, profile/settings, and personal access tokens
|
|
@@ -53,17 +54,95 @@ Note: From v1.2.2+ the CLI wrapper (`bin/affine-mcp`) ensures Node runs the ESM
|
|
|
53
54
|
|
|
54
55
|
## Configuration
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
### Interactive login (recommended)
|
|
58
|
+
|
|
59
|
+
The easiest way to configure credentials:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm i -g affine-mcp-server
|
|
63
|
+
affine-mcp login
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This stores credentials in `~/.config/affine-mcp/config` (mode 600). The MCP server reads them automatically — no environment variables needed.
|
|
67
|
+
|
|
68
|
+
**AFFiNE Cloud** (`app.affine.pro`): you'll be prompted to paste an API token from Settings → Integrations → MCP Server.
|
|
69
|
+
|
|
70
|
+
**Self-hosted instances**: you can choose between email/password (recommended — auto-generates an API token) or pasting a token manually.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
$ affine-mcp login
|
|
74
|
+
Affine MCP Server — Login
|
|
75
|
+
|
|
76
|
+
Affine URL [https://app.affine.pro]: https://my-affine.example.com
|
|
77
|
+
|
|
78
|
+
Auth method — [1] Email/password (recommended) [2] Paste API token: 1
|
|
79
|
+
Email: user@example.com
|
|
80
|
+
Password: ****
|
|
81
|
+
Signing in...
|
|
82
|
+
✓ Signed in as: User Name <user@example.com>
|
|
83
|
+
|
|
84
|
+
Generating API token...
|
|
85
|
+
✓ Created token: ut_abc123... (name: affine-mcp-2026-02-18)
|
|
86
|
+
|
|
87
|
+
Detecting workspaces...
|
|
88
|
+
Found 1 workspace: abc-def-123 (by User Name, 1 member, 2/10/2026)
|
|
89
|
+
Auto-selected.
|
|
90
|
+
|
|
91
|
+
✓ Saved to /home/user/.config/affine-mcp/config (mode 600)
|
|
92
|
+
The MCP server will use these credentials automatically.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Other CLI commands:
|
|
96
|
+
- `affine-mcp status` — show current config and test connection
|
|
97
|
+
- `affine-mcp logout` — remove stored credentials
|
|
98
|
+
|
|
99
|
+
### Environment variables
|
|
100
|
+
|
|
101
|
+
You can also configure via environment variables (they override the config file):
|
|
57
102
|
|
|
58
103
|
- Required: `AFFINE_BASE_URL`
|
|
59
104
|
- Auth (choose one): `AFFINE_API_TOKEN` | `AFFINE_COOKIE` | `AFFINE_EMAIL` + `AFFINE_PASSWORD`
|
|
60
|
-
- Optional: `AFFINE_GRAPHQL_PATH` (default `/graphql`), `AFFINE_WORKSPACE_ID`, `AFFINE_LOGIN_AT_START` (
|
|
105
|
+
- Optional: `AFFINE_GRAPHQL_PATH` (default `/graphql`), `AFFINE_WORKSPACE_ID`, `AFFINE_LOGIN_AT_START` (set `sync` only when you must block startup)
|
|
61
106
|
|
|
62
107
|
Authentication priority:
|
|
63
108
|
1) `AFFINE_API_TOKEN` → 2) `AFFINE_COOKIE` → 3) `AFFINE_EMAIL` + `AFFINE_PASSWORD`
|
|
64
109
|
|
|
110
|
+
> **Cloudflare note**: `AFFINE_EMAIL`/`AFFINE_PASSWORD` auth requires programmatic access to `/api/auth/sign-in`. AFFiNE Cloud (`app.affine.pro`) is behind Cloudflare, which blocks these requests. Use `AFFINE_API_TOKEN` for cloud, or use `affine-mcp login` which handles this automatically. Email/password works for self-hosted instances without Cloudflare.
|
|
111
|
+
|
|
65
112
|
## Quick Start
|
|
66
113
|
|
|
114
|
+
### Claude Code
|
|
115
|
+
|
|
116
|
+
After running `affine-mcp login`, add to your project's `.mcp.json`:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"affine": {
|
|
122
|
+
"command": "affine-mcp"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
No `env` block needed — the server reads `~/.config/affine-mcp/config` automatically.
|
|
129
|
+
|
|
130
|
+
If you prefer explicit env vars instead of the config file:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"affine": {
|
|
136
|
+
"command": "affine-mcp",
|
|
137
|
+
"env": {
|
|
138
|
+
"AFFINE_BASE_URL": "https://app.affine.pro",
|
|
139
|
+
"AFFINE_API_TOKEN": "ut_xxx"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
67
146
|
### Claude Desktop
|
|
68
147
|
|
|
69
148
|
Add to your Claude Desktop configuration:
|
|
@@ -78,10 +157,25 @@ Add to your Claude Desktop configuration:
|
|
|
78
157
|
"affine": {
|
|
79
158
|
"command": "affine-mcp",
|
|
80
159
|
"env": {
|
|
81
|
-
"AFFINE_BASE_URL": "https://
|
|
160
|
+
"AFFINE_BASE_URL": "https://app.affine.pro",
|
|
161
|
+
"AFFINE_API_TOKEN": "ut_xxx"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Or with email/password for self-hosted instances (not supported on AFFiNE Cloud — see Cloudflare note above):
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"mcpServers": {
|
|
173
|
+
"affine": {
|
|
174
|
+
"command": "affine-mcp",
|
|
175
|
+
"env": {
|
|
176
|
+
"AFFINE_BASE_URL": "https://your-self-hosted-affine.com",
|
|
82
177
|
"AFFINE_EMAIL": "you@example.com",
|
|
83
|
-
"AFFINE_PASSWORD": "secret!"
|
|
84
|
-
"AFFINE_LOGIN_AT_START": "async"
|
|
178
|
+
"AFFINE_PASSWORD": "secret!"
|
|
85
179
|
}
|
|
86
180
|
}
|
|
87
181
|
}
|
|
@@ -89,28 +183,21 @@ Add to your Claude Desktop configuration:
|
|
|
89
183
|
```
|
|
90
184
|
|
|
91
185
|
Tips
|
|
92
|
-
- Prefer `
|
|
186
|
+
- Prefer `affine-mcp login` or `AFFINE_API_TOKEN` for zero‑latency startup.
|
|
93
187
|
- If your password contains `!` (zsh history expansion), wrap it in single quotes in shells or use the JSON config above.
|
|
94
188
|
|
|
95
189
|
### Codex CLI
|
|
96
190
|
|
|
97
191
|
Register the MCP server with Codex:
|
|
98
192
|
|
|
99
|
-
-
|
|
100
|
-
- `
|
|
101
|
-
- `codex mcp add affine --env AFFINE_BASE_URL=https://your-affine-instance.com --env 'AFFINE_EMAIL=you@example.com' --env 'AFFINE_PASSWORD=secret!' --env AFFINE_LOGIN_AT_START=async -- affine-mcp`
|
|
102
|
-
|
|
103
|
-
- Use npx (no global install)
|
|
104
|
-
- `codex mcp add affine --env AFFINE_BASE_URL=https://your-affine-instance.com --env 'AFFINE_EMAIL=you@example.com' --env 'AFFINE_PASSWORD=secret!' --env AFFINE_LOGIN_AT_START=async -- npx -y -p affine-mcp-server affine-mcp`
|
|
193
|
+
- With config file (after `affine-mcp login`):
|
|
194
|
+
- `codex mcp add affine -- affine-mcp`
|
|
105
195
|
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
- Cookie: `codex mcp add affine --env AFFINE_BASE_URL=https://... --env "AFFINE_COOKIE=affine_session=...; affine_csrf=..." -- affine-mcp`
|
|
196
|
+
- With API token:
|
|
197
|
+
- `codex mcp add affine --env AFFINE_BASE_URL=https://app.affine.pro --env AFFINE_API_TOKEN=ut_xxx -- affine-mcp`
|
|
109
198
|
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
- Command: `affine-mcp`
|
|
113
|
-
- Environment: `AFFINE_BASE_URL` + one auth method (`AFFINE_API_TOKEN` | `AFFINE_COOKIE` | `AFFINE_EMAIL`/`AFFINE_PASSWORD`)
|
|
199
|
+
- With email/password (self-hosted only):
|
|
200
|
+
- `codex mcp add affine --env AFFINE_BASE_URL=https://your-self-hosted-affine.com --env 'AFFINE_EMAIL=you@example.com' --env 'AFFINE_PASSWORD=secret!' -- affine-mcp`
|
|
114
201
|
|
|
115
202
|
### Cursor
|
|
116
203
|
|
|
@@ -124,8 +211,8 @@ Project-local (`.cursor/mcp.json`) example:
|
|
|
124
211
|
"affine": {
|
|
125
212
|
"command": "affine-mcp",
|
|
126
213
|
"env": {
|
|
127
|
-
"AFFINE_BASE_URL": "https://
|
|
128
|
-
"AFFINE_API_TOKEN": "
|
|
214
|
+
"AFFINE_BASE_URL": "https://app.affine.pro",
|
|
215
|
+
"AFFINE_API_TOKEN": "ut_xxx"
|
|
129
216
|
}
|
|
130
217
|
}
|
|
131
218
|
}
|
|
@@ -141,14 +228,78 @@ If you prefer `npx`:
|
|
|
141
228
|
"command": "npx",
|
|
142
229
|
"args": ["-y", "-p", "affine-mcp-server", "affine-mcp"],
|
|
143
230
|
"env": {
|
|
144
|
-
"AFFINE_BASE_URL": "https://
|
|
145
|
-
"AFFINE_API_TOKEN": "
|
|
231
|
+
"AFFINE_BASE_URL": "https://app.affine.pro",
|
|
232
|
+
"AFFINE_API_TOKEN": "ut_xxx"
|
|
146
233
|
}
|
|
147
234
|
}
|
|
148
235
|
}
|
|
149
236
|
}
|
|
150
237
|
```
|
|
151
238
|
|
|
239
|
+
### Remote Server
|
|
240
|
+
|
|
241
|
+
If you want to host the server remotely (e.g., using Render, Railway, Docker, or a VPS) and connect via HTTP MCP (Streamable HTTP on `/mcp`) instead of local `stdio`, run the server in HTTP mode.
|
|
242
|
+
|
|
243
|
+
#### Environment variables (HTTP mode)
|
|
244
|
+
|
|
245
|
+
Required:
|
|
246
|
+
- `MCP_TRANSPORT=http`
|
|
247
|
+
- `AFFINE_BASE_URL` (example: `https://app.affine.pro`)
|
|
248
|
+
- One auth method:
|
|
249
|
+
- `AFFINE_API_TOKEN` (recommended), or `AFFINE_COOKIE`, or `AFFINE_EMAIL` + `AFFINE_PASSWORD`
|
|
250
|
+
|
|
251
|
+
Recommended for remote/public deployments:
|
|
252
|
+
- `AFFINE_MCP_HTTP_HOST=0.0.0.0`
|
|
253
|
+
- `AFFINE_MCP_HTTP_TOKEN=<strong-random-token>` (protects `/mcp`, `/sse`, `/messages`)
|
|
254
|
+
- `AFFINE_MCP_HTTP_ALLOWED_ORIGINS=<comma-separated-origins>` (for browser clients)
|
|
255
|
+
|
|
256
|
+
Optional:
|
|
257
|
+
- `PORT` (defaults to `3000`; many platforms like Render inject this automatically)
|
|
258
|
+
- `AFFINE_WORKSPACE_ID`
|
|
259
|
+
- `AFFINE_GRAPHQL_PATH` (defaults to `/graphql`)
|
|
260
|
+
- `AFFINE_MCP_HTTP_ALLOW_ALL_ORIGINS=true` (testing only)
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Export your configuration first
|
|
264
|
+
export MCP_TRANSPORT=http
|
|
265
|
+
export AFFINE_API_TOKEN="your_token..."
|
|
266
|
+
export AFFINE_MCP_HTTP_HOST="0.0.0.0" # Default: 127.0.0.1
|
|
267
|
+
export AFFINE_MCP_HTTP_TOKEN="your-super-secret-token"
|
|
268
|
+
export PORT=3000
|
|
269
|
+
|
|
270
|
+
# Start in HTTP mode (Streamable HTTP on /mcp)
|
|
271
|
+
npm run start:http
|
|
272
|
+
# OR manually:
|
|
273
|
+
# MCP_TRANSPORT=http node dist/index.js
|
|
274
|
+
# ("sse" is still accepted at /sse)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Recommended presets
|
|
278
|
+
|
|
279
|
+
Local testing (HTTP mode):
|
|
280
|
+
- `MCP_TRANSPORT=http`
|
|
281
|
+
- `AFFINE_MCP_HTTP_HOST=127.0.0.1`
|
|
282
|
+
- `AFFINE_MCP_HTTP_TOKEN=<token>` (recommended even locally)
|
|
283
|
+
- `AFFINE_MCP_HTTP_ALLOWED_ORIGINS=http://localhost:3000` (if testing from a browser app)
|
|
284
|
+
|
|
285
|
+
Docker / container runtime:
|
|
286
|
+
- `MCP_TRANSPORT=http`
|
|
287
|
+
- `AFFINE_MCP_HTTP_HOST=0.0.0.0`
|
|
288
|
+
- `PORT=3000` (or container/platform port)
|
|
289
|
+
- `AFFINE_MCP_HTTP_TOKEN=<strong-token>`
|
|
290
|
+
- `AFFINE_MCP_HTTP_ALLOWED_ORIGINS=<your app origin(s)>`
|
|
291
|
+
|
|
292
|
+
Render / Railway / VPS (public endpoint):
|
|
293
|
+
- `MCP_TRANSPORT=http`
|
|
294
|
+
- `AFFINE_MCP_HTTP_HOST=0.0.0.0`
|
|
295
|
+
- `AFFINE_MCP_HTTP_TOKEN=<strong-token>`
|
|
296
|
+
- `AFFINE_MCP_HTTP_ALLOWED_ORIGINS=<your client origin(s)>`
|
|
297
|
+
|
|
298
|
+
Endpoints currently available:
|
|
299
|
+
- `/mcp` - MCP server (Streamable HTTP)
|
|
300
|
+
- `/sse` - SSE endpoint (old protocol compatible)
|
|
301
|
+
- `/messages` - Messages endpoint (old protocol compatible)
|
|
302
|
+
|
|
152
303
|
## Available Tools
|
|
153
304
|
|
|
154
305
|
### Workspace
|
|
@@ -159,14 +310,25 @@ If you prefer `npx`:
|
|
|
159
310
|
- `delete_workspace` – delete workspace permanently
|
|
160
311
|
|
|
161
312
|
### Documents
|
|
162
|
-
- `list_docs` – list documents with pagination
|
|
313
|
+
- `list_docs` – list documents with pagination (includes `node.tags`)
|
|
314
|
+
- `list_tags` – list all tags in a workspace
|
|
315
|
+
- `list_docs_by_tag` – list documents by tag
|
|
163
316
|
- `get_doc` – get document metadata
|
|
164
317
|
- `read_doc` – read document block content and plain text snapshot (WebSocket)
|
|
318
|
+
- `export_doc_markdown` – export document content as markdown
|
|
165
319
|
- `publish_doc` – make document public
|
|
166
320
|
- `revoke_doc` – revoke public access
|
|
167
321
|
- `create_doc` – create a new document (WebSocket)
|
|
322
|
+
- `create_doc_from_markdown` – create a document from markdown content
|
|
323
|
+
- `create_tag` – create a reusable workspace-level tag
|
|
324
|
+
- `add_tag_to_doc` – attach a tag to a document
|
|
325
|
+
- `remove_tag_from_doc` – detach a tag from a document
|
|
168
326
|
- `append_paragraph` – append a paragraph block (WebSocket)
|
|
169
327
|
- `append_block` – append canonical block types (text/list/code/media/embed/database/edgeless) with strict validation and placement control (`data_view` currently falls back to database)
|
|
328
|
+
- `add_database_column` – add a column to a database block (`rich-text`, `select`, `multi-select`, `number`, `checkbox`, `link`, `date`)
|
|
329
|
+
- `add_database_row` – add a row to a database block with values mapped by column name/ID
|
|
330
|
+
- `append_markdown` – append markdown content to an existing document
|
|
331
|
+
- `replace_doc_with_markdown` – replace the main note content with markdown content
|
|
170
332
|
- `delete_doc` – delete a document (WebSocket)
|
|
171
333
|
|
|
172
334
|
### Comments
|
|
@@ -210,13 +372,17 @@ npm run pack:check
|
|
|
210
372
|
|
|
211
373
|
- `tool-manifest.json` is the source of truth for publicly exposed tool names.
|
|
212
374
|
- CI validates that `registerTool(...)` declarations match the manifest exactly.
|
|
375
|
+
- For full tool-surface verification, run `npm run test:comprehensive`.
|
|
376
|
+
- For full environment verification, run `npm run test:e2e` (Docker + MCP + Playwright).
|
|
377
|
+
- Additional focused runners: `npm run test:db-create`, `npm run test:bearer`, `npm run test:playwright`.
|
|
213
378
|
|
|
214
379
|
## Troubleshooting
|
|
215
380
|
|
|
216
381
|
Authentication
|
|
217
|
-
-
|
|
382
|
+
- **Cloudflare (403 "Just a moment...")**: AFFiNE Cloud (`app.affine.pro`) uses Cloudflare protection, which blocks programmatic sign-in via `/api/auth/sign-in`. Use `AFFINE_API_TOKEN` instead, or run `affine-mcp login` which guides you through the right method automatically. Email/password auth only works for self-hosted instances.
|
|
383
|
+
- Email/Password: only works on self-hosted instances without Cloudflare. Ensure your instance allows password auth and credentials are valid.
|
|
218
384
|
- Cookie: copy cookies (e.g., `affine_session`, `affine_csrf`) from the browser DevTools after login
|
|
219
|
-
- Token: generate a personal access token; verify it hasn't expired
|
|
385
|
+
- Token: generate a personal access token; verify it hasn't expired. Run `affine-mcp status` to test.
|
|
220
386
|
- 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.
|
|
221
387
|
|
|
222
388
|
Connection
|
|
@@ -243,6 +409,21 @@ Workspace visibility
|
|
|
243
409
|
|
|
244
410
|
## Version History
|
|
245
411
|
|
|
412
|
+
### 1.7.0 (2026‑02‑27)
|
|
413
|
+
- Added Streamable HTTP MCP support on `/mcp` for remote hosting while keeping legacy SSE compatibility paths (`/sse`, `/messages`)
|
|
414
|
+
- Added HTTP deployment controls: `AFFINE_MCP_HTTP_HOST`, `AFFINE_MCP_HTTP_TOKEN`, `AFFINE_MCP_HTTP_ALLOWED_ORIGINS`, `AFFINE_MCP_HTTP_ALLOW_ALL_ORIGINS`
|
|
415
|
+
- Added `npm run start:http` for one-command HTTP mode startup
|
|
416
|
+
- Hardened HTTP request handling with explicit 50MB parser application and case-insensitive Bearer auth parsing
|
|
417
|
+
- Expanded docs with remote deployment/security presets (Docker, Render, Railway, VPS)
|
|
418
|
+
- Verified full release checks with `npm run ci`, `npm run test:e2e`, and `npm run test:comprehensive`
|
|
419
|
+
|
|
420
|
+
### 1.6.0 (2026‑02‑24)
|
|
421
|
+
- Added 11 document workflow tools: tags (`list_tags`, `list_docs_by_tag`, `create_tag`, `add_tag_to_doc`, `remove_tag_from_doc`), markdown roundtrip (`export_doc_markdown`, `create_doc_from_markdown`, `append_markdown`, `replace_doc_with_markdown`), and database operations (`add_database_column`, `add_database_row`)
|
|
422
|
+
- Added interactive CLI commands: `affine-mcp login`, `affine-mcp status`, `affine-mcp logout`
|
|
423
|
+
- Added Docker + Playwright E2E pipeline and CI workflow for auth/database regression checks
|
|
424
|
+
- Tool surface increased from 32 to 43 canonical tools
|
|
425
|
+
- Added release test commands (`test:e2e`, `test:db-create`, `test:bearer`, `test:playwright`) and package dependencies for markdown conversion + Playwright
|
|
426
|
+
|
|
246
427
|
### 1.5.0 (2026‑02‑13)
|
|
247
428
|
- Expanded `append_block` from Step1 to Step4 profiles: canonical text/list/code/divider/callout/latex/table/bookmark/media/embed plus `database`, `data_view`, `surface_ref`, `frame`, `edgeless_text`, `note` (`data_view` currently mapped to database for stability)
|
|
248
429
|
- Added strict field validation and canonical parent enforcement for page/note/surface containers
|
|
@@ -266,7 +447,7 @@ Workspace visibility
|
|
|
266
447
|
|
|
267
448
|
### 1.2.1 (2025‑09‑17)
|
|
268
449
|
- Default to asynchronous email/password login after MCP stdio handshake
|
|
269
|
-
-
|
|
450
|
+
- `AFFINE_LOGIN_AT_START` supports `sync` when you need blocking startup (default is non-blocking)
|
|
270
451
|
- Expanded docs for Codex/Claude using npm, npx, and local clone
|
|
271
452
|
|
|
272
453
|
### 1.2.0 (2025‑09‑16)
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetch } from "undici";
|
|
2
|
+
const AUTH_FETCH_TIMEOUT_MS = 30_000;
|
|
2
3
|
function extractCookiePairs(setCookies) {
|
|
3
4
|
const pairs = [];
|
|
4
5
|
for (const sc of setCookies) {
|
|
@@ -8,16 +9,38 @@ function extractCookiePairs(setCookies) {
|
|
|
8
9
|
}
|
|
9
10
|
return pairs.join("; ");
|
|
10
11
|
}
|
|
12
|
+
/** Reject cookie values containing CR/LF to prevent header injection. */
|
|
13
|
+
function assertNoCRLF(value, label) {
|
|
14
|
+
if (/[\r\n]/.test(value)) {
|
|
15
|
+
throw new Error(`${label} contains illegal CR/LF characters`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
11
18
|
export async function loginWithPassword(baseUrl, email, password) {
|
|
12
19
|
const url = `${baseUrl.replace(/\/$/, "")}/api/auth/sign-in`;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const timer = setTimeout(() => controller.abort(), AUTH_FETCH_TIMEOUT_MS);
|
|
22
|
+
let res;
|
|
23
|
+
try {
|
|
24
|
+
res = await fetch(url, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "Content-Type": "application/json" },
|
|
27
|
+
body: JSON.stringify({ email, password }),
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err.name === "AbortError")
|
|
33
|
+
throw new Error(`Sign-in request timed out after ${AUTH_FETCH_TIMEOUT_MS / 1000}s`);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
18
39
|
if (!res.ok) {
|
|
19
|
-
const
|
|
20
|
-
|
|
40
|
+
const raw = await res.text().catch(() => "");
|
|
41
|
+
const sanitized = raw.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
|
|
42
|
+
const truncated = sanitized.length > 200 ? sanitized.slice(0, 200) + "..." : sanitized;
|
|
43
|
+
throw new Error(`Sign-in failed: ${res.status} ${truncated}`);
|
|
21
44
|
}
|
|
22
45
|
const anyHeaders = res.headers;
|
|
23
46
|
let setCookies = [];
|
|
@@ -33,5 +56,6 @@ export async function loginWithPassword(baseUrl, email, password) {
|
|
|
33
56
|
throw new Error("Sign-in succeeded but no Set-Cookie received");
|
|
34
57
|
}
|
|
35
58
|
const cookieHeader = extractCookiePairs(setCookies);
|
|
59
|
+
assertNoCRLF(cookieHeader, "Cookie header from sign-in");
|
|
36
60
|
return { cookieHeader };
|
|
37
61
|
}
|