fix(gitea-mcp): address MCP tool issues from Sprint 6 #282
@@ -53,6 +53,7 @@ class GiteaClient:
|
|||||||
self,
|
self,
|
||||||
state: str = 'open',
|
state: str = 'open',
|
||||||
labels: Optional[List[str]] = None,
|
labels: Optional[List[str]] = None,
|
||||||
|
milestone: Optional[str] = None,
|
||||||
repo: Optional[str] = None
|
repo: Optional[str] = None
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
@@ -61,6 +62,7 @@ class GiteaClient:
|
|||||||
Args:
|
Args:
|
||||||
state: Issue state (open, closed, all)
|
state: Issue state (open, closed, all)
|
||||||
labels: Filter by labels
|
labels: Filter by labels
|
||||||
|
milestone: Filter by milestone title (exact match)
|
||||||
repo: Repository in 'owner/repo' format
|
repo: Repository in 'owner/repo' format
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -71,6 +73,8 @@ class GiteaClient:
|
|||||||
params = {'state': state}
|
params = {'state': state}
|
||||||
if labels:
|
if labels:
|
||||||
params['labels'] = ','.join(labels)
|
params['labels'] = ','.join(labels)
|
||||||
|
if milestone:
|
||||||
|
params['milestones'] = milestone
|
||||||
logger.info(f"Listing issues from {owner}/{target_repo} with state={state}")
|
logger.info(f"Listing issues from {owner}/{target_repo} with state={state}")
|
||||||
response = self.session.get(url, params=params)
|
response = self.session.get(url, params=params)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|||||||
@@ -26,6 +26,44 @@ logging.getLogger("mcp").setLevel(logging.ERROR)
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _coerce_types(arguments: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Coerce argument types to handle MCP serialization quirks.
|
||||||
|
|
||||||
|
MCP sometimes passes integers as strings and arrays as JSON strings.
|
||||||
|
This function normalizes them to the expected Python types.
|
||||||
|
"""
|
||||||
|
coerced = {}
|
||||||
|
for key, value in arguments.items():
|
||||||
|
if value is None:
|
||||||
|
coerced[key] = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Coerce integer fields
|
||||||
|
int_fields = {'issue_number', 'milestone_id', 'pr_number', 'depends_on', 'milestone', 'limit'}
|
||||||
|
if key in int_fields and isinstance(value, str):
|
||||||
|
try:
|
||||||
|
coerced[key] = int(value)
|
||||||
|
continue
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Coerce array fields that might be JSON strings
|
||||||
|
array_fields = {'labels', 'tags', 'issue_numbers', 'comments'}
|
||||||
|
if key in array_fields and isinstance(value, str):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(value)
|
||||||
|
if isinstance(parsed, list):
|
||||||
|
coerced[key] = parsed
|
||||||
|
continue
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
coerced[key] = value
|
||||||
|
|
||||||
|
return coerced
|
||||||
|
|
||||||
|
|
||||||
class GiteaMCPServer:
|
class GiteaMCPServer:
|
||||||
"""MCP Server for Gitea integration"""
|
"""MCP Server for Gitea integration"""
|
||||||
|
|
||||||
@@ -88,6 +126,10 @@ class GiteaMCPServer:
|
|||||||
"items": {"type": "string"},
|
"items": {"type": "string"},
|
||||||
"description": "Filter by labels"
|
"description": "Filter by labels"
|
||||||
},
|
},
|
||||||
|
"milestone": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by milestone title (exact match)"
|
||||||
|
},
|
||||||
"repo": {
|
"repo": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Repository name (for PMO mode)"
|
"description": "Repository name (for PMO mode)"
|
||||||
@@ -899,6 +941,9 @@ class GiteaMCPServer:
|
|||||||
List of TextContent with results
|
List of TextContent with results
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# Coerce types to handle MCP serialization quirks
|
||||||
|
arguments = _coerce_types(arguments)
|
||||||
|
|
||||||
# Route to appropriate tool handler
|
# Route to appropriate tool handler
|
||||||
if name == "list_issues":
|
if name == "list_issues":
|
||||||
result = await self.issue_tools.list_issues(**arguments)
|
result = await self.issue_tools.list_issues(**arguments)
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class IssueTools:
|
|||||||
self,
|
self,
|
||||||
state: str = 'open',
|
state: str = 'open',
|
||||||
labels: Optional[List[str]] = None,
|
labels: Optional[List[str]] = None,
|
||||||
|
milestone: Optional[str] = None,
|
||||||
repo: Optional[str] = None
|
repo: Optional[str] = None
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
@@ -106,6 +107,7 @@ class IssueTools:
|
|||||||
Args:
|
Args:
|
||||||
state: Issue state (open, closed, all)
|
state: Issue state (open, closed, all)
|
||||||
labels: Filter by labels
|
labels: Filter by labels
|
||||||
|
milestone: Filter by milestone title (exact match)
|
||||||
repo: Override configured repo (for PMO multi-repo)
|
repo: Override configured repo (for PMO multi-repo)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -124,7 +126,7 @@ class IssueTools:
|
|||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
return await loop.run_in_executor(
|
return await loop.run_in_executor(
|
||||||
None,
|
None,
|
||||||
lambda: self.gitea.list_issues(state, labels, repo)
|
lambda: self.gitea.list_issues(state, labels, milestone, repo)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_issue(
|
async def get_issue(
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ description: Begin sprint execution with relevant lessons learned from previous
|
|||||||
|
|
||||||
You are initiating sprint execution. The orchestrator agent will coordinate the work, analyze dependencies for parallel execution, search for relevant lessons learned, and guide you through the implementation process.
|
You are initiating sprint execution. The orchestrator agent will coordinate the work, analyze dependencies for parallel execution, search for relevant lessons learned, and guide you through the implementation process.
|
||||||
|
|
||||||
## Sprint Approval Verification
|
## Sprint Approval Verification (Recommended)
|
||||||
|
|
||||||
**CRITICAL: Sprint must be approved before execution.**
|
**RECOMMENDED: Sprint should be approved before execution.**
|
||||||
|
|
||||||
|
> **Note:** This is a recommended workflow practice, not code-enforced. The orchestrator
|
||||||
|
> SHOULD check for approval, but execution will proceed if approval is missing. For
|
||||||
|
> critical projects, consider making approval mandatory in your workflow.
|
||||||
|
|
||||||
The orchestrator checks for approval in the milestone description:
|
The orchestrator checks for approval in the milestone description:
|
||||||
|
|
||||||
@@ -19,16 +23,15 @@ get_milestone(milestone_id=17)
|
|||||||
|
|
||||||
**If Approval Missing:**
|
**If Approval Missing:**
|
||||||
```
|
```
|
||||||
⚠️ SPRINT NOT APPROVED
|
⚠️ SPRINT APPROVAL NOT FOUND (Warning)
|
||||||
|
|
||||||
Sprint 17 has not been approved for execution.
|
Sprint 17 milestone does not contain an approval record.
|
||||||
The milestone description does not contain an approval record.
|
|
||||||
|
|
||||||
Please run /sprint-plan to:
|
Recommended: Run /sprint-plan first to:
|
||||||
1. Review the sprint scope
|
1. Review the sprint scope
|
||||||
2. Approve the execution plan
|
2. Document the approved execution plan
|
||||||
|
|
||||||
Then run /sprint-start again.
|
Proceeding anyway - consider adding approval for audit trail.
|
||||||
```
|
```
|
||||||
|
|
||||||
**If Approval Found:**
|
**If Approval Found:**
|
||||||
@@ -42,10 +45,10 @@ Then run /sprint-start again.
|
|||||||
Proceeding with execution within approved scope...
|
Proceeding with execution within approved scope...
|
||||||
```
|
```
|
||||||
|
|
||||||
**Scope Enforcement:**
|
**Scope Enforcement (when approval exists):**
|
||||||
- Agents can ONLY create branches matching approved patterns
|
- Agents SHOULD only create branches matching approved patterns
|
||||||
- Agents can ONLY modify files within approved paths
|
- Agents SHOULD only modify files within approved paths
|
||||||
- Operations outside scope require re-approval via `/sprint-plan`
|
- Operations outside scope should trigger re-approval via `/sprint-plan`
|
||||||
|
|
||||||
## Branch Detection
|
## Branch Detection
|
||||||
|
|
||||||
@@ -66,11 +69,11 @@ If you are on a production or staging branch, you MUST stop and ask the user to
|
|||||||
|
|
||||||
The orchestrator agent will:
|
The orchestrator agent will:
|
||||||
|
|
||||||
1. **Verify Sprint Approval**
|
1. **Verify Sprint Approval** (Recommended)
|
||||||
- Check milestone description for `## Sprint Approval` section
|
- Check milestone description for `## Sprint Approval` section
|
||||||
- If no approval found, STOP and direct user to `/sprint-plan`
|
- If no approval found, WARN user and suggest `/sprint-plan` first
|
||||||
- If approval found, extract scope (branches, files)
|
- If approval found, extract scope (branches, files)
|
||||||
- Agents operate ONLY within approved scope
|
- Agents SHOULD operate within approved scope when available
|
||||||
|
|
||||||
2. **Detect Checkpoints (Resume Support)**
|
2. **Detect Checkpoints (Resume Support)**
|
||||||
- Check each open issue for `## Checkpoint` comments
|
- Check each open issue for `## Checkpoint` comments
|
||||||
|
|||||||
Reference in New Issue
Block a user