Skip to main content
Glama

create-watermark

Add watermarks or logos to images by specifying URLs, size, position, and opacity, then get a URL or save the watermarked image to a file.

Instructions

Add watermarks/logos to images using QuickChart - get watermarked image URL or save watermarked image to file

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesWhether to get watermarked image URL or save as file
outputPathNoPath where to save the file (only used with action=save_file)
mainImageUrlYesURL of the main image to watermark
markImageUrlYesURL of the watermark/logo image
opacityNoWatermark opacity (0.0 to 1.0)
imageWidthNoMain image width in pixels
imageHeightNoMain image height in pixels
markWidthNoWatermark width in pixels
markHeightNoWatermark height in pixels
markRatioNoWatermark size ratio relative to main image
positionNoWatermark position
positionXNoCustom X position in pixels
positionYNoCustom Y position in pixels
marginNoMargin from edges in pixels

Implementation Reference

  • The handler function that validates inputs, constructs the watermark configuration for QuickChart API, fetches the watermarked image, generates URL and base64, and optionally saves to file.
    export async function handleWatermarkTool(args: any): Promise<any> {
      const mainImageUrl = args.mainImageUrl as string;
      const markImageUrl = args.markImageUrl as string;
      const action = args.action as string;
      
      validateMainImageUrl(mainImageUrl);
      validateMarkImageUrl(markImageUrl);
      validateAction(action);
      validateOutputPath(args.outputPath, action);
      validateOpacity(args.opacity);
      validateDimensions(args.imageWidth, args.imageHeight);
      validateDimensions(args.markWidth, args.markHeight);
      validatePosition(args.position);
    
      const config = buildWatermarkConfig(mainImageUrl, markImageUrl, {
        opacity: args.opacity as number,
        imageWidth: args.imageWidth as number,
        imageHeight: args.imageHeight as number,
        markWidth: args.markWidth as number,
        markHeight: args.markHeight as number,
        markRatio: args.markRatio as number,
        position: args.position as string,
        positionX: args.positionX as number,
        positionY: args.positionY as number,
        margin: args.margin as number,
      });
      const watermarkUrl = buildWatermarkUrl(mainImageUrl, markImageUrl);
    
      const result: any = {
        content: [
          {
            type: "text",
            text: "Below is the watermarked image URL:",
          },
          {
            type: "text",
            text: watermarkUrl,
          },
        ],
        metadata: {
          watermarkType: "image",
          generatedAt: new Date().toISOString(),
          watermarkUrl: watermarkUrl,
        },
      };
    
      let pngData: any = null;
      try {
        pngData = await fetchWatermarkContent(config);
        const pngBase64 = Buffer.from(pngData).toString("base64");
    
        result.content.push(
          {
            type: "text",
            text: "Below is the PNG image:",
          },
          {
            type: "image",
            data: pngBase64,
            mimeType: "image/png",
          }
        );
        result.metadata.pngBase64 = pngBase64;
      } catch (error) {
        result.content.unshift({
          type: "text",
          text: "⚠️ Failed to fetch watermarked image",
        });
        result.content.push({
          type: "text",
          text: `Error: ${error instanceof Error ? error.message : String(error)}`,
        });
        result.metadata.error =
          error instanceof Error ? error.message : String(error);
      }
    
      if (action === "get_url") {
        return result;
      }
    
      const outputPath = getDownloadPath(
        args.outputPath as string | undefined,
        "png"
      );
    
      try {
        const dir = path.dirname(outputPath);
        if (!fs.existsSync(dir)) {
          fs.mkdirSync(dir, { recursive: true });
        }
    
        // If pngData is null, fetch it again for file saving
        const dataToSave = pngData || await fetchWatermarkContent(config);
        fs.writeFileSync(outputPath, dataToSave);
    
        result.metadata.savedPath = outputPath;
        result.content.push({
          type: "text",
          text: "Below is the saved file path:",
        });
        result.content.push({
          type: "text",
          text: outputPath,
        });
        return result;
      } catch (error) {
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to save watermarked image: ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    }
  • Tool definition including name, description, and detailed inputSchema with properties for watermark parameters, positions, sizes, and actions.
    export const CREATE_WATERMARK_TOOL: Tool = {
      name: "create-watermark",
      description:
        "Add watermarks/logos to images using QuickChart - get watermarked image URL or save watermarked image to file",
      inputSchema: {
        type: "object",
        properties: {
          action: {
            type: "string",
            enum: ["get_url", "save_file"],
            description: "Whether to get watermarked image URL or save as file",
          },
          outputPath: {
            type: "string",
            description:
              "Path where to save the file (only used with action=save_file)",
          },
          mainImageUrl: {
            type: "string",
            description: "URL of the main image to watermark",
          },
          markImageUrl: {
            type: "string",
            description: "URL of the watermark/logo image",
          },
          opacity: {
            type: "number",
            minimum: 0,
            maximum: 1,
            description: "Watermark opacity (0.0 to 1.0)",
          },
          imageWidth: {
            type: "integer",
            description: "Main image width in pixels",
          },
          imageHeight: {
            type: "integer",
            description: "Main image height in pixels",
          },
          markWidth: {
            type: "integer",
            description: "Watermark width in pixels",
          },
          markHeight: {
            type: "integer",
            description: "Watermark height in pixels",
          },
          markRatio: {
            type: "number",
            description: "Watermark size ratio relative to main image",
          },
          position: {
            type: "string",
            enum: [
              "center",
              "topLeft",
              "topMiddle",
              "topRight",
              "middleLeft",
              "middleRight",
              "bottomLeft",
              "bottomMiddle",
              "bottomRight",
            ],
            description: "Watermark position",
          },
          positionX: {
            type: "integer",
            description: "Custom X position in pixels",
          },
          positionY: {
            type: "integer",
            description: "Custom Y position in pixels",
          },
          margin: {
            type: "integer",
            description: "Margin from edges in pixels",
          },
        },
        required: ["action", "mainImageUrl", "markImageUrl"],
      },
    };
  • Registers the 'create-watermark' tool by mapping its name to the handleWatermarkTool handler in the ALL_TOOL_HANDLERS object, which is used to create filtered TOOL_HANDLERS.
    "create-watermark": {
      handler: handleWatermarkTool,
      toolName: ToolNames.WATERMARK,
    },
  • Imports the CREATE_WATERMARK_TOOL definition and handleWatermarkTool handler from watermark.ts
    import { CREATE_WATERMARK_TOOL, handleWatermarkTool } from "./watermark.js";
  • Adds the CREATE_WATERMARK_TOOL to the ALL_TOOLS array, which is filtered to create the exported TOOLS array.
    { tool: CREATE_WATERMARK_TOOL, name: ToolNames.WATERMARK },
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. While it mentions the two possible actions (get URL or save file), it doesn't describe important behavioral aspects like: whether this is a read-only or destructive operation, what permissions or authentication might be required, rate limits, error conditions, or what happens when saving files (overwrites? creates directories?). For a tool with 14 parameters and no annotation coverage, this is a significant gap.

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?

The description is perfectly concise - a single sentence that efficiently communicates the tool's purpose, the service it uses, and the two primary outcomes. Every word earns its place with zero wasted text, and the information is front-loaded appropriately.

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

Completeness2/5

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

Given the complexity (14 parameters, no annotations, no output schema), the description is insufficiently complete. It doesn't explain what the tool returns (URL format? file path? success indicator?), doesn't mention error handling, and provides minimal behavioral context. For a tool with this many configuration options and no structured output documentation, the description should do more to help the agent understand how to use it effectively.

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

Parameters3/5

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

The schema description coverage is 100%, so all parameters are documented in the schema itself. The description doesn't add any parameter-specific information beyond what's already in the schema descriptions. According to the scoring rules, when schema coverage is high (>80%), the baseline is 3 even with no param info in the description.

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 specific action ('Add watermarks/logos to images'), the tool/service used ('using QuickChart'), and the two possible outcomes ('get watermarked image URL or save watermarked image to file'). It distinguishes itself from sibling tools which are all about creating various visualizations (charts, barcodes, etc.) rather than modifying existing images with watermarks.

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 about when to use this tool (for watermarking images) and implies the two usage scenarios (URL generation vs file saving). However, it doesn't explicitly state when NOT to use it or mention alternatives for similar functionality, which prevents a perfect score.

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/TakanariShimbo/quickchart-mcp-server'

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