diff --git a/.mokogitea/mcp/src/index.ts b/.mokogitea/mcp/src/index.ts index 4782d9f0c3..27b0ee6720 100644 --- a/.mokogitea/mcp/src/index.ts +++ b/.mokogitea/mcp/src/index.ts @@ -431,7 +431,7 @@ server.tool( server.tool( 'gitea_issue_create', - 'Create a new issue', + 'Create or update an issue (searches by title first to prevent duplicates)', { ...OwnerRepo, title: z.string().describe('Issue title'), @@ -439,15 +439,66 @@ server.tool( labels: z.array(z.number()).optional().describe('Label IDs'), milestone: z.number().optional().describe('Milestone ID'), assignees: z.array(z.string()).optional().describe('Usernames to assign'), + status_id: z.number().optional().describe('Custom status definition ID'), + priority_id: z.number().optional().describe('Custom priority definition ID'), ...ConnectionParam, }, - async ({ owner, repo, title, body: issueBody, labels, milestone, assignees, connection }) => { + async ({ owner, repo, title, body: issueBody, labels, milestone, assignees, status_id, priority_id, connection }) => { + const c = clientFor(connection); + + // Search for existing issue with same title to prevent duplicates + const searchRes = await c.get(`/repos/${owner}/${repo}/issues`, { + type: 'issues', state: 'open', limit: '50', + }); + let existing: { number: number } | undefined; + if (searchRes.status < 400 && Array.isArray(searchRes.data)) { + existing = (searchRes.data as Array<{ number: number; title: string }>) + .find((i) => i.title === title); + } + // Also check closed issues if not found in open + if (!existing) { + const closedRes = await c.get(`/repos/${owner}/${repo}/issues`, { + type: 'issues', state: 'closed', limit: '50', + }); + if (closedRes.status < 400 && Array.isArray(closedRes.data)) { + existing = (closedRes.data as Array<{ number: number; title: string }>) + .find((i) => i.title === title); + } + } + + if (existing) { + // Update existing issue instead of creating a duplicate + const body: Record = {}; + if (issueBody !== undefined) body.body = issueBody; + if (labels) body.labels = labels; + if (milestone) body.milestone = milestone; + if (assignees) body.assignees = assignees; + const res = await c.patch(`/repos/${owner}/${repo}/issues/${existing.number}`, body); + // Set status/priority via dedicated endpoints + const issueData = res.data as { id?: number }; + if (issueData?.id) { + if (status_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${issueData.id}/custom-status`, { status_id }); + if (priority_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${issueData.id}/custom-priority`, { priority_id }); + } + const out = formatResponse(res); + out.content[0].text = `Updated existing issue #${existing.number} (duplicate prevented)\n${out.content[0].text}`; + return out; + } + + // No duplicate found - create new const body: Record = { title }; if (issueBody) body.body = issueBody; if (labels) body.labels = labels; if (milestone) body.milestone = milestone; if (assignees) body.assignees = assignees; - return formatResponse(await clientFor(connection).post(`/repos/${owner}/${repo}/issues`, body)); + const res = await c.post(`/repos/${owner}/${repo}/issues`, body); + // Set status/priority via dedicated endpoints + const newIssue = res.data as { id?: number }; + if (newIssue?.id) { + if (status_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${newIssue.id}/custom-status`, { status_id }); + if (priority_id !== undefined) await c.post(`/repos/${owner}/${repo}/issues/${newIssue.id}/custom-priority`, { priority_id }); + } + return formatResponse(res); }, );