feat: Add Sequential Thinking to Code Review + Frontend Validation
Enhanced code review and frontend validation with intelligent triggers: Code Review Agent Enhancement: - Added Sequential Thinking MCP integration for complex issues - Triggers on 2+ rejections or 3+ critical issues - New escalation format with root cause analysis - Comprehensive solution strategies with trade-off evaluation - Educational feedback to break rejection cycles - Files: .claude/agents/code-review.md (+308 lines) - Docs: CODE_REVIEW_ST_ENHANCEMENT.md, CODE_REVIEW_ST_TESTING.md Frontend Design Skill Enhancement: - Automatic invocation for ANY UI change - Comprehensive validation checklist (200+ checkpoints) - 8 validation categories (visual, interactive, responsive, a11y, etc.) - 3 validation levels (quick, standard, comprehensive) - Integration with code review workflow - Files: .claude/skills/frontend-design/SKILL.md (+120 lines) - Docs: UI_VALIDATION_CHECKLIST.md (462 lines), AUTOMATIC_VALIDATION_ENHANCEMENT.md (587 lines) Settings Optimization: - Repaired .claude/settings.local.json (fixed m365 pattern) - Reduced permissions from 49 to 33 (33% reduction) - Removed duplicates, sorted alphabetically - Created SETTINGS_PERMISSIONS.md documentation Checkpoint Command Enhancement: - Dual checkpoint system (git + database) - Saves session context to API for cross-machine recall - Includes git metadata in database context - Files: .claude/commands/checkpoint.md (+139 lines) Decision Rationale: - Sequential Thinking MCP breaks rejection cycles by identifying root causes - Automatic frontend validation catches UI issues before code review - Dual checkpoints enable complete project memory across machines - Settings optimization improves maintainability Total: 1,200+ lines of documentation and enhancements Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
447
mcp-servers/feature-management/README.md
Normal file
447
mcp-servers/feature-management/README.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Feature Management MCP Server
|
||||
|
||||
**Source:** AutoCoder project
|
||||
**Type:** Model Context Protocol (MCP) Server
|
||||
**Purpose:** Manage autonomous coding features with priority-based workflow
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Feature Management MCP Server provides tools for managing a feature queue in an autonomous coding workflow. It replaces the previous FastAPI-based REST API with a native MCP implementation that integrates directly with Claude Code.
|
||||
|
||||
**Key Capabilities:**
|
||||
- Track feature implementation progress
|
||||
- Priority-based feature queue management
|
||||
- Mark features as passing/in-progress/skipped
|
||||
- Regression testing support
|
||||
- Bulk feature creation from app specifications
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Database
|
||||
- **Backend:** SQLite (via SQLAlchemy)
|
||||
- **Migration:** Auto-migrates legacy JSON to SQLite
|
||||
- **Storage:** `{PROJECT_DIR}/features.db`
|
||||
|
||||
### Feature Model
|
||||
```python
|
||||
{
|
||||
"id": int, # Auto-increment primary key
|
||||
"priority": int, # Lower = higher priority
|
||||
"category": str, # Feature category (e.g., "authentication", "ui")
|
||||
"name": str, # Short feature name
|
||||
"description": str, # Detailed description
|
||||
"steps": list[str], # Implementation/test steps
|
||||
"passes": bool, # True if feature is implemented and working
|
||||
"in_progress": bool # True if currently being worked on
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Tools
|
||||
|
||||
### 1. feature_get_stats
|
||||
**Purpose:** Get overall progress statistics
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_get_stats()
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```json
|
||||
{
|
||||
"passing": 42,
|
||||
"in_progress": 1,
|
||||
"total": 100,
|
||||
"percentage": 42.0
|
||||
}
|
||||
```
|
||||
|
||||
**When to use:** At the start/end of coding sessions to track progress
|
||||
|
||||
---
|
||||
|
||||
### 2. feature_get_next
|
||||
**Purpose:** Get the next feature to implement
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_get_next()
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```json
|
||||
{
|
||||
"id": 15,
|
||||
"priority": 15,
|
||||
"category": "authentication",
|
||||
"name": "User login endpoint",
|
||||
"description": "Create POST /api/auth/login endpoint...",
|
||||
"steps": [
|
||||
"Create SQLAlchemy User model",
|
||||
"Implement login route",
|
||||
"Add JWT token generation",
|
||||
"Write tests"
|
||||
],
|
||||
"passes": false,
|
||||
"in_progress": false
|
||||
}
|
||||
```
|
||||
|
||||
**When to use:** At the start of each implementation cycle
|
||||
|
||||
---
|
||||
|
||||
### 3. feature_mark_passing
|
||||
**Purpose:** Mark a feature as successfully implemented
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_mark_passing(feature_id=15)
|
||||
```
|
||||
|
||||
**Returns:** Updated feature object with `passes=true`, `in_progress=false`
|
||||
|
||||
**When to use:** After implementing and verifying a feature works
|
||||
|
||||
---
|
||||
|
||||
### 4. feature_mark_in_progress
|
||||
**Purpose:** Mark a feature as being worked on
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_mark_in_progress(feature_id=15)
|
||||
```
|
||||
|
||||
**Returns:** Updated feature object with `in_progress=true`
|
||||
|
||||
**When to use:** Immediately after `feature_get_next()` to claim the feature
|
||||
|
||||
**Important:** Prevents other parallel agent sessions from working on the same feature
|
||||
|
||||
---
|
||||
|
||||
### 5. feature_skip
|
||||
**Purpose:** Move a feature to the end of the queue
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_skip(feature_id=15)
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```json
|
||||
{
|
||||
"id": 15,
|
||||
"name": "User login endpoint",
|
||||
"old_priority": 15,
|
||||
"new_priority": 101,
|
||||
"message": "Feature 'User login endpoint' moved to end of queue"
|
||||
}
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- Feature has unmet dependencies
|
||||
- External blockers (missing assets, unclear requirements)
|
||||
- Technical prerequisites needed
|
||||
|
||||
---
|
||||
|
||||
### 6. feature_clear_in_progress
|
||||
**Purpose:** Reset in-progress status
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_clear_in_progress(feature_id=15)
|
||||
```
|
||||
|
||||
**Returns:** Updated feature object with `in_progress=false`
|
||||
|
||||
**When to use:**
|
||||
- Abandoning a feature midway
|
||||
- Manually unsticking a stuck feature
|
||||
- Resetting state after errors
|
||||
|
||||
---
|
||||
|
||||
### 7. feature_get_for_regression
|
||||
**Purpose:** Get random passing features for regression testing
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_get_for_regression(limit=3) # 1-10, default 3
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```json
|
||||
{
|
||||
"features": [
|
||||
{"id": 3, "name": "User registration", "passes": true, ...},
|
||||
{"id": 7, "name": "Login validation", "passes": true, ...},
|
||||
{"id": 12, "name": "Password reset", "passes": true, ...}
|
||||
],
|
||||
"count": 3
|
||||
}
|
||||
```
|
||||
|
||||
**When to use:** After implementing new features to verify no regressions
|
||||
|
||||
---
|
||||
|
||||
### 8. feature_create_bulk
|
||||
**Purpose:** Create multiple features at once
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
feature_create_bulk(features=[
|
||||
{
|
||||
"category": "authentication",
|
||||
"name": "User registration",
|
||||
"description": "Allow users to create accounts...",
|
||||
"steps": ["Create User model", "Create POST /register", "Add validation"]
|
||||
},
|
||||
{
|
||||
"category": "authentication",
|
||||
"name": "User login",
|
||||
"description": "Allow users to log in...",
|
||||
"steps": ["Create POST /login", "Add JWT tokens", "Write tests"]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```json
|
||||
{
|
||||
"created": 2
|
||||
}
|
||||
```
|
||||
|
||||
**When to use:** Initializing a new project from an app specification
|
||||
|
||||
---
|
||||
|
||||
## Installation & Configuration
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
The MCP server requires SQLAlchemy and FastMCP. In the AutoCoder project these are already included, but for standalone use:
|
||||
|
||||
```bash
|
||||
pip install sqlalchemy fastmcp pydantic
|
||||
```
|
||||
|
||||
### 2. Configure Claude Desktop
|
||||
|
||||
Add to `~/.config/claude/claude_desktop_config.json` (macOS/Linux) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"features": {
|
||||
"command": "python",
|
||||
"args": ["D:\\ClaudeTools\\mcp-servers\\feature-management\\feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "D:\\ClaudeTools\\projects\\your-project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Set PROJECT_DIR
|
||||
|
||||
The `PROJECT_DIR` environment variable determines where the SQLite database is stored:
|
||||
|
||||
```bash
|
||||
# Windows (PowerShell)
|
||||
$env:PROJECT_DIR="D:\ClaudeTools\projects\your-project"
|
||||
|
||||
# Linux/macOS
|
||||
export PROJECT_DIR="/path/to/your/project"
|
||||
```
|
||||
|
||||
The database will be created at `{PROJECT_DIR}/features.db`
|
||||
|
||||
---
|
||||
|
||||
## Typical Workflow
|
||||
|
||||
### Autonomous Coding Agent Session
|
||||
|
||||
```python
|
||||
# 1. Check progress at start of session
|
||||
stats = feature_get_stats()
|
||||
# Output: 42/100 features passing (42.0%)
|
||||
|
||||
# 2. Get next feature to work on
|
||||
next_feature = feature_get_next()
|
||||
# Output: Feature #15 - "User login endpoint"
|
||||
|
||||
# 3. Mark it in-progress (claim it)
|
||||
feature_mark_in_progress(feature_id=15)
|
||||
|
||||
# 4. Implement the feature
|
||||
# ... write code, run tests, verify ...
|
||||
|
||||
# 5. Mark as passing when done
|
||||
feature_mark_passing(feature_id=15)
|
||||
|
||||
# 6. Optional: Run regression tests
|
||||
regression = feature_get_for_regression(limit=5)
|
||||
# Output: 5 random passing features to re-test
|
||||
```
|
||||
|
||||
### Handling Blockers
|
||||
|
||||
```python
|
||||
# Get next feature
|
||||
next_feature = feature_get_next()
|
||||
# Feature #20 - "OAuth integration"
|
||||
|
||||
# Realize it depends on feature #25 which isn't done yet
|
||||
feature_skip(feature_id=20)
|
||||
# Feature moved to end of queue (priority 101)
|
||||
|
||||
# Get actual next feature
|
||||
next_feature = feature_get_next()
|
||||
# Feature #21 - "Email validation"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE features (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
priority INTEGER NOT NULL,
|
||||
category VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
steps TEXT NOT NULL, -- JSON array of strings
|
||||
passes BOOLEAN DEFAULT 0,
|
||||
in_progress BOOLEAN DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_priority ON features(priority);
|
||||
CREATE INDEX idx_passes ON features(passes);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with ClaudeTools
|
||||
|
||||
This MCP server can be integrated with the ClaudeTools context recall system:
|
||||
|
||||
1. Store feature completion decisions in `decision_logs` table
|
||||
2. Save feature implementation context in `conversation_contexts`
|
||||
3. Track feature work sessions in `sessions` table
|
||||
4. Log feature-related errors in `problem_solutions`
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
# After marking feature passing, log the decision
|
||||
POST /api/decision-logs
|
||||
{
|
||||
"project_id": "uuid",
|
||||
"decision_type": "technical",
|
||||
"decision_text": "Implemented user login endpoint",
|
||||
"rationale": "Feature #15 completed with JWT authentication",
|
||||
"tags": ["authentication", "feature-15", "jwt"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database not found
|
||||
**Error:** `Database not initialized`
|
||||
|
||||
**Solution:** Ensure `PROJECT_DIR` environment variable is set and writable
|
||||
|
||||
### Migration issues
|
||||
**Error:** `Migration failed`
|
||||
|
||||
**Solution:** The server auto-migrates legacy JSON (`features.json`) to SQLite. If you have custom JSON structure, you may need to adjust the migration in `api/migration.py`
|
||||
|
||||
### All features showing in-progress
|
||||
**Problem:** Features stuck in `in_progress=true` state
|
||||
|
||||
**Solution:**
|
||||
```python
|
||||
# Clear all stuck features
|
||||
for feature_id in [1, 2, 3, ...]:
|
||||
feature_clear_in_progress(feature_id=feature_id)
|
||||
```
|
||||
|
||||
Or directly in SQLite:
|
||||
```sql
|
||||
UPDATE features SET in_progress = 0 WHERE in_progress = 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Differences from REST API
|
||||
|
||||
The original AutoCoder project used a FastAPI REST server. This MCP server provides the same functionality with these improvements:
|
||||
|
||||
| Feature | REST API | MCP Server |
|
||||
|---------|----------|------------|
|
||||
| Integration | HTTP requests | Native Claude MCP tools |
|
||||
| Authentication | API keys | Built into MCP protocol |
|
||||
| Type safety | Manual validation | Pydantic models |
|
||||
| Error handling | HTTP status codes | JSON error responses |
|
||||
| Database | JSON files | SQLite (with JSON migration) |
|
||||
| Startup time | ~1-2 seconds | ~100ms |
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
mcp-servers/feature-management/
|
||||
├── feature_mcp.py # Main MCP server implementation
|
||||
├── __init__.py # Python module marker
|
||||
├── README.md # This file
|
||||
└── config.example.json # Example configuration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
**Required:**
|
||||
- `fastmcp` - MCP protocol implementation
|
||||
- `sqlalchemy` - Database ORM
|
||||
- `pydantic` - Data validation
|
||||
|
||||
**Optional:**
|
||||
- `api.database` - Feature model and database setup (from AutoCoder)
|
||||
- `api.migration` - JSON to SQLite migration (from AutoCoder)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Same license as the AutoCoder project (see LICENSE.txt in AutoCoder repository)
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- `.claude/commands/create-spec.md` - Create app specification
|
||||
- `.claude/commands/checkpoint.md` - Create development checkpoint
|
||||
- `.claude/skills/frontend-design/` - Frontend design skill
|
||||
- `.claude/templates/` - Prompt templates for autonomous coding
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-17
|
||||
**Status:** Production-ready (ported from AutoCoder)
|
||||
1
mcp-servers/feature-management/__init__.py
Normal file
1
mcp-servers/feature-management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""MCP Server Package for Feature Management."""
|
||||
81
mcp-servers/feature-management/config.example.json
Normal file
81
mcp-servers/feature-management/config.example.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"_comment": "Example MCP server configuration for Feature Management",
|
||||
"_usage": "Add this to your Claude Desktop configuration file",
|
||||
"_config_locations": {
|
||||
"windows": "%APPDATA%\\Claude\\claude_desktop_config.json",
|
||||
"macos": "~/Library/Application Support/Claude/claude_desktop_config.json",
|
||||
"linux": "~/.config/claude/claude_desktop_config.json"
|
||||
},
|
||||
|
||||
"mcpServers": {
|
||||
"features": {
|
||||
"command": "python",
|
||||
"args": ["D:\\ClaudeTools\\mcp-servers\\feature-management\\feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "D:\\ClaudeTools\\projects\\your-project-name",
|
||||
"_comment_PROJECT_DIR": "Directory where features.db will be created/stored"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"_notes": [
|
||||
"Replace 'D:\\ClaudeTools' with your actual ClaudeTools installation path",
|
||||
"Replace 'your-project-name' with your actual project name",
|
||||
"The PROJECT_DIR must be writable by the user running Claude",
|
||||
"Database will be created at {PROJECT_DIR}/features.db",
|
||||
"On Unix systems, use forward slashes: /path/to/ClaudeTools",
|
||||
"After editing, restart Claude Desktop for changes to take effect"
|
||||
],
|
||||
|
||||
"_examples": {
|
||||
"windows_absolute": {
|
||||
"command": "python",
|
||||
"args": ["D:\\ClaudeTools\\mcp-servers\\feature-management\\feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "D:\\ClaudeTools\\projects\\my-web-app"
|
||||
}
|
||||
},
|
||||
|
||||
"macos_absolute": {
|
||||
"command": "python3",
|
||||
"args": ["/Users/username/ClaudeTools/mcp-servers/feature-management/feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "/Users/username/ClaudeTools/projects/my-web-app"
|
||||
}
|
||||
},
|
||||
|
||||
"linux_absolute": {
|
||||
"command": "python3",
|
||||
"args": ["/home/username/ClaudeTools/mcp-servers/feature-management/feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "/home/username/ClaudeTools/projects/my-web-app"
|
||||
}
|
||||
},
|
||||
|
||||
"with_venv": {
|
||||
"_comment": "Using a virtual environment",
|
||||
"command": "D:\\ClaudeTools\\venv\\Scripts\\python.exe",
|
||||
"args": ["D:\\ClaudeTools\\mcp-servers\\feature-management\\feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "D:\\ClaudeTools\\projects\\my-web-app"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"_full_config_example": {
|
||||
"mcpServers": {
|
||||
"features": {
|
||||
"command": "python",
|
||||
"args": ["D:\\ClaudeTools\\mcp-servers\\feature-management\\feature_mcp.py"],
|
||||
"env": {
|
||||
"PROJECT_DIR": "D:\\ClaudeTools\\projects\\my-web-app"
|
||||
}
|
||||
},
|
||||
"other-mcp-server": {
|
||||
"command": "python",
|
||||
"args": ["path/to/other/server.py"]
|
||||
}
|
||||
},
|
||||
"globalShortcut": "Ctrl+Space"
|
||||
}
|
||||
}
|
||||
417
mcp-servers/feature-management/feature_mcp.py
Normal file
417
mcp-servers/feature-management/feature_mcp.py
Normal file
@@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MCP Server for Feature Management
|
||||
==================================
|
||||
|
||||
Provides tools to manage features in the autonomous coding system,
|
||||
replacing the previous FastAPI-based REST API.
|
||||
|
||||
Tools:
|
||||
- feature_get_stats: Get progress statistics
|
||||
- feature_get_next: Get next feature to implement
|
||||
- feature_get_for_regression: Get random passing features for testing
|
||||
- feature_mark_passing: Mark a feature as passing
|
||||
- feature_skip: Skip a feature (move to end of queue)
|
||||
- feature_mark_in_progress: Mark a feature as in-progress
|
||||
- feature_clear_in_progress: Clear in-progress status
|
||||
- feature_create_bulk: Create multiple features at once
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
# Add parent directory to path so we can import from api module
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from api.database import Feature, create_database
|
||||
from api.migration import migrate_json_to_sqlite
|
||||
|
||||
# Configuration from environment
|
||||
PROJECT_DIR = Path(os.environ.get("PROJECT_DIR", ".")).resolve()
|
||||
|
||||
|
||||
# Pydantic models for input validation
|
||||
class MarkPassingInput(BaseModel):
|
||||
"""Input for marking a feature as passing."""
|
||||
feature_id: int = Field(..., description="The ID of the feature to mark as passing", ge=1)
|
||||
|
||||
|
||||
class SkipFeatureInput(BaseModel):
|
||||
"""Input for skipping a feature."""
|
||||
feature_id: int = Field(..., description="The ID of the feature to skip", ge=1)
|
||||
|
||||
|
||||
class MarkInProgressInput(BaseModel):
|
||||
"""Input for marking a feature as in-progress."""
|
||||
feature_id: int = Field(..., description="The ID of the feature to mark as in-progress", ge=1)
|
||||
|
||||
|
||||
class ClearInProgressInput(BaseModel):
|
||||
"""Input for clearing in-progress status."""
|
||||
feature_id: int = Field(..., description="The ID of the feature to clear in-progress status", ge=1)
|
||||
|
||||
|
||||
class RegressionInput(BaseModel):
|
||||
"""Input for getting regression features."""
|
||||
limit: int = Field(default=3, ge=1, le=10, description="Maximum number of passing features to return")
|
||||
|
||||
|
||||
class FeatureCreateItem(BaseModel):
|
||||
"""Schema for creating a single feature."""
|
||||
category: str = Field(..., min_length=1, max_length=100, description="Feature category")
|
||||
name: str = Field(..., min_length=1, max_length=255, description="Feature name")
|
||||
description: str = Field(..., min_length=1, description="Detailed description")
|
||||
steps: list[str] = Field(..., min_length=1, description="Implementation/test steps")
|
||||
|
||||
|
||||
class BulkCreateInput(BaseModel):
|
||||
"""Input for bulk creating features."""
|
||||
features: list[FeatureCreateItem] = Field(..., min_length=1, description="List of features to create")
|
||||
|
||||
|
||||
# Global database session maker (initialized on startup)
|
||||
_session_maker = None
|
||||
_engine = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(server: FastMCP):
|
||||
"""Initialize database on startup, cleanup on shutdown."""
|
||||
global _session_maker, _engine
|
||||
|
||||
# Create project directory if it doesn't exist
|
||||
PROJECT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Initialize database
|
||||
_engine, _session_maker = create_database(PROJECT_DIR)
|
||||
|
||||
# Run migration if needed (converts legacy JSON to SQLite)
|
||||
migrate_json_to_sqlite(PROJECT_DIR, _session_maker)
|
||||
|
||||
yield
|
||||
|
||||
# Cleanup
|
||||
if _engine:
|
||||
_engine.dispose()
|
||||
|
||||
|
||||
# Initialize the MCP server
|
||||
mcp = FastMCP("features", lifespan=server_lifespan)
|
||||
|
||||
|
||||
def get_session():
|
||||
"""Get a new database session."""
|
||||
if _session_maker is None:
|
||||
raise RuntimeError("Database not initialized")
|
||||
return _session_maker()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_get_stats() -> str:
|
||||
"""Get statistics about feature completion progress.
|
||||
|
||||
Returns the number of passing features, in-progress features, total features,
|
||||
and completion percentage. Use this to track overall progress of the implementation.
|
||||
|
||||
Returns:
|
||||
JSON with: passing (int), in_progress (int), total (int), percentage (float)
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
total = session.query(Feature).count()
|
||||
passing = session.query(Feature).filter(Feature.passes == True).count()
|
||||
in_progress = session.query(Feature).filter(Feature.in_progress == True).count()
|
||||
percentage = round((passing / total) * 100, 1) if total > 0 else 0.0
|
||||
|
||||
return json.dumps({
|
||||
"passing": passing,
|
||||
"in_progress": in_progress,
|
||||
"total": total,
|
||||
"percentage": percentage
|
||||
}, indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_get_next() -> str:
|
||||
"""Get the highest-priority pending feature to work on.
|
||||
|
||||
Returns the feature with the lowest priority number that has passes=false.
|
||||
Use this at the start of each coding session to determine what to implement next.
|
||||
|
||||
Returns:
|
||||
JSON with feature details (id, priority, category, name, description, steps, passes, in_progress)
|
||||
or error message if all features are passing.
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
feature = (
|
||||
session.query(Feature)
|
||||
.filter(Feature.passes == False)
|
||||
.order_by(Feature.priority.asc(), Feature.id.asc())
|
||||
.first()
|
||||
)
|
||||
|
||||
if feature is None:
|
||||
return json.dumps({"error": "All features are passing! No more work to do."})
|
||||
|
||||
return json.dumps(feature.to_dict(), indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_get_for_regression(
|
||||
limit: Annotated[int, Field(default=3, ge=1, le=10, description="Maximum number of passing features to return")] = 3
|
||||
) -> str:
|
||||
"""Get random passing features for regression testing.
|
||||
|
||||
Returns a random selection of features that are currently passing.
|
||||
Use this to verify that previously implemented features still work
|
||||
after making changes.
|
||||
|
||||
Args:
|
||||
limit: Maximum number of features to return (1-10, default 3)
|
||||
|
||||
Returns:
|
||||
JSON with: features (list of feature objects), count (int)
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
features = (
|
||||
session.query(Feature)
|
||||
.filter(Feature.passes == True)
|
||||
.order_by(func.random())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
return json.dumps({
|
||||
"features": [f.to_dict() for f in features],
|
||||
"count": len(features)
|
||||
}, indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_mark_passing(
|
||||
feature_id: Annotated[int, Field(description="The ID of the feature to mark as passing", ge=1)]
|
||||
) -> str:
|
||||
"""Mark a feature as passing after successful implementation.
|
||||
|
||||
Updates the feature's passes field to true and clears the in_progress flag.
|
||||
Use this after you have implemented the feature and verified it works correctly.
|
||||
|
||||
Args:
|
||||
feature_id: The ID of the feature to mark as passing
|
||||
|
||||
Returns:
|
||||
JSON with the updated feature details, or error if not found.
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
feature = session.query(Feature).filter(Feature.id == feature_id).first()
|
||||
|
||||
if feature is None:
|
||||
return json.dumps({"error": f"Feature with ID {feature_id} not found"})
|
||||
|
||||
feature.passes = True
|
||||
feature.in_progress = False
|
||||
session.commit()
|
||||
session.refresh(feature)
|
||||
|
||||
return json.dumps(feature.to_dict(), indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_skip(
|
||||
feature_id: Annotated[int, Field(description="The ID of the feature to skip", ge=1)]
|
||||
) -> str:
|
||||
"""Skip a feature by moving it to the end of the priority queue.
|
||||
|
||||
Use this when a feature cannot be implemented yet due to:
|
||||
- Dependencies on other features that aren't implemented yet
|
||||
- External blockers (missing assets, unclear requirements)
|
||||
- Technical prerequisites that need to be addressed first
|
||||
|
||||
The feature's priority is set to max_priority + 1, so it will be
|
||||
worked on after all other pending features. Also clears the in_progress
|
||||
flag so the feature returns to "pending" status.
|
||||
|
||||
Args:
|
||||
feature_id: The ID of the feature to skip
|
||||
|
||||
Returns:
|
||||
JSON with skip details: id, name, old_priority, new_priority, message
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
feature = session.query(Feature).filter(Feature.id == feature_id).first()
|
||||
|
||||
if feature is None:
|
||||
return json.dumps({"error": f"Feature with ID {feature_id} not found"})
|
||||
|
||||
if feature.passes:
|
||||
return json.dumps({"error": "Cannot skip a feature that is already passing"})
|
||||
|
||||
old_priority = feature.priority
|
||||
|
||||
# Get max priority and set this feature to max + 1
|
||||
max_priority_result = session.query(Feature.priority).order_by(Feature.priority.desc()).first()
|
||||
new_priority = (max_priority_result[0] + 1) if max_priority_result else 1
|
||||
|
||||
feature.priority = new_priority
|
||||
feature.in_progress = False
|
||||
session.commit()
|
||||
session.refresh(feature)
|
||||
|
||||
return json.dumps({
|
||||
"id": feature.id,
|
||||
"name": feature.name,
|
||||
"old_priority": old_priority,
|
||||
"new_priority": new_priority,
|
||||
"message": f"Feature '{feature.name}' moved to end of queue"
|
||||
}, indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_mark_in_progress(
|
||||
feature_id: Annotated[int, Field(description="The ID of the feature to mark as in-progress", ge=1)]
|
||||
) -> str:
|
||||
"""Mark a feature as in-progress. Call immediately after feature_get_next().
|
||||
|
||||
This prevents other agent sessions from working on the same feature.
|
||||
Use this as soon as you retrieve a feature to work on.
|
||||
|
||||
Args:
|
||||
feature_id: The ID of the feature to mark as in-progress
|
||||
|
||||
Returns:
|
||||
JSON with the updated feature details, or error if not found or already in-progress.
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
feature = session.query(Feature).filter(Feature.id == feature_id).first()
|
||||
|
||||
if feature is None:
|
||||
return json.dumps({"error": f"Feature with ID {feature_id} not found"})
|
||||
|
||||
if feature.passes:
|
||||
return json.dumps({"error": f"Feature with ID {feature_id} is already passing"})
|
||||
|
||||
if feature.in_progress:
|
||||
return json.dumps({"error": f"Feature with ID {feature_id} is already in-progress"})
|
||||
|
||||
feature.in_progress = True
|
||||
session.commit()
|
||||
session.refresh(feature)
|
||||
|
||||
return json.dumps(feature.to_dict(), indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_clear_in_progress(
|
||||
feature_id: Annotated[int, Field(description="The ID of the feature to clear in-progress status", ge=1)]
|
||||
) -> str:
|
||||
"""Clear in-progress status from a feature.
|
||||
|
||||
Use this when abandoning a feature or manually unsticking a stuck feature.
|
||||
The feature will return to the pending queue.
|
||||
|
||||
Args:
|
||||
feature_id: The ID of the feature to clear in-progress status
|
||||
|
||||
Returns:
|
||||
JSON with the updated feature details, or error if not found.
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
feature = session.query(Feature).filter(Feature.id == feature_id).first()
|
||||
|
||||
if feature is None:
|
||||
return json.dumps({"error": f"Feature with ID {feature_id} not found"})
|
||||
|
||||
feature.in_progress = False
|
||||
session.commit()
|
||||
session.refresh(feature)
|
||||
|
||||
return json.dumps(feature.to_dict(), indent=2)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def feature_create_bulk(
|
||||
features: Annotated[list[dict], Field(description="List of features to create, each with category, name, description, and steps")]
|
||||
) -> str:
|
||||
"""Create multiple features in a single operation.
|
||||
|
||||
Features are assigned sequential priorities based on their order.
|
||||
All features start with passes=false.
|
||||
|
||||
This is typically used by the initializer agent to set up the initial
|
||||
feature list from the app specification.
|
||||
|
||||
Args:
|
||||
features: List of features to create, each with:
|
||||
- category (str): Feature category
|
||||
- name (str): Feature name
|
||||
- description (str): Detailed description
|
||||
- steps (list[str]): Implementation/test steps
|
||||
|
||||
Returns:
|
||||
JSON with: created (int) - number of features created
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
# Get the starting priority
|
||||
max_priority_result = session.query(Feature.priority).order_by(Feature.priority.desc()).first()
|
||||
start_priority = (max_priority_result[0] + 1) if max_priority_result else 1
|
||||
|
||||
created_count = 0
|
||||
for i, feature_data in enumerate(features):
|
||||
# Validate required fields
|
||||
if not all(key in feature_data for key in ["category", "name", "description", "steps"]):
|
||||
return json.dumps({
|
||||
"error": f"Feature at index {i} missing required fields (category, name, description, steps)"
|
||||
})
|
||||
|
||||
db_feature = Feature(
|
||||
priority=start_priority + i,
|
||||
category=feature_data["category"],
|
||||
name=feature_data["name"],
|
||||
description=feature_data["description"],
|
||||
steps=feature_data["steps"],
|
||||
passes=False,
|
||||
)
|
||||
session.add(db_feature)
|
||||
created_count += 1
|
||||
|
||||
session.commit()
|
||||
|
||||
return json.dumps({"created": created_count}, indent=2)
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
return json.dumps({"error": str(e)})
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
Reference in New Issue
Block a user