@rui.branco/jira-mcp 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +24 -21
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -2505,24 +2505,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2505
2505
 
2506
2506
  const content = [{ type: "text", text: result.text }];
2507
2507
 
2508
- // Add Jira images. Anthropic vision only accepts png/jpeg/gif/webp;
2509
- // SVG and other formats must be skipped or the API rejects the whole
2510
- // response with "Improperly formed request". Also skip files over 5MB.
2511
- const mimeByExt = {
2512
- ".png": "image/png",
2513
- ".jpg": "image/jpeg",
2514
- ".jpeg": "image/jpeg",
2515
- ".gif": "image/gif",
2516
- ".webp": "image/webp",
2517
- };
2508
+ // Anthropic vision only accepts png/jpeg/gif/webp under 5MB. We sniff
2509
+ // the magic bytes instead of trusting the filename, since a misnamed
2510
+ // file (e.g. SVG saved as .png) gets the whole response rejected with
2511
+ // "Could not process image".
2518
2512
  const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
2513
+ const detectImageMime = (buf) => {
2514
+ if (!buf || buf.length < 12) return null;
2515
+ if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return "image/png";
2516
+ if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return "image/jpeg";
2517
+ if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) return "image/gif";
2518
+ if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
2519
+ buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) return "image/webp";
2520
+ return null;
2521
+ };
2519
2522
  for (const imagePath of result.jiraImages) {
2520
2523
  try {
2521
- const ext = path.extname(imagePath).toLowerCase();
2522
- const mimeType = mimeByExt[ext];
2523
- if (!mimeType) continue;
2524
2524
  const imageData = fs.readFileSync(imagePath);
2525
2525
  if (imageData.length > MAX_IMAGE_BYTES) continue;
2526
+ const mimeType = detectImageMime(imageData);
2527
+ if (!mimeType) continue;
2526
2528
  content.push({
2527
2529
  type: "image",
2528
2530
  data: imageData.toString("base64"),
@@ -2533,17 +2535,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2533
2535
  }
2534
2536
  }
2535
2537
 
2536
- // Add Figma images (now supports multiple images per design)
2538
+ // Figma images (multiple per design). Validate magic bytes here too.
2537
2539
  for (const design of result.figmaDesigns) {
2538
2540
  if (design.images && design.images.length > 0) {
2539
2541
  for (const img of design.images) {
2540
- if (img.buffer && img.buffer.length <= MAX_IMAGE_BYTES) {
2541
- content.push({
2542
- type: "image",
2543
- data: img.buffer.toString("base64"),
2544
- mimeType: "image/png",
2545
- });
2546
- }
2542
+ if (!img.buffer || img.buffer.length > MAX_IMAGE_BYTES) continue;
2543
+ const mimeType = detectImageMime(img.buffer);
2544
+ if (!mimeType) continue;
2545
+ content.push({
2546
+ type: "image",
2547
+ data: img.buffer.toString("base64"),
2548
+ mimeType: mimeType,
2549
+ });
2547
2550
  }
2548
2551
  }
2549
2552
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rui.branco/jira-mcp",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Jira MCP server for Claude Code - fetch tickets, search with JQL, update tickets, manage comments, change status, and get Figma designs",
5
5
  "main": "index.js",
6
6
  "bin": {