Enhance MCP development features and introduce skills management
- Added configuration options for requiring human approval before applying LLM-generated MCP patches. - Updated Docker setup to include skills directory. - Integrated skills management into the backend, allowing for procedural guides and skill matching. - Refactored database initialization to apply Alembic migrations. - Enhanced task approval process to handle MCP patch applications with optional approval. - Introduced new schemas for skills and updated existing APIs to support skills functionality. This commit lays the groundwork for improved agent capabilities and better management of MCP development processes.
This commit is contained in:
97
backend/app/api/skills.py
Normal file
97
backend/app/api/skills.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Agent skills API — list, view, edit, and preview skill matching."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from app.core.deps import require_admin, require_readonly
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas import SkillOut, SkillUpdate, SkillsListOut
|
||||
from app.services.audit import record_audit
|
||||
from app.services.skill_registry import (
|
||||
get_skill,
|
||||
list_skills_summary,
|
||||
match_skills,
|
||||
read_skill_raw,
|
||||
save_skill_raw,
|
||||
)
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
router = APIRouter(prefix="/api/skills", tags=["skills"])
|
||||
|
||||
|
||||
@router.get("", response_model=SkillsListOut)
|
||||
async def list_skills(_: User = Depends(require_readonly)):
|
||||
return SkillsListOut(
|
||||
skills=list_skills_summary(),
|
||||
path_hint="skills/",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/preview")
|
||||
async def preview_skill_match(
|
||||
body: dict,
|
||||
_: User = Depends(require_readonly),
|
||||
):
|
||||
"""Preview which skills match a task title + issue (and optional rule id)."""
|
||||
title = str(body.get("title") or "")
|
||||
issue = str(body.get("issue") or "")
|
||||
matched_rule_id = body.get("matched_rule_id")
|
||||
if matched_rule_id is not None:
|
||||
matched_rule_id = str(matched_rule_id)
|
||||
matched = match_skills(title, issue, matched_rule_id=matched_rule_id)
|
||||
return {"matched": [s.as_dict() for s in matched]}
|
||||
|
||||
|
||||
@router.get("/{skill_id}", response_model=SkillOut)
|
||||
async def get_skill_detail(skill_id: str, _: User = Depends(require_readonly)):
|
||||
skill = get_skill(skill_id)
|
||||
if not skill:
|
||||
raise HTTPException(status_code=404, detail="Skill not found")
|
||||
return SkillOut(
|
||||
id=skill["id"],
|
||||
name=skill["name"],
|
||||
description=skill["description"],
|
||||
priority=skill.get("priority") or 0,
|
||||
rule_ids=list(skill.get("rule_ids") or []),
|
||||
match=skill.get("match") or {},
|
||||
markdown=read_skill_raw(skill_id),
|
||||
path_hint=f"skills/{skill_id}/SKILL.md",
|
||||
)
|
||||
|
||||
|
||||
@router.put("/{skill_id}", response_model=SkillOut)
|
||||
async def update_skill(
|
||||
skill_id: str,
|
||||
body: SkillUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_admin),
|
||||
):
|
||||
try:
|
||||
save_skill_raw(skill_id, body.markdown)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise HTTPException(status_code=400, detail=f"Invalid skill: {exc}") from exc
|
||||
|
||||
await record_audit(
|
||||
db,
|
||||
user,
|
||||
"skills.update",
|
||||
"skills",
|
||||
0,
|
||||
{"skill_id": skill_id, "bytes": len(body.markdown)},
|
||||
)
|
||||
skill = get_skill(skill_id)
|
||||
if not skill:
|
||||
raise HTTPException(status_code=404, detail="Skill not found")
|
||||
return SkillOut(
|
||||
id=skill["id"],
|
||||
name=skill["name"],
|
||||
description=skill["description"],
|
||||
priority=skill.get("priority") or 0,
|
||||
rule_ids=list(skill.get("rule_ids") or []),
|
||||
match=skill.get("match") or {},
|
||||
markdown=read_skill_raw(skill_id),
|
||||
path_hint=f"skills/{skill_id}/SKILL.md",
|
||||
)
|
||||
Reference in New Issue
Block a user