upload_template
Upload a template file to create a reusable versioned template. Use the returned Template ID with render_document to generate documents.
Instructions
Upload and store a reusable Carbone template. Once uploaded, use render_document with the returned Template ID to generate documents from it. Supports versioning: multiple versions can live under a single stable Template ID, with deployedAt controlling which version is active. Accepted formats: DOCX, XLSX, PPTX, ODT, ODS, ODP, ODG, HTML, XHTML, IDML, XML, Markdown, PDF, and more.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| template | Yes | The template file. Accepts a local file path (e.g. /home/user/invoice.docx), a URL (https://example.com/template.docx), or a base64-encoded string. Supported formats: DOCX, XLSX, PPTX, ODT, ODS, ODP, ODG, HTML, XHTML, IDML, XML, Markdown (MD), PDF, and more. Full list: https://carbone.io/documentation/developer/http-api/generate-reports.html#output-file-type | |
| name | Yes | Display name for the template (e.g. "Invoice Template", "NDA Contract"). | |
| id | No | Existing Template ID (64-bit format) to add this upload to its version history. If omitted, a new Template ID is generated. Providing a Version ID (SHA-256) is not allowed and will cause an error. | |
| versioning | No | Enable template versioning (default: true). When true, a stable Template ID is generated and multiple versions can be managed under it. When false, behaves as legacy mode and returns only a templateId (SHA-256 hash). | |
| category | No | Group templates into folders/categories (e.g. "invoices", "legal", "hr"). | |
| comment | No | Free-text comment to describe the template version or its purpose. | |
| tags | No | Tags for searchability and filtering (e.g. ["sales", "billing", "v2"]). | |
| sample | No | Sample input data attached to the template for testing in Carbone Studio. Each item must include data, complement, translations, and enum objects. | |
| deployedAt | No | UTC Unix timestamp (seconds) to set as the deployment time for this version. Carbone uses the version with the most recent deployedAt when rendering via Template ID. Use 42000000000 to deploy immediately (special "NOW" sentinel value). | |
| expireAt | No | UTC Unix timestamp (seconds) at which this template will be automatically deleted. Use 42000000000 to delete immediately (special "NOW" sentinel value). |
Implementation Reference
- src/tools/templates.ts:175-215 (handler)The handleUploadTemplate function that executes the upload_template tool logic. It resolves the template file (path/URL/base64), calls the Carbone client's uploadTemplate, and formats the result (versioned or legacy).
export async function handleUploadTemplate( args: { template: string; name: string; id?: string; versioning?: boolean; category?: string; comment?: string; tags?: string[]; sample?: Array<{ data: Record<string, unknown>; complement: Record<string, unknown>; translations: Record<string, unknown>; enum: Record<string, unknown> }>; deployedAt?: number; expireAt?: number; }, client: CarboneClient, options?: CallOptions ) { try { const template = await resolveFileInput(args.template); const result: UploadTemplateResult = await client.uploadTemplate({ ...args, template }, options); // The API returns different shapes depending on whether versioning is enabled const lines: string[] = ['Template uploaded successfully!', '']; if ('id' in result) { lines.push(`Template ID : ${result.id}`); lines.push(`Version ID : ${result.versionId}`); if (result.type) lines.push(`Type : ${result.type}`); if (result.size) lines.push(`Size : ${result.size} bytes`); } else { // Legacy / versioning disabled response lines.push(`Template ID : ${result.templateId}`); } lines.push(`Name : ${args.name}`); return { content: [{ type: 'text' as const, text: lines.join('\n') }] }; } catch (error) { return { isError: true, content: [{ type: 'text' as const, text: formatError(error) }], }; } } - src/tools/templates.ts:99-173 (schema)uploadTemplateSchema defines the input schema for the upload_template tool using Zod: template (file path/URL/base64), name, optional id, versioning, category, comment, tags, sample data, deployedAt, expireAt.
export const uploadTemplateSchema = { template: z .string() .min(1) .describe( 'The template file. Accepts a local file path (e.g. /home/user/invoice.docx), ' + 'a URL (https://example.com/template.docx), or a base64-encoded string. ' + 'Supported formats: DOCX, XLSX, PPTX, ODT, ODS, ODP, ODG, HTML, XHTML, IDML, XML, Markdown (MD), PDF, and more. ' + 'Full list: https://carbone.io/documentation/developer/http-api/generate-reports.html#output-file-type' ), name: z .string() .min(1) .describe('Display name for the template (e.g. "Invoice Template", "NDA Contract").'), id: z .string() .optional() .describe( 'Existing Template ID (64-bit format) to add this upload to its version history. ' + 'If omitted, a new Template ID is generated. ' + 'Providing a Version ID (SHA-256) is not allowed and will cause an error.' ), versioning: z .boolean() .optional() .default(true) .describe( 'Enable template versioning (default: true). ' + 'When true, a stable Template ID is generated and multiple versions can be managed under it. ' + 'When false, behaves as legacy mode and returns only a templateId (SHA-256 hash).' ), category: z .string() .optional() .describe('Group templates into folders/categories (e.g. "invoices", "legal", "hr").'), comment: z .string() .optional() .describe('Free-text comment to describe the template version or its purpose.'), tags: z .array(z.string()) .optional() .describe('Tags for searchability and filtering (e.g. ["sales", "billing", "v2"]).'), sample: z .array( z.object({ data: z.record(z.string(), z.unknown()).describe('JSON dataset for {d.} tags.'), complement: z.record(z.string(), z.unknown()).describe('Extra data for {c.} tags.'), translations: z.record(z.string(), z.unknown()).describe('Localization map for {t()} tags.'), enum: z.record(z.string(), z.unknown()).describe('Enumerations for :convEnum() formatter.'), }) ) .optional() .describe( 'Sample input data attached to the template for testing in Carbone Studio. ' + 'Each item must include data, complement, translations, and enum objects.' ), deployedAt: z .number() .int() .optional() .describe( 'UTC Unix timestamp (seconds) to set as the deployment time for this version. ' + 'Carbone uses the version with the most recent deployedAt when rendering via Template ID. ' + 'Use 42000000000 to deploy immediately (special "NOW" sentinel value).' ), expireAt: z .number() .int() .optional() .describe( 'UTC Unix timestamp (seconds) at which this template will be automatically deleted. ' + 'Use 42000000000 to delete immediately (special "NOW" sentinel value).' ), }; - src/tools/index.ts:87-91 (registration)Registration of upload_template tool on the MCP server via server.registerTool with the tool name, description, schema, and handler callback.
server.registerTool( uploadTemplateToolName, { description: uploadTemplateDescription, inputSchema: uploadTemplateSchema }, (args, extra) => handleUploadTemplate(args, client, { apiKey: extra.authInfo?.token }) ); - src/validation/schemas.ts:72-83 (schema)Alternative Zod schema for upload_template validation (UploadTemplateSchema) used for general validation purposes.
export const UploadTemplateSchema = z.object({ template: z.string().min(1, 'Template content required'), name: z.string().min(1, 'Template name required'), id: z.string().optional(), versioning: z.boolean().optional().default(true), category: z.string().optional(), comment: z.string().optional(), tags: z.array(z.string()).optional(), sample: z.array(SampleItemSchema).optional(), deployedAt: z.number().int().optional(), expireAt: z.number().int().optional(), }); - src/utils/file.ts:55-86 (helper)resolveFileInput helper that converts local file paths, URLs, or base64 strings into base64 content for upload. Used by handleUploadTemplate to resolve the template argument.
export async function resolveFileInput(input: string): Promise<string> { // Remote URL if (input.startsWith('http://') || input.startsWith('https://')) { const response = await fetch(input); if (!response.ok) { throw new Error( `Failed to download file from URL: ${response.status} ${response.statusText}` ); } const buffer = Buffer.from(await response.arrayBuffer()); return buffer.toString('base64'); } // Local file path: absolute (/…), relative (./… or ../…), home (~), or Windows (C:\…) const isLocalPath = input.startsWith('/') || input.startsWith('./') || input.startsWith('../') || input.startsWith('~') || /^[A-Za-z]:[/\\]/.test(input); if (isLocalPath) { const resolvedPath = input.startsWith('~') ? input.replace('~', process.env['HOME'] ?? process.env['USERPROFILE'] ?? '~') : input; const buffer = await readFile(resolvedPath); return buffer.toString('base64'); } // Already base64 return input; }