Skip to main content
Glama

apply_hwp_text_style

Apply color, bold, italic, underline, or font size to the first occurrence of target text in a HWP document. Specify formatting options to update the character style.

Instructions

Apply formatting (color/bold/italic/underline/font_size) to the first run that contains target_text. Adds a new charPr to header.xml and retargets the matching hp:run. Color is a 6-digit hex (e.g. 'FF0000'). Args: file_path, target_text, color/bold/italic/underline/font_size (any subset), output_path (optional).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathYes
target_textYes
colorNo
boldNo
italicNo
underlineNo
font_sizeNo
output_pathNo

Implementation Reference

  • Core implementation of applyHwpxTextStyle: reads an .hwpx file, adds a new <hh:charPr> to header.xml with the requested text style (color/bold/italic/underline/fontSize), then retargets the first <hp:run> containing the target_text to use that new charPr.
    export async function applyHwpxTextStyle(
      inputPath: string,
      outputPath: string,
      targetText: string,
      style: TextStyle
    ): Promise<{ retargeted: number; charPrId: string }> {
      const bytes = await readFile(inputPath);
      const zip = await JSZip.loadAsync(bytes);
      const headerName = Object.keys(zip.files).find((n) => /^Contents\/header\.xml$/i.test(n));
      if (!headerName) throw new Error("Contents/header.xml missing — cannot edit charPr");
      let header = await zip.files[headerName].async("string");
    
      // Find the existing <hh:charPrList> ... </hh:charPrList> if present, else the
      // last <hh:charPr id="..."> element block.
      const listMatch = /<hh:charPrList>([\s\S]*?)<\/hh:charPrList>/.exec(header);
      const charPrRegex = /<hh:charPr [^>]*>[\s\S]*?<\/hh:charPr>/g;
      const list = listMatch ? listMatch[1] : header;
      const all = [...list.matchAll(charPrRegex)];
      if (all.length === 0) throw new Error("No <hh:charPr> found in header.xml");
      const baseCharPr = all[0][0];
      const baseId = baseCharPr.match(/id="(\d+)"/)?.[1] ?? "0";
      const maxId = Math.max(
        ...all.map((m) => Number(m[0].match(/id="(\d+)"/)?.[1] ?? 0))
      );
      const newId = String(maxId + 1);
    
      // Mutate baseCharPr to a new charPr with the requested style
      let mutated = baseCharPr.replace(/id="\d+"/, `id="${newId}"`);
      if (style.color) {
        if (/<hc:color [^>]*>/.test(mutated)) {
          mutated = mutated.replace(/<hc:color [^>]*\/>/, `<hc:color val="#${style.color}"/>`);
        } else {
          mutated = mutated.replace(
            /<\/hh:charPr>/,
            `<hc:color val="#${style.color}"/></hh:charPr>`
          );
        }
      }
      // bold / italic / underline are usually attributes, not children
      if (style.bold !== undefined) {
        if (/bold="[^"]*"/.test(mutated)) {
          mutated = mutated.replace(/bold="[^"]*"/, `bold="${style.bold ? "1" : "0"}"`);
        } else {
          mutated = mutated.replace(/<hh:charPr /, `<hh:charPr bold="${style.bold ? "1" : "0"}" `);
        }
      }
      if (style.italic !== undefined) {
        if (/italic="[^"]*"/.test(mutated)) {
          mutated = mutated.replace(/italic="[^"]*"/, `italic="${style.italic ? "1" : "0"}"`);
        } else {
          mutated = mutated.replace(/<hh:charPr /, `<hh:charPr italic="${style.italic ? "1" : "0"}" `);
        }
      }
      if (style.fontSize) {
        if (/height="\d+"/.test(mutated)) {
          mutated = mutated.replace(/height="\d+"/, `height="${style.fontSize}"`);
        }
      }
    
      const newHeader = header.replace(baseCharPr, baseCharPr + mutated);
      zip.file(headerName, newHeader);
    
      // Now retarget the first run carrying targetText
      const sectionName = Object.keys(zip.files).find((n) => /^Contents\/section\d+\.xml$/i.test(n))!;
      const xml = await zip.files[sectionName].async("string");
      const runRegex = new RegExp(
        `(<hp:run )(charPrIDRef="\\d+")(>[^<]*<hp:t>[^<]*${escapeRegex(
          targetText
        )}[^<]*</hp:t>)`,
        "g"
      );
      let retargeted = 0;
      const newXml = xml.replace(runRegex, (_m, openPart, _ref, rest) => {
        if (retargeted > 0) return _m;
        retargeted = 1;
        return `${openPart}charPrIDRef="${newId}"${rest}`;
      });
    
      if (retargeted === 0) {
        return { retargeted: 0, charPrId: newId };
      }
    
      zip.file(sectionName, newXml);
      if (zip.files["mimetype"]) {
        const mt = await zip.files["mimetype"].async("string");
        zip.file("mimetype", mt, { compression: "STORE" });
      }
      const out = await zip.generateAsync({
        type: "uint8array",
        compression: "DEFLATE",
        compressionOptions: { level: 6 },
      });
      await writeFile(outputPath, out);
      return { retargeted, charPrId: newId };
    }
  • Tool handler for apply_hwp_text_style: validates input via preflight(), builds a TextStyle object from args, calls applyHwpxTextStyle from hwpx-mutate, and returns a user-facing success/error message.
    export async function applyHwpTextStyle(args: ApplyStyleArgs): Promise<string> {
      const err = preflight(args.file_path);
      if (err) return err;
      const out = args.output_path && args.output_path.length > 0
        ? args.output_path
        : defaultOutput(args.file_path, "styled");
      const style: TextStyle = {};
      if (args.color) style.color = args.color.replace(/^#/, "");
      if (args.bold !== undefined) style.bold = args.bold;
      if (args.italic !== undefined) style.italic = args.italic;
      if (args.underline !== undefined) style.underline = args.underline;
      if (args.font_size !== undefined) style.fontSize = args.font_size;
      try {
        const r = await applyHwpxTextStyle(args.file_path, out, args.target_text, style);
        if (r.retargeted === 0) return `대상 텍스트를 찾지 못했습니다 (target not found): ${args.target_text}`;
        return `텍스트 서식 적용 (style applied): '${args.target_text}' → charPrId=${r.charPrId}\n저장 (saved): ${out}`;
      } catch (e) {
        return `서식 적용 오류 (style error): ${(e as Error).message}`;
      }
    }
  • ApplyStyleArgs interface defining input schema for the tool: file_path, target_text, optional color/bold/italic/underline/font_size, and output_path.
    export interface ApplyStyleArgs {
      file_path: string;
      target_text: string;
      color?: string;
      bold?: boolean;
      italic?: boolean;
      underline?: boolean;
      font_size?: number;
      output_path?: string;
    }
  • TextStyle interface used internally: color (6-digit hex), bold, italic, underline, fontSize.
    export interface TextStyle {
      color?: string;          // 6-digit hex e.g. "FF0000"
      bold?: boolean;
      italic?: boolean;
      underline?: boolean;
      fontSize?: number;       // HWP units; 1300 ≈ 13pt
    }
  • src/server.ts:433-451 (registration)
    Tool registration with name 'apply_hwp_text_style', description, and JSON inputSchema (file_path, target_text, optional color/bold/italic/underline/font_size, output_path).
    {
      name: "apply_hwp_text_style",
      description:
        "Apply formatting (color/bold/italic/underline/font_size) to the first run that contains target_text. Adds a new charPr to header.xml and retargets the matching <hp:run>. Color is a 6-digit hex (e.g. 'FF0000'). Args: file_path, target_text, color/bold/italic/underline/font_size (any subset), output_path (optional).",
      inputSchema: {
        type: "object",
        properties: {
          file_path: { type: "string" },
          target_text: { type: "string" },
          color: { type: "string" },
          bold: { type: "boolean" },
          italic: { type: "boolean" },
          underline: { type: "boolean" },
          font_size: { type: "number" },
          output_path: { type: "string" },
        },
        required: ["file_path", "target_text"],
      },
    },
  • src/server.ts:27-36 (registration)
    Import of applyHwpTextStyle from './tools/edit.js'.
      applyHwpTextStyle,
      applyHwpParagraphStyle,
      mergeHwpCellsHorizontal,
      mergeHwpCellsVertical,
      deleteHwpImage,
      setHwpFieldValue,
      setHwpParagraphText,
      setHwpCellText,
    } from "./tools/edit.js";
    import { renderHwpHtml, renderHwpEquationSvg } from "./tools/render-extra.js";
  • src/server.ts:534-534 (registration)
    Registration of apply_hwp_text_style in the HANDLERS record mapping tool names to handler functions.
    apply_hwp_text_style: applyHwpTextStyle,
