Skip to content

Add close_issue and reopen_issue MCP tools to issue management server#641

Merged
acreeger merged 1 commit intoepic/610from
feat/issue-640__close-reopen-issues
Feb 19, 2026
Merged

Add close_issue and reopen_issue MCP tools to issue management server#641
acreeger merged 1 commit intoepic/610from
feat/issue-640__close-reopen-issues

Conversation

@acreeger
Copy link
Collaborator

Fixes #640

Add close_issue and reopen_issue MCP tools to issue management server

Context

The swarm orchestrator needs to close child issues after merging their work into the epic branch. Currently there are no close_issue or reopen_issue tools in the issue management MCP server, so completed issues remain open, cluttering the issue tracker.

Summary

Add provider-agnostic close_issue and reopen_issue tools to the issue management MCP server, with implementations across all providers (GitHub, Linear, Jira).

Requirements

IssueTracker interface (src/lib/IssueTracker.ts)

  • Add closeIssue(issueId: string | number, repo?: string): Promise<void>
  • Add reopenIssue(issueId: string | number, repo?: string): Promise<void>

Provider implementations

  • GitHubService: Use gh issue close <number> / gh issue reopen <number>
  • LinearService: Use Linear API to transition issue state to closed/reopened
  • JiraIssueTracker: Use Jira API to transition issue state

MCP tools (src/mcp/issue-management-server.ts)

  • close_issue — Takes { number: string, repo?: string }, calls issueTracker.closeIssue()
  • reopen_issue — Takes { number: string, repo?: string }, calls issueTracker.reopenIssue()

Allowed tools

  • Add mcp__issue_management__close_issue to the swarm orchestrator's allowed tools in src/commands/ignite.ts
  • Add to issue/PR workflow allowed tools as well

Orchestrator prompt (templates/prompts/swarm-orchestrator-prompt.txt)

  • In Phase 3, Step 3.3 (Update State): After merging and updating metadata state, add a step to close the child issue using mcp__issue_management__close_issue
  • Do NOT close failed issues (Phase 4)

Also fix the orchestrator's broken jq metadata commands

  • Step 3.3 and Phase 4 currently use jq commands targeting iloom-metadata.json in the worktree root, which doesn't exist (metadata is stored centrally at ~/.config/iloom-ai/looms/)
  • Replace with calls to mcp__recap__set_loom_state with worktreePath parameter

Tests

  • Add tests for closeIssue/reopenIssue in each provider's test file
  • Follow existing test patterns

This PR was created automatically by iloom.

@acreeger
Copy link
Collaborator Author

acreeger commented Feb 19, 2026

Complexity Assessment

Classification: SIMPLE

Metrics:

  • Estimated files affected: 8
  • Estimated lines of code: 180
  • Breaking changes: No
  • Database migrations: No
  • Cross-cutting changes: No
  • File architecture quality: Good - changes are isolated additions to specific methods
  • Architectural signals triggered: None
  • Overall risk level: Low

Reasoning: Straightforward feature addition following established provider patterns. All modifications are additive (new interface methods, new MCP tools, new CLI allowed-tools entries), isolated to specific locations within large files. Implementation approach is obvious from requirements - each provider gets ~15 LOC close/reopen implementations following existing patterns. No architectural decisions or integration complexity required.

@acreeger
Copy link
Collaborator Author

acreeger commented Feb 19, 2026

Combined Analysis & Plan - Issue #640

Executive Summary

Add close_issue, reopen_issue, and edit_issue MCP tools to the issue management server. All three follow the established provider pattern: add methods to IssueManagementProvider interface, implement in GitHub/Linear/Jira providers, register tools in MCP server, and wire into allowed tools for swarm orchestrator and issue/PR workflows. The orchestrator prompt also needs a step to close child issues after merge.

Implementation Overview

High-Level Execution Phases

  1. Types and Interface: Add input/result types and methods to IssueManagementProvider
  2. Utility Functions: Add closeIssue/reopenIssue/editIssue to github.ts utils, and updateIssue to JiraApiClient
  3. Provider Implementations: Implement in all 3 providers (GitHub, Linear, Jira)
  4. MCP Server: Register 3 new tools
  5. Allowed Tools & Prompt: Update ignite.ts and swarm orchestrator prompt

