prompt(mcp): fix inaccurate and misleading tool descriptions
Audit of all 41 tool descriptions against the actual implementation found factually wrong or misleading texts: - list_comments claimed '(paginated)' — it takes only pageId and returns ALL comments in one call (internal pagination); now also states that RESOLVED threads are included and how to filter them. In-app twin synced. - search claimed the limit default is 'applied by the client' — the client deliberately omits it so the SERVER applies its default. - create_page's '(automatically moves it to the correct hierarchy)' said nothing useful — now documents parentPageId nesting semantics; move_page drops the stale 'essential for organizing pages created via create_page'. - share_page now warns the page becomes accessible to ANYONE with the URL. - get_page (both transports) now explains inline <span data-comment-id> tags are comment anchors (incl. resolved) — markup, not page text. - patch_node/delete_node/insert_node pointed only at the expensive page-JSON view for block ids — now route through the cheap page outline first. - docmost_transform marks 'Примечания переводчика' as the DEFAULT notesHeading, overridable for non-Russian pages. Checks: @docmost/mcp tests 450/450 (incl. the server-instructions guard); server ai-chat-tools spec 20/20; mcp build/ artifacts rebuilt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <span data-comment-id> 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.'),
|
||||
}),
|
||||
|
||||
@@ -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 <span data-comment-id> " +
|
||||
"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 <app>/share/<key>/p/<slugId>.",
|
||||
"URL. The URL format is <app>/share/<key>/p/<slugId>. 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: {
|
||||
|
||||
@@ -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 ' +
|
||||
|
||||
@@ -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 <span data-comment-id> " +
|
||||
"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 <app>/share/<key>/p/<slugId>.",
|
||||
"URL. The URL format is <app>/share/<key>/p/<slugId>. 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: {
|
||||
|
||||
@@ -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 ' +
|
||||
|
||||
Reference in New Issue
Block a user