postmd-mcp-server 2.4.0 → 2.5.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/.github/workflows/publish.yml +28 -1
- package/README.md +27 -18
- package/package.json +4 -3
- package/scripts/smoke-test.mjs +6 -17
- package/server.json +4 -4
- package/src/server.js +79 -156
|
@@ -74,7 +74,34 @@ jobs:
|
|
|
74
74
|
run: |
|
|
75
75
|
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
76
76
|
|
|
77
|
+
# 레지스트리는 소유권을 npm 으로 확인한다. 방금 올린 판의 메타데이터를 받아 mcpName 이
|
|
78
|
+
# 서버 이름과 같은지 본다. 그런데 npm 에 올린 직후에는 그 버전이 아직 보이지 않아
|
|
79
|
+
# 곧바로 부르면 404 로 떨어진다. 2.4.0 이 실제로 이렇게 실패했다 - npm 에는 올라갔는데
|
|
80
|
+
# 레지스트리만 빠져 두 곳이 어긋났다. 보일 때까지 기다린 다음 넘어간다.
|
|
81
|
+
- name: Wait until npm serves this version
|
|
82
|
+
run: |
|
|
83
|
+
NAME=$(node -p "require('./package.json').name")
|
|
84
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
85
|
+
for i in $(seq 1 30); do
|
|
86
|
+
MCP_NAME=$(curl -fsS "https://registry.npmjs.org/$NAME/$VERSION" 2>/dev/null \
|
|
87
|
+
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{process.stdout.write(JSON.parse(s).mcpName||'')}catch(e){}})")
|
|
88
|
+
if [ -n "$MCP_NAME" ]; then
|
|
89
|
+
echo "npm serves $NAME@$VERSION with mcpName $MCP_NAME"
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
echo "attempt $i/30: not visible yet"
|
|
93
|
+
sleep 10
|
|
94
|
+
done
|
|
95
|
+
echo "::error::npm did not serve $NAME@$VERSION within 5 minutes"
|
|
96
|
+
exit 1
|
|
97
|
+
|
|
98
|
+
# 기다린 뒤에도 순간적으로 실패할 수 있다. 같은 버전을 다시 올리는 것은 문제가 없다.
|
|
77
99
|
- name: Publish to the MCP registry
|
|
78
100
|
run: |
|
|
79
101
|
./mcp-publisher login github-oidc
|
|
80
|
-
|
|
102
|
+
for i in 1 2 3; do
|
|
103
|
+
if ./mcp-publisher publish; then exit 0; fi
|
|
104
|
+
echo "publish failed ($i/3), retrying in 20s"
|
|
105
|
+
sleep 20
|
|
106
|
+
done
|
|
107
|
+
exit 1
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# PostMD MCP Server
|
|
2
2
|
|
|
3
|
-
[Model Context Protocol](https://modelcontextprotocol.io) server for **[PostMD](https://postmd.turink.com)** — publish a Markdown document, get a web page you share by link. Optional groups, document passwords
|
|
3
|
+
[Model Context Protocol](https://modelcontextprotocol.io) server for **[PostMD](https://postmd.turink.com)** — publish a Markdown document, get a web page you share by link, and add a document graph that the viewer draws beside the text: the reading order, the bodies and rules a document names, a procedure spread over its chapters. Optional groups, document passwords and viewer themes. This server wraps PostMD's public API (`/api/v1`) so assistants can publish, read, update and organize documents.
|
|
4
4
|
|
|
5
|
-
**Publishing needs no account and no key.** With zero configuration this server can already turn Markdown into a shareable page, and the hosted server at `https://postmd.turink.com/mcp` needs no install either. An API key adds management:
|
|
5
|
+
**Publishing needs no account and no key.** With zero configuration this server can already turn Markdown into a shareable page, and the hosted server at `https://postmd.turink.com/mcp` needs no install either. An API key adds management: organizing documents in groups, and notes.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**30-day retention.** Documents have a 30-day retention period (`data.retainedUntil`) that extends by 30 days whenever the document is read (at most once per day). Documents without a password can be updated or deleted by anyone; password-protected documents require the password or the owner's API key.
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Where things are written down:** [/llms.txt](https://postmd.turink.com/llms.txt) lists what the service can do and which page answers each thing; it is the place to start. [/docs/api](https://postmd.turink.com/docs/api) is the HTTP reference and [/api-docs](https://postmd.turink.com/api-docs) the machine-readable spec.
|
|
10
10
|
|
|
11
11
|
## Hosted or local
|
|
12
12
|
|
|
@@ -14,13 +14,12 @@
|
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| Address | `https://postmd.turink.com/mcp` | `npx -y postmd-mcp-server` |
|
|
16
16
|
| Needs | nothing | Node.js 20 or later |
|
|
17
|
-
| Tools | 5 | all
|
|
18
|
-
| API key | not accepted | optional, for
|
|
17
|
+
| Tools | 5 | all 20 |
|
|
18
|
+
| API key | not accepted | optional, for member-scoped tools |
|
|
19
19
|
| Clients | any, including web-only ones such as ChatGPT and claude.ai | any that can run a local process |
|
|
20
20
|
|
|
21
21
|
The hosted server has no way to receive an API key, so it carries only the tools that need
|
|
22
|
-
none.
|
|
23
|
-
local-only.
|
|
22
|
+
none. Groups, notes and the tools that read a file from your disk are local-only.
|
|
24
23
|
|
|
25
24
|
## Configuration
|
|
26
25
|
|
|
@@ -37,8 +36,7 @@ Load order: this repo's `.env` (if present) is applied via `dotenv` without over
|
|
|
37
36
|
## Tools
|
|
38
37
|
|
|
39
38
|
Every tool below works on the local server. The hosted server carries five of them:
|
|
40
|
-
`postmd_create_document`, `postmd_get_document`, `postmd_get_document_raw`, and
|
|
41
|
-
`controlToken` instead of a key — `postmd_update_document` and `postmd_delete_document`.
|
|
39
|
+
`postmd_create_document`, `postmd_get_document`, `postmd_get_document_raw`, `postmd_update_document`, and `postmd_delete_document`.
|
|
42
40
|
|
|
43
41
|
Publishing and reading — no key needed:
|
|
44
42
|
|
|
@@ -49,18 +47,29 @@ Publishing and reading — no key needed:
|
|
|
49
47
|
| `postmd_get_document` | Metadata by `docCode` |
|
|
50
48
|
| `postmd_get_document_raw` | Stored Markdown body (optional `password`) |
|
|
51
49
|
|
|
52
|
-
Managing documents — key with `documents:write
|
|
53
|
-
|
|
54
|
-
Each of the first three also accepts `controlToken` instead of a key, for a document published anonymously.
|
|
50
|
+
Managing documents — key with `documents:write` (or password / no credential for unowned documents without password):
|
|
55
51
|
|
|
56
52
|
| Tool | Purpose |
|
|
57
53
|
|------|---------|
|
|
58
|
-
| `postmd_update_document` | Replace content and/or metadata; can clear password
|
|
54
|
+
| `postmd_update_document` | Replace content and/or metadata; can clear password |
|
|
59
55
|
| `postmd_update_document_from_file` | Same, body read from a local `filePath` |
|
|
60
56
|
| `postmd_delete_document` | Delete a document (no undo) |
|
|
61
|
-
| `postmd_upload_attachment` | Upload an image/PDF, get a URL to embed in Markdown |
|
|
62
57
|
| `postmd_create_documents_from_files` | Bulk-publish several `.md` files in one call |
|
|
63
|
-
| `postmd_move_document_to_group` | Move a document into a group
|
|
58
|
+
| `postmd_move_document_to_group` | Move a document into a group |
|
|
59
|
+
|
|
60
|
+
### Graphs
|
|
61
|
+
|
|
62
|
+
A PostMD document can carry graph data that the viewer draws in a panel beside the text, showing
|
|
63
|
+
how the parts of the document relate. No tool here creates or edits a graph, because there is no
|
|
64
|
+
endpoint for one: the data sits in the Markdown as an HTML comment and travels with the body.
|
|
65
|
+
|
|
66
|
+
Adding a graph to an existing document therefore means reading it with `postmd_get_document_raw`,
|
|
67
|
+
inserting the comment, and sending the whole body back with `postmd_update_document`. Publishing a
|
|
68
|
+
new document with a graph is an ordinary `postmd_create_document` call.
|
|
69
|
+
|
|
70
|
+
The format is at <https://postmd.turink.com/docs/graph>. Working out what the nodes are and how
|
|
71
|
+
they connect requires reading the document, which is the calling agent's part; PostMD only draws
|
|
72
|
+
what it finds.
|
|
64
73
|
|
|
65
74
|
### Replacing content on a document that has notes
|
|
66
75
|
|
|
@@ -100,7 +109,7 @@ Groups — key with `groups:read` / `groups:write`:
|
|
|
100
109
|
| `postmd_list_groups` | Groups visible to the key (paged) |
|
|
101
110
|
| `postmd_list_group_documents` | Documents in a group (paged, searchable, sortable) |
|
|
102
111
|
| `postmd_create_group` | New group |
|
|
103
|
-
| `postmd_update_group` | Rename
|
|
112
|
+
| `postmd_update_group` | Rename group |
|
|
104
113
|
| `postmd_delete_group` | Delete a group (documents survive) |
|
|
105
114
|
|
|
106
115
|
For uploads: either pass the full Markdown as the `markdown` argument, or pass a local `filePath` only so this server reads the file. The path must exist on the machine running the MCP server.
|
|
@@ -179,7 +188,7 @@ export POSTMD_API_KEY=pmk_…
|
|
|
179
188
|
npm run smoke
|
|
180
189
|
```
|
|
181
190
|
|
|
182
|
-
Creates a group and a passworded document, reads it back, updates it, clears the password, then deletes both. It also publishes one document with no credential and removes it
|
|
191
|
+
Creates a group and a passworded document, reads it back, updates it, clears the password, then deletes both. It also publishes one document with no credential and removes it without a token.
|
|
183
192
|
|
|
184
193
|
## Stack
|
|
185
194
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postmd-mcp-server",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "MCP server for PostMD — publish Markdown
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"description": "MCP server for PostMD — publish Markdown as a shareable web page, with document graphs beside the text",
|
|
5
5
|
"mcpName": "io.github.reinlainer/postmd-mcp-server",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/index.js",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"mcp",
|
|
26
26
|
"postmd",
|
|
27
27
|
"markdown",
|
|
28
|
-
"publishing"
|
|
28
|
+
"publishing",
|
|
29
|
+
"document-graph"
|
|
29
30
|
],
|
|
30
31
|
"license": "MIT",
|
|
31
32
|
"dependencies": {
|
package/scripts/smoke-test.mjs
CHANGED
|
@@ -125,10 +125,9 @@ if (groupId != null) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/*
|
|
128
|
-
9. 익명
|
|
128
|
+
9. 익명 발행 (비밀번호 없는 문서).
|
|
129
129
|
|
|
130
|
-
자격 증명을 붙이지 않고 부른다.
|
|
131
|
-
여기서만 Authorization 헤더를 뺀다.
|
|
130
|
+
자격 증명을 붙이지 않고 부른다.
|
|
132
131
|
*/
|
|
133
132
|
const anonForm = new FormData();
|
|
134
133
|
anonForm.append(
|
|
@@ -141,32 +140,22 @@ const anon = await anonRes.json().catch(() => null);
|
|
|
141
140
|
check("publish without a credential", anon?.resultCode === "200", JSON.stringify(anon));
|
|
142
141
|
|
|
143
142
|
const anonCode = anon?.data?.docCode;
|
|
144
|
-
|
|
145
|
-
check("answer carries a control token", typeof controlToken === "string" && controlToken.startsWith("pmt_"));
|
|
146
|
-
check("answer carries a deletion date", typeof anon?.data?.retainedUntil === "string");
|
|
143
|
+
check("answer carries a retention date", typeof anon?.data?.retainedUntil === "string");
|
|
147
144
|
check("answer explains the terms", typeof anon?.message === "string" && anon.message.length > 0);
|
|
148
145
|
|
|
149
|
-
if (anonCode
|
|
146
|
+
if (anonCode) {
|
|
150
147
|
const titled = new FormData();
|
|
151
148
|
titled.append("title", `mcp smoke anon ${stamp}`);
|
|
152
149
|
const changed = await fetch(`${base}/api/v1/documents/${anonCode}/update`, {
|
|
153
150
|
method: "POST",
|
|
154
|
-
headers: { "X-Document-Token": controlToken },
|
|
155
151
|
body: titled,
|
|
156
152
|
});
|
|
157
|
-
check("
|
|
158
|
-
|
|
159
|
-
const refused = await fetch(`${base}/api/v1/documents/${anonCode}/delete`, {
|
|
160
|
-
method: "POST",
|
|
161
|
-
headers: { "X-Document-Token": "pmt_wrong" },
|
|
162
|
-
});
|
|
163
|
-
check("a wrong token is refused", (await refused.json().catch(() => null))?.resultCode === "E_DOC_0008");
|
|
153
|
+
check("anyone can update a document without password", (await changed.json().catch(() => null))?.resultCode === "200");
|
|
164
154
|
|
|
165
155
|
const removed = await fetch(`${base}/api/v1/documents/${anonCode}/delete`, {
|
|
166
156
|
method: "POST",
|
|
167
|
-
headers: { "X-Document-Token": controlToken },
|
|
168
157
|
});
|
|
169
|
-
check("
|
|
158
|
+
check("anyone can delete a document without password", (await removed.json().catch(() => null))?.resultCode === "200");
|
|
170
159
|
}
|
|
171
160
|
|
|
172
161
|
console.log(failures ? `\n${failures} failure(s)` : "\nall good");
|
package/server.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.reinlainer/postmd-mcp-server",
|
|
4
|
-
"description": "Publish Markdown
|
|
5
|
-
"version": "2.
|
|
4
|
+
"description": "Publish Markdown as a shareable web page, with document graphs drawn beside the text",
|
|
5
|
+
"version": "2.5.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/reinlainer/postmd-mcp-server",
|
|
8
8
|
"source": "github"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "postmd-mcp-server",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.5.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"name": "POSTMD_API_KEY",
|
|
27
|
-
"description": "Needed only for managing documents
|
|
27
|
+
"description": "Needed only for managing documents and groups. Publishing and reading work without it.",
|
|
28
28
|
"isRequired": false,
|
|
29
29
|
"isSecret": true
|
|
30
30
|
},
|
package/src/server.js
CHANGED
|
@@ -32,13 +32,11 @@ const DEFAULT_BASE_URL = "https://postmd.turink.com";
|
|
|
32
32
|
/** initialize 때 클라이언트에 전달되어, 모델이 도구를 고르기 전에 읽는다. */
|
|
33
33
|
const INSTRUCTIONS_LOCAL =
|
|
34
34
|
"PostMD publishes Markdown as web pages. Use the postmd_* tools instead of calling " +
|
|
35
|
-
"the HTTP API directly.
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"document is deleted at that instant, and the token is the only way to update or " +
|
|
41
|
-
"delete it. It is shown once, so report it to the person along with the URL.";
|
|
35
|
+
"the HTTP API directly. Documents are kept for 30 days from publication or their last read. " +
|
|
36
|
+
"Documents without a password can be updated or deleted by anyone; password-protected " +
|
|
37
|
+
"documents require the password or the owner's API key. Pass the full Markdown in " +
|
|
38
|
+
"`markdown`, or pass a local `filePath` so this server reads the file itself. A successful " +
|
|
39
|
+
"create returns data.shareUrl — hand that URL to people. What else the service can do, and which page describes it, is listed at https://postmd.turink.com/llms.txt — including graph data, which is carried inside the Markdown rather than through a tool.";
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* 원격에는 키를 건네줄 길이 없고 서버 기계에 사용자의 파일도 없다. 그래서 그 둘을 말하지
|
|
@@ -46,12 +44,11 @@ const INSTRUCTIONS_LOCAL =
|
|
|
46
44
|
*/
|
|
47
45
|
const INSTRUCTIONS_REMOTE =
|
|
48
46
|
"PostMD publishes Markdown as web pages. Use the postmd_* tools instead of calling " +
|
|
49
|
-
"the HTTP API directly. Nothing here needs an account, a sign-in or an API key.
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"person along with the URL, and pass it back as `controlToken` to update or delete.";
|
|
47
|
+
"the HTTP API directly. Nothing here needs an account, a sign-in or an API key. " +
|
|
48
|
+
"Documents are kept for 30 days from publication or their last read. Documents without a " +
|
|
49
|
+
"password can be updated or deleted by anyone. If a document has a password, provide it to " +
|
|
50
|
+
"update or delete. Pass the full Markdown in `markdown`. A successful create returns " +
|
|
51
|
+
"data.shareUrl - hand that URL to people. What else the service can do, and which page describes it, is listed at https://postmd.turink.com/llms.txt - including graph data, which is carried inside the Markdown rather than through a tool.";
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
54
|
* 원격에서 여는 도구. 자격 증명 없이 끝까지 가는 것만 남겼다 - 발행과 조회, 그리고 발행할
|
|
@@ -61,7 +58,7 @@ const INSTRUCTIONS_REMOTE =
|
|
|
61
58
|
* 실패하고, `filePath` 를 받는 도구는 그 파일이 이 서버가 도는 기계에 없다.
|
|
62
59
|
*
|
|
63
60
|
* 목록에 적은 것만 열린다. 새 도구가 생겨도 여기 이름을 적기 전에는 원격으로 나가지
|
|
64
|
-
*
|
|
61
|
+
* 않는다. 반대로 두면 키가 필요한 도구가 의도치 않게 외부에 노출된다.
|
|
65
62
|
*/
|
|
66
63
|
const REMOTE_TOOLS = new Set([
|
|
67
64
|
"postmd_create_document",
|
|
@@ -141,10 +138,8 @@ function missingKey(ctx, scopes) {
|
|
|
141
138
|
if (ctx.key) return null;
|
|
142
139
|
if (ctx.remote) {
|
|
143
140
|
return textErr(
|
|
144
|
-
"This
|
|
145
|
-
"
|
|
146
|
-
"publisher uses to change or remove it. For the key-based tools, run the local " +
|
|
147
|
-
"server: npx -y postmd-mcp-server, with POSTMD_API_KEY set."
|
|
141
|
+
"This tool requires an API key, which cannot be passed to the remote server. " +
|
|
142
|
+
"Run the local server: npx -y postmd-mcp-server, with POSTMD_API_KEY set."
|
|
148
143
|
);
|
|
149
144
|
}
|
|
150
145
|
return textErr(
|
|
@@ -219,17 +214,6 @@ async function readLocalFile(filePath) {
|
|
|
219
214
|
}
|
|
220
215
|
|
|
221
216
|
/** 첨부는 서버가 확장자로 받아 준다. Content-Type 은 예의상 맞춰 보낸다. */
|
|
222
|
-
const ATTACHMENT_MIME = {
|
|
223
|
-
png: "image/png",
|
|
224
|
-
jpg: "image/jpeg",
|
|
225
|
-
jpeg: "image/jpeg",
|
|
226
|
-
gif: "image/gif",
|
|
227
|
-
webp: "image/webp",
|
|
228
|
-
svg: "image/svg+xml",
|
|
229
|
-
bmp: "image/bmp",
|
|
230
|
-
pdf: "application/pdf",
|
|
231
|
-
};
|
|
232
|
-
|
|
233
217
|
/**
|
|
234
218
|
* 만든 문서에는 나눠 줄 주소를 붙여 준다. 에이전트의 다음 행동이 바로 그것이다.
|
|
235
219
|
*
|
|
@@ -254,7 +238,6 @@ function documentForm(a, markdownBuffer) {
|
|
|
254
238
|
}
|
|
255
239
|
if (a.title != null) form.append("title", String(a.title));
|
|
256
240
|
if (a.password != null) form.append("password", String(a.password));
|
|
257
|
-
if (a.shareEndDate != null) form.append("shareEndDate", String(a.shareEndDate));
|
|
258
241
|
if (a.viewerStyle != null) form.append("viewerStyle", String(a.viewerStyle));
|
|
259
242
|
return form;
|
|
260
243
|
}
|
|
@@ -267,10 +250,14 @@ async function createDocument(ctx, a, markdownBuffer) {
|
|
|
267
250
|
return fromEnvelope(r);
|
|
268
251
|
}
|
|
269
252
|
|
|
253
|
+
function passwordHeader(a) {
|
|
254
|
+
const pwd = a.currentPassword ?? a.password;
|
|
255
|
+
return pwd ? { "X-Document-Password": String(pwd) } : {};
|
|
256
|
+
}
|
|
257
|
+
|
|
270
258
|
async function updateDocument(ctx, a, markdownBuffer) {
|
|
271
259
|
const form = documentForm(a, markdownBuffer);
|
|
272
260
|
if (a.clearPassword === true) form.append("clearPassword", "true");
|
|
273
|
-
if (a.clearShareEndDate === true) form.append("clearShareEndDate", "true");
|
|
274
261
|
if ([...form.keys()].length === 0) {
|
|
275
262
|
return textErr("Nothing to update: pass new markdown, or at least one metadata field.");
|
|
276
263
|
}
|
|
@@ -291,18 +278,12 @@ async function updateDocument(ctx, a, markdownBuffer) {
|
|
|
291
278
|
}
|
|
292
279
|
const r = await apiFetch(ctx, `/documents/${encodeURIComponent(a.docCode)}/update`, {
|
|
293
280
|
method: "POST",
|
|
294
|
-
headers:
|
|
281
|
+
headers: passwordHeader(a),
|
|
295
282
|
body: form,
|
|
296
283
|
});
|
|
297
284
|
return fromEnvelope(r);
|
|
298
285
|
}
|
|
299
286
|
|
|
300
|
-
/**
|
|
301
|
-
* 익명 발행 문서의 제어 토큰 인자.
|
|
302
|
-
*
|
|
303
|
-
* 키를 대신하는 것이 아니라 그 문서 하나에만 듣는다. 그래서 키가 없어도 이 값이 있으면
|
|
304
|
-
* 도구를 부를 수 있고, 키가 있어도 남의 익명 문서에는 이 값이 있어야 한다.
|
|
305
|
-
*/
|
|
306
287
|
const NOTES_ON_REPLACE_PROP = {
|
|
307
288
|
type: "string",
|
|
308
289
|
enum: ["keep", "abort"],
|
|
@@ -314,35 +295,9 @@ const NOTES_ON_REPLACE_PROP = {
|
|
|
314
295
|
"anchored to its text, and the answer says how many.",
|
|
315
296
|
};
|
|
316
297
|
|
|
317
|
-
const CONTROL_TOKEN_PROP = {
|
|
318
|
-
type: "string",
|
|
319
|
-
description:
|
|
320
|
-
"Control token from an anonymous publish answer (pmt_...). Lets you act on that one " +
|
|
321
|
-
"document without an API key.",
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
/** 제어 토큰을 헤더로 옮긴다. 서버는 회원 인증 헤더와 나눠서 받는다. */
|
|
325
|
-
function tokenHeader(a) {
|
|
326
|
-
return a.controlToken ? { "X-Document-Token": String(a.controlToken) } : {};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* 키가 없어도 제어 토큰이 있으면 통과시킨다.
|
|
331
|
-
*
|
|
332
|
-
* 토큰만으로 되는 일에 키를 요구하면, 방금 익명으로 발행하고 토큰을 받은 쪽이 자기 문서를
|
|
333
|
-
* 손대지 못한다.
|
|
334
|
-
*/
|
|
335
|
-
function missingKeyUnlessToken(ctx, a, scopes) {
|
|
336
|
-
return a.controlToken ? null : missingKey(ctx, scopes);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
298
|
/** 문서 메타데이터 공통 속성. 만들기·고치기 스키마가 나눠 쓴다. */
|
|
340
299
|
const DOC_META_PROPS = {
|
|
341
300
|
password: { type: "string", description: "Readers must supply this password to see the content." },
|
|
342
|
-
shareEndDate: {
|
|
343
|
-
type: "string",
|
|
344
|
-
description: "yyyyMMdd. The document stops being served after this date. Omit for no end date.",
|
|
345
|
-
},
|
|
346
301
|
viewerStyle: {
|
|
347
302
|
type: "string",
|
|
348
303
|
description:
|
|
@@ -355,18 +310,21 @@ const TOOL_DEFS = [
|
|
|
355
310
|
name: "postmd_create_document",
|
|
356
311
|
description:
|
|
357
312
|
"Publish Markdown as a PostMD web page. No API key required — anyone can publish. " +
|
|
358
|
-
"Returns docCode and data.shareUrl; hand shareUrl to people.
|
|
359
|
-
"
|
|
360
|
-
"
|
|
361
|
-
"
|
|
362
|
-
"member
|
|
363
|
-
|
|
313
|
+
"Returns docCode and data.shareUrl; hand shareUrl to people. Documents have a " +
|
|
314
|
+
"30-day retention period (data.retainedUntil) that extends on each read. Documents " +
|
|
315
|
+
"without a password can be updated or deleted by anyone; password-protected documents " +
|
|
316
|
+
"require the password or the owner's API key. With an API key the document belongs to " +
|
|
317
|
+
"that member; groupId files it into that group (key with documents:write).",
|
|
318
|
+
annotations: { title: "Publish document", destructiveHint: false },
|
|
364
319
|
inputSchema: {
|
|
365
320
|
type: "object",
|
|
366
321
|
properties: {
|
|
367
322
|
markdown: {
|
|
368
323
|
type: "string",
|
|
369
|
-
description:
|
|
324
|
+
description:
|
|
325
|
+
"Full Markdown document as one UTF-8 string (the entire source, not a summary). " +
|
|
326
|
+
"May include graph data in an HTML comment, which the viewer draws beside the text; " +
|
|
327
|
+
"the format is at /docs/graph.",
|
|
370
328
|
},
|
|
371
329
|
title: {
|
|
372
330
|
type: "string",
|
|
@@ -383,8 +341,8 @@ const TOOL_DEFS = [
|
|
|
383
341
|
name: "postmd_create_document_from_file",
|
|
384
342
|
description:
|
|
385
343
|
"Same as postmd_create_document, but reads the Markdown from filePath on the machine " +
|
|
386
|
-
"running this MCP server — use it for large files instead of pasting the body.
|
|
387
|
-
|
|
344
|
+
"running this MCP server — use it for large files instead of pasting the body.",
|
|
345
|
+
annotations: { title: "Publish document from file", destructiveHint: false },
|
|
388
346
|
inputSchema: {
|
|
389
347
|
type: "object",
|
|
390
348
|
properties: {
|
|
@@ -406,6 +364,7 @@ const TOOL_DEFS = [
|
|
|
406
364
|
"Publish several .md files in one call (bulk upload). Requires an API key with " +
|
|
407
365
|
"documents:write. The outer resultCode is 200 even if some files failed — check " +
|
|
408
366
|
"data.succeeded and each entry in data.results.",
|
|
367
|
+
annotations: { title: "Publish documents from files", destructiveHint: false },
|
|
409
368
|
inputSchema: {
|
|
410
369
|
type: "object",
|
|
411
370
|
properties: {
|
|
@@ -423,10 +382,10 @@ const TOOL_DEFS = [
|
|
|
423
382
|
{
|
|
424
383
|
name: "postmd_get_document",
|
|
425
384
|
description:
|
|
426
|
-
"Get document metadata by docCode: title, fileName, hasPassword,
|
|
385
|
+
"Get document metadata by docCode: title, fileName, hasPassword, " +
|
|
427
386
|
"viewerStyle, timestamps. Public — no API key needed. Content is not included; " +
|
|
428
387
|
"use postmd_get_document_raw for the Markdown source.",
|
|
429
|
-
annotations: { readOnlyHint: true },
|
|
388
|
+
annotations: { title: "Get document info", readOnlyHint: true },
|
|
430
389
|
inputSchema: {
|
|
431
390
|
type: "object",
|
|
432
391
|
properties: { docCode: { type: "string", description: "Document code, e.g. P-123-456-789." } },
|
|
@@ -437,8 +396,8 @@ const TOOL_DEFS = [
|
|
|
437
396
|
name: "postmd_get_document_raw",
|
|
438
397
|
description:
|
|
439
398
|
"Get the stored Markdown source of a document. Public — no API key needed. " +
|
|
440
|
-
"Password-protected documents need `password
|
|
441
|
-
annotations: { readOnlyHint: true },
|
|
399
|
+
"Password-protected documents need `password`.",
|
|
400
|
+
annotations: { title: "Get document source", readOnlyHint: true },
|
|
442
401
|
inputSchema: {
|
|
443
402
|
type: "object",
|
|
444
403
|
properties: {
|
|
@@ -451,26 +410,31 @@ const TOOL_DEFS = [
|
|
|
451
410
|
{
|
|
452
411
|
name: "postmd_update_document",
|
|
453
412
|
description:
|
|
454
|
-
"Update a document.
|
|
455
|
-
"
|
|
456
|
-
"the stored content; any metadata field replaces that field. clearPassword
|
|
457
|
-
"
|
|
458
|
-
|
|
413
|
+
"Update a document. Password-protected documents require the password or the owner's API key; " +
|
|
414
|
+
"documents without a password can be updated by anyone. Include `markdown` to replace " +
|
|
415
|
+
"the stored content; any metadata field replaces that field. clearPassword removes the password. " +
|
|
416
|
+
"Replacing the content requires notesOnReplace.",
|
|
417
|
+
annotations: { title: "Update document", destructiveHint: true },
|
|
459
418
|
inputSchema: {
|
|
460
419
|
type: "object",
|
|
461
420
|
properties: {
|
|
462
421
|
docCode: { type: "string" },
|
|
463
422
|
markdown: {
|
|
464
423
|
type: "string",
|
|
465
|
-
description:
|
|
424
|
+
description:
|
|
425
|
+
"Full new Markdown body as one UTF-8 string. Omit if only metadata changes. " +
|
|
426
|
+
"Adding or revising a graph means sending the whole body with the graph comment in it; " +
|
|
427
|
+
"the format is at /docs/graph.",
|
|
466
428
|
},
|
|
467
429
|
title: { type: "string" },
|
|
468
430
|
fileName: { type: "string", description: "Upload filename when replacing content. Default document.md." },
|
|
469
431
|
...DOC_META_PROPS,
|
|
432
|
+
currentPassword: {
|
|
433
|
+
type: "string",
|
|
434
|
+
description: "Current password to authenticate if the document is password-protected.",
|
|
435
|
+
},
|
|
470
436
|
clearPassword: { type: "boolean", description: "true removes the password." },
|
|
471
|
-
clearShareEndDate: { type: "boolean", description: "true removes the end date, making sharing open-ended." },
|
|
472
437
|
notesOnReplace: NOTES_ON_REPLACE_PROP,
|
|
473
|
-
controlToken: CONTROL_TOKEN_PROP,
|
|
474
438
|
},
|
|
475
439
|
required: ["docCode"],
|
|
476
440
|
},
|
|
@@ -479,7 +443,8 @@ const TOOL_DEFS = [
|
|
|
479
443
|
name: "postmd_update_document_from_file",
|
|
480
444
|
description:
|
|
481
445
|
"Same as postmd_update_document, but reads the new Markdown from filePath on the " +
|
|
482
|
-
"machine running this MCP server.
|
|
446
|
+
"machine running this MCP server.",
|
|
447
|
+
annotations: { title: "Update document from file", destructiveHint: true },
|
|
483
448
|
inputSchema: {
|
|
484
449
|
type: "object",
|
|
485
450
|
properties: {
|
|
@@ -491,10 +456,12 @@ const TOOL_DEFS = [
|
|
|
491
456
|
title: { type: "string" },
|
|
492
457
|
fileName: { type: "string", description: "Upload filename. Defaults to the basename of filePath." },
|
|
493
458
|
...DOC_META_PROPS,
|
|
459
|
+
currentPassword: {
|
|
460
|
+
type: "string",
|
|
461
|
+
description: "Current password to authenticate if the document is password-protected.",
|
|
462
|
+
},
|
|
494
463
|
clearPassword: { type: "boolean", description: "true removes the password." },
|
|
495
|
-
clearShareEndDate: { type: "boolean", description: "true removes the end date, making sharing open-ended." },
|
|
496
464
|
notesOnReplace: NOTES_ON_REPLACE_PROP,
|
|
497
|
-
controlToken: CONTROL_TOKEN_PROP,
|
|
498
465
|
},
|
|
499
466
|
required: ["docCode", "filePath", "notesOnReplace"],
|
|
500
467
|
},
|
|
@@ -502,33 +469,19 @@ const TOOL_DEFS = [
|
|
|
502
469
|
{
|
|
503
470
|
name: "postmd_delete_document",
|
|
504
471
|
description:
|
|
505
|
-
"Delete a document.
|
|
506
|
-
"
|
|
507
|
-
|
|
508
|
-
"a month later.",
|
|
509
|
-
inputSchema: {
|
|
510
|
-
type: "object",
|
|
511
|
-
properties: { docCode: { type: "string" }, controlToken: CONTROL_TOKEN_PROP },
|
|
512
|
-
required: ["docCode"],
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
name: "postmd_upload_attachment",
|
|
517
|
-
description:
|
|
518
|
-
"Upload an image or PDF to reference from a document. Requires an API key with " +
|
|
519
|
-
"documents:write. Allowed types: png, jpg, jpeg, gif, webp, svg, bmp, pdf. Use the " +
|
|
520
|
-
"returned data.url as the image/link target in your Markdown, then publish the " +
|
|
521
|
-
"Markdown with postmd_create_document.",
|
|
472
|
+
"Delete a document. Password-protected documents require the password or the owner's API key; " +
|
|
473
|
+
"documents without a password can be deleted by anyone.",
|
|
474
|
+
annotations: { title: "Delete document", destructiveHint: true },
|
|
522
475
|
inputSchema: {
|
|
523
476
|
type: "object",
|
|
524
477
|
properties: {
|
|
525
|
-
|
|
478
|
+
docCode: { type: "string" },
|
|
479
|
+
password: {
|
|
526
480
|
type: "string",
|
|
527
|
-
description: "
|
|
481
|
+
description: "Document password if the document has one and you are not authenticated as its owner.",
|
|
528
482
|
},
|
|
529
|
-
fileName: { type: "string", description: "Upload filename. Defaults to the basename of filePath." },
|
|
530
483
|
},
|
|
531
|
-
required: ["
|
|
484
|
+
required: ["docCode"],
|
|
532
485
|
},
|
|
533
486
|
},
|
|
534
487
|
{
|
|
@@ -538,7 +491,7 @@ const TOOL_DEFS = [
|
|
|
538
491
|
"first. Requires an API key with documents:read. Each note carries mine and " +
|
|
539
492
|
"manageable flags — trust them instead of re-deriving permissions. Pass `password` " +
|
|
540
493
|
"for a password-protected document.",
|
|
541
|
-
annotations: { readOnlyHint: true },
|
|
494
|
+
annotations: { title: "List notes on a document", readOnlyHint: true },
|
|
542
495
|
inputSchema: {
|
|
543
496
|
type: "object",
|
|
544
497
|
properties: {
|
|
@@ -557,6 +510,7 @@ const TOOL_DEFS = [
|
|
|
557
510
|
"from ownership: on the key member's own document choose PRIVATE (only they see it) or " +
|
|
558
511
|
"SHARED; on anyone else's document every note is SHARED, so omit scope. Documents " +
|
|
559
512
|
"nobody owns — anonymous uploads and service-owned pages — take no notes at all.",
|
|
513
|
+
annotations: { title: "Add note or highlight", destructiveHint: false },
|
|
560
514
|
inputSchema: {
|
|
561
515
|
type: "object",
|
|
562
516
|
properties: {
|
|
@@ -580,6 +534,7 @@ const TOOL_DEFS = [
|
|
|
580
534
|
"Edit a note you wrote. Requires an API key with documents:write. Omitting scope " +
|
|
581
535
|
"keeps the current one; a scope you do send follows the ownership rule above. The " +
|
|
582
536
|
"note must keep text or a colour.",
|
|
537
|
+
annotations: { title: "Edit note", destructiveHint: false },
|
|
583
538
|
inputSchema: {
|
|
584
539
|
type: "object",
|
|
585
540
|
properties: {
|
|
@@ -599,6 +554,7 @@ const TOOL_DEFS = [
|
|
|
599
554
|
"Mark a note as settled, or undo it with resolved=false. Meaningful on SHARED " +
|
|
600
555
|
"notes; the author or the document owner may set it. Requires an API key with " +
|
|
601
556
|
"documents:write.",
|
|
557
|
+
annotations: { title: "Resolve or reopen note", destructiveHint: false },
|
|
602
558
|
inputSchema: {
|
|
603
559
|
type: "object",
|
|
604
560
|
properties: {
|
|
@@ -614,6 +570,7 @@ const TOOL_DEFS = [
|
|
|
614
570
|
description:
|
|
615
571
|
"Delete a note: your own, or a SHARED note on a document you own. Requires an API " +
|
|
616
572
|
"key with documents:write.",
|
|
573
|
+
annotations: { title: "Delete note", destructiveHint: true },
|
|
617
574
|
inputSchema: {
|
|
618
575
|
type: "object",
|
|
619
576
|
properties: { docCode: { type: "string" }, noteId: { type: "number" } },
|
|
@@ -625,13 +582,13 @@ const TOOL_DEFS = [
|
|
|
625
582
|
description:
|
|
626
583
|
"List every note the key's member wrote, across all documents, with docCode and " +
|
|
627
584
|
"documentTitle beside each one. Requires an API key with documents:read.",
|
|
628
|
-
annotations: { readOnlyHint: true },
|
|
585
|
+
annotations: { title: "List my notes", readOnlyHint: true },
|
|
629
586
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
630
587
|
},
|
|
631
588
|
{
|
|
632
589
|
name: "postmd_list_groups",
|
|
633
590
|
description: "List groups the key's member belongs to. Requires an API key with groups:read. Paged.",
|
|
634
|
-
annotations: { readOnlyHint: true },
|
|
591
|
+
annotations: { title: "List groups", readOnlyHint: true },
|
|
635
592
|
inputSchema: {
|
|
636
593
|
type: "object",
|
|
637
594
|
properties: {
|
|
@@ -646,13 +603,11 @@ const TOOL_DEFS = [
|
|
|
646
603
|
description:
|
|
647
604
|
"List documents in a group. Requires an API key with groups:read and documents:read. " +
|
|
648
605
|
"Paged; q searches title and file name (substring, case-insensitive).",
|
|
649
|
-
annotations: { readOnlyHint: true },
|
|
606
|
+
annotations: { title: "List documents in a group", readOnlyHint: true },
|
|
650
607
|
inputSchema: {
|
|
651
608
|
type: "object",
|
|
652
609
|
properties: {
|
|
653
610
|
groupId: { type: "number" },
|
|
654
|
-
folderId: { type: "number", description: "Only documents filed in this folder." },
|
|
655
|
-
rootOnly: { type: "boolean", description: "true → only documents not in any folder." },
|
|
656
611
|
q: { type: "string", description: "Search text for title and file name." },
|
|
657
612
|
sort: {
|
|
658
613
|
type: "string",
|
|
@@ -667,15 +622,14 @@ const TOOL_DEFS = [
|
|
|
667
622
|
{
|
|
668
623
|
name: "postmd_move_document_to_group",
|
|
669
624
|
description:
|
|
670
|
-
"Move a document you own into a group
|
|
671
|
-
"
|
|
672
|
-
|
|
625
|
+
"Move a document you own into a group. A document belongs to exactly one group, so this " +
|
|
626
|
+
"replaces its current group. Requires an API key with documents:write.",
|
|
627
|
+
annotations: { title: "Move document to a group", destructiveHint: false },
|
|
673
628
|
inputSchema: {
|
|
674
629
|
type: "object",
|
|
675
630
|
properties: {
|
|
676
631
|
docCode: { type: "string" },
|
|
677
632
|
groupId: { type: "number" },
|
|
678
|
-
folderId: { type: "number", description: "File it into this folder of that group." },
|
|
679
633
|
},
|
|
680
634
|
required: ["docCode", "groupId"],
|
|
681
635
|
},
|
|
@@ -683,13 +637,12 @@ const TOOL_DEFS = [
|
|
|
683
637
|
{
|
|
684
638
|
name: "postmd_create_group",
|
|
685
639
|
description:
|
|
686
|
-
"Create a group. Requires an API key with groups:write. Documents can then be filed "
|
|
687
|
-
|
|
640
|
+
"Create a group. Requires an API key with groups:write. Documents can then be filed into it.",
|
|
641
|
+
annotations: { title: "Create group", destructiveHint: false },
|
|
688
642
|
inputSchema: {
|
|
689
643
|
type: "object",
|
|
690
644
|
properties: {
|
|
691
645
|
name: { type: "string" },
|
|
692
|
-
expireDate: { type: "string", description: "yyyyMMdd. The group stops working after this date." },
|
|
693
646
|
},
|
|
694
647
|
required: ["name"],
|
|
695
648
|
},
|
|
@@ -697,15 +650,13 @@ const TOOL_DEFS = [
|
|
|
697
650
|
{
|
|
698
651
|
name: "postmd_update_group",
|
|
699
652
|
description:
|
|
700
|
-
"Rename a group
|
|
701
|
-
|
|
653
|
+
"Rename a group. Owner only. Requires an API key with groups:write.",
|
|
654
|
+
annotations: { title: "Update group", destructiveHint: false },
|
|
702
655
|
inputSchema: {
|
|
703
656
|
type: "object",
|
|
704
657
|
properties: {
|
|
705
658
|
groupId: { type: "number" },
|
|
706
659
|
name: { type: "string" },
|
|
707
|
-
expireDate: { type: "string", description: "yyyyMMdd." },
|
|
708
|
-
clearExpireDate: { type: "boolean", description: "true removes the expiry date." },
|
|
709
660
|
},
|
|
710
661
|
required: ["groupId"],
|
|
711
662
|
},
|
|
@@ -715,6 +666,7 @@ const TOOL_DEFS = [
|
|
|
715
666
|
description:
|
|
716
667
|
"Delete a group. Owner only; the default group cannot be deleted. Documents in it " +
|
|
717
668
|
"are not deleted. Requires an API key with groups:write.",
|
|
669
|
+
annotations: { title: "Delete group", destructiveHint: true },
|
|
718
670
|
inputSchema: {
|
|
719
671
|
type: "object",
|
|
720
672
|
properties: { groupId: { type: "number" } },
|
|
@@ -757,7 +709,6 @@ async function runTool(ctx, name, args) {
|
|
|
757
709
|
return textErr(e instanceof Error ? e.message : String(e));
|
|
758
710
|
}
|
|
759
711
|
if (a.password != null) form.append("password", String(a.password));
|
|
760
|
-
if (a.shareEndDate != null) form.append("shareEndDate", String(a.shareEndDate));
|
|
761
712
|
if (a.viewerStyle != null) form.append("viewerStyle", String(a.viewerStyle));
|
|
762
713
|
if (a.groupId != null) form.append("groupId", String(a.groupId));
|
|
763
714
|
const r = await apiFetch(ctx, "/documents/bulk", { method: "POST", body: form });
|
|
@@ -787,13 +738,9 @@ async function runTool(ctx, name, args) {
|
|
|
787
738
|
}
|
|
788
739
|
}
|
|
789
740
|
case "postmd_update_document": {
|
|
790
|
-
const denied = missingKeyUnlessToken(ctx, a, "documents:write");
|
|
791
|
-
if (denied) return denied;
|
|
792
741
|
return await updateDocument(ctx, a, a.markdown ?? null);
|
|
793
742
|
}
|
|
794
743
|
case "postmd_update_document_from_file": {
|
|
795
|
-
const denied = missingKeyUnlessToken(ctx, a, "documents:write");
|
|
796
|
-
if (denied) return denied;
|
|
797
744
|
try {
|
|
798
745
|
const { buffer, suggestedName } = await readLocalFile(a.filePath);
|
|
799
746
|
return await updateDocument(ctx, { ...a, fileName: a.fileName ?? suggestedName }, buffer);
|
|
@@ -802,30 +749,12 @@ async function runTool(ctx, name, args) {
|
|
|
802
749
|
}
|
|
803
750
|
}
|
|
804
751
|
case "postmd_delete_document": {
|
|
805
|
-
const denied = missingKeyUnlessToken(ctx, a, "documents:write");
|
|
806
|
-
if (denied) return denied;
|
|
807
752
|
const r = await apiFetch(ctx, `/documents/${encodeURIComponent(a.docCode)}/delete`, {
|
|
808
753
|
method: "POST",
|
|
809
|
-
headers:
|
|
754
|
+
headers: passwordHeader(a),
|
|
810
755
|
});
|
|
811
756
|
return fromEnvelope(r);
|
|
812
757
|
}
|
|
813
|
-
case "postmd_upload_attachment": {
|
|
814
|
-
const denied = missingKey(ctx, "documents:write");
|
|
815
|
-
if (denied) return denied;
|
|
816
|
-
try {
|
|
817
|
-
const { buffer, suggestedName } = await readLocalFile(a.filePath);
|
|
818
|
-
const fileName = a.fileName || suggestedName;
|
|
819
|
-
const ext = path.extname(fileName).slice(1).toLowerCase();
|
|
820
|
-
const mime = ATTACHMENT_MIME[ext] || "application/octet-stream";
|
|
821
|
-
const form = new FormData();
|
|
822
|
-
form.append("file", new Blob([buffer], { type: mime }), fileName);
|
|
823
|
-
const r = await apiFetch(ctx, "/documents/uploads", { method: "POST", body: form });
|
|
824
|
-
return fromEnvelope(r);
|
|
825
|
-
} catch (e) {
|
|
826
|
-
return textErr(e instanceof Error ? e.message : String(e));
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
758
|
case "postmd_list_notes": {
|
|
830
759
|
const denied = missingKey(ctx, "documents:read");
|
|
831
760
|
if (denied) return denied;
|
|
@@ -907,8 +836,6 @@ async function runTool(ctx, name, args) {
|
|
|
907
836
|
const denied = missingKey(ctx, "groups:read and documents:read");
|
|
908
837
|
if (denied) return denied;
|
|
909
838
|
const qs = query({
|
|
910
|
-
folderId: a.folderId,
|
|
911
|
-
rootOnly: a.rootOnly,
|
|
912
839
|
q: a.q,
|
|
913
840
|
sort: a.sort,
|
|
914
841
|
page: a.page,
|
|
@@ -921,7 +848,6 @@ async function runTool(ctx, name, args) {
|
|
|
921
848
|
const denied = missingKey(ctx, "documents:write");
|
|
922
849
|
if (denied) return denied;
|
|
923
850
|
const body = { groupId: a.groupId };
|
|
924
|
-
if (a.folderId != null) body.folderId = a.folderId;
|
|
925
851
|
const r = await apiFetch(ctx, `/documents/${encodeURIComponent(a.docCode)}/group`, {
|
|
926
852
|
method: "POST",
|
|
927
853
|
headers: { "Content-Type": "application/json" },
|
|
@@ -933,7 +859,6 @@ async function runTool(ctx, name, args) {
|
|
|
933
859
|
const denied = missingKey(ctx, "groups:write");
|
|
934
860
|
if (denied) return denied;
|
|
935
861
|
const body = { name: a.name };
|
|
936
|
-
if (a.expireDate != null) body.expireDate = a.expireDate;
|
|
937
862
|
const r = await apiFetch(ctx, "/groups", {
|
|
938
863
|
method: "POST",
|
|
939
864
|
headers: { "Content-Type": "application/json" },
|
|
@@ -946,8 +871,6 @@ async function runTool(ctx, name, args) {
|
|
|
946
871
|
if (denied) return denied;
|
|
947
872
|
const body = {};
|
|
948
873
|
if (a.name != null) body.name = a.name;
|
|
949
|
-
if (a.expireDate != null) body.expireDate = a.expireDate;
|
|
950
|
-
if (a.clearExpireDate === true) body.clearExpireDate = true;
|
|
951
874
|
const r = await apiFetch(ctx, `/groups/${Number(a.groupId)}/update`, {
|
|
952
875
|
method: "POST",
|
|
953
876
|
headers: { "Content-Type": "application/json" },
|