Version: 0.1.0
Model Context Protocol (MCP) server enabling Claude and Claude Code to perform CRUD operations on Miro boards.
This MCP server provides Claude with the ability to:
- Read Miro boards with full spatial and structural understanding
- Create items (sticky notes, shapes, text) with precise coordinate control
- Update existing items
- Delete items
- Analyze board structure, relationships, and spatial organization
Primary Design Goal: Enable Claude Code to programmatically create aesthetically pleasing, well-organized Miro diagrams with precise coordinate control.
- ✅ OAuth 2.0 authentication with token management
- ✅ Read boards with full geometric details
- ✅ Create sticky notes at precise coordinates
- ✅ Create shapes (rectangles, circles, etc.) at precise coordinates
- ✅ Create connectors between items
- ✅ Query board bounds and item geometry
- Update item content, position, and style
- Delete items
- Batch operations
- Frame support
- Advanced spatial analysis
- Template system for common patterns
- Smart layout suggestions
- Content extraction and analysis
This server is optimized for precise coordinate control to enable aesthetic diagram creation:
- All creation operations accept explicit
x, ycoordinates - Claude Code calculates layouts programmatically
- Helper tools provide coordinate calculations (grid, radial, etc.)
- Full geometric information available for spatial reasoning
miro-mcp/
├── miro_mcp/
│ ├── server.py # FastMCP server with tool definitions
│ ├── auth.py # OAuth 2.0 flow and token management
│ ├── client.py # Miro API client with rate limiting
│ ├── models.py # Pydantic data models
│ ├── analyzer.py # Spatial analysis and clustering
│ └── config.py # Configuration management
├── tests/
│ ├── test_auth.py
│ ├── test_client.py
│ └── test_tools.py
├── docs/
│ ├── ARCHITECTURE.md # Detailed architecture documentation
│ ├── API.md # Miro API integration details
│ └── USAGE.md # Usage examples and patterns
├── pyproject.toml
└── README.md
- Python 3.10 or higher
- Miro account with developer access
- Claude Desktop or Claude Code
git clone https://github.com/yourusername/miro-mcp.git
cd miro-mcppip install -e .- Go to Miro Developer Portal
- Create a new app
- Configure OAuth settings:
- Redirect URI:
http://localhost:8000/callback - Scopes:
boards:read,boards:write
- Redirect URI:
- Copy Client ID and Client Secret
export MIRO_CLIENT_ID="your_client_id"
export MIRO_CLIENT_SECRET="your_client_secret"
export MIRO_REDIRECT_URI="http://localhost:8000/callback"Or create a .env file:
MIRO_CLIENT_ID=your_client_id
MIRO_CLIENT_SECRET=your_client_secret
MIRO_REDIRECT_URI=http://localhost:8000/callbackpython -m miro_mcp.auth setupThis will:
- Start a local OAuth callback server
- Open your browser for authorization
- Save tokens securely
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"miro": {
"command": "python",
"args": ["-m", "miro_mcp.server"],
"env": {
"MIRO_CLIENT_ID": "your_client_id",
"MIRO_CLIENT_SECRET": "your_client_secret"
}
}
}
}# Ask Claude Code:
# "Read the board at https://miro.com/app/board/uXjVKB..."
board = await miro_read_board("uXjVKB...")
print(f"Board has {len(board.items)} items")
print(f"Bounds: {board.bounds.width} x {board.bounds.height}")# "Create a 3-step process flow diagram"
# Get board bounds to find empty space
board = await miro_read_board(board_id)
start_x = board.bounds.max_x + 500
start_y = board.bounds.center_y
# Create shapes with precise spacing
steps = ["Plan", "Execute", "Review"]
shape_ids = []
for i, step in enumerate(steps):
x = start_x + i * 400
shape = await miro_create_shape(
board_id=board_id,
shape_type="rectangle",
content=step,
x=x,
y=start_y,
width=300,
height=150,
color="light_blue"
)
shape_ids.append(shape.id)
# Connect them
for i in range(len(shape_ids) - 1):
await miro_create_connector(
board_id=board_id,
start_item_id=shape_ids[i],
end_item_id=shape_ids[i+1],
start_position="right",
end_position="left"
)# "What are the main themes on this brainstorm board?"
board = await miro_read_board(board_id)
# Extract all sticky note content
notes = [item.content for item in board.items if item.type == "sticky_note"]
# Claude analyzes the text content
# Can identify clusters, themes, patternsList all boards accessible to the authenticated user.
Read complete board contents with full geometric details.
Returns:
- All items (sticky notes, shapes, text, frames)
- Connectors with start/end relationships
- Complete position and dimension data
- Board bounds
Get bounding box of all content on a board.
Get precise position and dimensions of a specific item.
Create a sticky note at exact coordinates.
Parameters:
board_id: Target board IDcontent: Text contentx,y: Coordinates (required)width,height: Dimensions (optional)color: Note color (optional)
Create a shape (rectangle, circle, triangle, etc.) at exact coordinates.
Parameters:
board_id: Target board IDshape_type: rectangle, circle, triangle, rhombus, etc.content: Optional text inside shapex,y: Coordinates (required)width,height: Dimensions (optional)color: Shape color (optional)
Create a connector line between two items.
Parameters:
board_id: Target board IDstart_item_id: Source item IDend_item_id: Destination item IDstart_position: Which edge to connect from (top/bottom/left/right/auto)end_position: Which edge to connect to (top/bottom/left/right/auto)
Calculate positions for grid layout.
Calculate positions for radial/circular layout.
# 1. Read existing board
board = await miro_read_board(board_id)
# 2. Analyze spatial organization
# - Where is content?
# - What spacing is used?
# - What colors/styles are present?
# 3. Place new content in harmony with existing
new_x = board.bounds.max_x + 500 # To the right# Calculate all positions first
positions = []
for i, item in enumerate(items):
row = i // 5
col = i % 5
x = base_x + col * 300
y = base_y + row * 300
positions.append((x, y))
# Then create all items
for (x, y), content in zip(positions, items):
await miro_create_sticky_note(board_id, content, x, y)# Create hierarchical org chart
root_x, root_y = 1000, 500
spacing_x = 400
spacing_y = 300
# Top level
ceo = await miro_create_shape(board_id, "rectangle", "CEO", root_x, root_y)
# Second level
for i, dept in enumerate(["Engineering", "Sales", "Marketing"]):
x = root_x - spacing_x + i * spacing_x
y = root_y + spacing_y
dept_shape = await miro_create_shape(board_id, "rectangle", dept, x, y)
await miro_create_connector(board_id, ceo.id, dept_shape.id)pytest tests/mypy miro_mcp/black miro_mcp/
ruff check miro_mcp/- Verify Client ID and Client Secret are correct
- Check redirect URI matches exactly (including port)
- Ensure required scopes are enabled
- Try re-running
python -m miro_mcp.auth setup
- Server implements automatic rate limiting (100 req/min)
- Exponential backoff on 429 errors
- Consider batching operations when possible
- Miro uses arbitrary coordinate space (can be negative, very large)
- Always query
miro_get_board_boundsto understand scale - Test with small offsets first to verify placement
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT License - see LICENSE file for details
- OAuth authentication
- Basic read operations
- Create sticky notes and shapes
- Connector support
- Comprehensive error handling
- Test coverage
- Update operations
- Delete operations
- Frame support
- Batch operations
- Advanced spatial analysis
- Template system
- Pattern recognition
- Content extraction
- Image support
For issues, questions, or contributions:
- GitHub Issues: [github.com/yourusername/miro-mcp/issues]
- Documentation: [docs/]
Added
- Project instructions and development guidelines (.claude/CLAUDE.md)
- Spec-driven development infrastructure (specs/ directory)
- Testing conventions (no mocks, real implementations)
- Coordinate-based design strategy documentation