iwork-mcp 0.5.0 → 0.5.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.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Ask Claude to build spreadsheets, write documents, and create presentations —
14
14
 
15
15
  **Keynote** — Create presentations, add/delete/duplicate/reorder slides, set titles and bullet points, add images and shapes, set transitions and presenter notes, start/stop slideshows, export to PDF/PowerPoint/HTML.
16
16
 
17
- 72 tools total.
17
+ 73 tools total.
18
18
 
19
19
  ## Install
20
20
 
@@ -50,7 +50,7 @@ claude mcp add iwork -- npx -y iwork-mcp
50
50
 
51
51
  ## Tools
52
52
 
53
- ### Numbers (35 tools)
53
+ ### Numbers (36 tools)
54
54
 
55
55
  | Tool | Description |
56
56
  |------|-------------|
@@ -89,6 +89,7 @@ claude mcp add iwork -- npx -y iwork-mcp
89
89
  | `numbers_format_cells` | Set font, size, color, bold, italic, alignment, background |
90
90
  | `numbers_set_column_width` | Set column width |
91
91
  | `numbers_set_row_height` | Set row height |
92
+ | `numbers_create_sheet_with_table` | Create a full sheet with table, data, and formatting in one fast call |
92
93
 
93
94
  ### Pages (15 tools)
94
95
 
@@ -605,9 +605,9 @@ export function registerNumbersTools(server) {
605
605
  }
606
606
 
607
607
  function hexToRGB(hex) {
608
- const r = parseInt(hex.slice(1, 3), 16) / 255;
609
- const g = parseInt(hex.slice(3, 5), 16) / 255;
610
- const b = parseInt(hex.slice(5, 7), 16) / 255;
608
+ const r = parseInt(hex.slice(1, 3), 16) * 257;
609
+ const g = parseInt(hex.slice(3, 5), 16) * 257;
610
+ const b = parseInt(hex.slice(5, 7), 16) * 257;
611
611
  return [r, g, b];
612
612
  }
613
613
 
