# DSS MCP Tools Specification ## New Tools for Project & Figma Management Instead of REST endpoints, the following MCP tools should be implemented in `tools/dss_mcp/tools/project_tools.py`: --- ## 1. `dss_create_project` **Purpose:** Create a new DSS project with empty Figma manifest **Input Schema:** ```json { "type": "object", "properties": { "name": { "type": "string", "description": "Project name (required)" }, "root_path": { "type": "string", "description": "Project root path (default: '.')" }, "description": { "type": "string", "description": "Optional project description" } }, "required": ["name", "root_path"] } ``` **Implementation:** ```python async def create_project(self, name: str, root_path: str, description: str = "") -> Dict: """Create a new project with empty Figma manifest""" # 1. Insert into projects table (name, root_path, description, created_at) # 2. Create figma.json manifest in project folder: # { # "version": "1.0", # "files": [], # "lastUpdated": "2025-12-05T..." # } # 3. Return project metadata (id, name, root_path) # 4. Emit project-created event project_id = conn.execute( "INSERT INTO projects (name, root_path, description, created_at) VALUES (?, ?, ?, ?)", (name, root_path, description, datetime.now().isoformat()) ).lastrowid # Create manifest file manifest_path = os.path.join(root_path, "figma.json") manifest = { "version": "1.0", "files": [], "lastUpdated": datetime.now().isoformat() } os.makedirs(root_path, exist_ok=True) with open(manifest_path, 'w') as f: json.dump(manifest, f, indent=2) return { "project_id": project_id, "name": name, "root_path": root_path, "manifest_path": manifest_path, "status": "created" } ``` **Returns:** ```json { "project_id": "1", "name": "my-design-system", "root_path": "./packages/design", "manifest_path": "./packages/design/figma.json", "status": "created" } ``` --- ## 2. `dss_setup_figma_credentials` **Purpose:** Store Figma API token at user level (encrypted) **Input Schema:** ```json { "type": "object", "properties": { "api_token": { "type": "string", "description": "Figma Personal Access Token" } }, "required": ["api_token"] } ``` **Implementation:** ```python async def setup_figma_credentials(self, api_token: str, user_id: str = None) -> Dict: """Store and validate Figma API credentials at user level""" # 1. Validate token by testing Figma API headers = {"X-Figma-Token": api_token} async with httpx.AsyncClient() as client: response = await client.get("https://api.figma.com/v1/me", headers=headers) if not response.is_success: raise ValueError("Invalid Figma API token") user_data = response.json() # 2. Encrypt and store in project_integrations table (user-scoped) # Use project_id=NULL for global user credentials cipher = MCPConfig.get_cipher() encrypted_config = cipher.encrypt( json.dumps({"api_token": api_token}).encode() ).decode() conn.execute( """INSERT OR REPLACE INTO project_integrations (project_id, user_id, integration_type, config, enabled, created_at) VALUES (NULL, ?, 'figma', ?, 1, ?)""", (user_id or "anonymous", encrypted_config, datetime.now().isoformat()) ) # 3. Update integration_health conn.execute( """INSERT OR REPLACE INTO integration_health (integration_type, is_healthy, last_success_at) VALUES ('figma', 1, ?)""", (datetime.now().isoformat(),) ) return { "status": "configured", "figma_user": user_data.get("name"), "workspace": user_data.get("email"), "message": "Figma credentials stored securely at user level" } ``` **Returns:** ```json { "status": "configured", "figma_user": "John Designer", "workspace": "john@company.com", "message": "Figma credentials stored securely at user level" } ``` --- ## 3. `dss_get_project_manifest` **Purpose:** Read project's figma.json manifest **Input Schema:** ```json { "type": "object", "properties": { "project_id": { "type": "string", "description": "Project ID" } }, "required": ["project_id"] } ``` **Implementation:** ```python async def get_project_manifest(self, project_id: str) -> Dict: """Get project's Figma manifest""" # 1. Get project path from database project = conn.execute( "SELECT root_path FROM projects WHERE id = ?", (project_id,) ).fetchone() if not project: raise ValueError(f"Project {project_id} not found") # 2. Read figma.json manifest_path = os.path.join(project["root_path"], "figma.json") if os.path.exists(manifest_path): with open(manifest_path, 'r') as f: manifest = json.load(f) else: manifest = { "version": "1.0", "files": [], "lastUpdated": datetime.now().isoformat() } return manifest ``` **Returns:** ```json { "version": "1.0", "files": [ { "key": "figd_abc123", "name": "Design Tokens", "linkedAt": "2025-12-05T15:30:00Z" } ], "lastUpdated": "2025-12-05T16:00:00Z" } ``` --- ## 4. `dss_add_figma_file` **Purpose:** Add Figma file reference to project manifest **Input Schema:** ```json { "type": "object", "properties": { "project_id": { "type": "string", "description": "Project ID" }, "file_key": { "type": "string", "description": "Figma file key (e.g., figd_abc123 or full URL)" }, "file_name": { "type": "string", "description": "Optional display name for the file" } }, "required": ["project_id", "file_key"] } ``` **Implementation:** ```python async def add_figma_file(self, project_id: str, file_key: str, file_name: str = None) -> Dict: """Add Figma file to project manifest""" # 1. Extract file key from URL if needed if "figma.com" in file_key: match = re.search(r"file/([a-zA-Z0-9]+)", file_key) file_key = match.group(1) if match else file_key # 2. Get project and load manifest project = conn.execute( "SELECT root_path FROM projects WHERE id = ?", (project_id,) ).fetchone() manifest_path = os.path.join(project["root_path"], "figma.json") with open(manifest_path, 'r') as f: manifest = json.load(f) # 3. Check if file already linked existing = next((f for f in manifest["files"] if f["key"] == file_key), None) if existing: raise ValueError(f"File {file_key} already linked to project") # 4. Add file to manifest manifest["files"].append({ "key": file_key, "name": file_name or f"Figma File {file_key[:8]}", "linkedAt": datetime.now().isoformat() }) manifest["lastUpdated"] = datetime.now().isoformat() # 5. Write manifest back with open(manifest_path, 'w') as f: json.dump(manifest, f, indent=2) return { "project_id": project_id, "file_key": file_key, "file_name": file_name, "status": "added", "files_count": len(manifest["files"]) } ``` **Returns:** ```json { "project_id": "1", "file_key": "figd_abc123", "file_name": "Design Tokens", "status": "added", "files_count": 1 } ``` --- ## 5. `dss_discover_figma_files` **Purpose:** Discover available Figma files and suggest linking **Input Schema:** ```json { "type": "object", "properties": { "project_id": { "type": "string", "description": "Project ID" } }, "required": ["project_id"] } ``` **Implementation:** ```python async def discover_figma_files(self, project_id: str, user_id: str = None) -> Dict: """Discover available Figma files from user's workspaces""" # 1. Get user's Figma credentials from project_integrations creds = conn.execute( """SELECT config FROM project_integrations WHERE integration_type='figma' AND (project_id IS NULL OR project_id=?) LIMIT 1""", (project_id,) ).fetchone() if not creds: raise ValueError("Figma credentials not configured. Run /setup-figma first.") # 2. Decrypt credentials cipher = MCPConfig.get_cipher() config = json.loads(cipher.decrypt(creds["config"].encode()).decode()) api_token = config["api_token"] # 3. Fetch user's teams from Figma API headers = {"X-Figma-Token": api_token} available_files = [] async with httpx.AsyncClient() as client: # Get teams resp = await client.get("https://api.figma.com/v1/teams", headers=headers) teams = resp.json().get("teams", []) # Get projects in each team for team in teams: team_resp = await client.get( f"https://api.figma.com/v1/teams/{team['id']}/projects", headers=headers ) projects = team_resp.json().get("projects", []) for project in projects: # Get files in each project files_resp = await client.get( f"https://api.figma.com/v1/projects/{project['id']}/files", headers=headers ) files = files_resp.json().get("files", []) for file in files: available_files.append({ "key": file["key"], "name": file["name"], "team": team["name"], "project": project["name"] }) # 4. Get currently linked files manifest = await self.get_project_manifest(project_id) linked_keys = {f["key"] for f in manifest["files"]} # 5. Return available files (excluding already linked) available = [f for f in available_files if f["key"] not in linked_keys] return { "project_id": project_id, "linked_files": manifest["files"], "available_files": available[:10], # Top 10 suggestions "total_available": len(available), "message": f"Found {len(available)} available Figma files" } ``` **Returns:** ```json { "project_id": "1", "linked_files": [ {"key": "figd_abc123", "name": "Design Tokens", "linkedAt": "..."} ], "available_files": [ {"key": "figd_xyz789", "name": "Components", "team": "Design", "project": "Main"}, {"key": "figd_def456", "name": "Icons", "team": "Design", "project": "Main"} ], "total_available": 2, "message": "Found 2 available Figma files" } ``` --- ## 6. `dss_list_project_figma_files` **Purpose:** List all Figma files currently linked to project **Input Schema:** ```json { "type": "object", "properties": { "project_id": { "type": "string", "description": "Project ID" } }, "required": ["project_id"] } ``` **Implementation:** ```python async def list_project_figma_files(self, project_id: str) -> Dict: """List all Figma files in project manifest""" manifest = await self.get_project_manifest(project_id) return { "project_id": project_id, "files": manifest["files"], "count": len(manifest["files"]) } ``` **Returns:** ```json { "project_id": "1", "files": [ { "key": "figd_abc123", "name": "Design Tokens", "linkedAt": "2025-12-05T15:30:00Z" }, { "key": "figd_xyz789", "name": "Components", "linkedAt": "2025-12-05T16:00:00Z" } ], "count": 2 } ``` --- ## Implementation Checklist - [ ] Add 6 new tools to `project_tools.py` - [ ] Create manifest read/write helper functions - [ ] Add encryption for Figma tokens in `project_integrations` table - [ ] Add to `PROJECT_TOOLS` list - [ ] Register in `handler.py` tool registry - [ ] Add audit logging to `mcp_tool_usage` table - [ ] Update integration_health on success/failure - [ ] Add circuit breaker for Figma API calls - [ ] Add input validation for file keys and tokens - [ ] Test with MCP client --- ## Architecture Benefits ✅ **No REST endpoints** - All work via MCP tools ✅ **User-level credentials** - Figma tokens stored per-user in database ✅ **Manifest-driven** - figma.json declares project dependencies ✅ **Versionable** - Manifests can be checked into git ✅ **Discoverable** - Claude can list available Figma files ✅ **Audit trail** - All operations logged in mcp_tool_usage ✅ **Circuit breaker** - Protected against cascading API failures ✅ **Encrypted storage** - Credentials encrypted with Fernet This is the true **MCP-first architecture** for DSS! 🚀