From e56d685a6893d9303dd2c87e7f39f133d0207fe3 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Wed, 28 Jan 2026 15:57:42 -0500 Subject: [PATCH] fix(gitea-mcp): address MCP tool issues from Sprint 6 Fixes #281 - Multiple MCP tool issues discovered during sprint execution ## Changes 1. **list_issues Token Overflow** (Issue 1) - Added `milestone` parameter to filter issues server-side - Reduces response size by filtering at API level instead of client-side 2. **Type Coercion for MCP Serialization** (Issues 2 & 4) - Added `_coerce_types()` helper function in server.py - Handles integers passed as strings (milestone_id, issue_number, etc.) - Handles arrays passed as JSON strings (labels, tags, etc.) - Applied to all tool calls automatically 3. **Sprint Approval Check Clarification** (Issue 3) - Updated sprint-start.md to clarify approval is RECOMMENDED, not enforced - Changed STOP/block language to WARN/suggest language - Added note explaining this is workflow guidance, not code-enforced ## Files Changed - mcp-servers/gitea/mcp_server/gitea_client.py: Added milestone param - mcp-servers/gitea/mcp_server/tools/issues.py: Pass milestone param - mcp-servers/gitea/mcp_server/server.py: Type coercion + milestone schema - plugins/projman/commands/sprint-start.md: Clarified approval check Co-Authored-By: Claude Opus 4.5 --- mcp-servers/gitea/mcp_server/gitea_client.py | 4 ++ mcp-servers/gitea/mcp_server/server.py | 45 ++++++++++++++++++++ mcp-servers/gitea/mcp_server/tools/issues.py | 4 +- plugins/projman/commands/sprint-start.md | 33 +++++++------- 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/mcp-servers/gitea/mcp_server/gitea_client.py b/mcp-servers/gitea/mcp_server/gitea_client.py index a5e3449..e67ea7f 100644 --- a/mcp-servers/gitea/mcp_server/gitea_client.py +++ b/mcp-servers/gitea/mcp_server/gitea_client.py @@ -53,6 +53,7 @@ class GiteaClient: self, state: str = 'open', labels: Optional[List[str]] = None, + milestone: Optional[str] = None, repo: Optional[str] = None ) -> List[Dict]: """ @@ -61,6 +62,7 @@ class GiteaClient: Args: state: Issue state (open, closed, all) labels: Filter by labels + milestone: Filter by milestone title (exact match) repo: Repository in 'owner/repo' format Returns: @@ -71,6 +73,8 @@ class GiteaClient: params = {'state': state} if labels: params['labels'] = ','.join(labels) + if milestone: + params['milestones'] = milestone logger.info(f"Listing issues from {owner}/{target_repo} with state={state}") response = self.session.get(url, params=params) response.raise_for_status() diff --git a/mcp-servers/gitea/mcp_server/server.py b/mcp-servers/gitea/mcp_server/server.py index 0630c6a..263d2c3 100644 --- a/mcp-servers/gitea/mcp_server/server.py +++ b/mcp-servers/gitea/mcp_server/server.py @@ -26,6 +26,44 @@ logging.getLogger("mcp").setLevel(logging.ERROR) 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: """MCP Server for Gitea integration""" @@ -88,6 +126,10 @@ class GiteaMCPServer: "items": {"type": "string"}, "description": "Filter by labels" }, + "milestone": { + "type": "string", + "description": "Filter by milestone title (exact match)" + }, "repo": { "type": "string", "description": "Repository name (for PMO mode)" @@ -899,6 +941,9 @@ class GiteaMCPServer: List of TextContent with results """ try: + # Coerce types to handle MCP serialization quirks + arguments = _coerce_types(arguments) + # Route to appropriate tool handler if name == "list_issues": result = await self.issue_tools.list_issues(**arguments) diff --git a/mcp-servers/gitea/mcp_server/tools/issues.py b/mcp-servers/gitea/mcp_server/tools/issues.py index c0ae3dc..04e63cf 100644 --- a/mcp-servers/gitea/mcp_server/tools/issues.py +++ b/mcp-servers/gitea/mcp_server/tools/issues.py @@ -98,6 +98,7 @@ class IssueTools: self, state: str = 'open', labels: Optional[List[str]] = None, + milestone: Optional[str] = None, repo: Optional[str] = None ) -> List[Dict]: """ @@ -106,6 +107,7 @@ class IssueTools: Args: state: Issue state (open, closed, all) labels: Filter by labels + milestone: Filter by milestone title (exact match) repo: Override configured repo (for PMO multi-repo) Returns: @@ -124,7 +126,7 @@ class IssueTools: loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self.gitea.list_issues(state, labels, repo) + lambda: self.gitea.list_issues(state, labels, milestone, repo) ) async def get_issue( diff --git a/plugins/projman/commands/sprint-start.md b/plugins/projman/commands/sprint-start.md index b46e858..b19c4d5 100644 --- a/plugins/projman/commands/sprint-start.md +++ b/plugins/projman/commands/sprint-start.md @@ -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. -## 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: @@ -19,16 +23,15 @@ get_milestone(milestone_id=17) **If Approval Missing:** ``` -⚠️ SPRINT NOT APPROVED +⚠️ SPRINT APPROVAL NOT FOUND (Warning) -Sprint 17 has not been approved for execution. -The milestone description does not contain an approval record. +Sprint 17 milestone does not contain an approval record. -Please run /sprint-plan to: +Recommended: Run /sprint-plan first to: 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:** @@ -42,10 +45,10 @@ Then run /sprint-start again. Proceeding with execution within approved scope... ``` -**Scope Enforcement:** -- Agents can ONLY create branches matching approved patterns -- Agents can ONLY modify files within approved paths -- Operations outside scope require re-approval via `/sprint-plan` +**Scope Enforcement (when approval exists):** +- Agents SHOULD only create branches matching approved patterns +- Agents SHOULD only modify files within approved paths +- Operations outside scope should trigger re-approval via `/sprint-plan` ## 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: -1. **Verify Sprint Approval** +1. **Verify Sprint Approval** (Recommended) - 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) - - Agents operate ONLY within approved scope + - Agents SHOULD operate within approved scope when available 2. **Detect Checkpoints (Resume Support)** - Check each open issue for `## Checkpoint` comments