AffinityMCP
by tacyan
README.md
# AffinityMCP
The Universal MCP Server (Rust) exposes tools for Canva workflows—and **Affinity Photo/Designer/Publisher automation**—designed for **natural language** usage in MCP-compatible clients.
## Features
- 🎨 **Natural Language Control**: Control Affinity applications using natural language commands
- 🖼️ **File Operations**: Open files, create new documents, export in various formats
- ⚡ **16-Parallel Processing**: Process up to 16 files simultaneously for maximum efficiency
- 🔧 **Advanced Tools**: Apply filters, get document info, and more
- 🚀 **Easy Setup**: Simple setup for beginners with comprehensive documentation
- 🌐 **Cross-Platform Ready**: Currently macOS (AppleScript), Windows support planned
## Quick Start
For beginners, see [SETUP_GUIDE.md](SETUP_GUIDE.md) for detailed step-by-step instructions.
**Quick installation:**
```bash
# Build
cargo build --release
cp target/release/affinity-mcp dist/
# Use with Cursor
# Add to Cursor MCP settings:
{
"mcpServers": {
"affinity-mcp": {
"command": "npx",
"args": ["affinity-mcp"],
"autoStart": true
}
}
}
```
## Installation
### Prerequisites
- Node.js 18+ (for the thin npx wrapper)
- Rust 1.76+ / Cargo (for local builds)
- Set `AFFINITY_MCP_API_KEY` if using Canva Connect/MCP endpoints.
### Get an API key
- If your tools require Canva APIs, obtain credentials from the Canva developer console or MCP server guidance. Otherwise, you can skip this step.
### Build locally
```bash
cd /path/to/affinity-mcp
cargo build --release
# copy target/release/affinity-mcp -> ./dist/
cp target/release/affinity-mcp dist/
```
## Setup: Claude Code (CLI)
Use this one-liner (replace with your real values):
```bash
claude mcp add AffinityMCP -s user -e AFFINITY_MCP_API_KEY="sk-your-real-key" -- npx affinity-mcp
```
To remove:
```bash
claude mcp remove AffinityMCP
```
## Setup: Cursor (Do not commit .cursor/mcp.json here)
Create the config in your client:
```json
{
"mcpServers": {
"affinity-mcp": {
"command": "npx",
"args": ["affinity-mcp"],
"env": { "AFFINITY_MCP_API_KEY": "sk-your-real-key" },
"autoStart": true
}
}
}
```
## Other Clients and Agents
<details><summary>VS Code</summary>
```bash
code --add-mcp '{"name":"affinity-mcp","command":"npx","args":["affinity-mcp"],"env":{"AFFINITY_MCP_API_KEY":"sk-your-real-key"}}'
```
</details>
<details><summary>Claude Desktop</summary>
Follow the MCP install guide and reuse the standard config above.
</details>
<details><summary>LM Studio</summary>
- Command: npx
- Args: ["affinity-mcp"]
- Env: AFFINITY_MCP_API_KEY=sk-your-real-key
</details>
<details><summary>Goose</summary>
- Type: STDIO
- Command: npx
- Args: ["affinity-mcp"]
- Enabled: true
</details>
<details><summary>opencode</summary>
~/.config/opencode/opencode.json:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"affinity-mcp": {
"type": "local",
"command": ["npx", "affinity-mcp"],
"enabled": true
}
}
}
```
</details>
<details><summary>Qodo Gen</summary>
Add a new MCP and paste the standard JSON config.
</details>
<details><summary>Windsurf</summary>
See docs and reuse the standard config above.
</details>
## Setup: Codex (TOML)
Example (Serena):
```toml
[mcp_servers.serena]
command = "uvx"
args = ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--context", "codex"]
```
This server (minimal):
```toml
[mcp_servers.affinity-mcp]
command = "npx"
args = ["affinity-mcp"]
# Optional environment variables:
# AFFINITY_MCP_API_KEY = "sk-your-real-key"
# MCP_NAME = "affinity-mcp"
```
## Configuration (Env)
- `AFFINITY_MCP_API_KEY`: Canva API key or token (if applicable)
- `MCP_NAME`: Server name override (default: affinity-mcp)
If your tools are purely local, no API keys are required.
## Available Tools
### Affinity Tools (Natural Language Support)
All Affinity tools support natural language commands. Examples:
- "Open the file /path/to/image.jpg in Affinity Photo"
- "Create a new document with width 1920 and height 1080"
- "Export the current document as PDF to /path/to/output.pdf"
#### affinity.open_file
Open a file in Affinity application (auto-detects app from file extension).
- inputs: { "path": string, "app"?: "Photo"|"Designer"|"Publisher" }
- outputs: { "opened": boolean, "app": string, "path": string }
#### affinity.create_new
Create a new Affinity document.
- inputs: { "app": "Photo"|"Designer"|"Publisher", "width"?: number, "height"?: number }
- outputs: { "created": boolean, "app": string }
#### affinity.export
Export the currently open document.
- inputs: { "path": string, "format": "pdf"|"png"|"jpg"|"tiff"|"svg", "quality"?: number }
- outputs: { "exported": boolean, "path": string }
#### affinity.apply_filter
Apply a filter to the current document.
- inputs: { "filter_name": string, "intensity"?: number }
- outputs: { "applied": boolean, "filter_name": string }
#### affinity.get_active_document
Get information about the currently active document.
- inputs: {}
- outputs: { "is_open": boolean, "name"?: string, "path"?: string }
#### affinity.close_document
Close the currently open document.
- inputs: {}
- outputs: { "closed": boolean }
#### affinity.batch_open_files ⚡ **16-Parallel**
Open multiple files simultaneously (up to 16 files in parallel).
- inputs: { "paths": string[], "app"?: "Photo"|"Designer"|"Publisher" }
- outputs: { "success_count": number, "failure_count": number, "results": OpenFileResult[] }
**Natural language example**: "Open multiple files: /path/to/image1.jpg, /path/to/image2.jpg, /path/to/image3.jpg"
#### affinity.batch_export ⚡ **16-Parallel**
Export multiple documents simultaneously (up to 16 exports in parallel).
- inputs: { "exports": ExportParams[] }
- outputs: { "success_count": number, "failure_count": number, "results": ExportResult[] }
**Natural language example**: "Export all open documents as PDF files"
### Canva Tools
#### canva.create_design
- inputs: { "title": string, "template_id"?: string, "width"?: number, "height"?: number }
- outputs: { "design_id": string, "url"?: string }
## Example Usage
### Natural Language Commands
In Cursor or other MCP-compatible clients, you can use natural language:
**Example 1: Open a file**
```
User: "Open /Users/john/Desktop/image.jpg in Affinity Photo"
AI: [Calls affinity.open_file with path="/Users/john/Desktop/image.jpg", app="Photo"]
```
**Example 2: Create and export**
```
User: "Create a new Affinity Photo document with size 1920x1080, then export it as PDF to my Desktop"
AI: [Calls affinity.create_new, then affinity.export]
```
**Example 3: Get document info**
```
User: "What document is currently open?"
AI: [Calls affinity.get_active_document]
```
**Example 4: Batch operations (16-parallel)**
```
User: "Open these 5 images simultaneously: /path/to/img1.jpg, /path/to/img2.jpg, /path/to/img3.jpg, /path/to/img4.jpg, /path/to/img5.jpg"
AI: [Calls affinity.batch_open_files with paths array]
```
**Example 5: Batch export (16-parallel)**
```
User: "Export all open documents as PDF files to the Desktop"
AI: [Calls affinity.batch_export with multiple export configurations]
```
### Direct Tool Invocation (MCP tool call)
```json
{
"type": "tool",
"name": "affinity.open_file",
"arguments": {
"path": "/path/to/image.jpg",
"app": "Photo"
}
}
```
## Documentation
- **[SETUP_GUIDE.md](SETUP_GUIDE.md)**: Beginner-friendly setup guide with step-by-step instructions
- **[SPEC.md](SPEC.md)**: Technical specification (Japanese)
- **[README.md](README.md)**: This file (English documentation)
For complex schemas, see `src/tools/*.rs`.
## Name Consistency & Troubleshooting
Always use CANONICAL_ID (`affinity-mcp`) for identifiers and keys.
Use CANONICAL_DISPLAY (`AffinityMCP`) only for UI labels.
Do not mix legacy keys after registration.
### Consistency Matrix
- npm package name → `affinity-mcp`
- Binary name → `affinity-mcp`
- MCP server name → `affinity-mcp`
- Env default MCP_NAME → `affinity-mcp`
- Client registry key → `affinity-mcp`
- UI label → `AffinityMCP`
### Conflict Cleanup
Remove stale keys and re-add with `affinity-mcp` only.
Cursor is UI-configured only; this repo intentionally omits `.cursor/mcp.json`.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues