feat: add comment support (list, get, create, update, delete) #3
@@ -27,6 +27,7 @@ A Model Context Protocol (MCP) server for [Docmost](https://docmost.com/), enabl
|
|||||||
- **`create_comment`**: Create a page-level or inline comment. Content is provided as Markdown and automatically converted to ProseMirror JSON. Supports replies via `parentCommentId`.
|
- **`create_comment`**: Create a page-level or inline comment. Content is provided as Markdown and automatically converted to ProseMirror JSON. Supports replies via `parentCommentId`.
|
||||||
- **`update_comment`**: Update an existing comment's content (creator only).
|
- **`update_comment`**: Update an existing comment's content (creator only).
|
||||||
- **`delete_comment`**: Delete a comment (creator or space admin only).
|
- **`delete_comment`**: Delete a comment (creator or space admin only).
|
||||||
|
- **`check_new_comments`**: Check for new comments across a space (or a page subtree) since a given timestamp. Efficiently filters by `updatedAt` before fetching comments.
|
||||||
|
|
||||||
### Technical Details
|
### Technical Details
|
||||||
|
|
||||||
|
|||||||
118
src/index.ts
118
src/index.ts
@@ -418,6 +418,91 @@ class DocmostClient {
|
|||||||
.post("/comments/delete", { commentId })
|
.post("/comments/delete", { commentId })
|
||||||
.then((res) => res.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for new comments across pages in a space (optionally scoped to a subtree).
|
||||||
|
* 1. Fetch all pages in space, filter by updatedAt > since
|
||||||
|
* 2. If parentPageId given, keep only descendants of that page
|
||||||
|
* 3. For matching pages, fetch comments and filter by createdAt > since
|
||||||
|
*/
|
||||||
|
async checkNewComments(
|
||||||
|
spaceId: string,
|
||||||
|
since: string,
|
||||||
|
parentPageId?: string,
|
||||||
|
) {
|
||||||
|
await this.ensureAuthenticated();
|
||||||
|
|
||||||
|
const sinceDate = new Date(since);
|
||||||
|
|
||||||
|
// 1. Get all pages in the space
|
||||||
|
const allPages = await this.paginateAll<any>("/pages/recent", { spaceId });
|
||||||
|
|
||||||
|
// 2. If parentPageId specified, build set of descendant page IDs
|
||||||
|
let allowedPageIds: Set<string> | null = null;
|
||||||
|
if (parentPageId) {
|
||||||
|
allowedPageIds = new Set<string>();
|
||||||
|
// BFS to collect all descendants
|
||||||
|
const pageMap = new Map<string, any[]>();
|
||||||
|
for (const page of allPages) {
|
||||||
|
const pid = page.parentPageId || "__root__";
|
||||||
|
if (!pageMap.has(pid)) pageMap.set(pid, []);
|
||||||
|
pageMap.get(pid)!.push(page);
|
||||||
|
}
|
||||||
|
const queue = [parentPageId];
|
||||||
|
allowedPageIds.add(parentPageId);
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const current = queue.shift()!;
|
||||||
|
const children = pageMap.get(current) || [];
|
||||||
|
for (const child of children) {
|
||||||
|
allowedPageIds.add(child.id);
|
||||||
|
queue.push(child.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Filter pages by updatedAt > since and optional subtree
|
||||||
|
const recentlyUpdated = allPages.filter((page: any) => {
|
||||||
|
if (new Date(page.updatedAt) <= sinceDate) return false;
|
||||||
|
if (allowedPageIds && !allowedPageIds.has(page.id)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Fetch comments for each updated page and filter by createdAt > since
|
||||||
|
const results: any[] = [];
|
||||||
|
for (const page of recentlyUpdated) {
|
||||||
|
try {
|
||||||
|
const comments = await this.listComments(page.id);
|
||||||
|
const newComments = comments.filter(
|
||||||
|
(c: any) => new Date(c.createdAt) > sinceDate,
|
||||||
|
);
|
||||||
|
if (newComments.length > 0) {
|
||||||
|
results.push({
|
||||||
|
pageId: page.id,
|
||||||
|
pageTitle: page.title,
|
||||||
|
comments: newComments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
// Skip pages with errors (e.g. deleted between calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalNewComments = results.reduce(
|
||||||
|
(sum, r) => sum + r.comments.length,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
since,
|
||||||
|
scope: parentPageId
|
||||||
|
? `subtree of ${parentPageId}`
|
||||||
|
: `space ${spaceId}`,
|
||||||
|
checkedPages: recentlyUpdated.length,
|
||||||
|
pagesWithNewComments: results.length,
|
||||||
|
totalNewComments,
|
||||||
|
comments: results,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const docmostClient = new DocmostClient(API_URL);
|
const docmostClient = new DocmostClient(API_URL);
|
||||||
@@ -743,6 +828,39 @@ server.registerTool(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tool: check_new_comments
|
||||||
|
server.registerTool(
|
||||||
|
"check_new_comments",
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Check for new comments across pages in a space since a given timestamp. " +
|
||||||
|
"Optionally scope to a page subtree (folder). Returns only comments created after the specified time.",
|
||||||
|
inputSchema: {
|
||||||
|
spaceId: z.string().describe("Space ID to check for new comments"),
|
||||||
|
since: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
"ISO 8601 timestamp — only return comments created after this time (e.g. '2026-03-10T00:00:00Z')",
|
||||||
|
),
|
||||||
|
parentPageId: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"Optional root page ID to scope the check to a subtree (folder). " +
|
||||||
|
"Only pages under this parent will be checked.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ spaceId, since, parentPageId }) => {
|
||||||
|
const result = await docmostClient.checkNewComments(
|
||||||
|
spaceId,
|
||||||
|
since,
|
||||||
|
parentPageId,
|
||||||
|
);
|
||||||
|
return jsonContent(result);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
await server.connect(transport);
|
||||||
|
|||||||
Reference in New Issue
Block a user