Quick Stats

  • 10 files to modify
  • 0 new files to create
  • 0 files to delete
  • Dependencies: None

Complete Analysis & Implementation Details (click to expand)

Research Findings

Problem Space

  • Problem: Swarm orchestrator cannot close child issues after merging, leaving completed issues open
  • Architectural context: Extends MCP issue management server with 3 new tools following exact same pattern as existing tools (get_issue, create_issue, etc.)
  • Edge cases: Jira close/reopen requires transition lookups (Done/Reopen); Linear close uses updateLinearIssueState with state name "Done"; edit_issue with state changes should delegate to close/reopen for clarity

Codebase Research

  • MCP Provider interface: src/mcp/types.ts:277-344 - needs 3 new methods
  • GitHub provider: src/mcp/GitHubIssueManagementProvider.ts - uses executeGhCommand and src/utils/github.ts helpers
  • Linear provider: src/mcp/LinearIssueManagementProvider.ts - uses src/utils/linear.ts helpers; updateLinearIssueState() at line 347 already handles state transitions
  • Jira provider: src/mcp/JiraIssueManagementProvider.ts - uses JiraIssueTracker with JiraApiClient; transition pattern at JiraIssueTracker.ts:168-228
  • MCP server: src/mcp/issue-management-server.ts - tool registration pattern repeats for each tool
  • Allowed tools (swarm): src/commands/ignite.ts:902-914
  • Allowed tools (issue/PR workflow): src/commands/ignite.ts:351-366
  • Orchestrator prompt: templates/prompts/swarm-orchestrator-prompt.txt:185-188 Step 3.3

Duplication Opportunities

  • close_issue and reopen_issue could share a helper pattern since they're symmetric, but each is simple enough (1 CLI call for GitHub, 1 SDK call for Linear, 1 transition for Jira) that a shared helper adds more complexity than it saves.
  • The edit_issue tool handles state via the same close/reopen logic -- to avoid duplication, the edit_issue provider method should call the existing closeIssue/reopenIssue when state is being changed (or handle state in the same method body).

Medium Severity Risks

  • Jira transition names vary: Close/Reopen transition names differ per project; mitigated by using findTransitionByName with fallback names and config mapping (existing pattern).

Implementation Plan

Automated Test Cases to Create

Test File: src/mcp/GitHubIssueManagementProvider.test.ts (MODIFY)

// Add tests for closeIssue, reopenIssue, editIssue
describe('closeIssue', () => {
  it('calls gh issue close with correct args', async () => { /* ... */ })
  it('passes --repo when repo is provided', async () => { /* ... */ })
  it('throws on invalid issue number', async () => { /* ... */ })
})
describe('reopenIssue', () => {
  it('calls gh issue reopen with correct args', async () => { /* ... */ })
})
describe('editIssue', () => {
  it('calls gh issue edit with title', async () => { /* ... */ })
  it('calls gh issue edit with body', async () => { /* ... */ })
  it('calls gh issue edit with labels', async () => { /* ... */ })
  it('handles state change via close/reopen', async () => { /* ... */ })
})

Test File: src/mcp/LinearIssueManagementProvider.test.ts (MODIFY)

describe('closeIssue', () => {
  it('calls updateLinearIssueState with Done', async () => { /* ... */ })
})
describe('reopenIssue', () => {
  it('calls updateLinearIssueState with appropriate reopen state', async () => { /* ... */ })
})
describe('editIssue', () => {
  it('updates title via Linear SDK', async () => { /* ... */ })
  it('updates description via Linear SDK', async () => { /* ... */ })
  it('handles state change via close/reopen', async () => { /* ... */ })
})

Test File: src/mcp/JiraIssueManagementProvider.test.ts (MODIFY)

describe('closeIssue', () => {
  it('transitions issue to Done state', async () => { /* ... */ })
})
describe('reopenIssue', () => {
  it('transitions issue to Reopen state', async () => { /* ... */ })
})
describe('editIssue', () => {
  it('updates issue summary via Jira API', async () => { /* ... */ })
  it('updates issue description via Jira API', async () => { /* ... */ })
  it('handles state change via close/reopen', async () => { /* ... */ })
})

Files to Modify

1. src/mcp/types.ts

