SVG to VectorDrawable
convert-svg-to-android-drawableConvert SVG markup or files to Android VectorDrawable XML. Configure decimal precision, tint color, fill black, XML tag, and caching. Optionally write output to disk.
Instructions
Convert SVG markup or files into Android VectorDrawable XML quickly, optionally writing to disk.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| svg | No | Inline SVG markup to convert | |
| svgPath | No | Path to an SVG file to read | |
| outputPath | No | Optional output path for generated VectorDrawable XML | |
| floatPrecision | No | Decimal precision when serializing coordinates | |
| fillBlack | No | Force fill color black when missing | |
| xmlTag | No | Include XML declaration | |
| tint | No | Android tint color (e.g. #FF000000) | |
| cache | No | Reuse cached result for identical inputs within this process |
Implementation Reference
- src/tools/svgTool.js:85-147 (registration)Tool registration for 'convert-svg-to-android-drawable' — registers the tool with input schema and the async handler function that loads SVG, converts via svg2vectordrawable, caches results, optionally writes output, and returns the XML.
function registerSvgTool(server) { server.registerTool( 'convert-svg-to-android-drawable', { title: 'SVG to VectorDrawable', description: 'Convert SVG markup or files into Android VectorDrawable XML quickly, optionally writing to disk.', inputSchema: convertInputSchema }, async (params, extra) => { const svgCode = await loadSvg(params); const options = { floatPrecision: params.floatPrecision, fillBlack: params.fillBlack, xmlTag: params.xmlTag, tint: params.tint }; const cacheKey = makeCacheKey(svgCode, options); const startTime = process.hrtime.bigint(); let xml = null; if (params.cache) { xml = getCached(cacheKey); } if (!xml) { xml = await svg2vectordrawable(svgCode, options); if (!xml || typeof xml !== 'string') { throw new Error('Conversion did not produce XML'); } setCache(cacheKey, xml); } const savedPath = await maybeWriteOutput(params.outputPath, xml); const elapsedMs = Number(process.hrtime.bigint() - startTime) / 1_000_000; if (extra && typeof extra.sessionId === 'string') { server .sendLoggingMessage( { level: 'info', data: `Converted SVG in ${elapsedMs.toFixed(2)}ms` + (savedPath ? ` (saved to ${savedPath})` : '') }, extra.sessionId ) .catch(() => { /* best-effort logging */ }); } const content = []; if (savedPath) { content.push({ type: 'text', text: `Saved VectorDrawable to ${savedPath}` }); } content.push({ type: 'text', text: xml }); return { content }; } ); } - src/tools/svgTool.js:94-145 (handler)Async handler for 'convert-svg-to-android-drawable' — loads SVG (inline or file), constructs options, checks cache, calls svg2vectordrawable for conversion, handles caching, optionally writes to outputPath, logs timing, and returns XML content.
async (params, extra) => { const svgCode = await loadSvg(params); const options = { floatPrecision: params.floatPrecision, fillBlack: params.fillBlack, xmlTag: params.xmlTag, tint: params.tint }; const cacheKey = makeCacheKey(svgCode, options); const startTime = process.hrtime.bigint(); let xml = null; if (params.cache) { xml = getCached(cacheKey); } if (!xml) { xml = await svg2vectordrawable(svgCode, options); if (!xml || typeof xml !== 'string') { throw new Error('Conversion did not produce XML'); } setCache(cacheKey, xml); } const savedPath = await maybeWriteOutput(params.outputPath, xml); const elapsedMs = Number(process.hrtime.bigint() - startTime) / 1_000_000; if (extra && typeof extra.sessionId === 'string') { server .sendLoggingMessage( { level: 'info', data: `Converted SVG in ${elapsedMs.toFixed(2)}ms` + (savedPath ? ` (saved to ${savedPath})` : '') }, extra.sessionId ) .catch(() => { /* best-effort logging */ }); } const content = []; if (savedPath) { content.push({ type: 'text', text: `Saved VectorDrawable to ${savedPath}` }); } content.push({ type: 'text', text: xml }); return { content }; } - src/tools/svgTool.js:17-41 (schema)Input schema (convertInputSchema) using Zod v4 — defines svg/svgPath, outputPath, floatPrecision (default 2), fillBlack (default false), xmlTag (default false), tint, and cache (default true) with a refinement requiring svg or svgPath.
const convertInputSchema = z .object({ svg: z.string().min(1).describe('Inline SVG markup to convert').optional(), svgPath: z.string().min(1).describe('Path to an SVG file to read').optional(), outputPath: z .string() .min(1) .describe('Optional output path for generated VectorDrawable XML') .optional(), floatPrecision: z .number() .int() .min(0) .max(6) .default(2) .describe('Decimal precision when serializing coordinates'), fillBlack: z.boolean().default(false).describe('Force fill color black when missing'), xmlTag: z.boolean().default(false).describe('Include XML declaration'), tint: z.string().min(1).optional().describe('Android tint color (e.g. #FF000000)'), cache: z .boolean() .default(true) .describe('Reuse cached result for identical inputs within this process') }) .refine(data => data.svg || data.svgPath, { message: 'Provide either svg or svgPath' }); - Main entry point for the svg2vectordrawable vendor module — first optimizes SVG via SVGO with configurable floatPrecision, then delegates to svg-to-vectordrawable for the actual SVG-to-VectorDrawable conversion.
const { optimize } = require('svgo'); const svg2vectordrawable = require('./svg-to-vectordrawable'); const svgoConfig = require('./svgo-config'); module.exports = function(svgCode, options) { let floatPrecision = options ? options.floatPrecision : 2; const result = optimize(svgCode, svgoConfig(floatPrecision)); svgCode = result.data; return svg2vectordrawable(svgCode, options) }; - Core conversion function — parses SVG, instantiates JS2XML converter, runs convert() to transform SVG AST into Android VectorDrawable XML, optionally prepends XML declaration.
module.exports = function(svgCode, options) { let floatPrecision = 2; let strict = false; let fillBlack = false; let xmlTag = false; let tint; if (options) { if (options.floatPrecision) { floatPrecision = options.floatPrecision; } if (options.strict) { strict = options.strict; } if (options.fillBlack) { fillBlack = options.fillBlack; } if (options.xmlTag) { xmlTag = options.xmlTag; } if (options.tint) { tint = options.tint; } } return new Promise((resolve, reject) => { let data = parseSvg(svgCode); if (data.error) { reject(data.error); } else { let xml = new JS2XML().convert(data, floatPrecision, strict, fillBlack, tint); if (xmlTag) { xml = '<?xml version="1.0" encoding="utf-8"?>\n' + xml; } resolve(xml); } }); };