Skip to main content
Glama

add_comment

Destructive

Insert a comment or threaded reply into a Word document by specifying the paragraph and anchor text for a root comment, or a parent comment ID for a reply.

Instructions

Add a comment or threaded reply to a document. Provide target_paragraph_id + anchor_text for root comments, or parent_comment_id for replies.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathYesPath to the DOCX file.
target_paragraph_idNoParagraph ID to anchor the comment to (for root comments).
anchor_textNoText within the paragraph to anchor the comment to. If omitted, anchors to entire paragraph.
parent_comment_idNoParent comment ID for threaded replies.
authorYesComment author name.
textYesComment body text.
initialsNoAuthor initials (defaults to first letter of author name).

Implementation Reference

  • MCP tool handler for add_comment. Accepts params: file_path, target_paragraph_id, anchor_text, parent_comment_id, author, text, initials. Supports two modes: (1) root comment anchored to a paragraph text range, (2) threaded reply using parent_comment_id. Resolves the session, finds the paragraph by ID, locates anchor_text range or uses full paragraph, then delegates to session.doc.addComment() or session.doc.addCommentReply().
    export async function addComment(
      manager: SessionManager,
      params: {
        file_path?: string;
        target_paragraph_id?: string;
        anchor_text?: string;
        parent_comment_id?: number;
        author: string;
        text: string;
        initials?: string;
      },
    ): Promise<ToolResponse> {
      const resolved = await resolveSessionForTool(manager, params, { toolName: 'add_comment' });
      if (!resolved.ok) return resolved.response;
      const { session, metadata } = resolved;
    
      try {
        // Reply mode: parent_comment_id provided
        if (params.parent_comment_id != null) {
          const result = await session.doc.addCommentReply({
            parentCommentId: params.parent_comment_id,
            author: params.author,
            text: params.text,
            initials: params.initials,
          });
          manager.markEdited(session);
          return ok(mergeSessionResolutionMetadata({
            comment_id: result.commentId,
            parent_comment_id: result.parentCommentId,
            mode: 'reply',
            file_path: manager.normalizePath(session.originalPath),
          }, metadata));
        }
    
        // Root comment mode: target_paragraph_id required
        if (!params.target_paragraph_id) {
          return err(
            'MISSING_PARAMETER',
            'Either target_paragraph_id (for root comments) or parent_comment_id (for replies) is required.',
            'Provide target_paragraph_id + optional anchor_text for root comments, or parent_comment_id for threaded replies.',
          );
        }
    
        const pid = params.target_paragraph_id;
        const pEl = session.doc.getParagraphElementById(pid);
        if (!pEl) {
          return err(
            'ANCHOR_NOT_FOUND',
            `Paragraph ID ${pid} not found in document`,
            'Use grep or read_file to find valid paragraph IDs.',
          );
        }
    
        let start = 0;
        let end: number;
    
        if (params.anchor_text) {
          // Find anchor_text within the paragraph
          const paraText = session.doc.getParagraphTextById(pid) ?? '';
          const match = findUniqueSubstringMatch(paraText, params.anchor_text);
    
          if (match.status === 'not_found') {
            return err(
              'TEXT_NOT_FOUND',
              `anchor_text '${params.anchor_text}' not found in paragraph ${pid}`,
              'Verify anchor_text is present in the target paragraph.',
            );
          }
          if (match.status === 'multiple') {
            return err(
              'MULTIPLE_MATCHES',
              `Found ${match.matchCount} matches for anchor_text in paragraph ${pid}`,
              'Provide more specific anchor_text for a unique match.',
            );
          }
    
          start = match.start;
          end = match.end;
        } else {
          // Anchor to entire paragraph
          const paraText = session.doc.getParagraphTextById(pid) ?? '';
          end = paraText.length;
        }
    
        const result = await session.doc.addComment({
          paragraphId: pid,
          start,
          end,
          author: params.author,
          text: params.text,
          initials: params.initials,
        });
    
        manager.markEdited(session);
        return ok(mergeSessionResolutionMetadata({
          comment_id: result.commentId,
          anchor_paragraph_id: pid,
          anchor_text: params.anchor_text ?? null,
          mode: 'root',
          file_path: manager.normalizePath(session.originalPath),
        }, metadata));
      } catch (e: unknown) {
        return err('COMMENT_ERROR', errorMessage(e));
      }
    }
  • Tool catalog registration with Zod input schema for add_comment. Defines fields: file_path (optional), target_paragraph_id (optional), anchor_text (optional), parent_comment_id (optional), author (required), text (required), initials (optional). Marked as destructiveHint: true.
    {
      name: 'add_comment',
      description:
        'Add a comment or threaded reply to a document. Provide target_paragraph_id + anchor_text for root comments, or parent_comment_id for replies.',
      input: z.object({
        ...FILE_FIELD,
        target_paragraph_id: z.string().optional().describe('Paragraph ID to anchor the comment to (for root comments).'),
        anchor_text: z.string().optional().describe('Text within the paragraph to anchor the comment to. If omitted, anchors to entire paragraph.'),
        parent_comment_id: z.number().optional().describe('Parent comment ID for threaded replies.'),
        author: z.string().describe('Comment author name.'),
        text: z.string().describe('Comment body text.'),
        initials: z.string().optional().describe('Author initials (defaults to first letter of author name).'),
      }),
      annotations: { readOnlyHint: false, destructiveHint: true },
    },
  • Import of addComment handler from tools/add_comment.js.
    import { addComment } from './tools/add_comment.js';
  • Dispatch case in server.ts that routes 'add_comment' tool calls to the addComment handler function.
    case 'add_comment':
      return await addComment(sessions, args as Parameters<typeof addComment>[1]);
  • Core addComment function in docx-core that inserts a root comment into DOCX XML. Allocates comment ID, inserts commentRangeStart/commentRangeEnd markers in document body, adds a comment element to comments.xml, and ensures the author is in people.xml. Also includes addCommentReply for threaded replies and internal helpers (addCommentElement, insertCommentMarkers, linkReplyInCommentsExtended).
    export type AddCommentParams = {
      paragraphEl: Element;
      start?: number;
      end?: number;
      author: string;
      text: string;
      initials?: string;
    };
    
    export type AddCommentResult = {
      commentId: number;
    };
    
    /**
     * Insert a root comment anchored to a text range within a paragraph.
     *
     * - Allocates next comment ID from existing comments.xml
     * - Inserts commentRangeStart/commentRangeEnd markers in document body
     * - Inserts commentReference run after range end
     * - Adds comment entry to comments.xml
     * - Adds author to people.xml if not present
     */
    export async function addComment(
      documentXml: Document,
      zip: DocxZip,
      params: AddCommentParams,
    ): Promise<AddCommentResult> {
      const { paragraphEl, author, text, initials } = params;
      const start = params.start ?? 0;
      const end = params.end ?? getParagraphText(paragraphEl).length;
      if (start > end) {
        throw new Error(`Invalid comment range: start (${start}) must be <= end (${end})`);
      }
    
      // Load comments.xml
      const commentsXml = await zip.readText('word/comments.xml');
      const commentsDoc = parseXml(commentsXml);
    
      // Allocate next comment ID
      const commentId = allocateNextCommentId(commentsDoc);
    
      // Insert range markers and reference in document body
      insertCommentMarkers(documentXml, paragraphEl, commentId, start, end);
    
      // Add comment element to comments.xml
      const paraId = generateParaId();
      addCommentElement(commentsDoc, {
        id: commentId,
        author,
        initials: initials ?? author.charAt(0).toUpperCase(),
        text,
        paraId,
      });
      zip.writeText('word/comments.xml', serializeXml(commentsDoc));
    
      // Add author to people.xml
      await ensureAuthorInPeople(zip, author);
    
      return { commentId };
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate destructiveHint=true. Description adds value by explaining the two usage paths and how parameters interrelate. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences, front-loaded with main action. Every sentence provides necessary information without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Completeness is adequate for a tool with 7 parameters and 3 required. Covers both primary use cases. No output schema, but return value is obvious. Minor missing details about how to obtain paragraph IDs, but not critical.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with good descriptions. Description adds value beyond schema by explaining the conditional logic between parameters for root vs reply comments.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it adds a comment or threaded reply to a document. Distinguishes between root comments and replies with specific parameter combinations. Differentiates from sibling tools like delete_comment or get_comments.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear guidance on when to use target_paragraph_id+anchor_text for root comments versus parent_comment_id for replies. Lacks explicit exclusion of alternatives but context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/UseJunior/safe-docx'

If you have feedback or need assistance with the MCP directory API, please join our Discord server