postmd-mcp-server 2.2.0 → 3.0.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 CHANGED
@@ -49,6 +49,22 @@ Each of the first three also accepts `controlToken` instead of a key, for a docu
49
49
  | `postmd_create_documents_from_files` | Bulk-publish several `.md` files in one call |
50
50
  | `postmd_move_document_to_group` | Move a document into a group / folder |
51
51
 
52
+ ### Replacing content on a document that has notes
53
+
54
+ Notes are located by the text they quote, not by a stored position. Replacing a
55
+ document's body therefore moves or loses where they point: a note whose quote is
56
+ gone loses its place in the body, and one whose quote now appears elsewhere points
57
+ there instead. The note itself, including the quoted text, is kept either way.
58
+
59
+ Both update tools require `notesOnReplace` whenever new content is sent.
60
+
61
+ | Value | Effect |
62
+ |-------|--------|
63
+ | `keep` | Replaces the content and leaves the notes as they are |
64
+ | `abort` | Refuses when the document has notes anchored to its text, and says how many |
65
+
66
+ Metadata-only updates do not take it.
67
+
52
68
  Notes and highlights — key with `documents:read` / `documents:write`. A note is
53
69
  text anchored to a quoted passage; a highlight is the same object carrying only
54
70
  a colour. Visibility comes from ownership: on the key member's own document a note
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postmd-mcp-server",
3
- "version": "2.2.0",
3
+ "version": "3.0.0",
4
4
  "description": "MCP server for PostMD — publish Markdown, get a shareable web page",
5
5
  "mcpName": "io.github.reinlainer/postmd-mcp-server",
6
6
  "type": "module",
@@ -90,6 +90,22 @@ if (docCode) {
90
90
  const meta2 = await callJson(`/documents/${docCode}/meta`);
91
91
  check("password cleared", meta2.json?.data?.hasPassword === false, meta2.text);
92
92
 
93
+ // 5b. 본문을 갈아 끼울 때는 붙어 있는 메모를 어떻게 할지 골라야 한다
94
+ const bodyOnly = new FormData();
95
+ bodyOnly.append("file", new Blob([`# ${MARKER} v2\n\n다른 문장\n`], { type: "text/markdown" }), "v2.md");
96
+ const undecided = await callJson(`/documents/${docCode}/update`, { method: "POST", body: bodyOnly });
97
+ check(
98
+ "replacing content without notesOnReplace is refused",
99
+ undecided.json?.resultCode === "E_DOC_0009",
100
+ undecided.text,
101
+ );
102
+
103
+ const decided = new FormData();
104
+ decided.append("file", new Blob([`# ${MARKER} v2\n\n다른 문장\n`], { type: "text/markdown" }), "v2.md");
105
+ decided.append("notesOnReplace", "keep");
106
+ const replaced = await callJson(`/documents/${docCode}/update`, { method: "POST", body: decided });
107
+ check("replacing content with keep succeeds", replaced.json?.resultCode === "200", replaced.text);
108
+
93
109
  // 6. 그룹 목록에 보이는지
94
110
  if (groupId != null) {
95
111
  const listed = await callJson(`/groups/${groupId}/documents?q=smoke`);
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.reinlainer/postmd-mcp-server",
4
4
  "description": "Publish Markdown to PostMD and get a shareable web page",
5
- "version": "2.2.0",
5
+ "version": "3.0.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.2.0",
14
+ "version": "3.0.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
package/src/index.js CHANGED
@@ -223,6 +223,21 @@ async function updateDocument(ctx, a, markdownBuffer) {
223
223
  if ([...form.keys()].length === 0) {
224
224
  return textErr("Nothing to update: pass new markdown, or at least one metadata field.");
225
225
  }
226
+ /*
227
+ Replacing the content needs a decision about the notes anchored to it. Checked here so the
228
+ caller reads it as a missing argument rather than an HTTP error from the server.
229
+ */
230
+ if (markdownBuffer !== null) {
231
+ if (a.notesOnReplace !== "keep" && a.notesOnReplace !== "abort") {
232
+ return textErr(
233
+ "notesOnReplace is required when replacing the content: keep or abort. " +
234
+ "Notes are located by the text they quote, so replacing the body moves or loses " +
235
+ "where they point. keep replaces anyway; abort refuses when the document has notes " +
236
+ "anchored to its text.",
237
+ );
238
+ }
239
+ form.append("notesOnReplace", a.notesOnReplace);
240
+ }
226
241
  const r = await apiFetch(ctx, `/documents/${encodeURIComponent(a.docCode)}/update`, {
227
242
  method: "POST",
228
243
  headers: tokenHeader(a),
@@ -237,6 +252,17 @@ async function updateDocument(ctx, a, markdownBuffer) {
237
252
  * 키를 대신하는 것이 아니라 그 문서 하나에만 듣는다. 그래서 키가 없어도 이 값이 있으면
238
253
  * 도구를 부를 수 있고, 키가 있어도 남의 익명 문서에는 이 값이 있어야 한다.
239
254
  */
255
+ const NOTES_ON_REPLACE_PROP = {
256
+ type: "string",
257
+ enum: ["keep", "abort"],
258
+ description:
259
+ "Required when replacing the content. Notes are located by the text they quote, so " +
260
+ "replacing the body moves or loses where they point: a note whose quote is gone loses " +
261
+ "its place in the body, and one whose quote now appears elsewhere points there. " +
262
+ "keep replaces anyway and leaves the notes. abort refuses when the document has notes " +
263
+ "anchored to its text, and the answer says how many.",
264
+ };
265
+
240
266
  const CONTROL_TOKEN_PROP = {
241
267
  type: "string",
242
268
  description:
@@ -378,7 +404,7 @@ const TOOL_DEFS = [
378
404
  "or `controlToken` for an anonymously published one. Include `markdown` to replace " +
379
405
  "the stored content; any metadata field replaces that field. clearPassword / " +
380
406
  "clearShareEndDate remove the password / end date. Updating does not push back the " +
381
- "deletion date of an anonymous document.",
407
+ "deletion date of an anonymous document. Replacing the content requires notesOnReplace.",
382
408
  inputSchema: {
383
409
  type: "object",
384
410
  properties: {
@@ -392,6 +418,7 @@ const TOOL_DEFS = [
392
418
  ...DOC_META_PROPS,
393
419
  clearPassword: { type: "boolean", description: "true removes the password." },
394
420
  clearShareEndDate: { type: "boolean", description: "true removes the end date, making sharing open-ended." },
421
+ notesOnReplace: NOTES_ON_REPLACE_PROP,
395
422
  controlToken: CONTROL_TOKEN_PROP,
396
423
  },
397
424
  required: ["docCode"],
@@ -415,9 +442,10 @@ const TOOL_DEFS = [
415
442
  ...DOC_META_PROPS,
416
443
  clearPassword: { type: "boolean", description: "true removes the password." },
417
444
  clearShareEndDate: { type: "boolean", description: "true removes the end date, making sharing open-ended." },
445
+ notesOnReplace: NOTES_ON_REPLACE_PROP,
418
446
  controlToken: CONTROL_TOKEN_PROP,
419
447
  },
420
- required: ["docCode", "filePath"],
448
+ required: ["docCode", "filePath", "notesOnReplace"],
421
449
  },
422
450
  },
423
451
  {