# 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 -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- ``` # Miro API Configuration # Get your access token from https://developers.miro.com/ MIRO_ACCESS_TOKEN=your_miro_access_token_here ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Dependency directories node_modules/ # TypeScript compiled output build/ dist/ # Environment files .env # IDE files .idea/ .vscode/ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # OS specific .DS_Store Thumbs.db ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # MCP Miro Server [](https://archestra.ai/mcp-catalog/k-jarzyna__mcp-miro) [](https://smithery.ai/server/@k-jarzyna/mcp-miro) Model Context Protocol (MCP) server integrating with the [Miro](https://miro.com/) platform. It enables AI assistants (like Claude) to access Miro boards and manage their content through a standardized interface. --- ### Requirements - Node.js v16 or newer installed - Miro account with API token ### Generate Miro Access Token 1. Go to the [Miro Developer Portal](https://developers.miro.com/docs) 2. Create a new app or use an existing one 3. Make sure to create token with permission selected below 4. Generate OAuth token by selecting `Install app and get OAuth token` | Permission | Required | |-------------------|:--------:| | boards:read | ✅ | | boards:write | ✅ | | identity:read | ✅ | | identity:write | ✅ | | team:read | ✅ | | team:write | ✅ | | microphone:listen | ❌ | | screen:record | ❌ | | webcam:record | ❌ | | auditlogs:read | ❌ | | sessions:delete | ❌ | ### Connecting with Claude Desktop 1. Install [Claude Desktop](https://claude.ai/download) 2. Open or create the configuration file: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 3. Update it to include this server: ```json { "mcpServers":{ "miro":{ "command":"npx", "args":[ "-y", "@k-jarzyna/mcp-miro" ], "env":{ "MIRO_ACCESS_TOKEN":"your_miro_access_token" } } } } ``` 4. Restart Claude Desktop --- ## Available Tools and Resources #### Tools | Miro SDK Function | MCP Tool | Available | |-------------------|----------|-----------| | List boards | list-boards | ✅ | | Create board | create-board | ✅ | | Update board | update-board | ✅ | | Delete board | delete-board | ✅ | | Copy board | copy-board | ✅ | | Get specific board | get-specific-board | ✅ | | Get items on board | get-items-on-board | ✅ | | Get specific item | get-specific-item | ✅ | | Update item position | update-item-position | ✅ | | Delete item | delete-item | ✅ | | Create app card item | create-app-card-item | ✅ | | Get app card item | get-app-card-item | ✅ | | Update app card item | update-app-card-item | ✅ | | Delete app card item | delete-app-card-item | ✅ | | Create card item | create-card-item | ✅ | | Get card item | get-card-item | ✅ | | Update card item | update-card-item | ✅ | | Delete card item | delete-card-item | ✅ | | Create connector | create-connector | ✅ | | Get connectors | get-connectors | ✅ | | Get specific connector | get-specific-connector | ✅ | | Update connector | update-connector | ✅ | | Delete connector | delete-connector | ✅ | | Create sticky note item | create-sticky-note-item | ✅ | | Get sticky note item | get-sticky-note-item | ✅ | | Update sticky note item | update-sticky-note-item | ✅ | | Delete sticky note item | delete-sticky-note-item | ✅ | | Create frame | create-frame | ✅ | | Get frame item | get-frame-item | ✅ | | Update frame item | update-frame-item | ✅ | | Delete frame item | delete-frame-item | ✅ | | Create document item | create-document-item | ✅ | | Get document item | get-document-item | ✅ | | Update document item | update-document-item | ✅ | | Delete document item | delete-document-item | ✅ | | Create text item | create-text-item | ✅ | | Get text item | get-text-item | ✅ | | Update text item | update-text-item | ✅ | | Delete text item | delete-text-item | ✅ | | Create items in bulk | create-items-in-bulk | ✅ | | Create image item using URL | create-image-item-using-url | ✅ | | Create image item using file | create-image-item-using-file | ✅ | | Get image item | get-image-item | ✅ | | Update image item | update-image-item | ✅ | | Update image item using file | update-image-item-using-file | ✅ | | Delete image item | delete-image-item | ✅ | | Create shape item | create-shape-item | ✅ | | Get shape item | get-shape-item | ✅ | | Update shape item | update-shape-item | ✅ | | Delete shape item | delete-shape-item | ✅ | | Create embed item | create-embed-item | ✅ | | Get embed item | get-embed-item | ✅ | | Update embed item | update-embed-item | ✅ | | Delete embed item | delete-embed-item | ✅ | | Create tag | create-tag | ✅ | | Get tag | get-tag | ✅ | | Get all tags | get-all-tags | ✅ | | Update tag | update-tag | ✅ | | Delete tag | delete-tag | ✅ | | Attach tag | attach-tag | ✅ | | Detach tag | detach-tag | ✅ | | Get item tags | get-item-tags | ✅ | | Get all board members | get-all-board-members | ✅ | | Get specific board member | get-specific-board-member | ✅ | | Remove board member | remove-board-member | ✅ | | Share board | share-board | ✅ | | Update board member | update-board-member | ✅ | | Create group | create-group | ✅ | | Get all groups | get-all-groups | ✅ | | Get group | get-group | ✅ | | Get group items | get-group-items | ✅ | | Update group | update-group | ✅ | | Ungroup items | ungroup-items | ✅ | | Delete group | delete-group | ✅ | | Create items in bulk using file | create-items-in-bulk-using-file | ✅ | | Create mindmap node | create-mindmap-node | ✅ | | Get mindmap node | get-mindmap-node | ✅ | | Get mindmap nodes | get-mindmap-nodes | ✅ | | Delete mindmap node | delete-mindmap-node | ✅ | | Add project member | add-project-member | ✅ | | Create board export job | create-board-export-job | ✅ | | Get all cases | get-all-cases | ✅ | | Get all legal holds | get-all-legal-holds | ✅ | | Get audit logs | get-audit-logs | ✅ | | Get board classification | get-board-classification | ✅ | | Get board content logs | get-board-content-logs | ✅ | | Get board export job results | get-board-export-job-results | ✅ | | Get board export job status | get-board-export-job-status | ✅ | | Get case | get-case | ✅ | | Get legal hold | get-legal-hold | ✅ | | Get legal hold content items | get-legal-hold-content-items | ✅ | | Get organization info | get-organization-info | ✅ | | Get organization member | get-organization-member | ✅ | | Get organization members | get-organization-members | ✅ | | Get project member | get-project-member | ✅ | | Remove project member | remove-project-member | ✅ | | Update board classification | update-board-classification | ✅ | --- ## Local Development 1. Install dependencies: ```bash npm install ``` 2. Create a `.env` file based on the template: ```bash cp .env.template .env ``` 3. Edit the `.env` file and add your Miro access token 4. Build the server: ```bash npm run build ``` ### Running the Server To run the server: ```bash node build/index.js ``` --- ## License Apache License 2.0 This project is licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for details. ``` -------------------------------------------------------------------------------- /src/tool.ts: -------------------------------------------------------------------------------- ```typescript import { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ZodRawShape } from 'zod'; export type ToolSchema = { name: string; description: string; args: ZodRawShape; fn: ToolCallback<any>; } ``` -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; const server = new McpServer({ name: "mcp-miro", version: "1.0.0", capabilities: { tools: { listChanged: true }, resources: { listChanged: true } } }); export default server; ``` -------------------------------------------------------------------------------- /src/tool-bootstrapper.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ToolSchema } from './tool.js'; export class ToolBootstrapper { constructor( private readonly server: McpServer ) { } public register(tool: ToolSchema): this { this.server.tool(tool.name, tool.description, tool.args, tool.fn); return this; } } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile FROM node:lts-alpine # Create app directory WORKDIR /app # Install dependencies without running scripts COPY package.json package-lock.json ./ RUN npm install --ignore-scripts # Copy source and build COPY . . RUN npm run build # Expose no ports; use stdio CMD ["node", "build/index.js"] ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": false, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "noImplicitAny": false, "preserveSymlinks": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /src/server-response.ts: -------------------------------------------------------------------------------- ```typescript export class ServerResponse { content: { type: string, text: string }[]; isError: boolean = false; public static text(text: string): any { return Object.assign(new ServerResponse(), { content: [ { type: 'text', text } ] }) } public static error(err: string): any { return Object.assign(new ServerResponse(), { content: [ { type: 'text', text: typeof err === 'object' ? JSON.stringify(err) : err }, ], isError: true }) } } ``` -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- ```typescript import { MiroLowlevelApi } from "@mirohq/miro-api"; export class MiroClient { private static instance: MiroClient; private api: MiroLowlevelApi; private constructor() { const accessToken = process.env.MIRO_ACCESS_TOKEN; if (!accessToken) { throw new Error('MIRO_ACCESS_TOKEN environment variable is required'); } this.api = new MiroLowlevelApi(accessToken); } public static getApi(): MiroLowlevelApi { if (!MiroClient.instance) { MiroClient.instance = new MiroClient(); } return MiroClient.instance.api; } } export default MiroClient; ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml startCommand: type: stdio configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: - miroAccessToken properties: miroAccessToken: type: string description: Miro OAuth access token commandFunction: # A JS function that produces the CLI command based on the given config to start the MCP on stdio. |- (config) => ({ command: 'node', args: ['build/index.js'], env: { MIRO_ACCESS_TOKEN: config.miroAccessToken } }) exampleConfig: miroAccessToken: your_miro_access_token_here ``` -------------------------------------------------------------------------------- /src/tools/getOrganizationInfo.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getOrganizationInfoTool: ToolSchema = { name: "get-organization-info", description: "Retrieves organization information (Enterprise only)", args: { orgId: z.string().describe("id of the organization") }, fn: async ({ orgId }) => { try { const response = await MiroClient.getApi().enterpriseGetOrganization(orgId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving organization info: ${error}\n`); return ServerResponse.error(error); } } }; export default getOrganizationInfoTool; ``` -------------------------------------------------------------------------------- /src/tools/listBoards.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const listBoardsTool: ToolSchema = { name: "list-boards", description: "List all available Miro boards", args: { limit: z.number().optional().nullish().describe("Maximum number of boards to return (default: 50)"), offset: z.number().optional().nullish().describe("Offset for pagination (default: 0)") }, fn: async ({ limit = 50, offset = 0 }) => { try { const boardsData = await MiroClient.getApi().getBoards(); return ServerResponse.text(JSON.stringify(boardsData, null, 2)) } catch (error) { process.stderr.write(`Error fetching Miro boards: ${error}\n`); return ServerResponse.error(error) } } } export default listBoardsTool; ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "@k-jarzyna/mcp-miro", "version": "1.0.9", "main": "build/index.js", "type": "module", "scripts": { "build": "tsc", "clean": "rm -rf build/", "prebuild": "npm run clean", "postbuild": "chmod +x build/index.js" }, "bin": { "mcp-miro": "build/index.js" }, "keywords": [], "author": "Konrad Jarzyna", "license": "Apache-2.0", "description": "Miro integration for Model Context Protocol", "dependencies": { "@mirohq/miro-api": "^2.2.4", "@modelcontextprotocol/sdk": "^1.8.0", "dotenv": "^16.4.7", "zod": "^3.24.2" }, "devDependencies": { "@types/node": "^22.14.0", "typescript": "^5.8.2" }, "repository": { "type": "git", "url": "https://github.com/k-jarzyna/mcp-miro" }, "files": [ "build" ], "homepage": "https://github.com/k-jarzyna/mcp-miro" } ``` -------------------------------------------------------------------------------- /src/tools/getCase.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getCaseTool: ToolSchema = { name: "get-case", description: "Retrieves information about a specific eDiscovery case (Enterprise only)", args: { orgId: z.string().describe("The ID of the organization for which you want to retrieve the case information"), caseId: z.string().describe("The ID of the case you want to retrieve") }, fn: async ({ orgId, caseId }) => { try { const response = await MiroClient.getApi().getCase(orgId, caseId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving case: ${error}\n`); return ServerResponse.error(error); } } }; export default getCaseTool; ``` -------------------------------------------------------------------------------- /src/tools/getSpecificBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getSpecificBoardTool: ToolSchema = { name: "get-specific-board", description: "Retrieve information about a specific Miro board by its ID", args: { boardId: z.string().describe("Unique identifier (ID) of the board that you want to retrieve") }, fn: async ({ boardId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const boardData = await MiroClient.getApi().getSpecificBoard(boardId); return ServerResponse.text(JSON.stringify(boardData, null, 2)); } catch (error) { process.stderr.write(`Error fetching specific Miro board: ${error}\n`); return ServerResponse.error(error); } } } export default getSpecificBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/getOrganizationMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getOrganizationMemberTool: ToolSchema = { name: "get-organization-member", description: "Retrieves information about a specific organization member (Enterprise only)", args: { orgId: z.string().describe("id of the organization"), memberId: z.string().describe("id of the organization member") }, fn: async ({ orgId, memberId }) => { try { const response = await MiroClient.getApi().enterpriseGetOrganizationMember(orgId, memberId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving organization member: ${error}\n`); return ServerResponse.error(error); } } }; export default getOrganizationMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/getBoardExportJobResults.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getBoardExportJobResultsTool: ToolSchema = { name: "get-board-export-job-results", description: "Retrieves the results of a board export job (Enterprise only)", args: { orgId: z.string().describe("Unique identifier of the organization"), jobId: z.string().describe("Unique identifier of the job") }, fn: async ({ orgId, jobId }) => { try { const response = await MiroClient.getApi().enterpriseBoardExportJobResults(orgId, jobId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving board export job results: ${error}\n`); return ServerResponse.error(error); } } }; export default getBoardExportJobResultsTool; ``` -------------------------------------------------------------------------------- /src/tools/getBoardExportJobStatus.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getBoardExportJobStatusTool: ToolSchema = { name: "get-board-export-job-status", description: "Retrieves the status of a board export job (Enterprise only)", args: { orgId: z.string().describe("Unique identifier of the organization"), jobId: z.string().describe("Unique identifier of the board export job") }, fn: async ({ orgId, jobId }) => { try { const response = await MiroClient.getApi().enterpriseBoardExportJobStatus(orgId, jobId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving board export job status: ${error}\n`); return ServerResponse.error(error); } } }; export default getBoardExportJobStatusTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteMindmapNode.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteMindmapNodeTool: ToolSchema = { name: "delete-mindmap-node", description: "Delete a mind map node from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board from which you want to delete the mind map node"), itemId: z.string().describe("Unique identifier (ID) of the mind map node that you want to delete") }, fn: async ({ boardId, itemId }) => { try { const response = await MiroClient.getApi().deleteMindmapNodeExperimental(boardId, itemId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error deleting Miro mind map node: ${error}\n`); return ServerResponse.error(error); } } }; export default deleteMindmapNodeTool; ``` -------------------------------------------------------------------------------- /src/tools/getTag.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getTagTool: ToolSchema = { name: "get-tag", description: "Retrieve information about a specific tag on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the tag"), tagId: z.string().describe("Unique identifier (ID) of the tag that you want to retrieve") }, fn: async ({ boardId, tagId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!tagId) { return ServerResponse.error("Tag ID is required"); } const result = await MiroClient.getApi().getTag(boardId, tagId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getTagTool; ``` -------------------------------------------------------------------------------- /src/tools/getMindmapNode.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getMindmapNodeTool: ToolSchema = { name: "get-mindmap-node", description: "Retrieve information about a specific mind map node on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board from which you want to retrieve a mind map node"), itemId: z.string().describe("Unique identifier (ID) of the mind map node that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { const response = await MiroClient.getApi().getMindmapNodeExperimental(boardId, itemId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving Miro mind map node: ${error}\n`); return ServerResponse.error(error); } } }; export default getMindmapNodeTool; ``` -------------------------------------------------------------------------------- /src/tools/getBoardClassification.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getBoardClassificationTool: ToolSchema = { name: "get-board-classification", description: "Retrieves board classification for a board (Enterprise only)", args: { orgId: z.string().describe("id of the organization"), teamId: z.string().describe("id of the team"), boardId: z.string().describe("Unique identifier of the board that you want to retrieve"), }, fn: async ({ orgId, teamId, boardId }) => { try { const response = await MiroClient.getApi().enterpriseDataclassificationBoardGet(orgId, teamId, boardId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving board classification: ${error}\n`); return ServerResponse.error(error); } } }; export default getBoardClassificationTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteTag.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteTagTool: ToolSchema = { name: "delete-tag", description: "Delete a specific tag from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the tag"), tagId: z.string().describe("Unique identifier (ID) of the tag that you want to delete") }, fn: async ({ boardId, tagId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!tagId) { return ServerResponse.error("Tag ID is required"); } await MiroClient.getApi().deleteTag(boardId, tagId); return ServerResponse.text(JSON.stringify({ success: true, message: "Tag deleted successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteTagTool; ``` -------------------------------------------------------------------------------- /src/tools/getEmbedItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getEmbedItemTool: ToolSchema = { name: "get-embed-item", description: "Retrieve information about a specific embed item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the embed"), itemId: z.string().describe("Unique identifier (ID) of the embed that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const result = await MiroClient.getApi().getEmbedItem(boardId, itemId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getEmbedItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getShapeItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getShapeItemTool: ToolSchema = { name: "get-shape-item", description: "Retrieve information about a specific shape item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the shape"), itemId: z.string().describe("Unique identifier (ID) of the shape that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const result = await MiroClient.getApi().getShapeItem(boardId, itemId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getShapeItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getItemTags.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getItemTagsTool: ToolSchema = { name: "get-item-tags", description: "Retrieve all tags attached to a specific item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the item"), itemId: z.string().describe("Unique identifier (ID) of the item whose tags you want to retrieve"), }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const result = await MiroClient.getApi().getTagsFromItem(boardId, itemId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getItemTagsTool; ``` -------------------------------------------------------------------------------- /src/tools/getCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getCardItemTool: ToolSchema = { name: "get-card-item", description: "Retrieve information about a specific card item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the card"), itemId: z.string().describe("Unique identifier (ID) of the card that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const itemData = await MiroClient.getApi().getCardItem(boardId, itemId); return ServerResponse.text(JSON.stringify(itemData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/removeBoardMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const removeBoardMemberTool: ToolSchema = { name: "remove-board-member", description: "Remove a specific member from a Miro board", args: { boardId: z.string().describe("ID of the board"), memberId: z.string().describe("ID of the board member to remove") }, fn: async ({ boardId, memberId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!memberId) { return ServerResponse.error("Member ID is required"); } const result = await MiroClient.getApi().removeBoardMember(boardId, memberId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error removing board member: ${error}\n`); return ServerResponse.error(error); } } } export default removeBoardMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteBoardTool: ToolSchema = { name: "delete-board", description: "Delete a Miro board by its ID. Deleted boards go to Trash (on paid plans) and can be restored via UI within 90 days after deletion.", args: { boardId: z.string().describe("Unique identifier (ID) of the board that you want to delete") }, fn: async ({ boardId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } await MiroClient.getApi().deleteBoard(boardId); return ServerResponse.text(JSON.stringify({ success: true, message: `Board ${boardId} has been successfully deleted.` }, null, 2)); } catch (error) { process.stderr.write(`Error deleting Miro board: ${error}\n`); return ServerResponse.error(error); } } } export default deleteBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/getTextItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getTextItemTool: ToolSchema = { name: "get-text-item", description: "Retrieve information about a specific text item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the text item"), itemId: z.string().describe("Unique identifier (ID) of the text item that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const textData = await MiroClient.getApi().getTextItem(boardId, itemId); return ServerResponse.text(JSON.stringify(textData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getTextItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getGroup.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getGroupTool: ToolSchema = { name: "get-group", description: "Retrieve information about a specific group on a Miro board", args: { boardId: z.string().describe("ID of the board that contains the group"), groupId: z.string().describe("ID of the group that you want to retrieve") }, fn: async ({ boardId, groupId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!groupId) { return ServerResponse.error("Group ID is required"); } const result = await MiroClient.getApi().getGroupById(boardId, groupId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error retrieving group: ${error}\n`); return ServerResponse.error(error); } } } export default getGroupTool; ``` -------------------------------------------------------------------------------- /src/tools/getSpecificItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getSpecificItemTool: ToolSchema = { name: "get-specific-item", description: "Retrieve information about a specific item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the item"), itemId: z.string().describe("Unique identifier (ID) of the item that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const itemData = await MiroClient.getApi().getSpecificItem(boardId, itemId); return ServerResponse.text(JSON.stringify(itemData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getSpecificItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteEmbedItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteEmbedItemTool: ToolSchema = { name: "delete-embed-item", description: "Delete a specific embed item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the embed"), itemId: z.string().describe("Unique identifier (ID) of the embed that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteEmbedItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Embed deleted successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteEmbedItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteShapeItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteShapeItemTool: ToolSchema = { name: "delete-shape-item", description: "Delete a specific shape item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the shape"), itemId: z.string().describe("Unique identifier (ID) of the shape that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteShapeItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Shape deleted successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteShapeItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getAppCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getAppCardItemTool: ToolSchema = { name: "get-app-card-item", description: "Retrieve information about a specific app card item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the app card"), itemId: z.string().describe("Unique identifier (ID) of the app card that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const itemData = await MiroClient.getApi().getAppCardItem(boardId, itemId); return ServerResponse.text(JSON.stringify(itemData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getAppCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteTextItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteTextItemTool: ToolSchema = { name: "delete-text-item", description: "Delete a specific text item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the text item"), itemId: z.string().describe("Unique identifier (ID) of the text item that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteTextItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Text item successfully deleted" })); } catch (error) { return ServerResponse.error(error); } } } export default deleteTextItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getDocumentItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getDocumentItemTool: ToolSchema = { name: "get-document-item", description: "Retrieve information about a specific document item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the document"), itemId: z.string().describe("Unique identifier (ID) of the document that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const documentData = await MiroClient.getApi().getDocumentItem(boardId, itemId); return ServerResponse.text(JSON.stringify(documentData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getDocumentItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getImageItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getImageItemTool: ToolSchema = { name: "get-image-item", description: "Retrieve information about a specific image item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the image"), itemId: z.string().describe("Unique identifier (ID) of the image that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } // Use generic getItem for image item const result = await MiroClient.getApi().getImageItem(boardId, itemId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getImageItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getLegalHold.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getLegalHoldTool: ToolSchema = { name: "get-legal-hold", description: "Retrieves information about a specific legal hold (Enterprise only)", args: { orgId: z.string().describe("The ID of the organization for which you want to retrieve the legal hold information"), caseId: z.string().describe("The ID of the case for which you want to retrieve the legal hold information"), legalHoldId: z.string().describe("The ID of the legal hold you want to retrieve") }, fn: async ({ orgId, caseId, legalHoldId }) => { try { const response = await MiroClient.getApi().getLegalHold(orgId, caseId, legalHoldId); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving legal hold: ${error}\n`); return ServerResponse.error(error); } } }; export default getLegalHoldTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteDocumentItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteDocumentItemTool: ToolSchema = { name: "delete-document-item", description: "Delete a specific document item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the document"), itemId: z.string().describe("Unique identifier (ID) of the document that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteDocumentItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Document item successfully deleted" })); } catch (error) { return ServerResponse.error(error); } } } export default deleteDocumentItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteImageItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteImageItemTool: ToolSchema = { name: "delete-image-item", description: "Delete a specific image item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the image"), itemId: z.string().describe("Unique identifier (ID) of the image that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } // Use generic deleteItem await MiroClient.getApi().deleteItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Image deleted successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteImageItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getAllCases.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getAllCasesTool: ToolSchema = { name: "get-all-cases", description: "Retrieves the list of eDiscovery cases in an organization (Enterprise only)", args: { limit: z.number().describe("The maximum number of items in the result list"), orgId: z.string().describe("The ID of the organization for which you want to retrieve the list of cases"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ limit, orgId, cursor }) => { try { const query: any = {}; if (cursor) query.cursor = cursor; const response = await MiroClient.getApi().getAllCases(limit, orgId, query); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving cases: ${error}\n`); return ServerResponse.error(error); } } }; export default getAllCasesTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteItemTool: ToolSchema = { name: "delete-item", description: "Delete a specific item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the item"), itemId: z.string().describe("Unique identifier (ID) of the item that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: `Item ${itemId} successfully deleted from board ${boardId}` }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getItemsOnBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getItemsOnBoardTool: ToolSchema = { name: "get-items-on-board", description: "Retrieve all items on a specific Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board whose items you want to retrieve"), limit: z.number().optional().nullish().describe("Maximum number of items to return (default: 50)"), offset: z.number().optional().nullish().describe("Offset for pagination (default: 0)") }, fn: async ({ boardId, limit = 50, offset = 0 }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const itemsData = await MiroClient.getApi().getItems(boardId, { limit: limit.toString(), }); return ServerResponse.text(JSON.stringify(itemsData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getItemsOnBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/getStickyNoteItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getStickyNoteItemTool: ToolSchema = { name: "get-sticky-note-item", description: "Retrieve information about a specific sticky note item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the sticky note"), itemId: z.string().describe("Unique identifier (ID) of the sticky note that you want to retrieve") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const stickyNoteData = await MiroClient.getApi().getStickyNoteItem(boardId, itemId); return ServerResponse.text(JSON.stringify(stickyNoteData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getStickyNoteItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getSpecificBoardMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getSpecificBoardMemberTool: ToolSchema = { name: "get-specific-board-member", description: "Retrieve details of a specific member on a Miro board", args: { boardId: z.string().describe("ID of the board"), memberId: z.string().describe("ID of the specific board member to retrieve") }, fn: async ({ boardId, memberId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!memberId) { return ServerResponse.error("Member ID is required"); } const result = await MiroClient.getApi().getSpecificBoardMember(boardId, memberId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error retrieving specific board member: ${error}\n`); return ServerResponse.error(error); } } } export default getSpecificBoardMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/getFrameItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getFrameItemTool: ToolSchema = { name: "get-frame-item", description: "Retrieve information for a specific frame on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the frame that you want to retrieve"), itemId: z.string().describe("Unique identifier (ID) of the frame that you want to retrieve") }, fn: async ({ boardId, itemId }: { boardId: string, itemId: string }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const result = await MiroClient.getApi().getFrameItem(boardId, itemId); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getFrameItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteConnector.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteConnectorTool: ToolSchema = { name: "delete-connector", description: "Delete a specific connector from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the connector"), connectorId: z.string().describe("Unique identifier (ID) of the connector that you want to delete") }, fn: async ({ boardId, connectorId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!connectorId) { return ServerResponse.error("Connector ID is required"); } await MiroClient.getApi().deleteConnector(boardId, connectorId); return ServerResponse.text(JSON.stringify({ success: true, message: "Connector deleted successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteConnectorTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteFrameItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteFrameItemTool: ToolSchema = { name: "delete-frame-item", description: "Delete a frame from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board from which you want to delete the frame"), itemId: z.string().describe("Unique identifier (ID) of the frame that you want to delete") }, fn: async ({ boardId, itemId }: { boardId: string, itemId: string }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteFrameItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Frame successfully deleted" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteFrameItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getSpecificConnector.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getSpecificConnectorTool: ToolSchema = { name: "get-specific-connector", description: "Retrieve information about a specific connector on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the connector"), connectorId: z.string().describe("Unique identifier (ID) of the connector that you want to retrieve") }, fn: async ({ boardId, connectorId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!connectorId) { return ServerResponse.error("Connector ID is required"); } const connectorData = await MiroClient.getApi().getConnector(boardId, connectorId); return ServerResponse.text(JSON.stringify(connectorData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getSpecificConnectorTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteStickyNoteItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteStickyNoteItemTool: ToolSchema = { name: "delete-sticky-note-item", description: "Delete a specific sticky note item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the sticky note"), itemId: z.string().describe("Unique identifier (ID) of the sticky note that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteStickyNoteItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: "Sticky note deleted successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteStickyNoteItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteCardItemTool: ToolSchema = { name: "delete-card-item", description: "Delete a specific card item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the card"), itemId: z.string().describe("Unique identifier (ID) of the card that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteCardItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: `Card Item ${itemId} successfully deleted from board ${boardId}` }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteAppCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteAppCardItemTool: ToolSchema = { name: "delete-app-card-item", description: "Delete a specific app card item from a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the app card"), itemId: z.string().describe("Unique identifier (ID) of the app card that you want to delete") }, fn: async ({ boardId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().deleteAppCardItem(boardId, itemId); return ServerResponse.text(JSON.stringify({ success: true, message: `App Card Item ${itemId} successfully deleted from board ${boardId}` }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default deleteAppCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/getAllGroups.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getAllGroupsTool: ToolSchema = { name: "get-all-groups", description: "Retrieve all groups on a Miro board", args: { boardId: z.string().describe("ID of the board whose groups you want to retrieve"), limit: z.number().optional().nullish().describe("Maximum number of groups to return (default: 50)"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ boardId, limit, cursor }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const options: any = {}; if (limit) options.limit = limit; if (cursor) options.cursor = cursor; const result = await MiroClient.getApi().getAllGroups(boardId, options); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error retrieving groups: ${error}\n`); return ServerResponse.error(error); } } } export default getAllGroupsTool; ``` -------------------------------------------------------------------------------- /src/tools/getAllBoardMembers.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getAllBoardMembersTool: ToolSchema = { name: "get-all-board-members", description: "Retrieve all members of a specific Miro board", args: { boardId: z.string().describe("ID of the board to retrieve members from"), limit: z.number().optional().nullish().describe("Maximum number of members to retrieve (default: 50)"), offset: z.number().optional().nullish().describe("Offset for pagination (default: 0)") }, fn: async ({ boardId, limit = 50, offset = 0 }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const result = await MiroClient.getApi().getBoardMembers(boardId, { limit: limit.toString(), offset: offset.toString() }); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error retrieving board members: ${error}\n`); return ServerResponse.error(error); } } } export default getAllBoardMembersTool; ``` -------------------------------------------------------------------------------- /src/tools/createGroup.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const createGroupTool: ToolSchema = { name: "create-group", description: "Create a new group on a Miro board", args: { boardId: z.string().describe("ID of the board where the group will be created"), data: z.object({ items: z.array(z.string()).describe("List of item IDs to include in the group") }).describe("Group data with item IDs to include in the group") }, fn: async ({ boardId, data }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!data || !data.items || data.items.length === 0) { return ServerResponse.error("At least one item ID is required in the 'items' array"); } const result = await MiroClient.getApi().createGroup(boardId, { data }); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error creating group: ${error}\n`); return ServerResponse.error(error); } } } export default createGroupTool; ``` -------------------------------------------------------------------------------- /src/tools/removeProjectMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const removeProjectMemberTool: ToolSchema = { name: "remove-project-member", description: "Removes a member from a project (Enterprise only)", args: { orgId: z.string().describe("The ID of the organization to which the project belongs"), teamId: z.string().describe("The ID of the team to which the project belongs"), projectId: z.string().describe("The ID of the project from which you want to remove a member"), memberId: z.string().describe("The ID of the member that you want to remove from a project") }, fn: async ({ orgId, teamId, projectId, memberId }) => { try { const response = await MiroClient.getApi().enterpriseDeleteProjectMember( orgId, teamId, projectId, memberId ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error removing project member: ${error}\n`); return ServerResponse.error(error); } } }; export default removeProjectMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/shareBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const shareBoardTool: ToolSchema = { name: "share-board", description: "Share a Miro board with specific access level and optional team assignment", args: { boardId: z.string().describe("ID of the board to share"), accessLevel: z.enum(['private', 'view', 'comment', 'edit']).describe("Access level for shared board"), teamId: z.string().optional().nullish().describe("Team ID to assign the board to"), }, fn: async ({ boardId, accessLevel, teamId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const boardChanges = { sharingPolicy: { access: accessLevel }, teamId }; const result = await MiroClient.getApi().updateBoard(boardId, boardChanges); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error sharing Miro board: ${error}\n`); return ServerResponse.error(error); } } } export default shareBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/getMindmapNodes.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getMindmapNodesTool: ToolSchema = { name: "get-mindmap-nodes", description: "Retrieve a list of mind map nodes on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board from which you want to retrieve mind map nodes"), limit: z.number().optional().nullish().describe("Maximum number of results to return (default: 50)"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ boardId, limit, cursor }) => { try { // Prepare query parameters const query: any = {}; if (limit) query.limit = limit.toString(); if (cursor) query.cursor = cursor; const response = await MiroClient.getApi().getMindmapNodesExperimental(boardId, query); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving Miro mind map nodes: ${error}\n`); return ServerResponse.error(error); } } }; export default getMindmapNodesTool; ``` -------------------------------------------------------------------------------- /src/tools/getAllTags.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getAllTagsTool: ToolSchema = { name: "get-all-tags", description: "Retrieve all tags on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board for which you want to retrieve all tags"), limit: z.number().optional().nullish().describe("Maximum number of tags to return (default: 50)"), offset: z.number().optional().nullish().describe("Offset for pagination (default: 0)") }, fn: async ({ boardId, limit, offset }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const query: Record<string, any> = {}; if (limit !== undefined) { query.limit = limit; } if (offset !== undefined) { query.offset = offset; } const result = await MiroClient.getApi().getTagsFromBoard(boardId, query); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getAllTagsTool; ``` -------------------------------------------------------------------------------- /src/tools/getConnectors.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getConnectorsTool: ToolSchema = { name: "get-connectors", description: "Retrieve all connectors on a specific Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board whose connectors you want to retrieve"), limit: z.number().optional().nullish().describe("Maximum number of connectors to return (default: 50)"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ boardId, limit, cursor }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const queryParams: { limit?: string; cursor?: string } = {}; if (limit) queryParams.limit = limit.toString(); if (cursor) queryParams.cursor = cursor; const connectorsData = await MiroClient.getApi().getConnectors(boardId, queryParams); return ServerResponse.text(JSON.stringify(connectorsData, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default getConnectorsTool; ``` -------------------------------------------------------------------------------- /src/tools/getAllLegalHolds.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getLegalHoldsTool: ToolSchema = { name: "get-all-legal-holds", description: "Retrieves the list of all legal holds within a case (Enterprise only)", args: { limit: z.number().describe("The maximum number of items in the result list"), orgId: z.string().describe("The ID of the organization for which you want to retrieve the list of legal holds"), caseId: z.string().describe("The ID of the case for which you want to retrieve the list of legal holds"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ limit, orgId, caseId, cursor }) => { try { const query: any = {}; if (cursor) query.cursor = cursor; const response = await MiroClient.getApi().getAllLegalHolds(limit, orgId, caseId, query); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving legal holds: ${error}\n`); return ServerResponse.error(error); } } }; export default getLegalHoldsTool; ``` -------------------------------------------------------------------------------- /src/tools/attachTag.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const attachTagTool: ToolSchema = { name: "attach-tag", description: "Attach a tag to an item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the tag and item"), tagId: z.string().describe("Unique identifier (ID) of the tag that you want to attach"), itemId: z.string().describe("Unique identifier (ID) of the item to which you want to attach the tag") }, fn: async ({ boardId, tagId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!tagId) { return ServerResponse.error("Tag ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } await MiroClient.getApi().attachTagToItem(boardId, itemId, tagId); return ServerResponse.text(JSON.stringify({ success: true, message: "Tag attached successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default attachTagTool; ``` -------------------------------------------------------------------------------- /src/tools/getProjectMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getProjectMemberTool: ToolSchema = { name: "get-project-member", description: "Retrieves information about a specific project member (Enterprise only)", args: { orgId: z.string().describe("The ID of the organization to which the project belongs"), teamId: z.string().describe("The ID of the team to which the project belongs"), projectId: z.string().describe("The ID of the project from which you want to retrieve specific member information"), memberId: z.string().describe("The ID of the member for which you want to retrieve information") }, fn: async ({ orgId, teamId, projectId, memberId }) => { try { const response = await MiroClient.getApi().enterpriseGetProjectMember( orgId, teamId, projectId, memberId ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving project member: ${error}\n`); return ServerResponse.error(error); } } }; export default getProjectMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/deleteGroup.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const deleteGroupTool: ToolSchema = { name: "delete-group", description: "Delete a specific group from a Miro board", args: { boardId: z.string().describe("ID of the board that contains the group"), groupId: z.string().describe("ID of the group that you want to delete"), deleteItems: z.boolean().optional().nullish().describe("Indicates whether the items should be removed. Set to true to delete items in the group, false to keep them") }, fn: async ({ boardId, groupId, deleteItems }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!groupId) { return ServerResponse.error("Group ID is required"); } await MiroClient.getApi().deleteGroup(boardId, groupId, deleteItems ?? false); return ServerResponse.text(JSON.stringify({ success: true, message: "Group successfully deleted" }, null, 2)); } catch (error) { process.stderr.write(`Error deleting group: ${error}\n`); return ServerResponse.error(error); } } } export default deleteGroupTool; ``` -------------------------------------------------------------------------------- /src/tools/updateBoardClassification.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateBoardClassificationTool: ToolSchema = { name: "update-board-classification", description: "Updates board classification for an existing board (Enterprise only)", args: { orgId: z.string().describe("id of the organization"), teamId: z.string().describe("id of the team"), boardId: z.string().describe("Unique identifier of the board that you want to update"), labelId: z.string().describe("Unique identifier of the classification label to apply") }, fn: async ({ orgId, teamId, boardId, labelId }) => { try { const dataClassificationLabelId = { labelId: labelId }; const response = await MiroClient.getApi().enterpriseDataclassificationBoardSet( orgId, teamId, boardId, dataClassificationLabelId ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error updating board classification: ${error}\n`); return ServerResponse.error(error); } } }; export default updateBoardClassificationTool; ``` -------------------------------------------------------------------------------- /src/tools/createBoardExportJob.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const createBoardExportJobTool: ToolSchema = { name: "create-board-export-job", description: "Creates an export job for one or more boards (Enterprise only)", args: { orgId: z.string().describe("Unique identifier of the organization"), requestId: z.string().describe("Unique identifier of the board export job"), boardIds: z.array(z.string()).describe("Array of board IDs to export"), format: z.enum(["pdf", "csv"]).optional().nullish().describe("Export format (default: pdf)") }, fn: async ({ orgId, requestId, boardIds, format }) => { try { const createBoardExportRequest = { boardIds: boardIds, ...(format && { format: format }) }; const response = await MiroClient.getApi().enterpriseCreateBoardExport( orgId, requestId, createBoardExportRequest ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error creating board export job: ${error}\n`); return ServerResponse.error(error); } } }; export default createBoardExportJobTool; ``` -------------------------------------------------------------------------------- /src/tools/detachTag.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const detachTagTool: ToolSchema = { name: "detach-tag", description: "Detach a tag from an item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the tag and item"), tagId: z.string().describe("Unique identifier (ID) of the tag that you want to detach"), itemId: z.string().describe("Unique identifier (ID) of the item from which you want to detach the tag") }, fn: async ({ boardId, tagId, itemId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!tagId) { return ServerResponse.error("Tag ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } // Use the SDK method to detach a tag from an item await MiroClient.getApi().removeTagFromItem(boardId, itemId, tagId); return ServerResponse.text(JSON.stringify({ success: true, message: "Tag detached successfully" }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default detachTagTool; ``` -------------------------------------------------------------------------------- /src/tools/ungroupItems.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const ungroupItemsTool: ToolSchema = { name: "ungroup-items", description: "Ungroup a specific group on a Miro board", args: { boardId: z.string().describe("ID of the board that contains the group"), groupId: z.string().describe("ID of the group that you want to ungroup"), deleteItems: z.boolean().optional().nullish().describe("Indicates whether the items should be removed. By default, false.") }, fn: async ({ boardId, groupId, deleteItems }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!groupId) { return ServerResponse.error("Group ID is required"); } const options: any = {}; if (deleteItems !== undefined) options.deleteItems = deleteItems; await MiroClient.getApi().unGroup(boardId, groupId, options); return ServerResponse.text(JSON.stringify({ success: true, message: "Group successfully ungrouped" }, null, 2)); } catch (error) { process.stderr.write(`Error ungrouping items: ${error}\n`); return ServerResponse.error(error); } } } export default ungroupItemsTool; ``` -------------------------------------------------------------------------------- /src/tools/createTag.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { TagCreateRequest } from '@mirohq/miro-api/dist/model/tagCreateRequest.js'; const createTagTool: ToolSchema = { name: "create-tag", description: "Create a new tag on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the tag will be created"), data: z.object({ title: z.string().describe("Title of the tag") }).describe("The content and configuration of the tag"), fillColor: z.string().optional().nullish().describe("Fill color of the tag (hex format, e.g. #000000)") }, fn: async ({ boardId, data, fillColor }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new TagCreateRequest(); createRequest.title = data.title; if (fillColor) { createRequest.fillColor = fillColor; } const result = await MiroClient.getApi().createTag(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createTagTool; ``` -------------------------------------------------------------------------------- /src/tools/addProjectMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const addProjectMemberTool: ToolSchema = { name: "add-project-member", description: "Adds a member to a project (Enterprise only)", args: { orgId: z.string().describe("The ID of the organization to which the project belongs"), teamId: z.string().describe("The ID of the team to which the project belongs"), projectId: z.string().describe("The ID of the project to which you want to add a user"), email: z.string().describe("Email ID of the user to add to the project"), role: z.enum(["owner", "editor", "commenter", "viewer"]).describe("Role to assign to the user") }, fn: async ({ orgId, teamId, projectId, email, role }) => { try { const addProjectMemberRequest = { email, role }; const response = await MiroClient.getApi().enterpriseAddProjectMember( orgId, teamId, projectId, addProjectMemberRequest ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error adding project member: ${error}\n`); return ServerResponse.error(error); } } }; export default addProjectMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/createBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const createBoardTool: ToolSchema = { name: "create-board", description: "Create a new Miro board with specified name and sharing policies", args: { name: z.string().describe("Name of the board to create"), description: z.string().optional().nullish().describe("Description of the board"), sharingPolicy: z.enum(['private', 'view', 'comment', 'edit']).optional().nullish().describe("Sharing policy for the board"), teamId: z.string().optional().nullish().describe("Team ID to assign the board to") }, fn: async ({ name, description, sharingPolicy, teamId }) => { try { if (!name) { return ServerResponse.error("Board name is required"); } const boardChanges = { name, description, sharingPolicy: { access: sharingPolicy || 'private' }, teamId }; const boardData = await MiroClient.getApi().createBoard(boardChanges); return ServerResponse.text(JSON.stringify(boardData, null, 2)); } catch (error) { process.stderr.write(`Error creating Miro board: ${error}\n`); return ServerResponse.error(error); } } } export default createBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/getGroupItems.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getGroupItemsTool: ToolSchema = { name: "get-group-items", description: "Retrieve all items in a specific group on a Miro board", args: { boardId: z.string().describe("ID of the board that contains the group"), groupId: z.string().describe("ID of the group whose items you want to retrieve"), limit: z.number().optional().nullish().describe("Maximum number of items to return (default: 50)"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ boardId, groupId, limit, cursor }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!groupId) { return ServerResponse.error("Group ID is required"); } const options: any = {}; if (limit) options.limit = limit; if (cursor) options.cursor = cursor; const result = await MiroClient.getApi().getItemsByGroupId(boardId, groupId, options); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error retrieving group items: ${error}\n`); return ServerResponse.error(error); } } } export default getGroupItemsTool; ``` -------------------------------------------------------------------------------- /src/tools/updateGroup.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateGroupTool: ToolSchema = { name: "update-group", description: "Update a specific group on a Miro board with new items", args: { boardId: z.string().describe("ID of the board that contains the group"), groupId: z.string().describe("ID of the group that you want to update"), data: z.object({ items: z.array(z.string()).describe("Updated list of item IDs to include in the group") }).describe("Updated group data with item IDs to include in the group") }, fn: async ({ boardId, groupId, data }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!groupId) { return ServerResponse.error("Group ID is required"); } if (!data || !data.items || data.items.length === 0) { return ServerResponse.error("At least one item ID is required in the 'items' array"); } const result = await MiroClient.getApi().updateGroup(boardId, groupId, { data }); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error updating group: ${error}\n`); return ServerResponse.error(error); } } } export default updateGroupTool; ``` -------------------------------------------------------------------------------- /src/tools/getAuditLogs.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getAuditLogsTool: ToolSchema = { name: "get-audit-logs", description: "Retrieves a page of audit events from the last 90 days (Enterprise only)", args: { createdAfter: z.string().describe("Retrieve audit logs created after this date (ISO 8601 format)"), createdBefore: z.string().describe("Retrieve audit logs created before this date (ISO 8601 format)"), cursor: z.string().optional().nullish().describe("Cursor for pagination"), limit: z.number().optional().nullish().describe("Maximum number of results to return (default: 100)"), sorting: z.enum(["ASC", "DESC"]).optional().nullish().describe("Sort order for results (default: ASC)") }, fn: async ({ createdAfter, createdBefore, cursor, limit, sorting }) => { try { const query: any = {}; if (cursor) query.cursor = cursor; if (limit) query.limit = limit; if (sorting) query.sorting = sorting; const response = await MiroClient.getApi().enterpriseGetAuditLogs( createdAfter, createdBefore, query ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving audit logs: ${error}\n`); return ServerResponse.error(error); } } }; export default getAuditLogsTool; ``` -------------------------------------------------------------------------------- /src/tools/getLegalHoldContentItems.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getLegalHoldContentItemsTool: ToolSchema = { name: "get-legal-hold-content-items", description: "Retrieves the list of content items under legal hold (Enterprise only)", args: { orgId: z.string().describe("The ID of the organization for which you want to retrieve the list of content items under hold"), caseId: z.string().describe("The ID of the case for which you want to retrieve the list of content items under hold"), legalHoldId: z.string().describe("The ID of the legal hold for which you want to retrieve the list of content items under hold"), limit: z.number().describe("The maximum number of items in the result list"), cursor: z.string().optional().nullish().describe("Cursor for pagination") }, fn: async ({ orgId, caseId, legalHoldId, limit, cursor }) => { try { const query: any = {}; if (cursor) query.cursor = cursor; const response = await MiroClient.getApi().getLegalHoldContentItems( orgId, caseId, legalHoldId, limit, query ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving legal hold content items: ${error}\n`); return ServerResponse.error(error); } } }; export default getLegalHoldContentItemsTool; ``` -------------------------------------------------------------------------------- /src/tools/updateTag.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { TagUpdateRequest } from '@mirohq/miro-api/dist/model/tagUpdateRequest.js'; const updateTagTool: ToolSchema = { name: "update-tag", description: "Update an existing tag on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the tag"), tagId: z.string().describe("Unique identifier (ID) of the tag that you want to update"), title: z.string().optional().nullish().describe("Updated title of the tag"), fillColor: z.string().optional().nullish().describe("Updated fill color of the tag (hex format, e.g. #000000)") }, fn: async ({ boardId, tagId, title, fillColor }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!tagId) { return ServerResponse.error("Tag ID is required"); } const updateRequest = new TagUpdateRequest(); if (title !== undefined) { updateRequest.title = title; } if (fillColor !== undefined) { updateRequest.fillColor = fillColor; } const result = await MiroClient.getApi().updateTag(boardId, tagId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateTagTool; ``` -------------------------------------------------------------------------------- /src/tools/updateBoardMember.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateBoardMemberTool: ToolSchema = { name: "update-board-member", description: "Update a specific member's role or status on a Miro board", args: { boardId: z.string().describe("ID of the board"), memberId: z.string().describe("ID of the board member to update"), role: z.enum(['member', 'admin', 'owner']).optional().nullish().describe("New role for the board member"), status: z.enum(['active', 'pending', 'blocked']).optional().nullish().describe("New status for the board member") }, fn: async ({ boardId, memberId, role, status }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!memberId) { return ServerResponse.error("Member ID is required"); } if (!role && !status) { return ServerResponse.error("At least one of role or status must be provided"); } const memberChanges: any = {}; if (role) memberChanges.role = role; if (status) memberChanges.status = status; const result = await MiroClient.getApi().updateBoardMember(boardId, memberId, memberChanges); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { process.stderr.write(`Error updating board member: ${error}\n`); return ServerResponse.error(error); } } } export default updateBoardMemberTool; ``` -------------------------------------------------------------------------------- /src/tools/updateBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateBoardTool: ToolSchema = { name: "update-board", description: "Update an existing Miro board with new settings", args: { boardId: z.string().describe("Unique identifier (ID) of the board that you want to update"), name: z.string().optional().nullish().describe("New name for the board"), description: z.string().optional().nullish().describe("New description for the board"), sharingPolicy: z.enum(['private', 'view', 'comment', 'edit']).optional().nullish().describe("New sharing policy for the board"), teamId: z.string().optional().nullish().describe("New team ID to assign the board to") }, fn: async ({ boardId, name, description, sharingPolicy, teamId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const boardChanges = {}; if (name) boardChanges['name'] = name; if (description !== undefined) boardChanges['description'] = description; if (sharingPolicy) boardChanges['sharingPolicy'] = { access: sharingPolicy }; if (teamId) boardChanges['teamId'] = teamId; const boardData = await MiroClient.getApi().updateBoard(boardId, boardChanges); return ServerResponse.text(JSON.stringify(boardData, null, 2)); } catch (error) { process.stderr.write(`Error updating Miro board: ${error}\n`); return ServerResponse.error(error); } } } export default updateBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/copyBoard.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const copyBoardTool: ToolSchema = { name: "copy-board", description: "Create a copy of an existing Miro board with optional new settings", args: { copyFrom: z.string().describe("Unique identifier (ID) of the board that you want to copy"), name: z.string().optional().nullish().describe("Name for the new copied board"), description: z.string().optional().nullish().describe("Description for the new copied board"), sharingPolicy: z.enum(['private', 'view', 'comment', 'edit']).optional().nullish().describe("Sharing policy for the new copied board"), teamId: z.string().optional().nullish().describe("Team ID to assign the new copied board to") }, fn: async ({ copyFrom, name, description, sharingPolicy, teamId }) => { try { if (!copyFrom) { return ServerResponse.error("Source board ID is required"); } const copyBoardChanges = {}; if (name) copyBoardChanges['name'] = name; if (description !== undefined) copyBoardChanges['description'] = description; if (sharingPolicy) copyBoardChanges['sharingPolicy'] = { access: sharingPolicy }; if (teamId) copyBoardChanges['teamId'] = teamId; const boardData = await MiroClient.getApi().copyBoard(copyFrom, copyBoardChanges); return ServerResponse.text(JSON.stringify(boardData, null, 2)); } catch (error) { process.stderr.write(`Error copying Miro board: ${error}\n`); return ServerResponse.error(error); } } } export default copyBoardTool; ``` -------------------------------------------------------------------------------- /src/tools/updateItemPosition.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateItemPositionTool: ToolSchema = { name: "update-item-position", description: "Update the position or parent of a specific item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the item"), itemId: z.string().describe("Unique identifier (ID) of the item that you want to update"), position: z.object({ x: z.number().optional().nullish().describe("X coordinate of the item"), y: z.number().optional().nullish().describe("Y coordinate of the item") }).optional().nullish().describe("New position coordinates for the item"), parentId: z.string().optional().nullish().describe("Unique identifier (ID) of the new parent item") }, fn: async ({ boardId, itemId, position, parentId }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateData = {}; if (position) { updateData['position'] = position; } if (parentId) { updateData['parent'] = { id: parentId }; } if (Object.keys(updateData).length === 0) { return ServerResponse.error("At least one update parameter (position or parentId) is required"); } const updatedItem = await MiroClient.getApi().updateItemPositionOrParent(boardId, itemId, updateData); return ServerResponse.text(JSON.stringify(updatedItem, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateItemPositionTool; ``` -------------------------------------------------------------------------------- /src/tools/getOrganizationMembers.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getOrganizationMembersTool: ToolSchema = { name: "get-organization-members", description: "Retrieves a list of members for an organization (Enterprise only)", args: { orgId: z.string().describe("id of the organization"), emails: z.string().optional().nullish().describe("Filter by comma-separated email addresses"), role: z.enum(['organization_internal_admin', 'organization_internal_user', 'organization_external_user', 'organization_team_guest_user', 'unknown']).optional().nullish().describe("Filter by user role"), license: z.enum(['full', 'occasional', 'free', 'free_restricted', 'full_trial', 'unknown']).optional().nullish().describe("Filter by license type"), active: z.boolean().optional().nullish().describe("Filter by active status"), cursor: z.string().optional().nullish().describe("Cursor for pagination"), limit: z.number().optional().nullish().describe("Maximum number of results to return") }, fn: async ({ orgId, emails, role, license, active, cursor, limit }) => { try { const query: any = {}; if (emails) query.emails = emails; if (role) query.role = role; if (license) query.license = license; if (active !== undefined) query.active = active; if (cursor) query.cursor = cursor; if (limit) query.limit = limit; const response = await MiroClient.getApi().enterpriseGetOrganizationMembers(orgId, query); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving organization members: ${error}\n`); return ServerResponse.error(error); } } }; export default getOrganizationMembersTool; ``` -------------------------------------------------------------------------------- /src/tools/getBoardContentLogs.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const getBoardContentLogsTool: ToolSchema = { name: "get-board-content-logs", description: "Retrieves content change logs of board items (Enterprise only)", args: { orgId: z.string().describe("Unique identifier of the organization"), from: z.string().describe("Start date for filtering (ISO 8601 format)"), to: z.string().describe("End date for filtering (ISO 8601 format)"), boardIds: z.array(z.string()).optional().nullish().describe("List of board IDs to filter by"), emails: z.array(z.string()).optional().nullish().describe("List of user emails to filter by"), cursor: z.string().optional().nullish().describe("Cursor for pagination"), limit: z.number().optional().nullish().describe("Maximum number of results to return"), sorting: z.enum(["asc", "desc"]).optional().nullish().describe("Sort order for results") }, fn: async ({ orgId, from, to, boardIds, emails, cursor, limit, sorting }) => { try { const query: any = {}; if (boardIds) query.boardIds = boardIds; if (emails) query.emails = emails; if (cursor) query.cursor = cursor; if (limit) query.limit = limit; if (sorting) query.sorting = sorting; // Convert string dates to Date objects const fromDate = new Date(from); const toDate = new Date(to); const response = await MiroClient.getApi().enterpriseBoardContentItemLogsFetch( orgId, fromDate, toDate, query ); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error retrieving board content logs: ${error}\n`); return ServerResponse.error(error); } } }; export default getBoardContentLogsTool; ``` -------------------------------------------------------------------------------- /src/tools/createMindmapNode.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const createMindmapNodeTool: ToolSchema = { name: "create-mindmap-node", description: "Create a new mind map node on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where you want to create the node"), data: z.object({ content: z.string().describe("Text content for the mind map node"), parentId: z.string().optional().nullish().describe("ID of the parent node (if this is a child node)"), style: z.object({ fillColor: z.string().optional().nullish().describe("Fill color for the node"), textColor: z.string().optional().nullish().describe("Text color for the node") }).optional().nullish() }).describe("The content and style configuration of the mind map node"), position: z.object({ x: z.number().describe("X coordinate of the node"), y: z.number().describe("Y coordinate of the node") }).describe("Position of the node on the board") }, fn: async ({ boardId, data, position }) => { try { // Prepare the request data const mindmapCreateRequest = { data: { nodeView: { data: { type: "text", content: data.content } }, ...(data.parentId && { parentId: data.parentId }), ...(data.style && { style: data.style }) }, position }; // Call the Miro API to create a mind map node const response = await MiroClient.getApi().createMindmapNodesExperimental(boardId, mindmapCreateRequest); return ServerResponse.text(JSON.stringify(response.body, null, 2)); } catch (error) { process.stderr.write(`Error creating Miro mind map node: ${error}\n`); return ServerResponse.error(error); } } }; export default createMindmapNodeTool; ``` -------------------------------------------------------------------------------- /src/tools/createConnector.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { ConnectorCreationData } from '@mirohq/miro-api/dist/model/connectorCreationData.js'; const createConnectorTool: ToolSchema = { name: "create-connector", description: "Create a new connector between items on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the connector will be created"), startItem: z.object({ id: z.string().describe("ID of the item at the start of the connector") }).describe("Start item of the connector"), endItem: z.object({ id: z.string().describe("ID of the item at the end of the connector") }).describe("End item of the connector"), style: z.object({ strokeColor: z.string().optional().nullish().describe("Color of the connector stroke"), strokeWidth: z.number().optional().nullish().describe("Width of the connector stroke"), strokeStyle: z.string().optional().nullish().describe("Style of the connector stroke (normal, dashed, etc.)"), startStrokeCap: z.string().optional().nullish().describe("Start stroke cap style"), endStrokeCap: z.string().optional().nullish().describe("End stroke cap style") }).optional().nullish().describe("Style configuration of the connector") }, fn: async ({ boardId, startItem, endItem, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const connectorData = new ConnectorCreationData(); connectorData.startItem = startItem; connectorData.endItem = endItem; if (style) { connectorData.style = style; } const result = await MiroClient.getApi().createConnector(boardId, connectorData); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createConnectorTool; ``` -------------------------------------------------------------------------------- /src/tools/createDocumentItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { DocumentCreateRequest } from '@mirohq/miro-api/dist/model/documentCreateRequest.js'; import { DocumentUrlData } from '@mirohq/miro-api/dist/model/documentUrlData.js'; const createDocumentItemTool: ToolSchema = { name: "create-document-item", description: "Create a new document item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the document will be created"), data: z.object({ url: z.string().describe("URL of the document to be added to the board"), title: z.string().optional().nullish().describe("Title of the document") }).describe("The content and configuration of the document"), position: z.object({ x: z.number().describe("X coordinate of the document"), y: z.number().describe("Y coordinate of the document") }).describe("Position of the document on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the document"), height: z.number().optional().nullish().describe("Height of the document") }).optional().nullish().describe("Dimensions of the document") }, fn: async ({ boardId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new DocumentCreateRequest(); const documentData = new DocumentUrlData(); documentData.url = data.url; if (data.title !== undefined) documentData.title = data.title; createRequest.data = documentData; createRequest.position = position; if (geometry) { createRequest.geometry = geometry; } const result = await MiroClient.getApi().createDocumentItemUsingUrl(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createDocumentItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createTextItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { TextCreateRequest } from '@mirohq/miro-api/dist/model/textCreateRequest.js'; import { TextData } from '@mirohq/miro-api/dist/model/textData.js'; const createTextItemTool: ToolSchema = { name: "create-text-item", description: "Create a new text item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the text will be created"), data: z.object({ content: z.string().describe("Text content of the text item") }).describe("The content of the text item"), position: z.object({ x: z.number().describe("X coordinate of the text item"), y: z.number().describe("Y coordinate of the text item") }).describe("Position of the text item on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the text item") }).optional().nullish().describe("Dimensions of the text item"), style: z.object({ color: z.string().optional().nullish().describe("Color of the text"), fontSize: z.number().optional().nullish().describe("Font size of the text"), textAlign: z.string().optional().nullish().describe("Alignment of the text (left, center, right)") }).optional().nullish().describe("Style configuration of the text item") }, fn: async ({ boardId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new TextCreateRequest(); const textData = new TextData(); textData.content = data.content; createRequest.data = textData; createRequest.position = position; if (geometry) { createRequest.geometry = geometry; } if (style) { createRequest.style = style; } const result = await MiroClient.getApi().createTextItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createTextItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateConnector.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { ConnectorChangesData } from '@mirohq/miro-api/dist/model/connectorChangesData.js'; const updateConnectorTool: ToolSchema = { name: "update-connector", description: "Update an existing connector on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the connector"), connectorId: z.string().describe("Unique identifier (ID) of the connector that you want to update"), startItem: z.object({ id: z.string().describe("ID of the item at the start of the connector") }).optional().nullish().describe("Start item of the connector"), endItem: z.object({ id: z.string().describe("ID of the item at the end of the connector") }).optional().nullish().describe("End item of the connector"), style: z.object({ strokeColor: z.string().optional().nullish().describe("Updated color of the connector stroke"), strokeWidth: z.number().optional().nullish().describe("Updated width of the connector stroke"), strokeStyle: z.string().optional().nullish().describe("Updated style of the connector stroke (normal, dashed, etc.)"), startStrokeCap: z.string().optional().nullish().describe("Updated start stroke cap style"), endStrokeCap: z.string().optional().nullish().describe("Updated end stroke cap style") }).optional().nullish().describe("Updated style configuration of the connector") }, fn: async ({ boardId, connectorId, startItem, endItem, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!connectorId) { return ServerResponse.error("Connector ID is required"); } const changes = new ConnectorChangesData(); if (startItem) { changes.startItem = startItem; } if (endItem) { changes.endItem = endItem; } if (style) { changes.style = style; } const result = await MiroClient.getApi().updateConnector(boardId, connectorId, changes); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateConnectorTool; ``` -------------------------------------------------------------------------------- /src/tools/updateImageItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { ImageUpdateRequest } from '@mirohq/miro-api/dist/model/imageUpdateRequest.js'; const updateImageItemTool: ToolSchema = { name: "update-image-item", description: "Update an existing image item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where you want to update the item"), itemId: z.string().describe("Unique identifier (ID) of the image that you want to update"), data: z.object({ title: z.string().optional().nullish().describe("Updated title of the image") }).optional().nullish().describe("The updated content of the image"), position: z.object({ x: z.number().optional().nullish().describe("Updated X coordinate of the image"), y: z.number().optional().nullish().describe("Updated Y coordinate of the image"), origin: z.string().optional().nullish().describe("Updated origin of the image (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Updated reference point (canvas_center, etc.)") }).optional().nullish().describe("Updated position of the image on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the image"), height: z.number().optional().nullish().describe("Updated height of the image") }).optional().nullish().describe("Updated dimensions of the image") }, fn: async ({ boardId, itemId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest = new ImageUpdateRequest(); if (data) { updateRequest.data = {}; if (data.title !== undefined) { updateRequest.data.title = data.title; } } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } const result = await MiroClient.getApi().updateImageItemUsingUrl(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateImageItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createImageItemUsingUrl.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { ImageCreateRequest } from '@mirohq/miro-api/dist/model/imageCreateRequest.js'; import { ImageUrlData } from '@mirohq/miro-api/dist/model/imageUrlData.js'; const createImageItemUsingUrlTool: ToolSchema = { name: "create-image-item-using-url", description: "Create a new image item on a Miro board using a URL", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the image will be created"), data: z.object({ url: z.string().describe("URL of the image to be added to the board"), title: z.string().optional().nullish().describe("Title of the image") }).describe("The content and configuration of the image"), position: z.object({ x: z.number().describe("X coordinate of the image"), y: z.number().describe("Y coordinate of the image"), origin: z.string().optional().nullish().describe("Origin of the image (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)") }).describe("Position of the image on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the image"), height: z.number().optional().nullish().describe("Height of the image") }).optional().nullish().describe("Dimensions of the image") }, fn: async ({ boardId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new ImageCreateRequest(); const imageData = new ImageUrlData(); imageData.url = data.url; if (data.title !== undefined) { imageData.title = data.title; } createRequest.data = imageData; const completePosition = { ...position, origin: position.origin || "center", relativeTo: position.relativeTo || "canvas_center" }; createRequest.position = completePosition; if (geometry) { createRequest.geometry = geometry; } const result = await MiroClient.getApi().createImageItemUsingUrl(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createImageItemUsingUrlTool; ``` -------------------------------------------------------------------------------- /src/tools/createImageItemUsingFileFromDevice.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { UploadFileFromDeviceData } from '@mirohq/miro-api/dist/model/uploadFileFromDeviceData.js'; import { RequestFile } from '@mirohq/miro-api/dist/model/models.js'; const createImageItemUsingFileFromDeviceTool: ToolSchema = { name: "create-image-item-using-file", description: "Create a new image item on a Miro board using file from device or from chat", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the image will be created"), imageData: z.string().describe("Base64 encoded image data from the chat"), position: z.object({ x: z.number().describe("X coordinate of the image"), y: z.number().describe("Y coordinate of the image"), origin: z.string().optional().nullish().describe("Origin of the image (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)") }).describe("Position of the image on the board"), title: z.string().optional().nullish().describe("Title of the image") }, fn: async ({ boardId, imageData, position, title }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!imageData) { return ServerResponse.error("Image data is required"); } let imageBuffer; try { const base64Data = imageData.replace(/^data:image\/\w+;base64,/, ''); imageBuffer = Buffer.from(base64Data, 'base64'); } catch (error) { return ServerResponse.error(`Error decoding Base64 image data: ${error.message}`); } try { const metadata = {}; if (position) { metadata['position'] = { ...position, origin: position.origin || 'center', relativeTo: position.relativeTo || 'canvas_center' }; } if (title) { metadata['title'] = title; } const result = await MiroClient.getApi().createImageItemUsingLocalFile(boardId, imageBuffer, metadata); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(`Error uploading image to Miro: ${error.message}`); } } catch (error) { return ServerResponse.error(error); } } }; export default createImageItemUsingFileFromDeviceTool; ``` -------------------------------------------------------------------------------- /src/tools/updateEmbedItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateEmbedItemTool: ToolSchema = { name: "update-embed-item", description: "Update an existing embed item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the embed"), itemId: z.string().describe("Unique identifier (ID) of the embed that you want to update"), data: z.object({ mode: z.string().optional().nullish().describe("Updated mode of the embed (normal, inline, etc.)") }).optional().nullish().describe("The updated configuration of the embed"), position: z.object({ x: z.number().describe("Updated X coordinate of the embed"), y: z.number().describe("Updated Y coordinate of the embed"), origin: z.string().optional().nullish().describe("Origin of the embed (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)") }).optional().nullish().describe("Updated position of the embed on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the embed"), height: z.number().optional().nullish().describe("Updated height of the embed") }) .optional().nullish() .refine(data => !data || data.width !== undefined || data.height !== undefined, { message: "Either width or height must be provided if geometry is set" }) .refine(data => !data || !(data.width !== undefined && data.height !== undefined), { message: "Only one of width or height should be provided for items with fixed aspect ratio" }) .describe("Updated dimensions of the embed") }, fn: async ({ boardId, itemId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest: Record<string, any> = {}; if (data) { updateRequest.data = data; } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } const result = await MiroClient.getApi().updateEmbedItem(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateEmbedItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createEmbedItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { EmbedCreateRequest } from '@mirohq/miro-api/dist/model/embedCreateRequest.js'; import { EmbedUrlData } from '@mirohq/miro-api/dist/model/embedUrlData.js'; const createEmbedItemTool: ToolSchema = { name: "create-embed-item", description: "Create a new embed item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the embed will be created"), data: z.object({ url: z.string().describe("URL to be embedded on the board"), mode: z.string().optional().nullish().describe("Mode of the embed (normal, inline, etc.)") }).describe("The content and configuration of the embed"), position: z.object({ x: z.number().describe("X coordinate of the embed"), y: z.number().describe("Y coordinate of the embed"), origin: z.string().optional().nullish().describe("Origin of the embed (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)") }).describe("Position of the embed on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the embed"), height: z.number().optional().nullish().describe("Height of the embed") }) .refine(data => data.width !== undefined || data.height !== undefined, { message: "Either width or height must be provided" }) .refine(data => !(data.width !== undefined && data.height !== undefined), { message: "Only one of width or height should be provided for items with fixed aspect ratio" }) .describe("Dimensions of the embed") }, fn: async ({ boardId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new EmbedCreateRequest(); const embedData = new EmbedUrlData(); embedData.url = data.url; if (data.mode !== undefined) { embedData.mode = data.mode; } createRequest.data = embedData; const completePosition = { ...position, origin: position.origin || "center", relativeTo: position.relativeTo || "canvas_center" }; createRequest.position = completePosition; createRequest.geometry = geometry; const result = await MiroClient.getApi().createEmbedItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createEmbedItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { CardCreateRequest } from '@mirohq/miro-api/dist/model/cardCreateRequest.js'; import { CardData } from '@mirohq/miro-api/dist/model/cardData.js'; const createCardItemTool: ToolSchema = { name: "create-card-item", description: "Create a new card item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the card will be created"), data: z.object({ title: z.string().describe("Title of the card"), description: z.string().optional().nullish().describe("Description of the card"), assigneeId: z.string().optional().nullish().describe("User ID of the assignee"), dueDate: z.string().optional().nullish().describe("Due date for the card (ISO 8601 format)") }).describe("The content and configuration of the card"), position: z.object({ x: z.number().describe("X coordinate of the card"), y: z.number().describe("Y coordinate of the card") }).describe("Position of the card on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the card"), height: z.number().optional().nullish().describe("Height of the card"), rotation: z.number().optional().nullish().describe("Rotation angle of the card") }).optional().nullish().describe("Dimensions of the card"), style: z.object({ cardTheme: z.string().optional().nullish().describe("Color of the card") }).optional().nullish().describe("Style configuration of the card") }, fn: async ({ boardId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new CardCreateRequest(); const cardData = new CardData(); cardData.title = data.title; if (data.description !== undefined) cardData.description = data.description; if (data.assigneeId !== undefined) cardData.assigneeId = data.assigneeId; if (data.dueDate !== undefined) { cardData.dueDate = new Date(data.dueDate); } createRequest.data = cardData; createRequest.position = position; if (geometry) { createRequest.geometry = geometry; } if (style) { createRequest.style = style; } const result = await MiroClient.getApi().createCardItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateDocumentItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { DocumentUpdateRequest } from '@mirohq/miro-api/dist/model/documentUpdateRequest.js'; import { DocumentUrlData } from '@mirohq/miro-api/dist/model/documentUrlData.js'; const updateDocumentItemTool: ToolSchema = { name: "update-document-item", description: "Update an existing document item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the document"), itemId: z.string().describe("Unique identifier (ID) of the document that you want to update"), data: z.object({ url: z.string().optional().nullish().describe("Updated URL of the document"), title: z.string().optional().nullish().describe("Updated title of the document") }).optional().nullish().describe("The updated content and configuration of the document"), position: z.object({ x: z.number().optional().nullish().describe("Updated X coordinate of the document"), y: z.number().optional().nullish().describe("Updated Y coordinate of the document") }).optional().nullish().describe("Updated position of the document on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the document"), height: z.number().optional().nullish().describe("Updated height of the document") }).optional().nullish().describe("Updated dimensions of the document") }, fn: async ({ boardId, itemId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest = new DocumentUpdateRequest(); if (data) { const documentData = new DocumentUrlData(); if (data.url !== undefined) documentData.url = data.url; if (data.title !== undefined) documentData.title = data.title; if (Object.keys(documentData).length > 0) { updateRequest.data = documentData; } } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } if (Object.keys(updateRequest).length === 0) { return ServerResponse.error("No data provided for update"); } const result = await MiroClient.getApi().updateDocumentItemUsingUrl(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateDocumentItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateTextItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { TextUpdateRequest } from '@mirohq/miro-api/dist/model/textUpdateRequest.js'; import { TextData } from '@mirohq/miro-api/dist/model/textData.js'; const updateTextItemTool: ToolSchema = { name: "update-text-item", description: "Update an existing text item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the text item"), itemId: z.string().describe("Unique identifier (ID) of the text item that you want to update"), data: z.object({ content: z.string().optional().nullish().describe("Updated text content of the text item") }).optional().nullish().describe("The updated content of the text item"), position: z.object({ x: z.number().optional().nullish().describe("Updated X coordinate of the text item"), y: z.number().optional().nullish().describe("Updated Y coordinate of the text item") }).optional().nullish().describe("Updated position of the text item on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the text item") }).optional().nullish().describe("Updated dimensions of the text item"), style: z.object({ color: z.string().optional().nullish().describe("Updated color of the text"), fontSize: z.number().optional().nullish().describe("Updated font size of the text"), textAlign: z.string().optional().nullish().describe("Updated alignment of the text (left, center, right)") }).optional().nullish().describe("Updated style configuration of the text item") }, fn: async ({ boardId, itemId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest = new TextUpdateRequest(); if (data) { const textData = new TextData(); if (data.content !== undefined) textData.content = data.content; if (Object.keys(textData).length > 0) { updateRequest.data = textData; } } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } if (style) { updateRequest.style = style; } if (Object.keys(updateRequest).length === 0) { return ServerResponse.error("No data provided for update"); } const result = await MiroClient.getApi().updateTextItem(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateTextItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateImageItemUsingFileFromDevice.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { UploadFileFromDeviceData } from '@mirohq/miro-api/dist/model/uploadFileFromDeviceData.js'; import { RequestFile } from '@mirohq/miro-api/dist/model/models.js'; import * as fs from 'fs'; import FormData from 'form-data'; const updateImageItemUsingFileFromDeviceTool: ToolSchema = { name: "update-image-item-using-file", description: "Update an existing image item on a Miro board using file from device", args: { boardId: z.string().describe("Unique identifier (ID) of the board where you want to update the item"), itemId: z.string().describe("Unique identifier (ID) of the image that you want to update"), filePath: z.string().describe("Path to the new image file on the device"), title: z.string().optional().nullish().describe("Updated title of the image"), position: z.object({ x: z.number().optional().nullish().describe("Updated X coordinate of the image"), y: z.number().optional().nullish().describe("Updated Y coordinate of the image"), origin: z.string().optional().nullish().describe("Updated origin of the image (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Updated reference point (canvas_center, etc.)") }).optional().nullish().describe("Updated position of the image on the board") }, fn: async ({ boardId, itemId, filePath, title, position }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } if (!filePath) { return ServerResponse.error("File path is required"); } // Implementacja aktualizacji obrazu z pliku lokalnego nie jest możliwa bezpośrednio w MCP // ponieważ wymaga dostępu do systemu plików klienta oraz konstruktora FormData const instructionsForUser = { message: "Aktualizacja obrazu z pliku lokalnego nie jest obecnie obsługiwana bezpośrednio w MCP Miro.", alternatives: [ "1. Użyj funkcji 'update-image-item' z URL do obrazu.", "2. Alternatywnie, możesz użyć bezpośrednio API Miro przez SDK lub żądania HTTP:", " - Utwórz formularz FormData", " - Dodaj plik do formularza", " - Wyślij żądanie PATCH do https://api.miro.com/v2/boards/{boardId}/images/{itemId}", " - Dodaj nagłówek 'Content-Type: multipart/form-data' oraz token autoryzacyjny" ], documentation: "Więcej informacji znajdziesz w dokumentacji API Miro: https://developers.miro.com/reference/update-image-item-using-file-from-device" }; return ServerResponse.text(JSON.stringify(instructionsForUser, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateImageItemUsingFileFromDeviceTool; ``` -------------------------------------------------------------------------------- /src/tools/createFrameItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { FrameCreateRequest } from '@mirohq/miro-api/dist/model/frameCreateRequest.js'; import { FrameChanges } from '@mirohq/miro-api/dist/model/frameChanges.js'; const createFrameItemTool: ToolSchema = { name: "create-frame", description: "Create a new frame on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the frame will be created"), data: z.object({ title: z.string().optional().nullish().describe("Title of the frame. This title appears at the top of the frame."), format: z.string().optional().nullish().describe("Format of the frame. Only 'custom' is supported currently."), type: z.string().optional().nullish().describe("Type of the frame. Only 'freeform' is supported currently."), showContent: z.boolean().optional().nullish().describe("Hide or reveal the content inside a frame (Enterprise plan only).") }).describe("The content and configuration of the frame"), position: z.object({ x: z.number().describe("X coordinate of the frame"), y: z.number().describe("Y coordinate of the frame") }).describe("Position of the frame on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the frame"), height: z.number().optional().nullish().describe("Height of the frame") }).optional().nullish().describe("Dimensions of the frame"), style: z.object({ fillColor: z.string().optional().nullish().describe("Fill color for the frame. Hex values like #f5f6f8, #d5f692, etc.") }).optional().nullish().describe("Style configuration of the frame") }, fn: async ({ boardId, data, position, geometry, style }: { boardId: string, data: { title?: string, format?: string, type?: string, showContent?: boolean }, position: { x: number, y: number }, geometry?: { width?: number, height?: number }, style?: { fillColor?: string } }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new FrameCreateRequest(); const frameData = new FrameChanges(); if (data.title !== undefined) frameData.title = data.title; if (data.format !== undefined) frameData.format = data.format; if (data.type !== undefined) frameData.type = data.type; if (data.showContent !== undefined) frameData.showContent = data.showContent; createRequest.data = frameData; createRequest.position = position; if (geometry) { createRequest.geometry = geometry; } if (style) { createRequest.style = style; } const result = await MiroClient.getApi().createFrameItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createFrameItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createAppCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { AppCardCreateRequest } from '@mirohq/miro-api/dist/model/appCardCreateRequest.js'; import { AppCardDataChanges } from '@mirohq/miro-api/dist/model/appCardDataChanges.js'; import { CustomField } from '@mirohq/miro-api/dist/model/customField.js'; import { ToolSchema } from '../tool.js'; const createAppCardItemTool: ToolSchema = { name: "create-app-card-item", description: "Create a new app card item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the app card will be created"), data: z.object({ title: z.string().describe("Title of the app card"), description: z.string().optional().nullish().describe("Description of the app card"), status: z.string().optional().nullish().describe("Status text of the app card"), fields: z.array(z.object({ value: z.string().describe("Value of the field"), iconShape: z.string().optional().nullish().describe("Shape of the icon"), fillColor: z.string().optional().nullish().describe("Fill color of the field"), textColor: z.string().optional().nullish().describe("Color of the text"), })).optional().nullish().describe("Custom fields to display on the app card") }).describe("The content and configuration of the app card"), position: z.object({ x: z.number().describe("X coordinate of the app card"), y: z.number().describe("Y coordinate of the app card") }).describe("Position of the app card on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the app card"), height: z.number().optional().nullish().describe("Height of the app card") }).optional().nullish().describe("Dimensions of the app card") }, fn: async ({boardId, data, position, geometry}) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new AppCardCreateRequest(); const appCardData = new AppCardDataChanges(); appCardData.title = data.title; if (data.description !== undefined) appCardData.description = data.description; if (data.status !== undefined) appCardData.status = data.status; if (data.fields) { appCardData.fields = data.fields.map(field => { const customField = new CustomField(); customField.value = field.value; if (field.iconShape) customField.iconShape = field.iconShape; if (field.fillColor) customField.fillColor = field.fillColor; if (field.textColor) customField.textColor = field.textColor; return customField; }); } createRequest.data = appCardData; createRequest.position = position; if (geometry) { createRequest.geometry = geometry; } const result = await MiroClient.getApi().createAppCardItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createAppCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { CardUpdateRequest } from '@mirohq/miro-api/dist/model/cardUpdateRequest.js'; import { CardData } from '@mirohq/miro-api/dist/model/cardData.js'; const updateCardItemTool: ToolSchema = { name: "update-card-item", description: "Update an existing card item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the card"), itemId: z.string().describe("Unique identifier (ID) of the card that you want to update"), data: z.object({ title: z.string().optional().nullish().describe("Updated title of the card"), description: z.string().optional().nullish().describe("Updated description of the card"), assigneeId: z.string().optional().nullish().describe("Updated user ID of the assignee"), dueDate: z.string().optional().nullish().describe("Updated due date for the card (ISO 8601 format)") }).optional().nullish().describe("The updated content and configuration of the card"), position: z.object({ x: z.number().describe("Updated X coordinate of the card"), y: z.number().describe("Updated Y coordinate of the card") }).optional().nullish().describe("Updated position of the card on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the card"), height: z.number().optional().nullish().describe("Updated height of the card"), rotation: z.number().optional().nullish().describe("Updated rotation angle of the card") }).optional().nullish().describe("Updated dimensions of the card"), style: z.object({ cardTheme: z.string().optional().nullish().describe("Updated color of the card") }).optional().nullish().describe("Updated style configuration of the card") }, fn: async ({ boardId, itemId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateData = new CardUpdateRequest(); if (data) { const cardData = new CardData(); if (data.title !== undefined) cardData.title = data.title; if (data.description !== undefined) cardData.description = data.description; if (data.assigneeId !== undefined) cardData.assigneeId = data.assigneeId; if (data.dueDate !== undefined) { cardData.dueDate = new Date(data.dueDate); } updateData.data = cardData; } if (position) { updateData.position = position; } if (geometry) { updateData.geometry = geometry; } if (style) { updateData.style = style; } const result = await MiroClient.getApi().updateCardItem(boardId, itemId, updateData); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateShapeItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; const updateShapeItemTool: ToolSchema = { name: "update-shape-item", description: "Update an existing shape item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the shape"), itemId: z.string().describe("Unique identifier (ID) of the shape that you want to update"), data: z.object({ shape: z.string().optional().nullish().describe("Updated type of the shape (rectangle, circle, triangle, etc.)"), content: z.string().optional().nullish().describe("Updated text content to display inside the shape") }).optional().nullish().describe("The updated content and configuration of the shape"), position: z.object({ x: z.number().describe("Updated X coordinate of the shape"), y: z.number().describe("Updated Y coordinate of the shape") }).optional().nullish().describe("Updated position of the shape on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the shape"), height: z.number().optional().nullish().describe("Updated height of the shape"), rotation: z.number().optional().nullish().describe("Rotation angle of the shape") }).optional().nullish().describe("Updated dimensions of the shape"), style: z.object({ borderColor: z.string().optional().nullish().describe("Updated color of the shape border (hex format, e.g. #000000)"), borderWidth: z.number().optional().nullish().describe("Updated width of the shape border"), borderStyle: z.string().optional().nullish().describe("Updated style of the shape border (normal, dashed, etc.)"), borderOpacity: z.number().optional().nullish().describe("Updated opacity of the shape border (0-1)"), fillColor: z.string().optional().nullish().describe("Updated fill color of the shape (hex format, e.g. #000000)"), fillOpacity: z.number().optional().nullish().describe("Updated opacity of the shape fill (0-1)"), color: z.string().optional().nullish().describe("Updated color of the text in the shape (hex format, e.g. #000000)") }).optional().nullish().describe("Updated style configuration of the shape") }, fn: async ({ boardId, itemId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest: Record<string, any> = {}; if (data) { updateRequest.data = data; } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } if (style) { updateRequest.style = style; } const result = await MiroClient.getApi().updateShapeItem(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateShapeItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateFrameItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { FrameUpdateRequest } from '@mirohq/miro-api/dist/model/frameUpdateRequest.js'; import { FrameChanges } from '@mirohq/miro-api/dist/model/frameChanges.js'; const updateFrameItemTool: ToolSchema = { name: "update-frame-item", description: "Update a frame on a Miro board based on the data, style, or geometry properties provided in the request body", args: { boardId: z.string().describe("Unique identifier (ID) of the board where you want to update the frame"), itemId: z.string().describe("Unique identifier (ID) of the frame that you want to update"), data: z.object({ title: z.string().optional().nullish().describe("Title of the frame. This title appears at the top of the frame."), format: z.string().optional().nullish().describe("Format of the frame. Only 'custom' is supported currently."), type: z.string().optional().nullish().describe("Type of the frame. Only 'freeform' is supported currently."), showContent: z.boolean().optional().nullish().describe("Hide or reveal the content inside a frame (Enterprise plan only).") }).optional().nullish().describe("The updated content and configuration of the frame"), position: z.object({ x: z.number().describe("X coordinate of the frame"), y: z.number().describe("Y coordinate of the frame") }).optional().nullish().describe("Updated position of the frame on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the frame"), height: z.number().optional().nullish().describe("Height of the frame") }).optional().nullish().describe("Updated dimensions of the frame"), style: z.object({ fillColor: z.string().optional().nullish().describe("Fill color for the frame. Hex values like #f5f6f8, #d5f692, etc.") }).optional().nullish().describe("Updated style configuration of the frame") }, fn: async ({ boardId, itemId, data, position, geometry, style }: { boardId: string, itemId: string, data?: { title?: string, format?: string, type?: string, showContent?: boolean }, position?: { x: number, y: number }, geometry?: { width?: number, height?: number }, style?: { fillColor?: string } }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest = new FrameUpdateRequest(); if (data) { const frameData = new FrameChanges(); if (data.title !== undefined) frameData.title = data.title; if (data.format !== undefined) frameData.format = data.format; if (data.type !== undefined) frameData.type = data.type; if (data.showContent !== undefined) frameData.showContent = data.showContent; updateRequest.data = frameData; } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } if (style) { updateRequest.style = style; } const result = await MiroClient.getApi().updateFrameItem(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateFrameItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateAppCardItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { AppCardUpdateRequest } from '@mirohq/miro-api/dist/model/appCardUpdateRequest.js'; import { AppCardDataChanges } from '@mirohq/miro-api/dist/model/appCardDataChanges.js'; import { CustomField } from '@mirohq/miro-api/dist/model/customField.js'; const updateAppCardItemTool: ToolSchema = { name: "update-app-card-item", description: "Update an existing app card item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the app card"), itemId: z.string().describe("Unique identifier (ID) of the app card that you want to update"), data: z.object({ title: z.string().optional().nullish().describe("Updated title of the app card"), description: z.string().optional().nullish().describe("Updated description of the app card"), status: z.string().optional().nullish().describe("Updated status text of the app card"), fields: z.array(z.object({ value: z.string().describe("Value of the field"), iconShape: z.string().optional().nullish().describe("Shape of the icon"), fillColor: z.string().optional().nullish().describe("Fill color of the field"), textColor: z.string().optional().nullish().describe("Color of the text"), })).optional().nullish().describe("Updated custom fields to display on the app card") }).optional().nullish().describe("The updated content and configuration of the app card"), position: z.object({ x: z.number().describe("Updated X coordinate of the app card"), y: z.number().describe("Updated Y coordinate of the app card") }).optional().nullish().describe("Updated position of the app card on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the app card"), height: z.number().optional().nullish().describe("Updated height of the app card") }).optional().nullish().describe("Updated dimensions of the app card") }, fn: async ({ boardId, itemId, data, position, geometry }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest = new AppCardUpdateRequest(); if (data) { const appCardData = new AppCardDataChanges(); if (data.title !== undefined) appCardData.title = data.title; if (data.description !== undefined) appCardData.description = data.description; if (data.status !== undefined) appCardData.status = data.status; if (data.fields) { appCardData.fields = data.fields.map(field => { const customField = new CustomField(); customField.value = field.value; if (field.iconShape) customField.iconShape = field.iconShape; if (field.fillColor) customField.fillColor = field.fillColor; if (field.textColor) customField.textColor = field.textColor; return customField; }); } updateRequest.data = appCardData; } if (position) { updateRequest.position = position; } if (geometry) { updateRequest.geometry = geometry; } const result = await MiroClient.getApi().updateAppCardItem(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateAppCardItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createShapeItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { ShapeCreateRequest } from '@mirohq/miro-api/dist/model/shapeCreateRequest.js'; import { ShapeData } from '@mirohq/miro-api/dist/model/shapeData.js'; const validShapeTypes = ['rectangle', 'round_rectangle', 'circle', 'triangle', 'rhombus', 'parallelogram', 'trapezoid', 'pentagon', 'hexagon', 'octagon', 'wedge_round_rectangle_callout', 'star', 'flow_chart_predefined_process', 'cloud', 'cross', 'can', 'right_arrow', 'left_arrow', 'left_right_arrow', 'left_brace', 'right_brace']; const createShapeItemTool: ToolSchema = { name: "create-shape-item", description: "Create a new shape item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the shape will be created"), data: z.object({ shape: z.string().describe("Type of the shape (rectangle, circle, triangle, etc.)"), content: z.string().optional().nullish().describe("Text content to display inside the shape") }).describe("The content and configuration of the shape"), position: z.object({ x: z.number().describe("X coordinate of the shape"), y: z.number().describe("Y coordinate of the shape") }).describe("Position of the shape on the board"), geometry: z.object({ width: z.number().describe("Width of the shape"), height: z.number().describe("Height of the shape"), rotation: z.number().optional().nullish().describe("Rotation angle of the shape") }).describe("Dimensions of the shape"), style: z.object({ borderColor: z.string().optional().nullish().describe("Color of the shape border (hex format, e.g. #000000)"), borderWidth: z.number().optional().nullish().describe("Width of the shape border"), borderStyle: z.string().optional().nullish().describe("Style of the shape border (normal, dashed, etc.)"), borderOpacity: z.number().optional().nullish().describe("Opacity of the shape border (0-1)"), fillColor: z.string().optional().nullish().describe("Fill color of the shape (hex format, e.g. #000000)"), fillOpacity: z.number().optional().nullish().describe("Opacity of the shape fill (0-1)"), color: z.string().optional().nullish().describe("Color of the text in the shape (hex format, e.g. #000000)") }).optional().nullish().describe("Style configuration of the shape") }, fn: async ({ boardId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!validShapeTypes.includes(data.type)) { return ServerResponse.error("Invalid shape type. Valid types are: " + validShapeTypes.join(", ")); } const createRequest = new ShapeCreateRequest(); const shapeData = new ShapeData(); // Set shape type via property assignment (shapeData as any).type = data.type; if (data.content !== undefined) { shapeData.content = data.content; } createRequest.data = shapeData; const completePosition = { ...position, origin: position.origin || "center", relativeTo: position.relativeTo || "canvas_center" }; createRequest.position = completePosition; createRequest.geometry = geometry; if (style) { createRequest.style = style as Record<string, any>; } const result = await MiroClient.getApi().createShapeItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createShapeItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createStickyNoteItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { StickyNoteCreateRequest } from '@mirohq/miro-api/dist/model/stickyNoteCreateRequest.js'; import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js'; const validColors = [ 'light_yellow', 'yellow', 'orange', 'light_green', 'green', 'light_blue', 'blue', 'light_pink', 'pink', 'light_purple', 'purple', 'black', 'gray', 'light_gray', 'white' ]; const validTextAligns = ['left', 'center', 'right']; const validShapes = ['square', 'rectangle', 'circle', 'triangle', 'rhombus']; const createStickyNoteItemTool: ToolSchema = { name: "create-sticky-note-item", description: "Create a new sticky note item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the sticky note will be created"), data: z.object({ content: z.string().describe("Text content of the sticky note"), shape: z.string().optional().nullish().describe("Shape of the sticky note (square, rectangle, circle, triangle, rhombus)") }).describe("The content and configuration of the sticky note"), position: z.object({ x: z.number().describe("X coordinate of the sticky note"), y: z.number().describe("Y coordinate of the sticky note"), origin: z.string().optional().nullish().describe("Origin of the sticky note (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)") }).describe("Position of the sticky note on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Width of the sticky note"), height: z.number().optional().nullish().describe("Height of the sticky note") }).optional().nullish().describe("Dimensions of the sticky note"), style: z.object({ fillColor: z.string().optional().nullish().describe("Fill color of the sticky note (use predefined values like 'light_yellow', 'light_green', etc.)"), textAlign: z.string().optional().nullish().describe("Alignment of the text (left, center, right)") }).optional().nullish().describe("Style configuration of the sticky note") }, fn: async ({ boardId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } const createRequest = new StickyNoteCreateRequest(); const stickyNoteData = new StickyNoteData(); stickyNoteData.content = data.content; if (data.shape) { if (!validShapes.includes(data.shape)) { console.warn(`Invalid shape: ${data.shape}. Using default: square`); stickyNoteData.shape = 'square'; } else { stickyNoteData.shape = data.shape; } } else { stickyNoteData.shape = 'square'; } createRequest.data = stickyNoteData; const completePosition = { ...position, origin: position.origin || "center", relativeTo: position.relativeTo || "canvas_center" }; createRequest.position = completePosition; if (geometry) { createRequest.geometry = geometry; } if (style) { const validatedStyle: Record<string, any> = {}; if (style.fillColor) { if (!validColors.includes(style.fillColor)) { console.warn(`Invalid color: ${style.fillColor}. Using default: light_yellow`); validatedStyle.fillColor = 'light_yellow'; } else { validatedStyle.fillColor = style.fillColor; } } else { validatedStyle.fillColor = 'light_yellow'; } if (style.textAlign) { if (!validTextAligns.includes(style.textAlign)) { console.warn(`Invalid text alignment: ${style.textAlign}. Using default: center`); validatedStyle.textAlign = 'center'; } else { validatedStyle.textAlign = style.textAlign; } } else { validatedStyle.textAlign = 'center'; } createRequest.style = validatedStyle; } else { createRequest.style = { fillColor: 'light_yellow', textAlign: 'center' }; } const result = await MiroClient.getApi().createStickyNoteItem(boardId, createRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default createStickyNoteItemTool; ``` -------------------------------------------------------------------------------- /src/tools/updateStickyNoteItem.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { StickyNoteUpdateRequest } from '@mirohq/miro-api/dist/model/stickyNoteUpdateRequest.js'; import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js'; const validColors = [ 'light_yellow', 'yellow', 'orange', 'light_green', 'green', 'light_blue', 'blue', 'light_pink', 'pink', 'light_purple', 'purple', 'black', 'gray', 'light_gray', 'white' ]; const validTextAligns = ['left', 'center', 'right']; const validShapes = ['square', 'rectangle', 'circle', 'triangle', 'rhombus']; const updateStickyNoteItemTool: ToolSchema = { name: "update-sticky-note-item", description: "Update an existing sticky note item on a Miro board", args: { boardId: z.string().describe("Unique identifier (ID) of the board that contains the sticky note"), itemId: z.string().describe("Unique identifier (ID) of the sticky note that you want to update"), data: z.object({ content: z.string().optional().nullish().describe("Updated text content of the sticky note"), shape: z.string().optional().nullish().describe("Updated shape of the sticky note (square, rectangle, circle, triangle, rhombus)") }).optional().nullish().describe("Updated content and configuration of the sticky note"), position: z.object({ x: z.number().optional().nullish().describe("Updated X coordinate of the sticky note"), y: z.number().optional().nullish().describe("Updated Y coordinate of the sticky note"), origin: z.string().optional().nullish().describe("Origin of the sticky note (center, top-left, etc.)"), relativeTo: z.string().optional().nullish().describe("Reference point (canvas_center, etc.)") }).optional().nullish().describe("Updated position of the sticky note on the board"), geometry: z.object({ width: z.number().optional().nullish().describe("Updated width of the sticky note"), height: z.number().optional().nullish().describe("Updated height of the sticky note") }).optional().nullish().describe("Updated dimensions of the sticky note"), style: z.object({ fillColor: z.string().optional().nullish().describe("Updated fill color of the sticky note (use predefined values)"), textAlign: z.string().optional().nullish().describe("Updated alignment of the text (left, center, right)"), textColor: z.string().optional().nullish().describe("Updated color of the text on the sticky note") }).optional().nullish().describe("Updated style configuration of the sticky note") }, fn: async ({ boardId, itemId, data, position, geometry, style }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!itemId) { return ServerResponse.error("Item ID is required"); } const updateRequest = new StickyNoteUpdateRequest(); if (data) { const stickyNoteData = new StickyNoteData(); if (data.content !== undefined) { stickyNoteData.content = data.content; } if (data.shape !== undefined) { if (!validShapes.includes(data.shape)) { console.warn(`Invalid shape: ${data.shape}. Skipping this field.`); } else { stickyNoteData.shape = data.shape; } } if (Object.keys(stickyNoteData).length > 0) { updateRequest.data = stickyNoteData; } } if (position) { if (position.x !== undefined || position.y !== undefined) { const completePosition: Record<string, any> = {}; if (position.x !== undefined) completePosition.x = position.x; if (position.y !== undefined) completePosition.y = position.y; completePosition.origin = position.origin || "center"; completePosition.relativeTo = position.relativeTo || "canvas_center"; updateRequest.position = completePosition; } } if (geometry && Object.keys(geometry).length > 0) { updateRequest.geometry = geometry; } if (style) { const validatedStyle: Record<string, any> = {}; if (style.fillColor) { if (!validColors.includes(style.fillColor)) { console.warn(`Invalid color: ${style.fillColor}. Skipping this field.`); } else { validatedStyle.fillColor = style.fillColor; } } if (style.textAlign) { if (!validTextAligns.includes(style.textAlign)) { console.warn(`Invalid text alignment: ${style.textAlign}. Skipping this field.`); } else { validatedStyle.textAlign = style.textAlign; } } if (style.textColor) { validatedStyle.textColor = style.textColor; } if (Object.keys(validatedStyle).length > 0) { updateRequest.style = validatedStyle; } } const result = await MiroClient.getApi().updateStickyNoteItem(boardId, itemId, updateRequest); return ServerResponse.text(JSON.stringify(result, null, 2)); } catch (error) { return ServerResponse.error(error); } } } export default updateStickyNoteItemTool; ``` -------------------------------------------------------------------------------- /src/tools/createItemsInBulkUsingFile.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { StickyNoteCreateRequest } from '@mirohq/miro-api/dist/model/stickyNoteCreateRequest.js'; import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js'; import { CardCreateRequest } from '@mirohq/miro-api/dist/model/cardCreateRequest.js'; import { CardData } from '@mirohq/miro-api/dist/model/cardData.js'; import { TextCreateRequest } from '@mirohq/miro-api/dist/model/textCreateRequest.js'; import { TextData } from '@mirohq/miro-api/dist/model/textData.js'; const validStickyNoteColors = [ 'light_yellow', 'yellow', 'orange', 'light_green', 'green', 'light_blue', 'blue', 'light_pink', 'pink', 'light_purple', 'purple', 'black', 'gray', 'white' ]; const validStickyNoteShapes = ['square', 'rectangle']; const validTextAligns = ['left', 'center', 'right']; const createItemsInBulkUsingFileTool: ToolSchema = { name: "create-items-in-bulk-using-file", description: "Create multiple items on a Miro board in a single operation using a JSON file from device", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the items will be created"), fileData: z.string().describe("Base64 encoded JSON file data containing items to create") }, fn: async ({ boardId, fileData }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!fileData) { return ServerResponse.error("File data is required"); } // Decode the base64 file data let jsonData; try { const base64Data = fileData.replace(/^data:application\/json;base64,/, ''); const fileBuffer = Buffer.from(base64Data, 'base64'); const fileContent = fileBuffer.toString('utf-8'); jsonData = JSON.parse(fileContent); } catch (error) { return ServerResponse.error(`Error decoding or parsing JSON file data: ${error.message}`); } // Validate that the decoded data contains an 'items' array if (!jsonData.items || !Array.isArray(jsonData.items) || jsonData.items.length === 0) { return ServerResponse.error("JSON file must contain a non-empty 'items' array"); } const items = jsonData.items; const results = []; const errors = []; const createPromises = items.map(async (item, index) => { try { // Validate item structure if (!item.type || !item.data || !item.position) { throw new Error(`Item at index ${index} is missing required fields (type, data, or position)`); } let result; if (item.type === 'sticky_note') { result = await createStickyNote(boardId, item); } else if (item.type === 'card') { result = await createCard(boardId, item); } else if (item.type === 'text') { result = await createText(boardId, item); } else { throw new Error(`Unsupported item type: ${item.type}`); } return { index, result }; } catch (error) { return { index, error: error.message || String(error) }; } }); const promiseResults = await Promise.all(createPromises); for (const promiseResult of promiseResults) { const { index, result, error } = promiseResult; if (error) { errors.push({ index, error }); } else if (result) { results.push({ index, item: result }); } } return ServerResponse.text(JSON.stringify({ created: results.length, failed: errors.length, results, errors }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } async function createStickyNote(boardId: string, item: any) { const createRequest = new StickyNoteCreateRequest(); const stickyNoteData = new StickyNoteData(); stickyNoteData.content = item.data.content; stickyNoteData.shape = item.data.shape || 'square'; createRequest.data = stickyNoteData; createRequest.position = item.position; if (item.style) { const style: Record<string, string> = {}; if (item.style.fillColor) { if (validStickyNoteColors.includes(item.style.fillColor)) { style.fillColor = item.style.fillColor; } else { style.fillColor = 'light_yellow'; } } if (item.style.textAlign) { if (validTextAligns.includes(item.style.textAlign)) { style.textAlign = item.style.textAlign; } else { style.textAlign = 'center'; } } createRequest.style = style; } return await MiroClient.getApi().createStickyNoteItem(boardId, createRequest); } async function createCard(boardId: string, item: any) { const createRequest = new CardCreateRequest(); const cardData = new CardData(); cardData.title = item.data.title; if (item.data.description) { cardData.description = item.data.description; } if (item.data.assigneeId) { cardData.assigneeId = item.data.assigneeId; } if (item.data.dueDate) { cardData.dueDate = new Date(item.data.dueDate); } createRequest.data = cardData; createRequest.position = item.position; if (item.style) { createRequest.style = item.style as Record<string, any>; } return await MiroClient.getApi().createCardItem(boardId, createRequest); } async function createText(boardId: string, item: any) { const createRequest = new TextCreateRequest(); const textData = new TextData(); textData.content = item.data.content; createRequest.data = textData; createRequest.position = item.position; if (item.style) { createRequest.style = item.style as Record<string, any>; } return await MiroClient.getApi().createTextItem(boardId, createRequest); } export default createItemsInBulkUsingFileTool; ``` -------------------------------------------------------------------------------- /src/tools/createItemsInBulk.ts: -------------------------------------------------------------------------------- ```typescript import MiroClient from '../client.js'; import { z } from 'zod'; import { ServerResponse } from '../server-response.js'; import { ToolSchema } from '../tool.js'; import { StickyNoteCreateRequest } from '@mirohq/miro-api/dist/model/stickyNoteCreateRequest.js'; import { StickyNoteData } from '@mirohq/miro-api/dist/model/stickyNoteData.js'; import { CardCreateRequest } from '@mirohq/miro-api/dist/model/cardCreateRequest.js'; import { CardData } from '@mirohq/miro-api/dist/model/cardData.js'; import { TextCreateRequest } from '@mirohq/miro-api/dist/model/textCreateRequest.js'; import { TextData } from '@mirohq/miro-api/dist/model/textData.js'; const validStickyNoteColors = [ 'light_yellow', 'yellow', 'orange', 'light_green', 'green', 'light_blue', 'blue', 'light_pink', 'pink', 'light_purple', 'purple', 'black', 'gray', 'white' ]; const validStickyNoteShapes = ['square', 'rectangle']; const validTextAligns = ['left', 'center', 'right']; const stickyNoteSchema = z.object({ type: z.literal('sticky_note'), data: z.object({ content: z.string().describe("Text content of the sticky note"), shape: z.enum(['square', 'rectangle']).optional().nullish().describe("Shape of the sticky note") }), position: z.object({ x: z.number().describe("X coordinate"), y: z.number().describe("Y coordinate") }), style: z.object({ fillColor: z.string().optional().nullish().describe("Fill color of the sticky note"), textAlign: z.enum(['left', 'center', 'right']).optional().nullish().describe("Text alignment") }).optional().nullish() }); const cardSchema = z.object({ type: z.literal('card'), data: z.object({ title: z.string().describe("Title of the card"), description: z.string().optional().nullish().describe("Description of the card"), assigneeId: z.string().optional().nullish().describe("User ID of the assignee"), dueDate: z.string().optional().nullish().describe("Due date in ISO 8601 format") }), position: z.object({ x: z.number().describe("X coordinate"), y: z.number().describe("Y coordinate") }), style: z.object({ fillColor: z.string().optional().nullish().describe("Fill color"), textColor: z.string().optional().nullish().describe("Text color") }).optional().nullish() }); const textSchema = z.object({ type: z.literal('text'), data: z.object({ content: z.string().describe("Text content") }), position: z.object({ x: z.number().describe("X coordinate"), y: z.number().describe("Y coordinate") }), style: z.object({ color: z.string().optional().nullish().describe("Text color (hex format, e.g. #000000)"), fontSize: z.number().optional().nullish().describe("Font size"), textAlign: z.enum(['left', 'center', 'right']).optional().nullish().describe("Text alignment") }).optional().nullish() }); const itemSchema = z.discriminatedUnion('type', [ stickyNoteSchema, cardSchema, textSchema ]); const createItemsInBulkTool: ToolSchema = { name: "create-items-in-bulk", description: "Create multiple items on a Miro board in a single operation", args: { boardId: z.string().describe("Unique identifier (ID) of the board where the items will be created"), items: z.array(itemSchema).describe("Array of items to create") }, fn: async ({ boardId, items }) => { try { if (!boardId) { return ServerResponse.error("Board ID is required"); } if (!items || !Array.isArray(items) || items.length === 0) { return ServerResponse.error("At least one item is required"); } const results = []; const errors = []; const createPromises = items.map(async (item, index) => { try { let result; if (item.type === 'sticky_note') { result = await createStickyNote(boardId, item); } else if (item.type === 'card') { result = await createCard(boardId, item); } else if (item.type === 'text') { result = await createText(boardId, item); } return { index, result }; } catch (error) { return { index, error: error.message || String(error) }; } }); const promiseResults = await Promise.all(createPromises); for (const promiseResult of promiseResults) { const { index, result, error } = promiseResult; if (error) { errors.push({ index, error }); } else if (result) { results.push({ index, item: result }); } } return ServerResponse.text(JSON.stringify({ created: results.length, failed: errors.length, results, errors }, null, 2)); } catch (error) { return ServerResponse.error(error); } } } async function createStickyNote(boardId: string, item: z.infer<typeof stickyNoteSchema>) { const createRequest = new StickyNoteCreateRequest(); const stickyNoteData = new StickyNoteData(); stickyNoteData.content = item.data.content; stickyNoteData.shape = item.data.shape || 'square'; createRequest.data = stickyNoteData; createRequest.position = item.position; if (item.style) { const style: Record<string, string> = {}; if (item.style.fillColor) { if (validStickyNoteColors.includes(item.style.fillColor)) { style.fillColor = item.style.fillColor; } else { style.fillColor = 'light_yellow'; } } if (item.style.textAlign) { if (validTextAligns.includes(item.style.textAlign)) { style.textAlign = item.style.textAlign; } else { style.textAlign = 'center'; } } createRequest.style = style; } return await MiroClient.getApi().createStickyNoteItem(boardId, createRequest); } async function createCard(boardId: string, item: z.infer<typeof cardSchema>) { const createRequest = new CardCreateRequest(); const cardData = new CardData(); cardData.title = item.data.title; if (item.data.description) { cardData.description = item.data.description; } if (item.data.assigneeId) { cardData.assigneeId = item.data.assigneeId; } if (item.data.dueDate) { cardData.dueDate = new Date(item.data.dueDate); } createRequest.data = cardData; createRequest.position = item.position; if (item.style) { createRequest.style = item.style as Record<string, any>; } return await MiroClient.getApi().createCardItem(boardId, createRequest); } // Helper function to create a text item async function createText(boardId: string, item: z.infer<typeof textSchema>) { const createRequest = new TextCreateRequest(); const textData = new TextData(); textData.content = item.data.content; createRequest.data = textData; createRequest.position = item.position; if (item.style) { createRequest.style = item.style as Record<string, any>; } return await MiroClient.getApi().createTextItem(boardId, createRequest); } export default createItemsInBulkTool; ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import fs from 'fs'; import path from 'path'; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import * as dotenv from "dotenv"; import server from './server.js'; import { ToolBootstrapper } from './tool-bootstrapper.js'; import listBoardsTool from './tools/listBoards.js'; import createBoardTool from './tools/createBoard.js'; import updateBoardTool from './tools/updateBoard.js'; import deleteBoardTool from './tools/deleteBoard.js'; import copyBoardTool from './tools/copyBoard.js'; import getSpecificBoardTool from './tools/getSpecificBoard.js'; import getItemsOnBoardTool from './tools/getItemsOnBoard.js'; import getSpecificItemTool from './tools/getSpecificItem.js'; import updateItemPositionTool from './tools/updateItemPosition.js'; import deleteItemTool from './tools/deleteItem.js'; import createAppCardItemTool from './tools/createAppCardItem.js'; import getAppCardItemTool from './tools/getAppCardItem.js'; import updateAppCardItemTool from './tools/updateAppCardItem.js'; import deleteAppCardItemTool from './tools/deleteAppCardItem.js'; import createCardItemTool from './tools/createCardItem.js'; import getCardItemTool from './tools/getCardItem.js'; import updateCardItemTool from './tools/updateCardItem.js'; import deleteCardItemTool from './tools/deleteCardItem.js'; import createConnectorTool from './tools/createConnector.js'; import getConnectorsTool from './tools/getConnectors.js'; import getSpecificConnectorTool from './tools/getSpecificConnector.js'; import updateConnectorTool from './tools/updateConnector.js'; import deleteConnectorTool from './tools/deleteConnector.js'; import createStickyNoteItemTool from './tools/createStickyNoteItem.js'; import getStickyNoteItemTool from './tools/getStickyNoteItem.js'; import updateStickyNoteItemTool from './tools/updateStickyNoteItem.js'; import deleteStickyNoteItemTool from './tools/deleteStickyNoteItem.js'; import createFrameItemTool from './tools/createFrameItem.js'; import getFrameItemTool from './tools/getFrameItem.js'; import updateFrameItemTool from './tools/updateFrameItem.js'; import deleteFrameItemTool from './tools/deleteFrameItem.js'; import createDocumentItemTool from './tools/createDocumentItem.js'; import getDocumentItemTool from './tools/getDocumentItem.js'; import updateDocumentItemTool from './tools/updateDocumentItem.js'; import deleteDocumentItemTool from './tools/deleteDocumentItem.js'; import createTextItemTool from './tools/createTextItem.js'; import getTextItemTool from './tools/getTextItem.js'; import updateTextItemTool from './tools/updateTextItem.js'; import deleteTextItemTool from './tools/deleteTextItem.js'; import createItemsInBulkTool from './tools/createItemsInBulk.js'; import createItemsInBulkUsingFileTool from './tools/createItemsInBulkUsingFile.js'; import createImageItemUsingUrlTool from './tools/createImageItemUsingUrl.js'; import createImageItemUsingFileFromDeviceTool from './tools/createImageItemUsingFileFromDevice.js'; import getImageItemTool from './tools/getImageItem.js'; import updateImageItemTool from './tools/updateImageItem.js'; import updateImageItemUsingFileFromDeviceTool from './tools/updateImageItemUsingFileFromDevice.js'; import deleteImageItemTool from './tools/deleteImageItem.js'; import createShapeItemTool from './tools/createShapeItem.js'; import getShapeItemTool from './tools/getShapeItem.js'; import updateShapeItemTool from './tools/updateShapeItem.js'; import deleteShapeItemTool from './tools/deleteShapeItem.js'; import createEmbedItemTool from './tools/createEmbedItem.js'; import getEmbedItemTool from './tools/getEmbedItem.js'; import updateEmbedItemTool from './tools/updateEmbedItem.js'; import deleteEmbedItemTool from './tools/deleteEmbedItem.js'; import createTagTool from './tools/createTag.js'; import getTagTool from './tools/getTag.js'; import getAllTagsTool from './tools/getAllTags.js'; import updateTagTool from './tools/updateTag.js'; import deleteTagTool from './tools/deleteTag.js'; import attachTagTool from './tools/attachTag.js'; import detachTagTool from './tools/detachTag.js'; import getItemTagsTool from './tools/getItemTags.js'; import getAllBoardMembers from './tools/getAllBoardMembers.js'; import getSpecificBoardMemberTool from './tools/getSpecificBoardMember.js'; import removeBoardMemberTool from './tools/removeBoardMember.js'; import shareBoardTool from './tools/shareBoard.js'; import updateBoardMemberTool from './tools/updateBoardMember.js'; import getAllGroupsTool from './tools/getAllGroups.js'; import getGroupTool from './tools/getGroup.js'; import getGroupItemsTool from './tools/getGroupItems.js'; import updateGroupTool from './tools/updateGroup.js'; import ungroupItemsTool from './tools/ungroupItems.js'; import deleteGroupTool from './tools/deleteGroup.js'; import createGroupTool from './tools/createGroup.js'; import createMindmapNodeTool from './tools/createMindmapNode.js'; import getMindmapNodeTool from './tools/getMindmapNode.js'; import getMindmapNodesTool from './tools/getMindmapNodes.js'; import deleteMindmapNodeTool from './tools/deleteMindmapNode.js'; import getBoardClassificationTool from './tools/getBoardClassification.js'; import updateBoardClassificationTool from './tools/updateBoardClassification.js'; import createBoardExportJobTool from './tools/createBoardExportJob.js'; import getBoardExportJobStatusTool from './tools/getBoardExportJobStatus.js'; import getBoardExportJobResultsTool from './tools/getBoardExportJobResults.js'; import getAuditLogsTool from './tools/getAuditLogs.js'; import getOrganizationInfoTool from './tools/getOrganizationInfo.js'; import getOrganizationMembersTool from './tools/getOrganizationMembers.js'; import getOrganizationMemberTool from './tools/getOrganizationMember.js'; import addProjectMemberTool from './tools/addProjectMember.js'; import getProjectMemberTool from './tools/getProjectMember.js'; import removeProjectMemberTool from './tools/removeProjectMember.js'; import getAllCasesTool from './tools/getAllCases.js'; import getCaseTool from './tools/getCase.js'; import getLegalHoldsTool from './tools/getAllLegalHolds.js'; import getLegalHoldTool from './tools/getLegalHold.js'; import getLegalHoldContentItemsTool from './tools/getLegalHoldContentItems.js'; import getBoardContentLogsTool from './tools/getBoardContentLogs.js'; dotenv.config(); new ToolBootstrapper(server) .register(listBoardsTool) .register(createBoardTool) .register(updateBoardTool) .register(deleteBoardTool) .register(copyBoardTool) .register(getSpecificBoardTool) .register(getItemsOnBoardTool) .register(getSpecificItemTool) .register(updateItemPositionTool) .register(deleteItemTool) .register(createAppCardItemTool) .register(getAppCardItemTool) .register(updateAppCardItemTool) .register(deleteAppCardItemTool) .register(createCardItemTool) .register(getCardItemTool) .register(updateCardItemTool) .register(deleteCardItemTool) .register(createConnectorTool) .register(getConnectorsTool) .register(getSpecificConnectorTool) .register(updateConnectorTool) .register(deleteConnectorTool) .register(createStickyNoteItemTool) .register(getStickyNoteItemTool) .register(updateStickyNoteItemTool) .register(deleteStickyNoteItemTool) .register(createFrameItemTool) .register(getFrameItemTool) .register(updateFrameItemTool) .register(deleteFrameItemTool) .register(createDocumentItemTool) .register(getDocumentItemTool) .register(updateDocumentItemTool) .register(deleteDocumentItemTool) .register(createTextItemTool) .register(getTextItemTool) .register(updateTextItemTool) .register(deleteTextItemTool) .register(createItemsInBulkTool) .register(createImageItemUsingUrlTool) .register(createImageItemUsingFileFromDeviceTool) .register(getImageItemTool) .register(updateImageItemTool) .register(updateImageItemUsingFileFromDeviceTool) .register(deleteImageItemTool) .register(createShapeItemTool) .register(getShapeItemTool) .register(updateShapeItemTool) .register(deleteShapeItemTool) .register(createEmbedItemTool) .register(getEmbedItemTool) .register(updateEmbedItemTool) .register(deleteEmbedItemTool) .register(createTagTool) .register(getTagTool) .register(getAllTagsTool) .register(updateTagTool) .register(deleteTagTool) .register(attachTagTool) .register(detachTagTool) .register(getItemTagsTool) .register(getAllBoardMembers) .register(getSpecificBoardMemberTool) .register(removeBoardMemberTool) .register(shareBoardTool) .register(updateBoardMemberTool) .register(createGroupTool) .register(getAllGroupsTool) .register(getGroupTool) .register(getGroupItemsTool) .register(updateGroupTool) .register(ungroupItemsTool) .register(deleteGroupTool) .register(createItemsInBulkUsingFileTool) .register(createMindmapNodeTool) .register(getMindmapNodeTool) .register(getMindmapNodesTool) .register(deleteMindmapNodeTool) .register(getBoardClassificationTool) .register(updateBoardClassificationTool) .register(createBoardExportJobTool) .register(getBoardExportJobStatusTool) .register(getBoardExportJobResultsTool) .register(getAuditLogsTool) .register(getOrganizationInfoTool) .register(getOrganizationMembersTool) .register(getOrganizationMemberTool) .register(addProjectMemberTool) .register(getProjectMemberTool) .register(removeProjectMemberTool) .register(getAllCasesTool) .register(getCaseTool) .register(getLegalHoldsTool) .register(getLegalHoldTool) .register(getLegalHoldContentItemsTool) .register(getBoardContentLogsTool); async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); if (!process.env.MIRO_ACCESS_TOKEN) { process.stderr.write("Warning: MIRO_ACCESS_TOKEN environment variable is not set. The server will not be able to connect to Miro.\n"); } } catch (error) { process.stderr.write(`Server error: ${error}\n`); process.exit(1); } } main().catch(error => { process.stderr.write(`Fatal error: ${error}\n`); process.exit(1); }); ```