Change: Add CloseIssueInput, ReopenIssueInput, EditIssueInput types and add closeIssue, reopenIssue, editIssue methods to IssueManagementProvider interface.

// New input types (after RemoveDependencyInput)
export interface CloseIssueInput {
  number: string
  repo?: string | undefined
}

export interface ReopenIssueInput {
  number: string
  repo?: string | undefined
}

export interface EditIssueInput {
  number: string
  title?: string | undefined
  body?: string | undefined
  state?: 'open' | 'closed' | undefined
  labels?: string[] | undefined
  repo?: string | undefined
}

// Add to IssueManagementProvider interface:
closeIssue(input: CloseIssueInput): Promise<void>
reopenIssue(input: ReopenIssueInput): Promise<void>
editIssue(input: EditIssueInput): Promise<void>

2. src/utils/github.ts

Change: Add closeGhIssue, reopenGhIssue, editGhIssue utility functions using executeGhCommand.

// closeGhIssue: gh issue close <number> [--repo]
// reopenGhIssue: gh issue reopen <number> [--repo]
// editGhIssue: gh issue edit <number> [--title] [--body] [--add-label/--remove-label] [--repo]

3. src/mcp/GitHubIssueManagementProvider.ts

Change: Implement closeIssue, reopenIssue, editIssue methods following existing pattern (validate numeric ID, delegate to utils, handle repo param).
Cross-cutting impact: editIssue with state field delegates to closeIssue/reopenIssue internally.

4. src/utils/linear.ts

Change: Add editLinearIssue function that uses client.updateIssue() to update title/description/labels. updateLinearIssueState already exists for state changes.

5. src/mcp/LinearIssueManagementProvider.ts

Change: Implement closeIssue (calls updateLinearIssueState(id, 'Done')), reopenIssue (calls updateLinearIssueState(id, 'Todo') or 'In Progress'), editIssue (calls new editLinearIssue for title/body, delegates to close/reopen for state).

6. src/lib/providers/jira/JiraApiClient.ts

Change: Add updateIssue(issueKey: string, fields: { summary?: string, description?: string }) method using PUT /issue/{key}.

7. src/mcp/JiraIssueManagementProvider.ts

Change: Implement closeIssue (transition to "Done"), reopenIssue (transition to "Reopen"/"To Do"), editIssue (update fields via JiraApiClient.updateIssue, delegate state to close/reopen). Follow pattern from moveIssueToInProgress at JiraIssueTracker.ts:168.

8. src/mcp/issue-management-server.ts

Change: Register 3 new tools (close_issue, reopen_issue, edit_issue) following existing tool registration pattern. Import new input types.

9. src/commands/ignite.ts

Change: Add mcp__issue_management__close_issue, mcp__issue_management__reopen_issue, mcp__issue_management__edit_issue to:

  • Swarm orchestrator allowed tools list at line 902-914
  • Issue/PR workflow base tools at line 351-363

10. templates/prompts/swarm-orchestrator-prompt.txt

Change: In Step 3.3 (line 185-188), after updating loom state to "done", add step: "3. Close the child issue: Call mcp__issue_management__close_issue with { number: "<child-issue-number>" }"

Test Files to Modify

11. src/mcp/GitHubIssueManagementProvider.test.ts (MODIFY)

12. src/mcp/LinearIssueManagementProvider.test.ts (MODIFY)

13. src/mcp/JiraIssueManagementProvider.test.ts (MODIFY)

Detailed Execution Order

