feat: add comment support (list, get, create, update, delete) #3

Closed
ITSalt wants to merge 3 commits from ITSalt/feat/comment-support into main
2 changed files with 119 additions and 0 deletions
Showing only changes of commit a8c8e0f40b - Show all commits

View File

@@ -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

View File

@@ -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);