Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
494
MCP_TOOLS_SPEC.md
Normal file
494
MCP_TOOLS_SPEC.md
Normal file
@@ -0,0 +1,494 @@
|
||||
# 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! 🚀
|
||||
Reference in New Issue
Block a user