Skip to main content
Glama

render_document

Merge JSON data with a Carbone template to generate documents in multiple formats, with support for translation, currency conversion, batch generation, and asynchronous rendering.

Instructions

Generate a document by merging a Carbone template with JSON data. Two modes: (1) pass templateId to use a previously uploaded template; (2) pass template (file path, URL, or base64) to upload and render in a single request without storing a template. Supports output format conversion, multilingual rendering, currency conversion, batch generation, and advanced PDF options (watermark, password, PDF/A). Async mode: pass webhookUrl to render asynchronously — Carbone will POST the renderId to your URL when the document is ready. Async mode is required when using batch generation (batchSplitBy).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
templateIdNoThe ID of a previously uploaded template to render. Two ID formats are accepted: (1) Template ID (64-bit) — stable identifier shared across versions; Carbone automatically uses the deployed version. (2) Version ID (SHA-256) — pins rendering to a specific version regardless of deployment status. Both are returned by upload_template. Mutually exclusive with template — provide exactly one, never both.
templateNoInline template for one-shot render without storing a template first. Accepts a local file path (e.g. /home/user/invoice.docx), a URL (https://example.com/template.docx), or a base64-encoded string. The template is uploaded and rendered in a single API request — no Template ID is returned. Use this for ephemeral renders; use upload_template + templateId when you need to reuse the template. Supported formats: DOCX, XLSX, PPTX, ODT, ODS, ODP, ODG, HTML, XHTML, IDML, XML, Markdown (MD), PDF, and more. Mutually exclusive with templateId — provide exactly one, never both.
dataYesJSON data merged into the template. Access fields with {d.fieldName} tags. Nested objects: {d.customer.name}. Array loops: {d.items[i].description} … {d.items[i+1]}. Conditionals: {d.status == "active" ? "Yes" : "No"}. For pure document conversion without data injection, pass {}.
convertToNoOutput format. If omitted, the output matches the template format. Documents : "pdf", "docx", "xlsx", "pptx", "odt", "ods", "odp", "odg", "rtf", "epub". Web/text : "html", "xhtml", "txt", "csv", "md", "xml", "idml". Images : "png", "jpg", "jpeg", "webp", "svg", "tiff", "bmp", "gif". Archive : "zip" (use with batchSplitBy for batch output). Simple usage: "pdf". Advanced usage: { "formatName": "pdf", "formatOptions": { ... } } for PDF-specific options.
converterNoConverter engine. Only relevant when convertTo is "pdf" (or an image rasterised from a document). "L" — LibreOffice (default): best all-round engine for DOCX, XLSX, PPTX, ODT, ODS, ODP. "O" — OnlyOffice: highest fidelity for Microsoft Office formats (DOCX, XLSX, PPTX). "C" — Chromium: best for HTML/CSS/JS templates — full browser rendering. If omitted, LibreOffice is used by default.
timezoneNoIANA timezone used to convert dates in the rendered document. Default: "Europe/Paris". Applied when templates use the :formatD formatter, e.g. {d.date:formatD(YYYY-MM-DD HH:mm)}. Common values: "UTC", "America/New_York", "America/Los_Angeles", "Europe/London", "Europe/Paris", "Europe/Berlin", "Asia/Tokyo", "Asia/Shanghai", "Australia/Sydney". Full list (TZ identifier column): https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
langNoLocale of the generated document. Affects three things: (1) {t(key)} translation tags — selects the matching translation from the translations map. (2) :formatN number formatter — applies locale-specific thousand/decimal separators. (3) :formatC currency formatter — applies locale-specific currency symbols and formatting. Format: BCP-47 lowercase, e.g. "fr-fr", "en-us", "de-de", "es-es", "pt-br", "zh-cn", "ja-jp". Full list: https://github.com/carboneio/carbone/blob/master/formatters/_locale.js
complementNoExtra data object accessible in templates with {c.field} tags (as opposed to {d.field} for main data). Useful for static or shared values that should not be mixed into the main dataset: company info, logo URLs, footer text, configuration constants. Example: { "company": "Acme Corp", "address": "123 Main St", "vatNumber": "FR12345" }
variableStrNoCarbone alias expressions evaluated once before rendering, available everywhere in the template. Used to pre-compute reusable values or shorten repetitive paths. Syntax: "{#aliasName = expression}". Example: "{#fullName = d.firstName + \" \" + d.lastName}{#total = d.price * d.qty}". Aliases are then used in the template as {#fullName}, {#total}. Documentation: https://carbone.io/documentation.html#alias
reportNameNoFilename for the generated document, returned in the Content-Disposition header. Supports Carbone tags resolved against the data at render time. Examples: "invoice.pdf" (static), "{d.type}-{d.id}.pdf" (dynamic), "{d.client}-{d.date:formatD(YYYY-MM)}.docx".
enumNoEnumeration map used with the :convEnum(TYPE) formatter to translate code values into human-readable labels. Define one key per enum type; each value is an object mapping code → label. Example: { "STATUS": { "1": "Active", "2": "Inactive", "3": "Pending" }, "ROLE": { "A": "Admin", "U": "User" } }. Template usage: {d.status:convEnum(STATUS)}, {d.role:convEnum(ROLE)}. Documentation: https://carbone.io/documentation.html#convenum-type-
translationsNoTranslation map for multilingual documents. Requires "lang" to be set to select the active locale. Top-level keys are BCP-47 locale codes; values are key → translated-string maps. Template usage: {t(greeting)} is replaced by the matching string for the active locale. Example: { "fr-fr": { "greeting": "Bonjour", "total": "Total" }, "en-us": { "greeting": "Hello", "total": "Total" } }. Documentation: https://carbone.io/documentation.html#translations
currencySourceNoISO 4217 currency code of the monetary amounts in the JSON data. Used by the :formatC formatter as the conversion source. Must be set together with currencyTarget and currencyRates. Example: "EUR" if all prices in your data are in euros.
currencyTargetNoISO 4217 currency code of the output document. The :formatC formatter converts amounts from currencySource to this currency using currencyRates. Must be set together with currencySource and currencyRates. Example: "USD" to display prices in US dollars. Documentation: https://carbone.io/documentation.html#formatc-precisionorformat-
currencyRatesNoExchange rate table used by :formatC for currency conversion. Keys are ISO 4217 currency codes; values are rates relative to a common base. The base currency should have rate 1. Example: { "EUR": 1, "USD": 1.08, "GBP": 0.86, "JPY": 160.5 }.
hardRefreshNoIf true, Carbone recomputes pagination and refreshes the table of contents after rendering. Requires convertTo to be defined. Use this for DOCX/ODT templates that contain a TOC field or cross-references that need updating after data injection.
batchSplitByNoJSON path to the array in your data that drives batch generation. One document is generated per element of the array; all documents are bundled together. Use batchOutput: "zip" to receive a single ZIP archive. Use batchReportName to customise each filename inside the ZIP. Example: "d.invoices" — produces one PDF per item in data.invoices. Example: "d.employees" — produces one contract per employee.
batchOutputNoContainer format for the batch result. Use "zip" to receive all generated documents as a single ZIP archive. Must be used together with batchSplitBy.
batchReportNameNoFilename pattern for each individual document inside the batch ZIP. Supports Carbone tags. Tags are resolved against the item's data (relative path) or the full dataset (absolute path). Examples: "invoice-{d.id}.pdf", "{d.client.name}-{d.date}.docx". Must be used together with batchSplitBy.
webhookUrlNoWebhook URL to enable asynchronous rendering. When provided, Carbone returns immediately and POSTs { "success": true, "data": { "renderId": "..." } } to this URL when the document is ready. The default render timeout is extended to 5 minutes on Carbone Cloud (vs 60 s for synchronous requests). Download the document with GET /render/:renderId once the webhook is received. Required when using batchSplitBy (batch generation is always asynchronous). Example: "https://your-server.com/carbone-webhook".
webhookHeadersNoCustom headers Carbone will include when POSTing to your webhookUrl. Pass plain header names as keys — the prefix "carbone-webhook-header-" is added automatically before sending to Carbone, and Carbone forwards the original header names to your webhook endpoint. Example: { "authorization": "my-secret", "custom-id": "12345", "custom-name": "Jane Doe" } — Carbone will call your URL with headers: authorization: my-secret, custom-id: 12345, custom-name: Jane Doe. Requires webhookUrl to be set.

Implementation Reference

  • The main handler function for the render_document tool. It validates that exactly one of templateId/template is provided, resolves a file input if needed, calls client.renderDocument(), and returns the rendered document content or an error.
    export async function handleRenderDocument(
      args: {
        templateId?: string;
        template?: string;
        data: Record<string, unknown>;
        convertTo?: z.infer<typeof renderDocumentSchema.convertTo>;
        converter?: string;
        timezone?: string;
        lang?: string;
        complement?: Record<string, unknown>;
        variableStr?: string;
        reportName?: string;
        enum?: Record<string, unknown>;
        translations?: Record<string, Record<string, string>>;
        currencySource?: string;
        currencyTarget?: string;
        currencyRates?: Record<string, number>;
        hardRefresh?: boolean;
        batchSplitBy?: string;
        batchOutput?: string;
        batchReportName?: string;
        webhookUrl?: string;
        webhookHeaders?: Record<string, string>;
      },
      client: CarboneClient,
      options?: CallOptions
    ) {
      // XOR: exactly one of templateId or template must be provided
      if ((args.templateId != null) === (args.template != null)) {
        return {
          isError: true,
          content: [{ type: 'text' as const, text: 'Provide either templateId or template, not both (and not neither).' }],
        };
      }
    
      try {
        const template = args.template ? await resolveFileInput(args.template) : undefined;
        const result = await client.renderDocument({ ...args, template }, options);
    
        if ('async' in result) {
          return { content: [{ type: 'text' as const, text: result.message }] };
        }
    
        const format = args.convertTo ?? result.filename.split('.').pop() ?? 'pdf';
        const content = toToolContent(result.buffer, result.filename, format);
        return { content: [content] };
      } catch (error) {
        return {
          isError: true,
          content: [{ type: 'text' as const, text: formatError(error) }],
        };
      }
    }
  • The input schema (renderDocumentSchema) defining all parameters for render_document: templateId, template, data, convertTo, converter, timezone, lang, complement, variableStr, reportName, enum, translations, currencySource/Target/Rates, hardRefresh, batchSplitBy/Output/ReportName, webhookUrl/Headers.
    export const renderDocumentSchema = {
      templateId: z
        .string()
        .min(1)
        .optional()
        .describe(
          'The ID of a previously uploaded template to render. Two ID formats are accepted: ' +
          '(1) Template ID (64-bit) — stable identifier shared across versions; Carbone automatically uses the deployed version. ' +
          '(2) Version ID (SHA-256) — pins rendering to a specific version regardless of deployment status. ' +
          'Both are returned by upload_template. ' +
          'Mutually exclusive with template — provide exactly one, never both.'
        ),
    
      template: z
        .string()
        .min(1)
        .optional()
        .describe(
          'Inline template for one-shot render without storing a template first. ' +
          'Accepts a local file path (e.g. /home/user/invoice.docx), ' +
          'a URL (https://example.com/template.docx), or a base64-encoded string. ' +
          'The template is uploaded and rendered in a single API request — no Template ID is returned. ' +
          'Use this for ephemeral renders; use upload_template + templateId when you need to reuse the template. ' +
          'Supported formats: DOCX, XLSX, PPTX, ODT, ODS, ODP, ODG, HTML, XHTML, IDML, XML, Markdown (MD), PDF, and more. ' +
          'Mutually exclusive with templateId — provide exactly one, never both.'
        ),
    
      data: z
        .record(z.string(), z.unknown())
        .describe(
          'JSON data merged into the template. ' +
          'Access fields with {d.fieldName} tags. ' +
          'Nested objects: {d.customer.name}. ' +
          'Array loops: {d.items[i].description} … {d.items[i+1]}. ' +
          'Conditionals: {d.status == "active" ? "Yes" : "No"}. ' +
          'For pure document conversion without data injection, pass {}.'
        ),
    
      convertTo: z
        .union([
          z.enum(OUTPUT_FORMATS),
          z.object({
            formatName: z.enum(OUTPUT_FORMATS).describe('Target format name.'),
            formatOptions: z
              .record(z.string(), z.unknown())
              .optional()
              .describe(
                'Advanced format options object. Examples by format: ' +
                'PDF — { "EncryptFile": true, "DocumentOpenPassword": "secret", "DocumentPermissionPassword": "owner" } password-protect; ' +
                'PDF — { "Watermarks": [{ "text": "DRAFT", "opacity": 0.2, "rotation": -45, "fontsize": 60 }] } up to 5 watermarks; ' +
                'PDF — { "SelectPdfVersion": 1 } PDF/A-1b compliance (use 2 for PDF/A-2, 3 for PDF/A-3); ' +
                'PDF — { "PageRange": "1-3,5" } export specific pages only; ' +
                'PDF — { "ConvertSlideshow": true } convert each slide to a separate PDF page; ' +
                'Images (PNG/JPG/WEBP) — { "Quality": 90 } compression quality 0-100; ' +
                'Images — { "density": 150 } DPI for rasterisation (default 96); ' +
                'CSV — { "fieldSeparator": ";" } custom column separator.'
              ),
          }),
        ])
        .optional()
        .describe(
          'Output format. If omitted, the output matches the template format. ' +
          'Documents : "pdf", "docx", "xlsx", "pptx", "odt", "ods", "odp", "odg", "rtf", "epub". ' +
          'Web/text  : "html", "xhtml", "txt", "csv", "md", "xml", "idml". ' +
          'Images    : "png", "jpg", "jpeg", "webp", "svg", "tiff", "bmp", "gif". ' +
          'Archive   : "zip" (use with batchSplitBy for batch output). ' +
          'Simple usage: "pdf". ' +
          'Advanced usage: { "formatName": "pdf", "formatOptions": { ... } } for PDF-specific options.'
        ),
    
      converter: z
        .enum(CONVERTERS)
        .optional()
        .describe(
          'Converter engine. Only relevant when convertTo is "pdf" (or an image rasterised from a document). ' +
          '"L" — LibreOffice (default): best all-round engine for DOCX, XLSX, PPTX, ODT, ODS, ODP. ' +
          '"O" — OnlyOffice: highest fidelity for Microsoft Office formats (DOCX, XLSX, PPTX). ' +
          '"C" — Chromium: best for HTML/CSS/JS templates — full browser rendering. ' +
          'If omitted, LibreOffice is used by default.'
        ),
    
      timezone: z
        .string()
        .optional()
        .describe(
          'IANA timezone used to convert dates in the rendered document. Default: "Europe/Paris". ' +
          'Applied when templates use the :formatD formatter, e.g. {d.date:formatD(YYYY-MM-DD HH:mm)}. ' +
          'Common values: "UTC", "America/New_York", "America/Los_Angeles", "Europe/London", ' +
          '"Europe/Paris", "Europe/Berlin", "Asia/Tokyo", "Asia/Shanghai", "Australia/Sydney". ' +
          'Full list (TZ identifier column): https://en.wikipedia.org/wiki/List_of_tz_database_time_zones'
        ),
    
      lang: z
        .string()
        .optional()
        .describe(
          'Locale of the generated document. Affects three things: ' +
          '(1) {t(key)} translation tags — selects the matching translation from the translations map. ' +
          '(2) :formatN number formatter — applies locale-specific thousand/decimal separators. ' +
          '(3) :formatC currency formatter — applies locale-specific currency symbols and formatting. ' +
          'Format: BCP-47 lowercase, e.g. "fr-fr", "en-us", "de-de", "es-es", "pt-br", "zh-cn", "ja-jp". ' +
          'Full list: https://github.com/carboneio/carbone/blob/master/formatters/_locale.js'
        ),
    
      complement: z
        .record(z.string(), z.unknown())
        .optional()
        .describe(
          'Extra data object accessible in templates with {c.field} tags (as opposed to {d.field} for main data). ' +
          'Useful for static or shared values that should not be mixed into the main dataset: ' +
          'company info, logo URLs, footer text, configuration constants. ' +
          'Example: { "company": "Acme Corp", "address": "123 Main St", "vatNumber": "FR12345" }'
        ),
    
      variableStr: z
        .string()
        .optional()
        .describe(
          'Carbone alias expressions evaluated once before rendering, available everywhere in the template. ' +
          'Used to pre-compute reusable values or shorten repetitive paths. ' +
          'Syntax: "{#aliasName = expression}". ' +
          'Example: "{#fullName = d.firstName + \\" \\" + d.lastName}{#total = d.price * d.qty}". ' +
          'Aliases are then used in the template as {#fullName}, {#total}. ' +
          'Documentation: https://carbone.io/documentation.html#alias'
        ),
    
      reportName: z
        .string()
        .optional()
        .describe(
          'Filename for the generated document, returned in the Content-Disposition header. ' +
          'Supports Carbone tags resolved against the data at render time. ' +
          'Examples: "invoice.pdf" (static), "{d.type}-{d.id}.pdf" (dynamic), "{d.client}-{d.date:formatD(YYYY-MM)}.docx".'
        ),
    
      enum: z
        .record(z.string(), z.unknown())
        .optional()
        .describe(
          'Enumeration map used with the :convEnum(TYPE) formatter to translate code values into human-readable labels. ' +
          'Define one key per enum type; each value is an object mapping code → label. ' +
          'Example: { "STATUS": { "1": "Active", "2": "Inactive", "3": "Pending" }, "ROLE": { "A": "Admin", "U": "User" } }. ' +
          'Template usage: {d.status:convEnum(STATUS)}, {d.role:convEnum(ROLE)}. ' +
          'Documentation: https://carbone.io/documentation.html#convenum-type-'
        ),
    
      translations: z
        .record(z.string(), z.record(z.string(), z.string()))
        .optional()
        .describe(
          'Translation map for multilingual documents. Requires "lang" to be set to select the active locale. ' +
          'Top-level keys are BCP-47 locale codes; values are key → translated-string maps. ' +
          'Template usage: {t(greeting)} is replaced by the matching string for the active locale. ' +
          'Example: { "fr-fr": { "greeting": "Bonjour", "total": "Total" }, "en-us": { "greeting": "Hello", "total": "Total" } }. ' +
          'Documentation: https://carbone.io/documentation.html#translations'
        ),
    
      currencySource: z
        .string()
        .optional()
        .describe(
          'ISO 4217 currency code of the monetary amounts in the JSON data. ' +
          'Used by the :formatC formatter as the conversion source. ' +
          'Must be set together with currencyTarget and currencyRates. ' +
          'Example: "EUR" if all prices in your data are in euros.'
        ),
    
      currencyTarget: z
        .string()
        .optional()
        .describe(
          'ISO 4217 currency code of the output document. ' +
          'The :formatC formatter converts amounts from currencySource to this currency using currencyRates. ' +
          'Must be set together with currencySource and currencyRates. ' +
          'Example: "USD" to display prices in US dollars. ' +
          'Documentation: https://carbone.io/documentation.html#formatc-precisionorformat-'
        ),
    
      currencyRates: z
        .record(z.string(), z.number())
        .optional()
        .describe(
          'Exchange rate table used by :formatC for currency conversion. ' +
          'Keys are ISO 4217 currency codes; values are rates relative to a common base. ' +
          'The base currency should have rate 1. ' +
          'Example: { "EUR": 1, "USD": 1.08, "GBP": 0.86, "JPY": 160.5 }.'
        ),
    
      hardRefresh: z
        .boolean()
        .optional()
        .describe(
          'If true, Carbone recomputes pagination and refreshes the table of contents after rendering. ' +
          'Requires convertTo to be defined. ' +
          'Use this for DOCX/ODT templates that contain a TOC field or cross-references that need updating after data injection.'
        ),
    
      batchSplitBy: z
        .string()
        .optional()
        .describe(
          'JSON path to the array in your data that drives batch generation. ' +
          'One document is generated per element of the array; all documents are bundled together. ' +
          'Use batchOutput: "zip" to receive a single ZIP archive. ' +
          'Use batchReportName to customise each filename inside the ZIP. ' +
          'Example: "d.invoices" — produces one PDF per item in data.invoices. ' +
          'Example: "d.employees" — produces one contract per employee.'
        ),
    
      batchOutput: z
        .string()
        .optional()
        .describe(
          'Container format for the batch result. ' +
          'Use "zip" to receive all generated documents as a single ZIP archive. ' +
          'Must be used together with batchSplitBy.'
        ),
    
      batchReportName: z
        .string()
        .optional()
        .describe(
          'Filename pattern for each individual document inside the batch ZIP. Supports Carbone tags. ' +
          'Tags are resolved against the item\'s data (relative path) or the full dataset (absolute path). ' +
          'Examples: "invoice-{d.id}.pdf", "{d.client.name}-{d.date}.docx". ' +
          'Must be used together with batchSplitBy.'
        ),
    
      webhookUrl: z
        .url()
        .optional()
        .describe(
          'Webhook URL to enable asynchronous rendering. ' +
          'When provided, Carbone returns immediately and POSTs { "success": true, "data": { "renderId": "..." } } to this URL when the document is ready. ' +
          'The default render timeout is extended to 5 minutes on Carbone Cloud (vs 60 s for synchronous requests). ' +
          'Download the document with GET /render/:renderId once the webhook is received. ' +
          'Required when using batchSplitBy (batch generation is always asynchronous). ' +
          'Example: "https://your-server.com/carbone-webhook".'
        ),
    
      webhookHeaders: z
        .record(z.string(), z.string())
        .optional()
        .describe(
          'Custom headers Carbone will include when POSTing to your webhookUrl. ' +
          'Pass plain header names as keys — the prefix "carbone-webhook-header-" is added automatically before sending to Carbone, ' +
          'and Carbone forwards the original header names to your webhook endpoint. ' +
          'Example: { "authorization": "my-secret", "custom-id": "12345", "custom-name": "Jane Doe" } — ' +
          'Carbone will call your URL with headers: authorization: my-secret, custom-id: 12345, custom-name: Jane Doe. ' +
          'Requires webhookUrl to be set.'
        ),
    };
  • Zod validation schema RenderDocumentSchema used for runtime input validation. Enforces the XOR constraint on templateId vs template via a .refine().
    export const RenderDocumentSchema = z.object({
      templateId: z.string().min(1).optional(),
      template:   z.string().min(1).optional(),
      data: z.record(z.string(), z.unknown()),
      convertTo: OutputFormatSchema.optional(),
      converter: z.enum(CONVERTERS).optional(),
      timezone: z.string().optional(),
      lang: z.string().optional(),
      complement: z.record(z.string(), z.unknown()).optional(),
      variableStr: z.string().optional(),
      reportName: z.string().optional(),
      enum: z.record(z.string(), z.unknown()).optional(),
      translations: z.record(z.string(), z.record(z.string(), z.string())).optional(),
      currencySource: z.string().optional(),
      currencyTarget: z.string().optional(),
      currencyRates: z.record(z.string(), z.number()).optional(),
      hardRefresh: z.boolean().optional(),
      batchSplitBy: z.string().optional(),
      batchOutput: z.string().optional(),
      batchReportName: z.string().optional(),
      webhookUrl: z.string().url().optional(),
      webhookHeaders: z.record(z.string(), z.string()).optional(),
    }).refine(
      (d) => (d.templateId != null) !== (d.template != null),
      { message: 'Provide either templateId or template, not both', path: ['templateId'] }
    );
  • Registration of render_document on the MCP server via server.registerTool(), binding the tool name, description, schema, and handler.
    server.registerTool(
      renderDocumentToolName,
      { description: renderDocumentDescription, inputSchema: renderDocumentSchema },
      (args, extra) => handleRenderDocument(args, client, { apiKey: extra.authInfo?.token })
    );
  • CarboneClient.renderDocument() — the low-level HTTP client method that builds the request body, determines the endpoint (stored vs inline template), handles async webhook mode, and returns the binary document buffer.
    async renderDocument(params: {
      templateId?: string;
      template?: string;
      data: object;
      convertTo?: OutputFormat;
      converter?: string;
      timezone?: string;
      lang?: string;
      complement?: Record<string, unknown>;
      variableStr?: string;
      reportName?: string;
      enum?: Record<string, unknown>;
      translations?: Record<string, Record<string, string>>;
      currencySource?: string;
      currencyTarget?: string;
      currencyRates?: Record<string, number>;
      hardRefresh?: boolean;
      batchSplitBy?: string;
      batchOutput?: string;
      batchReportName?: string;
      webhookUrl?: string;
      webhookHeaders?: Record<string, string>;
    }, options?: CallOptions): Promise<{ buffer: Buffer; filename: string } | { async: true; message: string }> {
      const body: Record<string, unknown> = { data: params.data };
      if (params.template)                   body['template']       = params.template;
      if (params.convertTo)                  body['convertTo']      = params.convertTo;
      if (params.converter)                  body['converter']      = params.converter;
      if (params.timezone)                   body['timezone']       = params.timezone;
      if (params.lang)                       body['lang']           = params.lang;
      if (params.complement)                 body['complement']     = params.complement;
      if (params.variableStr)                body['variableStr']    = params.variableStr;
      if (params.reportName)                 body['reportName']     = params.reportName;
      if (params.enum)                       body['enum']           = params.enum;
      if (params.translations)               body['translations']   = params.translations;
      if (params.currencySource)             body['currencySource'] = params.currencySource;
      if (params.currencyTarget)             body['currencyTarget'] = params.currencyTarget;
      if (params.currencyRates)              body['currencyRates']  = params.currencyRates;
      if (params.hardRefresh !== undefined)  body['hardRefresh']    = params.hardRefresh;
      if (params.batchSplitBy)               body['batchSplitBy']   = params.batchSplitBy;
      if (params.batchOutput)                body['batchOutput']    = params.batchOutput;
      if (params.batchReportName)            body['batchReportName'] = params.batchReportName;
    
      const isAsync = !!params.webhookUrl;
      const endpoint = params.template
        ? `/render/template${isAsync ? '' : '?download=true'}`
        : `/render/${params.templateId}${isAsync ? '' : '?download=true'}`;
    
      const headers: Record<string, string> = { 'Content-Type': 'application/json' };
      if (params.webhookUrl) {
        headers['carbone-webhook-url'] = params.webhookUrl;
        if (params.webhookHeaders) {
          for (const [name, value] of Object.entries(params.webhookHeaders)) {
            headers[`carbone-webhook-header-${name}`] = value;
          }
        }
      }
    
      const response = await this.request(endpoint, {
        method: 'POST',
        headers,
        body: JSON.stringify(body),
      }, options);
    
      if (isAsync) {
        const json = await response.json() as { success: boolean; message: string };
        return { async: true, message: json.message };
      }
    
      return this.handleBinaryResponse(response);
    }
Behavior5/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 comprehensively discloses behavioral traits: two rendering modes, async mode with webhook, batch generation, output format conversion, multilingual rendering, currency conversion, advanced PDF options (watermark, password, PDF/A), and hardRefresh behavior. No destructive actions are implied, and there is no contradiction with any missing annotations.

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 a single paragraph that front-loads the main action and then lists features. It is somewhat lengthy but each sentence adds value. Could benefit from structured bullet points, but it remains clear and not overly verbose.

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

Completeness5/5

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

Given the tool's complexity (21 parameters, nested objects, rich schema), the description is thorough. It explains the async workflow, batch generation, and advanced features. There is no output schema, but the description covers how to retrieve results (via webhook or renderId). It addresses many edge cases and provides sufficient context for correct invocation.

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 description coverage is 100%, and the description adds significant value beyond the schema. It explains the two modes and mutual exclusivity of templateId and template, describes the purpose of batchSplitBy, webhookUrl, and other complex parameters. However, the schema itself already provides thorough descriptions, so the additional value is still there but not overwhelming.

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's purpose: generating documents by merging a Carbone template with JSON data. It distinguishes two modes (using a previously uploaded templateId vs. inline template), and explicitly mentions many features (output format conversion, multilingual, currency conversion, batch generation, advanced PDF options). This distinguishes it from sibling tools like upload_template or convert_document.

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

Usage Guidelines5/5

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

The description provides explicit guidance on when to use each mode: 'use upload_template + templateId when you need to reuse the template' vs. inline for ephemeral renders. It also states that async mode is required for batch generation. This helps the agent select the right mode and understand prerequisites.

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/carboneio/carbone-mcp'

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