NOTE: These steps are executed in a SINGLE implementation run.

  1. Add types and interface methods

    • Files: src/mcp/types.ts
    • Add input types and 3 methods to IssueManagementProvider -> Verify: TypeScript shows errors in all 3 providers (expected)
  2. Add GitHub utility functions

    • Files: src/utils/github.ts
    • Add closeGhIssue, reopenGhIssue, editGhIssue using executeGhCommand -> Verify: Functions exported
  3. Implement GitHub provider methods

    • Files: src/mcp/GitHubIssueManagementProvider.ts
    • Implement 3 methods following existing pattern -> Verify: TypeScript compiles
  4. Add Linear utility function

    • Files: src/utils/linear.ts
    • Add editLinearIssue function -> Verify: Function exported
  5. Implement Linear provider methods

    • Files: src/mcp/LinearIssueManagementProvider.ts
    • Implement 3 methods using existing updateLinearIssueState + new editLinearIssue -> Verify: TypeScript compiles
  6. Add Jira API client method

    • Files: src/lib/providers/jira/JiraApiClient.ts
    • Add updateIssue method -> Verify: Method available
  7. Implement Jira provider methods

    • Files: src/mcp/JiraIssueManagementProvider.ts
    • Implement 3 methods using JiraIssueTracker transitions + JiraApiClient.updateIssue -> Verify: TypeScript compiles
  8. Register MCP tools

    • Files: src/mcp/issue-management-server.ts
    • Register close_issue, reopen_issue, edit_issue tools -> Verify: TypeScript compiles
  9. Update allowed tools

    • Files: src/commands/ignite.ts
    • Add 3 new tools to both allowed tools lists -> Verify: No compilation errors
  10. Update orchestrator prompt

    • Files: templates/prompts/swarm-orchestrator-prompt.txt
    • Add close_issue step to Step 3.3 -> Verify: Text correct
  11. Add tests

    • Files: src/mcp/GitHubIssueManagementProvider.test.ts, src/mcp/LinearIssueManagementProvider.test.ts, src/mcp/JiraIssueManagementProvider.test.ts
    • Add test cases for all 3 methods in all 3 providers -> Verify: Tests pass
  12. Build and verify

    • Run pnpm build -> Verify: Clean build
    • Run tests for affected files -> Verify: All pass

NOTE: Follow the project's development workflow as specified in CLAUDE.md.

Dependencies and Configuration

None

@acreeger
Copy link
Collaborator Author

acreeger commented Feb 19, 2026

Implementation Complete

Summary

Added close_issue, reopen_issue, and edit_issue MCP tools to the issue management server with full provider support across GitHub, Linear, and Jira. Updated the swarm orchestrator prompt to close child issues after successful merge (Step 3.3 only, not Phase 4 failure path).

Changes Made

  • src/mcp/types.ts: Added CloseIssueInput, ReopenIssueInput, EditIssueInput types and 3 interface methods
  • src/utils/github.ts: Added closeGhIssue, reopenGhIssue, editGhIssue utilities
  • src/utils/linear.ts: Added editLinearIssue utility
  • src/mcp/GitHubIssueManagementProvider.ts: Implemented closeIssue, reopenIssue, editIssue
  • src/mcp/LinearIssueManagementProvider.ts: Implemented closeIssue, reopenIssue, editIssue
  • src/mcp/JiraIssueManagementProvider.ts: Implemented closeIssue, reopenIssue, editIssue
  • src/lib/providers/jira/JiraIssueTracker.ts: Added closeIssue, reopenIssue transition methods
  • src/lib/providers/jira/JiraApiClient.ts: Added updateIssue method
  • src/mcp/issue-management-server.ts: Registered 3 new MCP tools
  • src/commands/ignite.ts: Added tools to allowed tools lists
  • templates/prompts/swarm-orchestrator-prompt.txt: Added close_issue to Step 3.3 (success path only)

Validation Results

  • ✅ Tests: 4070 passed (120 test files)
  • ✅ Typecheck: Passed
  • ✅ Build: Passed

Code Review Fixes Applied

  • Fixed label semantics documentation (additive, not replacement)
  • Fixed empty-string field updates being silently dropped (use !== undefined checks)

Test Coverage Details (click to expand)

src/mcp/GitHubIssueManagementProvider.test.ts

  • closeIssue: success, invalid number
  • reopenIssue: success, invalid number
  • editIssue: state change to closed, state change to open, field updates, combined state+field, no-op

src/mcp/LinearIssueManagementProvider.test.ts

  • closeIssue: success
  • reopenIssue: success
  • editIssue: state change, field updates, combined, no-op

src/mcp/JiraIssueManagementProvider.test.ts

  • closeIssue: success
  • reopenIssue: success
  • editIssue: state change, field updates, combined, no-op

src/commands/ignite.test.ts

  • Updated 3 existing tests to include new tools in allowed tools assertions

@acreeger acreeger force-pushed the feat/issue-640__close-reopen-issues branch from 63e87f3 to 53f677e Compare February 19, 2026 04:35
Add provider-agnostic issue lifecycle tools to the issue management MCP
server with implementations across GitHub, Linear, and Jira providers.

