diff --git a/apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts b/apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts index 8444aebc..9abd1c50 100644 --- a/apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts +++ b/apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts @@ -303,7 +303,9 @@ export class AiChatToolsService { getPage: tool({ description: 'Fetch a single page as Markdown by its page id. Returns the page ' + - 'title and its Markdown content.', + 'title and its Markdown content. Inline tags ' + + 'in the markdown are comment highlight anchors (also present for ' + + 'RESOLVED threads) — treat them as markup, not page text.', inputSchema: modelFriendlyInput({ pageId: z.string().describe('The id (or slugId) of the page.'), }), @@ -647,7 +649,9 @@ export class AiChatToolsService { listComments: tool({ description: - 'List all comments on a page (content as Markdown).', + 'List ALL comments on a page in one call, including RESOLVED ' + + 'threads — filter by resolvedAt when you need only open ones. ' + + 'Content is returned as Markdown.', inputSchema: modelFriendlyInput({ pageId: z.string().describe('The id of the page.'), }), diff --git a/packages/mcp/build/index.js b/packages/mcp/build/index.js index aca8fc64..a573231a 100644 --- a/packages/mcp/build/index.js +++ b/packages/mcp/build/index.js @@ -116,7 +116,9 @@ export function createDocmostMcpServer(config) { server.registerTool("get_page", { description: "Get page details with content converted to Markdown. The conversion is " + "LOSSY (block ids, exact table/callout structure are approximated); for a " + - "lossless representation use get_page_json.", + "lossless representation use get_page_json. Inline " + + "tags in the markdown are comment highlight anchors (also present for " + + "RESOLVED threads) — treat them as markup, not page text.", inputSchema: { pageId: z.string().min(1), }, @@ -213,7 +215,8 @@ export function createDocmostMcpServer(config) { }); // Tool: create_page server.registerTool("create_page", { - description: "Create a new page with content (automatically moves it to the correct hierarchy).", + description: "Create a new page from Markdown in a space. Pass parentPageId to nest " + + "it under a parent; omit it to create at the space root.", inputSchema: { title: z.string().min(1).describe("Title of the page"), content: z.string().min(1).describe("Markdown content"), @@ -435,7 +438,8 @@ export function createDocmostMcpServer(config) { // agent; this transport keeps the plain public-URL wording. server.registerTool("share_page", { description: "Make a page publicly accessible (idempotent) and return its public " + - "URL. The URL format is /share//p/.", + "URL. The URL format is /share//p/. This exposes the " + + "page content to ANYONE with the URL — do it only when explicitly asked.", inputSchema: { pageId: z.string().min(1).describe("ID of the page to share"), searchIndexing: z @@ -459,7 +463,7 @@ export function createDocmostMcpServer(config) { }); // Tool: move_page server.registerTool("move_page", { - description: "Move a page to a new parent (nesting) or root. Essential for organizing pages created via 'create_page'.", + description: "Move a page under a new parent (nesting) or to the space root.", inputSchema: { pageId: z.string().min(1), parentPageId: z @@ -511,7 +515,9 @@ export function createDocmostMcpServer(config) { // --- Comment tools (ported from upstream PR #3 by Max Nikitin) --- // Tool: list_comments server.registerTool("list_comments", { - description: "List all comments on a page (paginated). Content is returned as Markdown.", + description: "List ALL comments on a page in one call (pagination is handled " + + "internally), including RESOLVED threads — filter by resolvedAt when you " + + "need only open ones. Content is returned as Markdown.", inputSchema: { pageId: z.string().describe("ID of the page"), }, @@ -662,8 +668,9 @@ export function createDocmostMcpServer(config) { // different schema (limit 1-20); this transport is a plain REST full-text search // (limit up to 100). Different behaviour AND schema, so kept per-layer. server.registerTool("search", { - description: "Search for pages and content. Results are bounded by `limit` " + - "(default applied by the client, max 100).", + description: "Full-text search for pages and content across the whole workspace. " + + "Results are bounded by `limit` (1-100; when omitted the server applies " + + "its own default).", inputSchema: { query: z.string().min(1).describe("Search query"), limit: z @@ -713,7 +720,9 @@ export function createDocmostMcpServer(config) { "insertInlineFootnote(doc, {anchorText, text}) (author-inline footnote: " + "marker + dedup'd definition, list derived). Footnote convention: markers are " + "plain '[N]' text in the body; the notes are an orderedList under a " + - "heading whose text is 'Примечания переводчика'. The transform runs " + + "heading whose text is 'Примечания переводчика' (that is only the DEFAULT " + + "notesHeading — pass the notesHeading option to the helpers to use a " + + "heading matching the page's language). The transform runs " + "sandboxed (no require/process/fs/network, 5s timeout) and must return a " + "{type:'doc'} node.", inputSchema: { diff --git a/packages/mcp/build/tool-specs.js b/packages/mcp/build/tool-specs.js index f47e6149..f73d1d15 100644 --- a/packages/mcp/build/tool-specs.js +++ b/packages/mcp/build/tool-specs.js @@ -78,8 +78,8 @@ export const SHARED_TOOL_SPECS = { deleteNode: { mcpName: 'delete_node', inAppKey: 'deleteNode', - description: 'Remove a single block by its attrs.id (from the page-JSON view) WITHOUT ' + - 'resending the whole document.', + description: 'Remove a single block by its attrs.id (from the page outline or ' + + 'page-JSON view) WITHOUT resending the whole document.', buildShape: (z) => ({ pageId: z.string().min(1), nodeId: z.string().min(1), @@ -99,7 +99,8 @@ export const SHARED_TOOL_SPECS = { inAppKey: 'patchNode', description: 'Replace a single content block identified by its attrs.id with a new ' + 'ProseMirror node, WITHOUT resending the whole document; the replacement ' + - 'keeps the same node id. Get the block id from the page-JSON view, then ' + + 'keeps the same node id. Get the block id from the page outline (cheap) ' + + 'or the page-JSON view, then ' + 'pass a ProseMirror node to put in its place. Example node: a paragraph ' + '{"type":"paragraph","content":[{"type":"text","text":"Hello"}]} or a ' + 'heading {"type":"heading","attrs":{"level":2},"content":' + @@ -128,7 +129,8 @@ export const SHARED_TOOL_SPECS = { description: 'Insert a block before/after another block (by attrs.id or anchor text) ' + 'or append it at the end (top level). For before/after you MUST provide ' + 'EXACTLY ONE of anchorNodeId or anchorText. Get anchor block ids from the ' + - 'page-JSON view. Avoids resending the whole document. Can also insert ' + + 'page outline or the page-JSON view. Avoids resending the whole document. ' + + 'Can also insert ' + 'table structure: to add a tableRow, pass a tableRow node with position ' + 'before/after and anchor INSIDE the target table — anchorNodeId of any ' + 'block/cell in it, or anchorText matching the table; to add a ' + diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index 22fab8b2..6bbcea7c 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -156,7 +156,9 @@ server.registerTool( description: "Get page details with content converted to Markdown. The conversion is " + "LOSSY (block ids, exact table/callout structure are approximated); for a " + - "lossless representation use get_page_json.", + "lossless representation use get_page_json. Inline " + + "tags in the markdown are comment highlight anchors (also present for " + + "RESOLVED threads) — treat them as markup, not page text.", inputSchema: { pageId: z.string().min(1), }, @@ -297,7 +299,8 @@ server.registerTool( "create_page", { description: - "Create a new page with content (automatically moves it to the correct hierarchy).", + "Create a new page from Markdown in a space. Pass parentPageId to nest " + + "it under a parent; omit it to create at the space root.", inputSchema: { title: z.string().min(1).describe("Title of the page"), content: z.string().min(1).describe("Markdown content"), @@ -596,7 +599,8 @@ server.registerTool( { description: "Make a page publicly accessible (idempotent) and return its public " + - "URL. The URL format is /share//p/.", + "URL. The URL format is /share//p/. This exposes the " + + "page content to ANYONE with the URL — do it only when explicitly asked.", inputSchema: { pageId: z.string().min(1).describe("ID of the page to share"), searchIndexing: z @@ -628,7 +632,7 @@ server.registerTool( "move_page", { description: - "Move a page to a new parent (nesting) or root. Essential for organizing pages created via 'create_page'.", + "Move a page under a new parent (nesting) or to the space root.", inputSchema: { pageId: z.string().min(1), parentPageId: z @@ -708,7 +712,9 @@ server.registerTool( "list_comments", { description: - "List all comments on a page (paginated). Content is returned as Markdown.", + "List ALL comments on a page in one call (pagination is handled " + + "internally), including RESOLVED threads — filter by resolvedAt when you " + + "need only open ones. Content is returned as Markdown.", inputSchema: { pageId: z.string().describe("ID of the page"), }, @@ -924,8 +930,9 @@ server.registerTool( "search", { description: - "Search for pages and content. Results are bounded by `limit` " + - "(default applied by the client, max 100).", + "Full-text search for pages and content across the whole workspace. " + + "Results are bounded by `limit` (1-100; when omitted the server applies " + + "its own default).", inputSchema: { query: z.string().min(1).describe("Search query"), limit: z @@ -981,7 +988,9 @@ server.registerTool( "insertInlineFootnote(doc, {anchorText, text}) (author-inline footnote: " + "marker + dedup'd definition, list derived). Footnote convention: markers are " + "plain '[N]' text in the body; the notes are an orderedList under a " + - "heading whose text is 'Примечания переводчика'. The transform runs " + + "heading whose text is 'Примечания переводчика' (that is only the DEFAULT " + + "notesHeading — pass the notesHeading option to the helpers to use a " + + "heading matching the page's language). The transform runs " + "sandboxed (no require/process/fs/network, 5s timeout) and must return a " + "{type:'doc'} node.", inputSchema: { diff --git a/packages/mcp/src/tool-specs.ts b/packages/mcp/src/tool-specs.ts index 30efd3b8..c6dbd7da 100644 --- a/packages/mcp/src/tool-specs.ts +++ b/packages/mcp/src/tool-specs.ts @@ -116,8 +116,8 @@ export const SHARED_TOOL_SPECS = { mcpName: 'delete_node', inAppKey: 'deleteNode', description: - 'Remove a single block by its attrs.id (from the page-JSON view) WITHOUT ' + - 'resending the whole document.', + 'Remove a single block by its attrs.id (from the page outline or ' + + 'page-JSON view) WITHOUT resending the whole document.', buildShape: (z) => ({ pageId: z.string().min(1), nodeId: z.string().min(1), @@ -139,7 +139,8 @@ export const SHARED_TOOL_SPECS = { description: 'Replace a single content block identified by its attrs.id with a new ' + 'ProseMirror node, WITHOUT resending the whole document; the replacement ' + - 'keeps the same node id. Get the block id from the page-JSON view, then ' + + 'keeps the same node id. Get the block id from the page outline (cheap) ' + + 'or the page-JSON view, then ' + 'pass a ProseMirror node to put in its place. Example node: a paragraph ' + '{"type":"paragraph","content":[{"type":"text","text":"Hello"}]} or a ' + 'heading {"type":"heading","attrs":{"level":2},"content":' + @@ -174,7 +175,8 @@ export const SHARED_TOOL_SPECS = { 'Insert a block before/after another block (by attrs.id or anchor text) ' + 'or append it at the end (top level). For before/after you MUST provide ' + 'EXACTLY ONE of anchorNodeId or anchorText. Get anchor block ids from the ' + - 'page-JSON view. Avoids resending the whole document. Can also insert ' + + 'page outline or the page-JSON view. Avoids resending the whole document. ' + + 'Can also insert ' + 'table structure: to add a tableRow, pass a tableRow node with position ' + 'before/after and anchor INSIDE the target table — anchorNodeId of any ' + 'block/cell in it, or anchorText matching the table; to add a ' +