@@ -615,12 +615,10 @@ export function registerNumbersTools(server) {
615
615
  if (fmt.fontSize !== undefined) cell.fontSize = fmt.fontSize;
616
616
  if (fmt.fontName !== undefined) cell.fontName = fmt.fontName;
617
617
  if (fmt.textColor !== undefined) {
618
- const [r, g, b] = hexToRGB(fmt.textColor);
619
- cell.textColor = [r, g, b];
618
+ cell.textColor = hexToRGB(fmt.textColor);
620
619
  }
621
620
  if (fmt.backgroundColor !== undefined) {
622
- const [r, g, b] = hexToRGB(fmt.backgroundColor);
623
- cell.backgroundColor = [r, g, b];
621
+ cell.backgroundColor = hexToRGB(fmt.backgroundColor);
624
622
  }
625
623
  if (fmt.alignment !== undefined) {
626
624
  cell.alignment = fmt.alignment;
@@ -631,16 +629,16 @@ export function registerNumbersTools(server) {
631
629
  if (fmt.textWrap !== undefined) {
632
630
  cell.textWrap = fmt.textWrap;
633
631
  }
634
- // Bold/italic: switch font to bold/italic variant
632
+ // Bold/italic: switch font to bold/italic variant (PostScript names)
635
633
  if (fmt.bold !== undefined || fmt.italic !== undefined) {
636
634
  let fontName = fmt.fontName || cell.fontName();
637
- const baseName = fontName.replace(/ ?(Bold|Italic|Bold Italic|BoldItalic)$/i, "").trim();
638
- let suffix = "";
635
+ const baseName = fontName.replace(/[- ]?(Bold ?Italic|BoldItalic|Bold|Italic)$/i, "").trim();
639
636
  const wantBold = fmt.bold !== undefined ? fmt.bold : /Bold/i.test(fontName);
640
637
  const wantItalic = fmt.italic !== undefined ? fmt.italic : /Italic/i.test(fontName);
641
- if (wantBold && wantItalic) suffix = " Bold Italic";
642
- else if (wantBold) suffix = " Bold";
643
- else if (wantItalic) suffix = " Italic";
638
+ let suffix = "";
639
+ if (wantBold && wantItalic) suffix = "-BoldItalic";
640
+ else if (wantBold) suffix = "-Bold";
641
+ else if (wantItalic) suffix = "-Italic";
644
642
  cell.fontName = baseName + suffix;
645
643
  }
646
644
  }
@@ -684,4 +682,129 @@ export function registerNumbersTools(server) {
684
682
  table.rows[params.row - 1].height = params.height;
685
683
  return JSON.stringify({ row: params.row, height: params.height, set: true });
686
684
  `, { documentName, row, height, sheetName: sheetName ?? null, tableName: tableName ?? null })));
685
+ // ── Compound / Batch Tools ──
686
+ server.tool("numbers_create_sheet_with_table", "Create a complete sheet with a named table, data, and formatting in ONE operation. Much faster than calling individual tools. Use this for bulk setup like calendars, reports, dashboards.", {
687
+ documentName: z.string().describe("Name of the open document"),
688
+ sheetName: z.string().describe("Name for the new sheet"),
689
+ tableName: z.string().optional().describe("Name for the table (default: same as sheet name)"),
690
+ data: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])))
691
+ .describe("2D array of table data. First row can be headers."),
692
+ headerRowCount: z.number().optional().describe("Number of header rows (default: 0 for no header styling)"),
693
+ columnWidths: z.array(z.number()).optional().describe("Width in points for each column"),
694
+ rowHeights: z.array(z.number()).optional().describe("Height in points for each row"),
695
+ formatting: z.array(z.object({
696
+ cellRange: z.string().describe("Cell or range, e.g. 'A1' or 'A1:G1'"),
697
+ bold: z.boolean().optional(),
698
+ italic: z.boolean().optional(),
699
+ fontSize: z.number().optional(),
700
+ fontName: z.string().optional(),
701
+ textColor: z.string().optional().describe("Hex color, e.g. '#FF0000'"),
702
+ backgroundColor: z.string().optional().describe("Hex color, e.g. '#0000FF'"),
703
+ alignment: z.enum(["left", "center", "right", "auto"]).optional(),
704
+ verticalAlignment: z.enum(["top", "center", "bottom"]).optional(),
705
+ textWrap: z.boolean().optional(),
706
+ })).optional().describe("Array of formatting rules to apply"),
707
+ deleteDefaultTable: z.boolean().optional().describe("Delete the default 'Table 1' that Numbers auto-creates (default: true)"),
708
+ }, async ({ documentName, sheetName, tableName, data, headerRowCount, columnWidths, rowHeights, formatting, deleteDefaultTable }) => handleJXA(() => runJXA(`
709
+ const app = Application("Numbers");
710
+ const doc = app.documents.byName(params.documentName);
711
+
712
+ // Create the sheet
713
+ const sheet = app.Sheet();
714
+ doc.sheets.push(sheet);
715
+ sheet.name = params.sheetName;
716
+
717
+ // Delete the default "Table 1" that Numbers auto-creates
718
+ if (params.deleteDefaultTable !== false) {
719
+ try { app.delete(sheet.tables[0]); } catch(e) {}
720
+ }
721
+
722
+ // Create our table with the right dimensions
723
+ const dataRows = params.data.length;
724
+ const dataCols = Math.max(...params.data.map(r => r.length));
725
+ const table = app.Table({ rowCount: dataRows, columnCount: dataCols });
726
+ sheet.tables.push(table);
727
+ table.name = params.tableName || params.sheetName;
728
+
729
+ // Set header row count (default 0 = no grey header styling)
730
+ table.headerRowCount = (params.headerRowCount !== null && params.headerRowCount !== undefined) ? params.headerRowCount : 0;
731
+
732
+ // Write all data
733
+ const colCount = table.columnCount();
734
+ for (let r = 0; r < dataRows; r++) {
735
+ for (let c = 0; c < params.data[r].length; c++) {
736
+ const val = params.data[r][c];
737
+ if (val !== null) {
738
+ table.cells[r * colCount + c].value = val;
739
+ }
740
+ }
741
+ }
742
+
743
+ // Set column widths
744
+ if (params.columnWidths) {
745
+ for (let i = 0; i < Math.min(params.columnWidths.length, colCount); i++) {
746
+ table.columns[i].width = params.columnWidths[i];
747
+ }
748
+ }
749
+
750
+ // Set row heights
751
+ if (params.rowHeights) {
752
+ for (let i = 0; i < Math.min(params.rowHeights.length, dataRows); i++) {
753
+ table.rows[i].height = params.rowHeights[i];
754
+ }
755
+ }
756
+
757
+ // Apply formatting
758
+ function hexToRGB(hex) {
759
+ return [parseInt(hex.slice(1,3),16)*257, parseInt(hex.slice(3,5),16)*257, parseInt(hex.slice(5,7),16)*257];
760
+ }
761
+
762
+ if (params.formatting) {
763
+ for (const fmt of params.formatting) {
764
+ let cells = [];
765
+ if (fmt.cellRange.includes(":")) {
766
+ cells = table.ranges[fmt.cellRange].cells();
767
+ } else {
768
+ cells = [table.cells[fmt.cellRange]];
769
+ }
770
+ for (const cell of cells) {
771
+ if (fmt.fontSize !== undefined) cell.fontSize = fmt.fontSize;
772
+ if (fmt.fontName !== undefined) cell.fontName = fmt.fontName;
773
+ if (fmt.textColor !== undefined) cell.textColor = hexToRGB(fmt.textColor);
774
+ if (fmt.backgroundColor !== undefined) cell.backgroundColor = hexToRGB(fmt.backgroundColor);
775
+ if (fmt.alignment !== undefined) cell.alignment = fmt.alignment;
776
+ if (fmt.verticalAlignment !== undefined) cell.verticalAlignment = fmt.verticalAlignment;
777
+ if (fmt.textWrap !== undefined) cell.textWrap = fmt.textWrap;
778
+ if (fmt.bold !== undefined || fmt.italic !== undefined) {
779
+ let fontName = fmt.fontName || cell.fontName();
780
+ const baseName = fontName.replace(/[- ]?(Bold ?Italic|BoldItalic|Bold|Italic)$/i, "").trim();
781
+ const wantBold = fmt.bold !== undefined ? fmt.bold : /Bold/i.test(fontName);
782
+ const wantItalic = fmt.italic !== undefined ? fmt.italic : /Italic/i.test(fontName);
783
+ let suffix = "";
784
+ if (wantBold && wantItalic) suffix = "-BoldItalic";
785
+ else if (wantBold) suffix = "-Bold";
786
+ else if (wantItalic) suffix = "-Italic";
787
+ cell.fontName = baseName + suffix;
788
+ }
789
+ }
790
+ }
791
+ }
792
+
793
+ return JSON.stringify({
794
+ sheetName: sheet.name(),
795
+ tableName: table.name(),
796
+ rows: dataRows,
797
+ columns: dataCols,
798
+ headerRowCount: table.headerRowCount(),
799
+ });
800
+ `, {
801
+ documentName, sheetName,
802
+ tableName: tableName ?? null,
803
+ data,
804
+ headerRowCount: headerRowCount ?? null,
805
+ columnWidths: columnWidths ?? null,
806
+ rowHeights: rowHeights ?? null,
807
+ formatting: formatting ?? null,
808
+ deleteDefaultTable: deleteDefaultTable ?? true,
809
+ })));
687
810
  }
@@ -194,9 +194,9 @@ export function registerPagesTools(server) {
194
194
  if (fmt.italic !== undefined) paragraph.italic = fmt.italic;
195
195
  if (fmt.textColor !== undefined) {
196
196
  const hex = fmt.textColor;
197
- const r = parseInt(hex.slice(1, 3), 16) / 255;
198
- const g = parseInt(hex.slice(3, 5), 16) / 255;
199
- const b = parseInt(hex.slice(5, 7), 16) / 255;
197
+ const r = parseInt(hex.slice(1, 3), 16) * 257;
198
+ const g = parseInt(hex.slice(3, 5), 16) * 257;
199
+ const b = parseInt(hex.slice(5, 7), 16) * 257;
200
200
  paragraph.color = [r, g, b];
201
201
  }
202
202
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iwork-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "MCP server for Apple iWork (Numbers, Pages, Keynote) automation",
5
5
  "license": "MIT",
6
6
  "type": "module",