- Add closeIssue, reopenIssue, editIssue to IssueManagementProvider interface
- Implement across all 3 providers (GitHub CLI, Linear SDK, Jira API)
- Register close_issue, reopen_issue, edit_issue MCP tools
- Add to swarm orchestrator and issue/PR workflow allowed tools
- Update swarm orchestrator prompt to close child issues on merge (Step 3.3)
- Add comprehensive tests (25 new test cases across 4 test files)

Closes #640
@acreeger acreeger force-pushed the feat/issue-640__close-reopen-issues branch from 53f677e to 190e393 Compare February 19, 2026 04:44
@acreeger acreeger marked this pull request as ready for review February 19, 2026 04:44
@acreeger
Copy link
Collaborator Author

iloom Session Summary

Key Themes:

  • All three issue providers (GitHub, Linear, Jira) already had utility-layer support for state transitions - the work was wiring them into the MCP provider interface.
  • The edit_issue tool intentionally delegates state changes to closeIssue/reopenIssue internally to avoid duplicating transition logic.
  • exactOptionalPropertyTypes requires !== undefined checks throughout - truthy checks silently drop empty-string field updates.

Session Details (click to expand)

Key Insights

  • The jq metadata commands fix mentioned in the issue body was already completed in PR feat: Swarm Mode v2 - Agent Team-Based Epic Execution #619 (commit ba48313). The orchestrator prompt already uses mcp__recap__set_loom_state calls.
  • edit_issue scope was explicitly added by @acreeger in an issue comment - it is not scope creep despite not being in the original issue body.
  • Linear already has updateLinearIssueState() in utils/linear.ts:347. Jira has transitionIssue() in JiraApiClient.ts:306. GitHub uses gh issue close/reopen/edit CLI commands. The utility layer already existed; MCP provider wiring was the missing piece.
  • Swarm orchestrator close_issue must only be added to Step 3.3 (success path). Phase 4 (failure handling) must NOT include a close call - this is an explicit requirement from the issue.
  • The editGhIssue utility uses --add-label (additive), not label replacement. The doc and MCP tool description were updated to say "add" to avoid misleading agents.

Decisions Made

  • State change delegation in editIssue: editIssue delegates to closeIssue/reopenIssue for state changes rather than calling transitions directly. This avoids duplicating transition logic across two methods and ensures consistent behavior.
  • Jira close/reopen pattern: Followed the existing JiraIssueTracker.moveIssueToInProgress pattern using findTransitionByName with fallback transition names (Done/Closed for close, Reopen/To Do for reopen). New closeIssue/reopenIssue methods were added to JiraIssueTracker (not just JiraApiClient) to maintain consistency with existing transition methods.
  • !== undefined over truthy checks: With exactOptionalPropertyTypes enabled, using title && { title } silently drops empty-string values. All field guards use !== undefined to allow clearing fields.

Challenges Resolved

  • IDE TypeScript diagnostics showed "class missing properties" errors for the provider implementations, but pnpm compile (tsc --noEmit) passed cleanly. VS Code's language server frequently shows stale diagnostics that don't reflect the actual compiler state - always verify with pnpm compile.
  • The label set semantics mismatch (--add-label vs "set" in docs) was surfaced by code review. The fix was documentation-only since full replace semantics would require fetching current labels first, which is a separate scope concern.

Lessons Learned

  • The MCP provider pattern is: interface in src/mcp/types.ts, utilities in src/utils/, provider implementations in src/mcp/*Provider.ts. For Jira specifically, JiraIssueManagementProvider delegates to JiraIssueTracker which uses JiraApiClient - don't bypass the tracker layer.
  • When adding tools to the swarm orchestrator allowed list, there are two separate lists in ignite.ts: the swarm orchestrator tools (~line 902) and the issue/PR workflow base tools (~line 351). Both need to be updated.
  • Linear's updateLinearIssueState() takes a state name string (e.g., 'Done', 'Todo') and handles the API transition lookup internally.

Generated with 🤖❤️ by iloom.ai

@acreeger acreeger merged commit f8684df into epic/610 Feb 19, 2026
@acreeger acreeger deleted the feat/issue-640__close-reopen-issues branch February 19, 2026 04:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant

Comments