Behavior4/5

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

No annotations are provided, so the description carries the full burden. It discloses that the tool modifies the first matching run, adds a charPr to header.xml, and retargets the run. This gives insight into the underlying behavior, though it could mention side effects like overwriting existing formatting.

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

Conciseness4/5

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

The description is concise with two sentences plus a brief arg list. The main purpose is front-loaded. However, the second sentence includes low-level implementation details (e.g., 'charsPr to header.xml') that may not be essential for an AI agent.

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

Completeness3/5

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

No output schema exists, but the description does not explain return values, error behavior, or what happens if output_path is omitted. It also lacks prerequisites like file existence. The description covers core functionality but leaves gaps for a complete understanding.

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?

The input schema has zero description coverage (0%), but the description lists all parameters and explains that formatting parameters are optional and color must be a 6-digit hex. This adds significant meaning beyond the raw schema, compensating for the lack of schema descriptions.

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?

The description clearly states the tool applies formatting (color, bold, italic, underline, font_size) to the first run containing target_text. It specifies the verb 'apply', the resource 'text style', and the scope (first run). This distinguishes it from siblings like apply_hwp_paragraph_style and replace_hwp_text.

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?

The description provides clear context for when to use the tool (formatting specific text in an HWP file) and specifies the color format as 6-digit hex. However, it does not explicitly mention when not to use it or suggest alternative tools.

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/treesoop/hwp-mcp'

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