This is page 2 of 2. Use http://codebase.md/k-jarzyna/mcp-miro?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .env.template
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│ ├── client.ts
│ ├── index.ts
│ ├── server-response.ts
│ ├── server.ts
│ ├── tool-bootstrapper.ts
│ ├── tool.ts
│ └── tools
│ ├── addProjectMember.ts
│ ├── attachTag.ts
│ ├── copyBoard.ts
│ ├── createAppCardItem.ts
│ ├── createBoard.ts
│ ├── createBoardExportJob.ts
│ ├── createCardItem.ts
│ ├── createConnector.ts
│ ├── createDocumentItem.ts
│ ├── createEmbedItem.ts
│ ├── createFrameItem.ts
│ ├── createGroup.ts
│ ├── createImageItemUsingFileFromDevice.ts
│ ├── createImageItemUsingUrl.ts
│ ├── createItemsInBulk.ts
│ ├── createItemsInBulkUsingFile.ts
│ ├── createMindmapNode.ts
│ ├── createShapeItem.ts
│ ├── createStickyNoteItem.ts
│ ├── createTag.ts
│ ├── createTextItem.ts
│ ├── deleteAppCardItem.ts
│ ├── deleteBoard.ts
│ ├── deleteCardItem.ts
│ ├── deleteConnector.ts
│ ├── deleteDocumentItem.ts
│ ├── deleteEmbedItem.ts
│ ├── deleteFrameItem.ts
│ ├── deleteGroup.ts
│ ├── deleteImageItem.ts
│ ├── deleteItem.ts
│ ├── deleteMindmapNode.ts
│ ├── deleteShapeItem.ts
│ ├── deleteStickyNoteItem.ts
│ ├── deleteTag.ts
│ ├── deleteTextItem.ts
│ ├── detachTag.ts
│ ├── getAllBoardMembers.ts
│ ├── getAllCases.ts
│ ├── getAllGroups.ts
│ ├── getAllLegalHolds.ts
│ ├── getAllTags.ts
│ ├── getAppCardItem.ts
│ ├── getAuditLogs.ts
│ ├── getBoardClassification.ts
│ ├── getBoardContentLogs.ts
│ ├── getBoardExportJobResults.ts
│ ├── getBoardExportJobStatus.ts
│ ├── getCardItem.ts
│ ├── getCase.ts
│ ├── getConnectors.ts
│ ├── getDocumentItem.ts
│ ├── getEmbedItem.ts
│ ├── getFrameItem.ts
│ ├── getGroup.ts
│ ├── getGroupItems.ts
│ ├── getImageItem.ts
│ ├── getItemsOnBoard.ts
│ ├── getItemTags.ts
│ ├── getLegalHold.ts
│ ├── getLegalHoldContentItems.ts
│ ├── getMindmapNode.ts
│ ├── getMindmapNodes.ts
│ ├── getOrganizationInfo.ts
│ ├── getOrganizationMember.ts
│ ├── getOrganizationMembers.ts
│ ├── getProjectMember.ts
│ ├── getShapeItem.ts
│ ├── getSpecificBoard.ts
│ ├── getSpecificBoardMember.ts
│ ├── getSpecificConnector.ts
│ ├── getSpecificItem.ts
│ ├── getStickyNoteItem.ts
│ ├── getTag.ts
│ ├── getTextItem.ts
│ ├── listBoards.ts
│ ├── removeBoardMember.ts
│ ├── removeProjectMember.ts
│ ├── shareBoard.ts
│ ├── ungroupItems.ts
│ ├── updateAppCardItem.ts
│ ├── updateBoard.ts
│ ├── updateBoardClassification.ts
│ ├── updateBoardMember.ts
│ ├── updateCardItem.ts
│ ├── updateConnector.ts
│ ├── updateDocumentItem.ts
│ ├── updateEmbedItem.ts
│ ├── updateFrameItem.ts
│ ├── updateGroup.ts
│ ├── updateImageItem.ts
│ ├── updateImageItemUsingFileFromDevice.ts
│ ├── updateItemPosition.ts
│ ├── updateShapeItem.ts
│ ├── updateStickyNoteItem.ts
│ ├── updateTag.ts
│ └── updateTextItem.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/src/tools/createStickyNoteItem.ts:
--------------------------------------------------------------------------------
```typescript
1 | import MiroClient from '../client.js';
2 | import { z } from 'zod';
3 | import { ServerResponse } from '../server-response.js';
4 | import { ToolSchema } from '../tool.js';
5 |
6 | import { StickyNoteCreateRequest } from '@mirohq/miro-api/dist/model/stickyNoteCreateRequest.js';
7 | import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js';
8 |
9 | const validColors = [
10 | 'light_yellow', 'yellow', 'orange',
11 | 'light_green', 'green',
12 | 'light_blue', 'blue',
13 | 'light_pink', 'pink',
14 | 'light_purple', 'purple',
15 | 'black', 'gray', 'light_gray', 'white'
16 | ];
17 |
18 | const validTextAligns = ['left', 'center', 'right'];
19 |
20 | const validShapes = ['square', 'rectangle', 'circle', 'triangle', 'rhombus'];
21 |
22 | const createStickyNoteItemTool: ToolSchema = {
23 | name: "create-sticky-note-item",
24 | description: "Create a new sticky note item on a Miro board",
25 | args: {
26 | boardId: z.string().describe("Unique identifier (ID) of the board where the sticky note will be created"),
27 | data: z.object({
28 | content: z.string().describe("Text content of the sticky note"),
29 | shape: z.string().optional().nullish().describe("Shape of the sticky note (square, rectangle, circle, triangle, rhombus)")
30 | }).describe("The content and configuration of the sticky note"),
31 | position: z.object({
32 | x: z.number().describe("X coordinate of the sticky note"),
33 | y: z.number().describe("Y coordinate of the sticky note"),
34 | origin: z.string().optional().nullish().describe("Origin of the sticky note (center, top-left, etc.)"),
35 | relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)")
36 | }).describe("Position of the sticky note on the board"),
37 | geometry: z.object({
38 | width: z.number().optional().nullish().describe("Width of the sticky note"),
39 | height: z.number().optional().nullish().describe("Height of the sticky note")
40 | }).optional().nullish().describe("Dimensions of the sticky note"),
41 | style: z.object({
42 | fillColor: z.string().optional().nullish().describe("Fill color of the sticky note (use predefined values like 'light_yellow', 'light_green', etc.)"),
43 | textAlign: z.string().optional().nullish().describe("Alignment of the text (left, center, right)")
44 | }).optional().nullish().describe("Style configuration of the sticky note")
45 | },
46 | fn: async ({ boardId, data, position, geometry, style }) => {
47 | try {
48 | if (!boardId) {
49 | return ServerResponse.error("Board ID is required");
50 | }
51 |
52 | const createRequest = new StickyNoteCreateRequest();
53 |
54 | const stickyNoteData = new StickyNoteData();
55 | stickyNoteData.content = data.content;
56 |
57 | if (data.shape) {
58 | if (!validShapes.includes(data.shape)) {
59 | console.warn(`Invalid shape: ${data.shape}. Using default: square`);
60 | stickyNoteData.shape = 'square';
61 | } else {
62 | stickyNoteData.shape = data.shape;
63 | }
64 | } else {
65 | stickyNoteData.shape = 'square';
66 | }
67 |
68 | createRequest.data = stickyNoteData;
69 |
70 | const completePosition = {
71 | ...position,
72 | origin: position.origin || "center",
73 | relativeTo: position.relativeTo || "canvas_center"
74 | };
75 | createRequest.position = completePosition;
76 |
77 | if (geometry) {
78 | createRequest.geometry = geometry;
79 | }
80 |
81 | if (style) {
82 | const validatedStyle: Record<string, any> = {};
83 |
84 | if (style.fillColor) {
85 | if (!validColors.includes(style.fillColor)) {
86 | console.warn(`Invalid color: ${style.fillColor}. Using default: light_yellow`);
87 | validatedStyle.fillColor = 'light_yellow';
88 | } else {
89 | validatedStyle.fillColor = style.fillColor;
90 | }
91 | } else {
92 | validatedStyle.fillColor = 'light_yellow';
93 | }
94 |
95 | if (style.textAlign) {
96 | if (!validTextAligns.includes(style.textAlign)) {
97 | console.warn(`Invalid text alignment: ${style.textAlign}. Using default: center`);
98 | validatedStyle.textAlign = 'center';
99 | } else {
100 | validatedStyle.textAlign = style.textAlign;
101 | }
102 | } else {
103 | validatedStyle.textAlign = 'center';
104 | }
105 |
106 | createRequest.style = validatedStyle;
107 | } else {
108 | createRequest.style = {
109 | fillColor: 'light_yellow',
110 | textAlign: 'center'
111 | };
112 | }
113 |
114 | const result = await MiroClient.getApi().createStickyNoteItem(boardId, createRequest);
115 | return ServerResponse.text(JSON.stringify(result, null, 2));
116 | } catch (error) {
117 | return ServerResponse.error(error);
118 | }
119 | }
120 | }
121 |
122 | export default createStickyNoteItemTool;
```
--------------------------------------------------------------------------------
/src/tools/updateStickyNoteItem.ts:
--------------------------------------------------------------------------------
```typescript
1 | import MiroClient from '../client.js';
2 | import { z } from 'zod';
3 | import { ServerResponse } from '../server-response.js';
4 | import { ToolSchema } from '../tool.js';
5 |
6 | import { StickyNoteUpdateRequest } from '@mirohq/miro-api/dist/model/stickyNoteUpdateRequest.js';
7 | import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js';
8 |
9 | const validColors = [
10 | 'light_yellow', 'yellow', 'orange',
11 | 'light_green', 'green',
12 | 'light_blue', 'blue',
13 | 'light_pink', 'pink',
14 | 'light_purple', 'purple',
15 | 'black', 'gray', 'light_gray', 'white'
16 | ];
17 |
18 | const validTextAligns = ['left', 'center', 'right'];
19 |
20 | const validShapes = ['square', 'rectangle', 'circle', 'triangle', 'rhombus'];
21 |
22 | const updateStickyNoteItemTool: ToolSchema = {
23 | name: "update-sticky-note-item",
24 | description: "Update an existing sticky note item on a Miro board",
25 | args: {
26 | boardId: z.string().describe("Unique identifier (ID) of the board that contains the sticky note"),
27 | itemId: z.string().describe("Unique identifier (ID) of the sticky note that you want to update"),
28 | data: z.object({
29 | content: z.string().optional().nullish().describe("Updated text content of the sticky note"),
30 | shape: z.string().optional().nullish().describe("Updated shape of the sticky note (square, rectangle, circle, triangle, rhombus)")
31 | }).optional().nullish().describe("Updated content and configuration of the sticky note"),
32 | position: z.object({
33 | x: z.number().optional().nullish().describe("Updated X coordinate of the sticky note"),
34 | y: z.number().optional().nullish().describe("Updated Y coordinate of the sticky note"),
35 | origin: z.string().optional().nullish().describe("Origin of the sticky note (center, top-left, etc.)"),
36 | relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)")
37 | }).optional().nullish().describe("Updated position of the sticky note on the board"),
38 | geometry: z.object({
39 | width: z.number().optional().nullish().describe("Updated width of the sticky note"),
40 | height: z.number().optional().nullish().describe("Updated height of the sticky note")
41 | }).optional().nullish().describe("Updated dimensions of the sticky note"),
42 | style: z.object({
43 | fillColor: z.string().optional().nullish().describe("Updated fill color of the sticky note (use predefined values)"),
44 | textAlign: z.string().optional().nullish().describe("Updated alignment of the text (left, center, right)"),
45 | textColor: z.string().optional().nullish().describe("Updated color of the text on the sticky note")
46 | }).optional().nullish().describe("Updated style configuration of the sticky note")
47 | },
48 | fn: async ({ boardId, itemId, data, position, geometry, style }) => {
49 | try {
50 | if (!boardId) {
51 | return ServerResponse.error("Board ID is required");
52 | }
53 |
54 | if (!itemId) {
55 | return ServerResponse.error("Item ID is required");
56 | }
57 |
58 | const updateRequest = new StickyNoteUpdateRequest();
59 |
60 | if (data) {
61 | const stickyNoteData = new StickyNoteData();
62 |
63 | if (data.content !== undefined) {
64 | stickyNoteData.content = data.content;
65 | }
66 |
67 | if (data.shape !== undefined) {
68 | if (!validShapes.includes(data.shape)) {
69 | console.warn(`Invalid shape: ${data.shape}. Skipping this field.`);
70 | } else {
71 | stickyNoteData.shape = data.shape;
72 | }
73 | }
74 |
75 | if (Object.keys(stickyNoteData).length > 0) {
76 | updateRequest.data = stickyNoteData;
77 | }
78 | }
79 |
80 | if (position) {
81 | if (position.x !== undefined || position.y !== undefined) {
82 | const completePosition: Record<string, any> = {};
83 |
84 | if (position.x !== undefined) completePosition.x = position.x;
85 | if (position.y !== undefined) completePosition.y = position.y;
86 |
87 | completePosition.origin = position.origin || "center";
88 | completePosition.relativeTo = position.relativeTo || "canvas_center";
89 |
90 | updateRequest.position = completePosition;
91 | }
92 | }
93 |
94 | if (geometry && Object.keys(geometry).length > 0) {
95 | updateRequest.geometry = geometry;
96 | }
97 |
98 | if (style) {
99 | const validatedStyle: Record<string, any> = {};
100 |
101 | if (style.fillColor) {
102 | if (!validColors.includes(style.fillColor)) {
103 | console.warn(`Invalid color: ${style.fillColor}. Skipping this field.`);
104 | } else {
105 | validatedStyle.fillColor = style.fillColor;
106 | }
107 | }
108 |
109 | if (style.textAlign) {
110 | if (!validTextAligns.includes(style.textAlign)) {
111 | console.warn(`Invalid text alignment: ${style.textAlign}. Skipping this field.`);
112 | } else {
113 | validatedStyle.textAlign = style.textAlign;
114 | }
115 | }
116 |
117 | if (style.textColor) {
118 | validatedStyle.textColor = style.textColor;
119 | }
120 |
121 | if (Object.keys(validatedStyle).length > 0) {
122 | updateRequest.style = validatedStyle;
123 | }
124 | }
125 |
126 | const result = await MiroClient.getApi().updateStickyNoteItem(boardId, itemId, updateRequest);
127 | return ServerResponse.text(JSON.stringify(result, null, 2));
128 | } catch (error) {
129 | return ServerResponse.error(error);
130 | }
131 | }
132 | }
133 |
134 | export default updateStickyNoteItemTool;
```
--------------------------------------------------------------------------------
/src/tools/createItemsInBulkUsingFile.ts:
--------------------------------------------------------------------------------
```typescript
1 | import MiroClient from '../client.js';
2 | import { z } from 'zod';
3 | import { ServerResponse } from '../server-response.js';
4 | import { ToolSchema } from '../tool.js';
5 |
6 | import { StickyNoteCreateRequest } from '@mirohq/miro-api/dist/model/stickyNoteCreateRequest.js';
7 | import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js';
8 | import { CardCreateRequest } from '@mirohq/miro-api/dist/model/cardCreateRequest.js';
9 | import { CardData } from '@mirohq/miro-api/dist/model/cardData.js';
10 | import { TextCreateRequest } from '@mirohq/miro-api/dist/model/textCreateRequest.js';
11 | import { TextData } from '@mirohq/miro-api/dist/model/textData.js';
12 |
13 | const validStickyNoteColors = [
14 | 'light_yellow', 'yellow', 'orange',
15 | 'light_green', 'green',
16 | 'light_blue', 'blue',
17 | 'light_pink', 'pink',
18 | 'light_purple', 'purple',
19 | 'black', 'gray', 'white'
20 | ];
21 |
22 | const validStickyNoteShapes = ['square', 'rectangle'];
23 | const validTextAligns = ['left', 'center', 'right'];
24 |
25 | const createItemsInBulkUsingFileTool: ToolSchema = {
26 | name: "create-items-in-bulk-using-file",
27 | description: "Create multiple items on a Miro board in a single operation using a JSON file from device",
28 | args: {
29 | boardId: z.string().describe("Unique identifier (ID) of the board where the items will be created"),
30 | fileData: z.string().describe("Base64 encoded JSON file data containing items to create")
31 | },
32 | fn: async ({ boardId, fileData }) => {
33 | try {
34 | if (!boardId) {
35 | return ServerResponse.error("Board ID is required");
36 | }
37 |
38 | if (!fileData) {
39 | return ServerResponse.error("File data is required");
40 | }
41 |
42 | // Decode the base64 file data
43 | let jsonData;
44 | try {
45 | const base64Data = fileData.replace(/^data:application\/json;base64,/, '');
46 | const fileBuffer = Buffer.from(base64Data, 'base64');
47 | const fileContent = fileBuffer.toString('utf-8');
48 | jsonData = JSON.parse(fileContent);
49 | } catch (error) {
50 | return ServerResponse.error(`Error decoding or parsing JSON file data: ${error.message}`);
51 | }
52 |
53 | // Validate that the decoded data contains an 'items' array
54 | if (!jsonData.items || !Array.isArray(jsonData.items) || jsonData.items.length === 0) {
55 | return ServerResponse.error("JSON file must contain a non-empty 'items' array");
56 | }
57 |
58 | const items = jsonData.items;
59 | const results = [];
60 | const errors = [];
61 |
62 | const createPromises = items.map(async (item, index) => {
63 | try {
64 | // Validate item structure
65 | if (!item.type || !item.data || !item.position) {
66 | throw new Error(`Item at index ${index} is missing required fields (type, data, or position)`);
67 | }
68 |
69 | let result;
70 |
71 | if (item.type === 'sticky_note') {
72 | result = await createStickyNote(boardId, item);
73 | } else if (item.type === 'card') {
74 | result = await createCard(boardId, item);
75 | } else if (item.type === 'text') {
76 | result = await createText(boardId, item);
77 | } else {
78 | throw new Error(`Unsupported item type: ${item.type}`);
79 | }
80 |
81 | return { index, result };
82 | } catch (error) {
83 | return { index, error: error.message || String(error) };
84 | }
85 | });
86 |
87 | const promiseResults = await Promise.all(createPromises);
88 |
89 | for (const promiseResult of promiseResults) {
90 | const { index, result, error } = promiseResult;
91 | if (error) {
92 | errors.push({ index, error });
93 | } else if (result) {
94 | results.push({ index, item: result });
95 | }
96 | }
97 |
98 | return ServerResponse.text(JSON.stringify({
99 | created: results.length,
100 | failed: errors.length,
101 | results,
102 | errors
103 | }, null, 2));
104 |
105 | } catch (error) {
106 | return ServerResponse.error(error);
107 | }
108 | }
109 | }
110 |
111 | async function createStickyNote(boardId: string, item: any) {
112 | const createRequest = new StickyNoteCreateRequest();
113 |
114 | const stickyNoteData = new StickyNoteData();
115 | stickyNoteData.content = item.data.content;
116 | stickyNoteData.shape = item.data.shape || 'square';
117 |
118 | createRequest.data = stickyNoteData;
119 | createRequest.position = item.position;
120 |
121 | if (item.style) {
122 | const style: Record<string, string> = {};
123 |
124 | if (item.style.fillColor) {
125 | if (validStickyNoteColors.includes(item.style.fillColor)) {
126 | style.fillColor = item.style.fillColor;
127 | } else {
128 | style.fillColor = 'light_yellow';
129 | }
130 | }
131 |
132 | if (item.style.textAlign) {
133 | if (validTextAligns.includes(item.style.textAlign)) {
134 | style.textAlign = item.style.textAlign;
135 | } else {
136 | style.textAlign = 'center';
137 | }
138 | }
139 |
140 | createRequest.style = style;
141 | }
142 |
143 | return await MiroClient.getApi().createStickyNoteItem(boardId, createRequest);
144 | }
145 |
146 | async function createCard(boardId: string, item: any) {
147 | const createRequest = new CardCreateRequest();
148 |
149 | const cardData = new CardData();
150 | cardData.title = item.data.title;
151 |
152 | if (item.data.description) {
153 | cardData.description = item.data.description;
154 | }
155 |
156 | if (item.data.assigneeId) {
157 | cardData.assigneeId = item.data.assigneeId;
158 | }
159 |
160 | if (item.data.dueDate) {
161 | cardData.dueDate = new Date(item.data.dueDate);
162 | }
163 |
164 | createRequest.data = cardData;
165 | createRequest.position = item.position;
166 |
167 | if (item.style) {
168 | createRequest.style = item.style as Record<string, any>;
169 | }
170 |
171 | return await MiroClient.getApi().createCardItem(boardId, createRequest);
172 | }
173 |
174 | async function createText(boardId: string, item: any) {
175 | const createRequest = new TextCreateRequest();
176 |
177 | const textData = new TextData();
178 | textData.content = item.data.content;
179 |
180 | createRequest.data = textData;
181 | createRequest.position = item.position;
182 |
183 | if (item.style) {
184 | createRequest.style = item.style as Record<string, any>;
185 | }
186 |
187 | return await MiroClient.getApi().createTextItem(boardId, createRequest);
188 | }
189 |
190 | export default createItemsInBulkUsingFileTool;
```
--------------------------------------------------------------------------------
/src/tools/createItemsInBulk.ts:
--------------------------------------------------------------------------------
```typescript
1 | import MiroClient from '../client.js';
2 | import { z } from 'zod';
3 | import { ServerResponse } from '../server-response.js';
4 | import { ToolSchema } from '../tool.js';
5 |
6 | import { StickyNoteCreateRequest } from '@mirohq/miro-api/dist/model/stickyNoteCreateRequest.js';
7 | import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js';
8 | import { CardCreateRequest } from '@mirohq/miro-api/dist/model/cardCreateRequest.js';
9 | import { CardData } from '@mirohq/miro-api/dist/model/cardData.js';
10 | import { TextCreateRequest } from '@mirohq/miro-api/dist/model/textCreateRequest.js';
11 | import { TextData } from '@mirohq/miro-api/dist/model/textData.js';
12 |
13 | const validStickyNoteColors = [
14 | 'light_yellow', 'yellow', 'orange',
15 | 'light_green', 'green',
16 | 'light_blue', 'blue',
17 | 'light_pink', 'pink',
18 | 'light_purple', 'purple',
19 | 'black', 'gray', 'white'
20 | ];
21 |
22 | const validStickyNoteShapes = ['square', 'rectangle'];
23 | const validTextAligns = ['left', 'center', 'right'];
24 |
25 | const stickyNoteSchema = z.object({
26 | type: z.literal('sticky_note'),
27 | data: z.object({
28 | content: z.string().describe("Text content of the sticky note"),
29 | shape: z.enum(['square', 'rectangle']).optional().nullish().describe("Shape of the sticky note")
30 | }),
31 | position: z.object({
32 | x: z.number().describe("X coordinate"),
33 | y: z.number().describe("Y coordinate")
34 | }),
35 | style: z.object({
36 | fillColor: z.string().optional().nullish().describe("Fill color of the sticky note"),
37 | textAlign: z.enum(['left', 'center', 'right']).optional().nullish().describe("Text alignment")
38 | }).optional().nullish()
39 | });
40 |
41 | const cardSchema = z.object({
42 | type: z.literal('card'),
43 | data: z.object({
44 | title: z.string().describe("Title of the card"),
45 | description: z.string().optional().nullish().describe("Description of the card"),
46 | assigneeId: z.string().optional().nullish().describe("User ID of the assignee"),
47 | dueDate: z.string().optional().nullish().describe("Due date in ISO 8601 format")
48 | }),
49 | position: z.object({
50 | x: z.number().describe("X coordinate"),
51 | y: z.number().describe("Y coordinate")
52 | }),
53 | style: z.object({
54 | fillColor: z.string().optional().nullish().describe("Fill color"),
55 | textColor: z.string().optional().nullish().describe("Text color")
56 | }).optional().nullish()
57 | });
58 |
59 | const textSchema = z.object({
60 | type: z.literal('text'),
61 | data: z.object({
62 | content: z.string().describe("Text content")
63 | }),
64 | position: z.object({
65 | x: z.number().describe("X coordinate"),
66 | y: z.number().describe("Y coordinate")
67 | }),
68 | style: z.object({
69 | color: z.string().optional().nullish().describe("Text color (hex format, e.g. #000000)"),
70 | fontSize: z.number().optional().nullish().describe("Font size"),
71 | textAlign: z.enum(['left', 'center', 'right']).optional().nullish().describe("Text alignment")
72 | }).optional().nullish()
73 | });
74 |
75 | const itemSchema = z.discriminatedUnion('type', [
76 | stickyNoteSchema,
77 | cardSchema,
78 | textSchema
79 | ]);
80 |
81 | const createItemsInBulkTool: ToolSchema = {
82 | name: "create-items-in-bulk",
83 | description: "Create multiple items on a Miro board in a single operation",
84 | args: {
85 | boardId: z.string().describe("Unique identifier (ID) of the board where the items will be created"),
86 | items: z.array(itemSchema).describe("Array of items to create")
87 | },
88 | fn: async ({ boardId, items }) => {
89 | try {
90 | if (!boardId) {
91 | return ServerResponse.error("Board ID is required");
92 | }
93 |
94 | if (!items || !Array.isArray(items) || items.length === 0) {
95 | return ServerResponse.error("At least one item is required");
96 | }
97 |
98 | const results = [];
99 | const errors = [];
100 |
101 | const createPromises = items.map(async (item, index) => {
102 | try {
103 | let result;
104 |
105 | if (item.type === 'sticky_note') {
106 | result = await createStickyNote(boardId, item);
107 | } else if (item.type === 'card') {
108 | result = await createCard(boardId, item);
109 | } else if (item.type === 'text') {
110 | result = await createText(boardId, item);
111 | }
112 |
113 | return { index, result };
114 | } catch (error) {
115 | return { index, error: error.message || String(error) };
116 | }
117 | });
118 |
119 | const promiseResults = await Promise.all(createPromises);
120 |
121 | for (const promiseResult of promiseResults) {
122 | const { index, result, error } = promiseResult;
123 | if (error) {
124 | errors.push({ index, error });
125 | } else if (result) {
126 | results.push({ index, item: result });
127 | }
128 | }
129 |
130 | return ServerResponse.text(JSON.stringify({
131 | created: results.length,
132 | failed: errors.length,
133 | results,
134 | errors
135 | }, null, 2));
136 |
137 | } catch (error) {
138 | return ServerResponse.error(error);
139 | }
140 | }
141 | }
142 |
143 | async function createStickyNote(boardId: string, item: z.infer<typeof stickyNoteSchema>) {
144 | const createRequest = new StickyNoteCreateRequest();
145 |
146 | const stickyNoteData = new StickyNoteData();
147 | stickyNoteData.content = item.data.content;
148 | stickyNoteData.shape = item.data.shape || 'square';
149 |
150 | createRequest.data = stickyNoteData;
151 | createRequest.position = item.position;
152 |
153 | if (item.style) {
154 | const style: Record<string, string> = {};
155 |
156 | if (item.style.fillColor) {
157 | if (validStickyNoteColors.includes(item.style.fillColor)) {
158 | style.fillColor = item.style.fillColor;
159 | } else {
160 | style.fillColor = 'light_yellow';
161 | }
162 | }
163 |
164 | if (item.style.textAlign) {
165 | if (validTextAligns.includes(item.style.textAlign)) {
166 | style.textAlign = item.style.textAlign;
167 | } else {
168 | style.textAlign = 'center';
169 | }
170 | }
171 |
172 | createRequest.style = style;
173 | }
174 |
175 | return await MiroClient.getApi().createStickyNoteItem(boardId, createRequest);
176 | }
177 |
178 | async function createCard(boardId: string, item: z.infer<typeof cardSchema>) {
179 | const createRequest = new CardCreateRequest();
180 |
181 | const cardData = new CardData();
182 | cardData.title = item.data.title;
183 |
184 | if (item.data.description) {
185 | cardData.description = item.data.description;
186 | }
187 |
188 | if (item.data.assigneeId) {
189 | cardData.assigneeId = item.data.assigneeId;
190 | }
191 |
192 | if (item.data.dueDate) {
193 | cardData.dueDate = new Date(item.data.dueDate);
194 | }
195 |
196 | createRequest.data = cardData;
197 | createRequest.position = item.position;
198 |
199 | if (item.style) {
200 | createRequest.style = item.style as Record<string, any>;
201 | }
202 |
203 | return await MiroClient.getApi().createCardItem(boardId, createRequest);
204 | }
205 |
206 | // Helper function to create a text item
207 | async function createText(boardId: string, item: z.infer<typeof textSchema>) {
208 | const createRequest = new TextCreateRequest();
209 |
210 | const textData = new TextData();
211 | textData.content = item.data.content;
212 |
213 | createRequest.data = textData;
214 | createRequest.position = item.position;
215 |
216 | if (item.style) {
217 | createRequest.style = item.style as Record<string, any>;
218 | }
219 |
220 | return await MiroClient.getApi().createTextItem(boardId, createRequest);
221 | }
222 |
223 | export default createItemsInBulkTool;
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 |
6 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7 | import * as dotenv from "dotenv";
8 | import server from './server.js';
9 | import { ToolBootstrapper } from './tool-bootstrapper.js';
10 | import listBoardsTool from './tools/listBoards.js';
11 | import createBoardTool from './tools/createBoard.js';
12 | import updateBoardTool from './tools/updateBoard.js';
13 | import deleteBoardTool from './tools/deleteBoard.js';
14 | import copyBoardTool from './tools/copyBoard.js';
15 | import getSpecificBoardTool from './tools/getSpecificBoard.js';
16 | import getItemsOnBoardTool from './tools/getItemsOnBoard.js';
17 | import getSpecificItemTool from './tools/getSpecificItem.js';
18 | import updateItemPositionTool from './tools/updateItemPosition.js';
19 | import deleteItemTool from './tools/deleteItem.js';
20 | import createAppCardItemTool from './tools/createAppCardItem.js';
21 | import getAppCardItemTool from './tools/getAppCardItem.js';
22 | import updateAppCardItemTool from './tools/updateAppCardItem.js';
23 | import deleteAppCardItemTool from './tools/deleteAppCardItem.js';
24 | import createCardItemTool from './tools/createCardItem.js';
25 | import getCardItemTool from './tools/getCardItem.js';
26 | import updateCardItemTool from './tools/updateCardItem.js';
27 | import deleteCardItemTool from './tools/deleteCardItem.js';
28 | import createConnectorTool from './tools/createConnector.js';
29 | import getConnectorsTool from './tools/getConnectors.js';
30 | import getSpecificConnectorTool from './tools/getSpecificConnector.js';
31 | import updateConnectorTool from './tools/updateConnector.js';
32 | import deleteConnectorTool from './tools/deleteConnector.js';
33 | import createStickyNoteItemTool from './tools/createStickyNoteItem.js';
34 | import getStickyNoteItemTool from './tools/getStickyNoteItem.js';
35 | import updateStickyNoteItemTool from './tools/updateStickyNoteItem.js';
36 | import deleteStickyNoteItemTool from './tools/deleteStickyNoteItem.js';
37 | import createFrameItemTool from './tools/createFrameItem.js';
38 | import getFrameItemTool from './tools/getFrameItem.js';
39 | import updateFrameItemTool from './tools/updateFrameItem.js';
40 | import deleteFrameItemTool from './tools/deleteFrameItem.js';
41 | import createDocumentItemTool from './tools/createDocumentItem.js';
42 | import getDocumentItemTool from './tools/getDocumentItem.js';
43 | import updateDocumentItemTool from './tools/updateDocumentItem.js';
44 | import deleteDocumentItemTool from './tools/deleteDocumentItem.js';
45 | import createTextItemTool from './tools/createTextItem.js';
46 | import getTextItemTool from './tools/getTextItem.js';
47 | import updateTextItemTool from './tools/updateTextItem.js';
48 | import deleteTextItemTool from './tools/deleteTextItem.js';
49 | import createItemsInBulkTool from './tools/createItemsInBulk.js';
50 | import createItemsInBulkUsingFileTool from './tools/createItemsInBulkUsingFile.js';
51 | import createImageItemUsingUrlTool from './tools/createImageItemUsingUrl.js';
52 | import createImageItemUsingFileFromDeviceTool from './tools/createImageItemUsingFileFromDevice.js';
53 | import getImageItemTool from './tools/getImageItem.js';
54 | import updateImageItemTool from './tools/updateImageItem.js';
55 | import updateImageItemUsingFileFromDeviceTool from './tools/updateImageItemUsingFileFromDevice.js';
56 | import deleteImageItemTool from './tools/deleteImageItem.js';
57 | import createShapeItemTool from './tools/createShapeItem.js';
58 | import getShapeItemTool from './tools/getShapeItem.js';
59 | import updateShapeItemTool from './tools/updateShapeItem.js';
60 | import deleteShapeItemTool from './tools/deleteShapeItem.js';
61 | import createEmbedItemTool from './tools/createEmbedItem.js';
62 | import getEmbedItemTool from './tools/getEmbedItem.js';
63 | import updateEmbedItemTool from './tools/updateEmbedItem.js';
64 | import deleteEmbedItemTool from './tools/deleteEmbedItem.js';
65 | import createTagTool from './tools/createTag.js';
66 | import getTagTool from './tools/getTag.js';
67 | import getAllTagsTool from './tools/getAllTags.js';
68 | import updateTagTool from './tools/updateTag.js';
69 | import deleteTagTool from './tools/deleteTag.js';
70 | import attachTagTool from './tools/attachTag.js';
71 | import detachTagTool from './tools/detachTag.js';
72 | import getItemTagsTool from './tools/getItemTags.js';
73 | import getAllBoardMembers from './tools/getAllBoardMembers.js';
74 | import getSpecificBoardMemberTool from './tools/getSpecificBoardMember.js';
75 | import removeBoardMemberTool from './tools/removeBoardMember.js';
76 | import shareBoardTool from './tools/shareBoard.js';
77 | import updateBoardMemberTool from './tools/updateBoardMember.js';
78 | import getAllGroupsTool from './tools/getAllGroups.js';
79 | import getGroupTool from './tools/getGroup.js';
80 | import getGroupItemsTool from './tools/getGroupItems.js';
81 | import updateGroupTool from './tools/updateGroup.js';
82 | import ungroupItemsTool from './tools/ungroupItems.js';
83 | import deleteGroupTool from './tools/deleteGroup.js';
84 | import createGroupTool from './tools/createGroup.js';
85 | import createMindmapNodeTool from './tools/createMindmapNode.js';
86 | import getMindmapNodeTool from './tools/getMindmapNode.js';
87 | import getMindmapNodesTool from './tools/getMindmapNodes.js';
88 | import deleteMindmapNodeTool from './tools/deleteMindmapNode.js';
89 | import getBoardClassificationTool from './tools/getBoardClassification.js';
90 | import updateBoardClassificationTool from './tools/updateBoardClassification.js';
91 | import createBoardExportJobTool from './tools/createBoardExportJob.js';
92 | import getBoardExportJobStatusTool from './tools/getBoardExportJobStatus.js';
93 | import getBoardExportJobResultsTool from './tools/getBoardExportJobResults.js';
94 | import getAuditLogsTool from './tools/getAuditLogs.js';
95 | import getOrganizationInfoTool from './tools/getOrganizationInfo.js';
96 | import getOrganizationMembersTool from './tools/getOrganizationMembers.js';
97 | import getOrganizationMemberTool from './tools/getOrganizationMember.js';
98 | import addProjectMemberTool from './tools/addProjectMember.js';
99 | import getProjectMemberTool from './tools/getProjectMember.js';
100 | import removeProjectMemberTool from './tools/removeProjectMember.js';
101 | import getAllCasesTool from './tools/getAllCases.js';
102 | import getCaseTool from './tools/getCase.js';
103 | import getLegalHoldsTool from './tools/getAllLegalHolds.js';
104 | import getLegalHoldTool from './tools/getLegalHold.js';
105 | import getLegalHoldContentItemsTool from './tools/getLegalHoldContentItems.js';
106 | import getBoardContentLogsTool from './tools/getBoardContentLogs.js';
107 |
108 | dotenv.config();
109 |
110 | new ToolBootstrapper(server)
111 | .register(listBoardsTool)
112 | .register(createBoardTool)
113 | .register(updateBoardTool)
114 | .register(deleteBoardTool)
115 | .register(copyBoardTool)
116 | .register(getSpecificBoardTool)
117 | .register(getItemsOnBoardTool)
118 | .register(getSpecificItemTool)
119 | .register(updateItemPositionTool)
120 | .register(deleteItemTool)
121 | .register(createAppCardItemTool)
122 | .register(getAppCardItemTool)
123 | .register(updateAppCardItemTool)
124 | .register(deleteAppCardItemTool)
125 | .register(createCardItemTool)
126 | .register(getCardItemTool)
127 | .register(updateCardItemTool)
128 | .register(deleteCardItemTool)
129 | .register(createConnectorTool)
130 | .register(getConnectorsTool)
131 | .register(getSpecificConnectorTool)
132 | .register(updateConnectorTool)
133 | .register(deleteConnectorTool)
134 | .register(createStickyNoteItemTool)
135 | .register(getStickyNoteItemTool)
136 | .register(updateStickyNoteItemTool)
137 | .register(deleteStickyNoteItemTool)
138 | .register(createFrameItemTool)
139 | .register(getFrameItemTool)
140 | .register(updateFrameItemTool)
141 | .register(deleteFrameItemTool)
142 | .register(createDocumentItemTool)
143 | .register(getDocumentItemTool)
144 | .register(updateDocumentItemTool)
145 | .register(deleteDocumentItemTool)
146 | .register(createTextItemTool)
147 | .register(getTextItemTool)
148 | .register(updateTextItemTool)
149 | .register(deleteTextItemTool)
150 | .register(createItemsInBulkTool)
151 | .register(createImageItemUsingUrlTool)
152 | .register(createImageItemUsingFileFromDeviceTool)
153 | .register(getImageItemTool)
154 | .register(updateImageItemTool)
155 | .register(updateImageItemUsingFileFromDeviceTool)
156 | .register(deleteImageItemTool)
157 | .register(createShapeItemTool)
158 | .register(getShapeItemTool)
159 | .register(updateShapeItemTool)
160 | .register(deleteShapeItemTool)
161 | .register(createEmbedItemTool)
162 | .register(getEmbedItemTool)
163 | .register(updateEmbedItemTool)
164 | .register(deleteEmbedItemTool)
165 | .register(createTagTool)
166 | .register(getTagTool)
167 | .register(getAllTagsTool)
168 | .register(updateTagTool)
169 | .register(deleteTagTool)
170 | .register(attachTagTool)
171 | .register(detachTagTool)
172 | .register(getItemTagsTool)
173 | .register(getAllBoardMembers)
174 | .register(getSpecificBoardMemberTool)
175 | .register(removeBoardMemberTool)
176 | .register(shareBoardTool)
177 | .register(updateBoardMemberTool)
178 | .register(createGroupTool)
179 | .register(getAllGroupsTool)
180 | .register(getGroupTool)
181 | .register(getGroupItemsTool)
182 | .register(updateGroupTool)
183 | .register(ungroupItemsTool)
184 | .register(deleteGroupTool)
185 | .register(createItemsInBulkUsingFileTool)
186 | .register(createMindmapNodeTool)
187 | .register(getMindmapNodeTool)
188 | .register(getMindmapNodesTool)
189 | .register(deleteMindmapNodeTool)
190 | .register(getBoardClassificationTool)
191 | .register(updateBoardClassificationTool)
192 | .register(createBoardExportJobTool)
193 | .register(getBoardExportJobStatusTool)
194 | .register(getBoardExportJobResultsTool)
195 | .register(getAuditLogsTool)
196 | .register(getOrganizationInfoTool)
197 | .register(getOrganizationMembersTool)
198 | .register(getOrganizationMemberTool)
199 | .register(addProjectMemberTool)
200 | .register(getProjectMemberTool)
201 | .register(removeProjectMemberTool)
202 | .register(getAllCasesTool)
203 | .register(getCaseTool)
204 | .register(getLegalHoldsTool)
205 | .register(getLegalHoldTool)
206 | .register(getLegalHoldContentItemsTool)
207 | .register(getBoardContentLogsTool);
208 |
209 | async function main() {
210 | try {
211 | const transport = new StdioServerTransport();
212 | await server.connect(transport);
213 |
214 | if (!process.env.MIRO_ACCESS_TOKEN) {
215 | process.stderr.write("Warning: MIRO_ACCESS_TOKEN environment variable is not set. The server will not be able to connect to Miro.\n");
216 | }
217 | } catch (error) {
218 | process.stderr.write(`Server error: ${error}\n`);
219 | process.exit(1);
220 | }
221 | }
222 |
223 | main().catch(error => {
224 | process.stderr.write(`Fatal error: ${error}\n`);
225 | process.exit(1);
226 | });
```