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 | }); ```