From 8541bbfc20ea69c1551f939bc4bea13e18fd7417 Mon Sep 17 00:00:00 2001 From: Bram Gadeyne Date: Fri, 15 May 2026 16:00:07 +0200 Subject: [PATCH 1/4] feat: add initial implementation for work item creation tool --- .../wit-work-item-write/.openspec.yaml | 2 + .../changes/wit-work-item-write/design.md | 93 +++++++++++++++++++ .../changes/wit-work-item-write/proposal.md | 31 +++++++ 3 files changed, 126 insertions(+) create mode 100644 openspec/changes/wit-work-item-write/.openspec.yaml create mode 100644 openspec/changes/wit-work-item-write/design.md create mode 100644 openspec/changes/wit-work-item-write/proposal.md diff --git a/openspec/changes/wit-work-item-write/.openspec.yaml b/openspec/changes/wit-work-item-write/.openspec.yaml new file mode 100644 index 00000000..9f708669 --- /dev/null +++ b/openspec/changes/wit-work-item-write/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-15 diff --git a/openspec/changes/wit-work-item-write/design.md b/openspec/changes/wit-work-item-write/design.md new file mode 100644 index 00000000..58e6af08 --- /dev/null +++ b/openspec/changes/wit-work-item-write/design.md @@ -0,0 +1,93 @@ +## Context + +The Azure DevOps MCP server currently supports reading work items and adding comments via the `IWorkItemContextService` and `WorkItemTools` classes. The .NET implementation follows a layered architecture: + +- **Tools Layer** (`dotnet/src/G5e.AzureDevOpsServerMCP.Tools/WorkItemTools.cs`): MCP tool definitions with `[McpServerTool]` attributes +- **Application Layer** (`dotnet/src/G5e.AzureDevOpsServerMCP.Application/Services/`): Business logic and Azure DevOps API interactions +- **Infrastructure Layer**: Uses `Microsoft.TeamFoundationServer.Client` for API calls + +Existing patterns: +- Async methods decorated with `[McpServerTool(Name = "...")]` and `[Description("...")]` +- Exception handling returning JSON error responses +- JSON serialization with indentation for responses +- Service methods accepting collection, project as parameters + +## Goals / Non-Goals + +**Goals:** +- Add `wit_work_item_write_create` tool to create new work items with title, type, and optional description +- Follow existing code patterns and error handling conventions +- Support essential work item creation parameters (type, title, description) +- Return created work item ID and URL +- Maintain consistency with TypeScript upstream tool contracts + +**Non-Goals:** +- Support for complex field assignments beyond standard fields in this iteration +- Work item linking or relationships during creation +- Template-based work item creation +- Bulk creation operations +- Custom workflow state initialization + +## Decisions + +**Decision 1: Service Method Location** +- **Choice**: Extend `IWorkItemContextService` with a `CreateWorkItemAsync` method +- **Rationale**: Maintains separation of concerns and consistency with existing pattern. New work item operations belong with other work item context operations. +- **Alternative Considered**: Create a separate `IWorkItemWriteService` → Would fragment service responsibilities + +**Decision 2: Tool Parameter Contract** +- **Choice**: Accept `collection`, `project`, `workItemType`, `title`, and optional `description` +- **Rationale**: These are the minimum required fields for work item creation. Collection and project contextualize the operation. +- **Alternative Considered**: Accepting a generic JSON object for fields → Too open-ended, harder to validate and document + +**Decision 3: API Integration Pattern** +- **Choice**: Use `WorkItemTrackingHttpClient` from `Microsoft.TeamFoundationServer.Client` (consistent with existing patterns) +- **Rationale**: Already in use throughout the codebase; provides native Azure DevOps Server support +- **Alternative Considered**: Use REST client directly → Duplicates existing abstraction; requires more error handling + +**Decision 4: Response Format** +- **Choice**: Return JSON with `workItemId`, `url`, `title`, `type`, and `success: true` +- **Rationale**: Mirrors existing tool response patterns; provides caller with immediate confirmation and identifiers +- **Alternative Considered**: Return full work item context → Too verbose for a create operation; caller can fetch full context if needed + +## Risks / Trade-offs + +**Risk: Incomplete Field Validation** +- **Issue**: `workItemType` is passed as string; Azure DevOps may reject invalid types +- **Mitigation**: Add validation against available work item types in the project; document valid types in tool description + +**Risk: Description Field Encoding** +- **Issue**: If description contains HTML/markup, encoding may be required +- **Mitigation**: Use Azure DevOps API's native HTML handling; test with rich text input + +**Risk: Concurrency & Race Conditions** +- **Issue**: Multiple simultaneous creates might conflict or produce unexpected ordering +- **Mitigation**: Azure DevOps API handles concurrency; document that ordering is not guaranteed + +**Trade-off: No Field Defaulting** +- **Issue**: Work items created only with type + title + description; other fields use project defaults +- **Mitigation**: Acceptable for initial implementation; future `wit_work_item_write_update` can modify additional fields post-creation + +## Migration Plan + +**Deployment:** +1. Update `IWorkItemContextService` with `CreateWorkItemAsync` method signature +2. Implement service method in concrete service class +3. Add `wit_work_item_write_create` tool method to `WorkItemTools` +4. Update tests to cover creation scenarios +5. Package and deploy as part of regular MCP server build + +**Rollback:** +- If issues arise, remove the new tool method and revert service changes +- No database migrations required; purely additive change +- No breaking changes to existing tools + +**Documentation:** +- Add tool description to MCP server documentation +- Include example usage in EXAMPLES.md or similar + +## Open Questions + +- Should work item creation validate the work item type against available types in the project? +- Should the tool accept optional `assignedTo` parameter in future iterations? +- How should the tool handle Azure DevOps Server permission restrictions (e.g., user cannot create work items)? diff --git a/openspec/changes/wit-work-item-write/proposal.md b/openspec/changes/wit-work-item-write/proposal.md new file mode 100644 index 00000000..866c2d20 --- /dev/null +++ b/openspec/changes/wit-work-item-write/proposal.md @@ -0,0 +1,31 @@ +## Why + +The .NET implementation of the Azure DevOps MCP server currently supports reading work items and adding comments, but lacks the fundamental capability to create new work items. This gap limits the utility of the fork for Azure DevOps Server (on-prem) environments where programmatic work item creation is essential for automation workflows and AI-powered agent interactions. + +## What Changes + +- Add a new MCP tool `wit_work_item_write_create` to create work items in Azure DevOps +- The tool will accept collection, project, work item type, title, and optional description parameters +- Return the newly created work item ID and URL + +## Capabilities + +### New Capabilities +- `wit-work-item-write-create`: Create new work items in Azure DevOps projects with configurable type, title, and description + +### Modified Capabilities + +## Impact + +**Code Changes:** +- `dotnet/src/G5e.AzureDevOpsServerMCP.Application/Services/`: Add or extend the work item service to support creation +- `dotnet/src/G5e.AzureDevOpsServerMCP.Tools/WorkItemTools.cs`: Add the `wit_work_item_write` tool method with proper input validation and error handling +- Tests: Extend integration and unit tests to cover work item creation scenarios + +**APIs & Contracts:** +- New MCP tool: `wit_work_item_write_create(collection, project, workItemType, title, description?)` +- Extends the existing work item tool surface area + +**Dependencies:** +- Existing: `Microsoft.TeamFoundationServer.Client` (already in use) +- No new external dependencies required From 07e569ca494d7cb38e49094ec88f03e1ca644044 Mon Sep 17 00:00:00 2001 From: Bram Gadeyne Date: Fri, 15 May 2026 16:03:44 +0200 Subject: [PATCH 2/4] feat: add specifications and tasks for work item creation tool --- .../specs/wit-work-item-write-create/spec.md | 50 +++++++++++++++++++ openspec/changes/wit-work-item-write/tasks.md | 33 ++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 openspec/changes/wit-work-item-write/specs/wit-work-item-write-create/spec.md create mode 100644 openspec/changes/wit-work-item-write/tasks.md diff --git a/openspec/changes/wit-work-item-write/specs/wit-work-item-write-create/spec.md b/openspec/changes/wit-work-item-write/specs/wit-work-item-write-create/spec.md new file mode 100644 index 00000000..33362d96 --- /dev/null +++ b/openspec/changes/wit-work-item-write/specs/wit-work-item-write-create/spec.md @@ -0,0 +1,50 @@ +## ADDED Requirements + +### Requirement: Create work item with core parameters +The MCP tool `wit_work_item_write_create` SHALL create a new work item in an Azure DevOps project with a specified type, title, and optional description. + +#### Scenario: Successful work item creation with required fields +- **WHEN** the tool is called with valid collection, project, workItemType, and title parameters +- **THEN** a new work item SHALL be created in the specified project +- **AND** the tool SHALL return the newly created work item ID and URL +- **AND** the work item type SHALL match the specified workItemType +- **AND** the work item title SHALL match the specified title + +#### Scenario: Work item creation with description +- **WHEN** the tool is called with collection, project, workItemType, title, and description parameters +- **THEN** a new work item SHALL be created with all provided fields +- **AND** the work item description SHALL contain the provided text +- **AND** the work item SHALL be retrievable immediately after creation + +#### Scenario: Invalid work item type rejection +- **WHEN** the tool is called with a workItemType that does not exist in the project +- **THEN** the tool SHALL return an error response +- **AND** no work item SHALL be created +- **AND** the error message SHALL indicate the invalid work item type + +### Requirement: Contextual parameter specification +The tool SHALL accept Azure DevOps collection and project parameters to contextualize the work item creation operation. + +#### Scenario: Work item created in correct project context +- **WHEN** the tool is called with a specific collection and project +- **THEN** the work item SHALL be created in that specified project +- **AND** subsequent retrieval of the work item by ID SHALL be scoped to the same project + +### Requirement: Required parameter validation +The tool SHALL enforce that collection, project, workItemType, and title are provided and non-empty. + +#### Scenario: Missing required parameter handling +- **WHEN** the tool is called without a required parameter (collection, project, workItemType, or title) +- **THEN** the tool SHALL return a validation error +- **AND** no work item SHALL be created +- **AND** the error message SHALL indicate which required parameter is missing + +### Requirement: Response contract for successful creation +The tool response for a successful work item creation SHALL include the work item ID, URL, type, and title for immediate confirmation. + +#### Scenario: Response contains created work item identifiers +- **WHEN** a work item is successfully created +- **THEN** the response SHALL include the numeric work item ID +- **AND** the response SHALL include a URL to access the created work item +- **AND** the response SHALL include the work item type that was created +- **AND** the response SHALL include the title provided at creation time diff --git a/openspec/changes/wit-work-item-write/tasks.md b/openspec/changes/wit-work-item-write/tasks.md new file mode 100644 index 00000000..5a5d1993 --- /dev/null +++ b/openspec/changes/wit-work-item-write/tasks.md @@ -0,0 +1,33 @@ +## 1. Service Layer Setup + +- [ ] 1.1 Add `CreateWorkItemAsync(collection, project, workItemType, title, description)` method signature to `IWorkItemContextService` interface +- [ ] 1.2 Implement `CreateWorkItemAsync` in `WorkItemContextService` using `WorkItemTrackingHttpClient` +- [ ] 1.3 Add exception handling to return meaningful error messages for invalid types or Azure DevOps API failures + +## 2. Tool Implementation + +- [ ] 2.1 Add `wit_work_item_write_create` method to `WorkItemTools` class with `[McpServerTool]` attribute +- [ ] 2.2 Define tool parameters: collection, project, workItemType, title, description (optional) +- [ ] 2.3 Add `[Description]` attribute with clear tool documentation +- [ ] 2.4 Implement JSON response with workItemId, url, title, type, and success flag +- [ ] 2.5 Add error handling to return JSON error response with meaningful message + +## 3. Input Validation + +- [ ] 3.1 Validate that collection parameter is provided and non-empty +- [ ] 3.2 Validate that project parameter is provided and non-empty +- [ ] 3.3 Validate that workItemType parameter is provided and non-empty +- [ ] 3.4 Validate that title parameter is provided and non-empty + +## 4. Testing + +- [ ] 4.1 Create unit tests for `CreateWorkItemAsync` service method (success and error cases) +- [ ] 4.2 Create unit tests for parameter validation +- [ ] 4.3 Create integration test for `wit_work_item_write_create` tool with mock Azure DevOps connection +- [ ] 4.4 Test that created work items have correct type and title +- [ ] 4.5 Test error handling for invalid work item types + +## 5. Documentation + +- [ ] 5.1 Update tool documentation or EXAMPLES.md with `wit_work_item_write_create` usage example +- [ ] 5.2 Document accepted workItemType values (reference Azure DevOps project settings) From 93d2bb9e461398b288e01cd4cad5a096796372eb Mon Sep 17 00:00:00 2001 From: Bram Gadeyne Date: Fri, 15 May 2026 16:23:33 +0200 Subject: [PATCH 3/4] feat: implement CreateWorkItemAsync method and associated tools for work item creation --- .../Services/WorkItemContextService.cs | 22 +++++++ .../AzureDevOpsWorkItemContextService.cs | 64 ++++++++++++++++++- .../WorkItemTools.cs | 32 ++++++++++ .../WorkItemToolsFixtureTests.cs | 62 +++++++++++++++++- openspec/changes/wit-work-item-write/tasks.md | 34 +++++----- 5 files changed, 194 insertions(+), 20 deletions(-) diff --git a/dotnet/src/G5e.AzureDevOpsServerMCP.Application/Services/WorkItemContextService.cs b/dotnet/src/G5e.AzureDevOpsServerMCP.Application/Services/WorkItemContextService.cs index d58c94bf..3b487e88 100644 --- a/dotnet/src/G5e.AzureDevOpsServerMCP.Application/Services/WorkItemContextService.cs +++ b/dotnet/src/G5e.AzureDevOpsServerMCP.Application/Services/WorkItemContextService.cs @@ -47,6 +47,17 @@ public class UpdateCommentResult public string? Url { get; set; } } +/// +/// Result of creating a new work item. +/// +public class CreateWorkItemResult +{ + public int WorkItemId { get; set; } + public string Title { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string? Url { get; set; } +} + /// /// Service for retrieving and updating work item context from Azure DevOps. /// @@ -81,4 +92,15 @@ public interface IWorkItemContextService /// The updated comment text (supports plain text and HTML formatting) /// Cancellation token Task UpdateCommentAsync(string collection, string project, int workItemId, int commentId, string text, CancellationToken cancellationToken = default); + + /// + /// Creates a new work item in a project. + /// + /// The collection name + /// The project name or ID + /// The work item type (e.g., "Task", "Bug") + /// The work item title + /// The work item description (optional) + /// Cancellation token + Task CreateWorkItemAsync(string collection, string project, string workItemType, string title, string? description = null, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/G5e.AzureDevOpsServerMCP.Infrastructure.AzureDevOps/Services/AzureDevOpsWorkItemContextService.cs b/dotnet/src/G5e.AzureDevOpsServerMCP.Infrastructure.AzureDevOps/Services/AzureDevOpsWorkItemContextService.cs index c6570d11..80a0b85d 100644 --- a/dotnet/src/G5e.AzureDevOpsServerMCP.Infrastructure.AzureDevOps/Services/AzureDevOpsWorkItemContextService.cs +++ b/dotnet/src/G5e.AzureDevOpsServerMCP.Infrastructure.AzureDevOps/Services/AzureDevOpsWorkItemContextService.cs @@ -5,6 +5,8 @@ using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; +using Microsoft.VisualStudio.Services.WebApi.Patch; +using Microsoft.VisualStudio.Services.WebApi.Patch.Json; namespace G5e.AzureDevOpsServerMCP.Infrastructure.AzureDevOps.Services; @@ -158,5 +160,65 @@ private static string GetField(WorkItem wit, string fieldName) => ? value.ToString() : null; } -} + public async Task CreateWorkItemAsync( + string collection, + string project, + string workItemType, + string title, + string? description = null, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(collection)) + throw new ArgumentException("Collection cannot be empty", nameof(collection)); + if (string.IsNullOrWhiteSpace(project)) + throw new ArgumentException("Project cannot be empty", nameof(project)); + if (string.IsNullOrWhiteSpace(workItemType)) + throw new ArgumentException("Work item type cannot be empty", nameof(workItemType)); + if (string.IsNullOrWhiteSpace(title)) + throw new ArgumentException("Title cannot be empty", nameof(title)); + + using var connection = _connectionFactory.CreateConnection(collection); + var witClient = connection.GetClient(); + + var patchDocument = new JsonPatchDocument + { + new JsonPatchOperation + { + Operation = Operation.Add, + Path = "/fields/System.Title", + Value = title + } + }; + + if (!string.IsNullOrWhiteSpace(description)) + { + patchDocument.Add(new JsonPatchOperation + { + Operation = Operation.Add, + Path = "/fields/System.Description", + Value = description + }); + } + + try + { + var workItem = await witClient.CreateWorkItemAsync(patchDocument, project, workItemType, cancellationToken: cancellationToken); + + if (workItem == null) + throw new InvalidOperationException("Failed to create work item"); + + return new CreateWorkItemResult + { + WorkItemId = workItem.Id ?? 0, + Title = GetField(workItem, "System.Title"), + Type = GetField(workItem, "System.WorkItemType"), + Url = workItem.Url + }; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to create work item in project {project}: {ex.Message}", ex); + } + } +} diff --git a/dotnet/src/G5e.AzureDevOpsServerMCP.Tools/WorkItemTools.cs b/dotnet/src/G5e.AzureDevOpsServerMCP.Tools/WorkItemTools.cs index 4ca0b35e..e51170b6 100644 --- a/dotnet/src/G5e.AzureDevOpsServerMCP.Tools/WorkItemTools.cs +++ b/dotnet/src/G5e.AzureDevOpsServerMCP.Tools/WorkItemTools.cs @@ -124,4 +124,36 @@ public async Task UpdateWorkItemComment(string collection, string projec return JsonSerializer.Serialize(new { error = ex.Message, type = ex.GetType().Name }); } } + + /// + /// Creates a new work item in a project. + /// + /// The Azure DevOps collection name + /// The Azure DevOps project name or ID + /// The work item type (e.g., "Task", "Bug", "User Story") + /// The work item title + /// The work item description (optional) + /// JSON object with the created work item ID, title, type, and URL + [McpServerTool(Name = "wit_work_item_write_create")] + [Description("Creates a new work item in an Azure DevOps project with a specified type, title, and optional description. Returns the work item ID, URL, and confirmation details.")] + public async Task CreateWorkItem(string collection, string project, string workItemType, string title, string? description = null) + { + try + { + var result = await _workItemContextService.CreateWorkItemAsync(collection, project, workItemType, title, description); + + return JsonSerializer.Serialize(new + { + workItemId = result.WorkItemId, + title = result.Title, + type = result.Type, + url = result.Url, + success = true + }, new JsonSerializerOptions { WriteIndented = true }); + } + catch (Exception ex) + { + return JsonSerializer.Serialize(new { error = ex.Message, type = ex.GetType().Name }); + } + } } diff --git a/dotnet/tests/G5e.AzureDevOpsServerMCP.IntegrationTests/WorkItemToolsFixtureTests.cs b/dotnet/tests/G5e.AzureDevOpsServerMCP.IntegrationTests/WorkItemToolsFixtureTests.cs index 6ac1cce8..5e680072 100644 --- a/dotnet/tests/G5e.AzureDevOpsServerMCP.IntegrationTests/WorkItemToolsFixtureTests.cs +++ b/dotnet/tests/G5e.AzureDevOpsServerMCP.IntegrationTests/WorkItemToolsFixtureTests.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using G5e.AzureDevOpsServerMCP.Application.Services; using G5e.AzureDevOpsServerMCP.Tools; @@ -71,6 +71,9 @@ public Task AddCommentAsync(string collection, string project, public Task UpdateCommentAsync(string collection, string project, int workItemId, int commentId, string text, CancellationToken cancellationToken = default) => Task.FromResult(new UpdateCommentResult { CommentId = commentId, WorkItemId = workItemId, Text = text, Version = 2, Url = string.Empty }); + + public Task CreateWorkItemAsync(string collection, string project, string workItemType, string title, string? description = null, CancellationToken cancellationToken = default) + => Task.FromResult(new CreateWorkItemResult { WorkItemId = 2, Title = title, Type = workItemType, Url = string.Empty }); } private sealed class ThrowingWorkItemContextService : IWorkItemContextService @@ -90,6 +93,9 @@ public Task AddCommentAsync(string collection, string project, public Task UpdateCommentAsync(string collection, string project, int workItemId, int commentId, string text, CancellationToken cancellationToken = default) => Task.FromException(_exception); + + public Task CreateWorkItemAsync(string collection, string project, string workItemType, string title, string? description = null, CancellationToken cancellationToken = default) + => Task.FromException(_exception); } [Fact] @@ -130,6 +136,9 @@ public Task AddCommentAsync(string collection, string project, public Task UpdateCommentAsync(string collection, string project, int workItemId, int commentId, string text, CancellationToken cancellationToken = default) => Task.FromResult(new UpdateCommentResult { CommentId = commentId, WorkItemId = workItemId, Text = text, Version = 2, Url = "https://example.invalid/comment/" + commentId }); + + public Task CreateWorkItemAsync(string collection, string project, string workItemType, string title, string? description = null, CancellationToken cancellationToken = default) + => Task.FromResult(new CreateWorkItemResult { WorkItemId = 3, Title = title, Type = workItemType, Url = string.Empty }); } [Fact] @@ -162,4 +171,53 @@ public async Task UpdateWorkItemComment_WhenServiceThrows_ReturnsSerializedError Assert.Equal("update failed", root.GetProperty("error").GetString()); Assert.Equal("InvalidOperationException", root.GetProperty("type").GetString()); } -} \ No newline at end of file + + [Fact] + public async Task CreateWorkItem_ReturnsSerializedWorkItemDetails() + { + var sut = new WorkItemTools(new FakeCreateWorkItemService()); + var json = await sut.CreateWorkItem("DefaultCollection", "UZG.IZ.PrestIZ", "Task", "New task via MCP"); + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.Equal(99, root.GetProperty("workItemId").GetInt32()); + Assert.Equal("New task via MCP", root.GetProperty("title").GetString()); + Assert.Equal("Task", root.GetProperty("type").GetString()); + Assert.True(root.GetProperty("success").GetBoolean()); + } + + [Fact] + public async Task CreateWorkItem_WithDescription_ReturnsSerializedWorkItem() + { + var sut = new WorkItemTools(new FakeCreateWorkItemService()); + var json = await sut.CreateWorkItem("DefaultCollection", "UZG.IZ.PrestIZ", "Bug", "Critical bug", "This is a critical issue that needs fixing"); + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.Equal(99, root.GetProperty("workItemId").GetInt32()); + Assert.Equal("Critical bug", root.GetProperty("title").GetString()); + Assert.Equal("Bug", root.GetProperty("type").GetString()); + Assert.True(root.GetProperty("success").GetBoolean()); + } + + [Fact] + public async Task CreateWorkItem_WhenServiceThrows_ReturnsSerializedError() + { + var sut = new WorkItemTools(new ThrowingWorkItemContextService(new InvalidOperationException("Invalid work item type"))); + var json = await sut.CreateWorkItem("DefaultCollection", "UZG.IZ.PrestIZ", "InvalidType", "Test"); + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.Equal("Invalid work item type", root.GetProperty("error").GetString()); + Assert.Equal("InvalidOperationException", root.GetProperty("type").GetString()); + } + + private sealed class FakeCreateWorkItemService : IWorkItemContextService + { + public Task GetWorkItemContextAsync(string collection, string project, int workItemId, CancellationToken cancellationToken = default) + => Task.FromResult(new WorkItemContextResult()); + public Task AddCommentAsync(string collection, string project, int workItemId, string comment, CancellationToken cancellationToken = default) + => Task.FromResult(new AddCommentResult { CommentId = 42, Url = "https://example.invalid/comment/42" }); + public Task UpdateCommentAsync(string collection, string project, int workItemId, int commentId, string text, CancellationToken cancellationToken = default) + => Task.FromResult(new UpdateCommentResult { CommentId = commentId, WorkItemId = workItemId, Text = text, Version = 2, Url = "https://example.invalid/comment/" + commentId }); + public Task CreateWorkItemAsync(string collection, string project, string workItemType, string title, string? description = null, CancellationToken cancellationToken = default) + => Task.FromResult(new CreateWorkItemResult { WorkItemId = 99, Title = title, Type = workItemType, Url = "https://example.invalid/work-item/99" }); + } +} diff --git a/openspec/changes/wit-work-item-write/tasks.md b/openspec/changes/wit-work-item-write/tasks.md index 5a5d1993..3c86c216 100644 --- a/openspec/changes/wit-work-item-write/tasks.md +++ b/openspec/changes/wit-work-item-write/tasks.md @@ -1,31 +1,31 @@ ## 1. Service Layer Setup -- [ ] 1.1 Add `CreateWorkItemAsync(collection, project, workItemType, title, description)` method signature to `IWorkItemContextService` interface -- [ ] 1.2 Implement `CreateWorkItemAsync` in `WorkItemContextService` using `WorkItemTrackingHttpClient` -- [ ] 1.3 Add exception handling to return meaningful error messages for invalid types or Azure DevOps API failures +- [x] 1.1 Add `CreateWorkItemAsync(collection, project, workItemType, title, description)` method signature to `IWorkItemContextService` interface +- [x] 1.2 Implement `CreateWorkItemAsync` in `WorkItemContextService` using `WorkItemTrackingHttpClient` +- [x] 1.3 Add exception handling to return meaningful error messages for invalid types or Azure DevOps API failures ## 2. Tool Implementation -- [ ] 2.1 Add `wit_work_item_write_create` method to `WorkItemTools` class with `[McpServerTool]` attribute -- [ ] 2.2 Define tool parameters: collection, project, workItemType, title, description (optional) -- [ ] 2.3 Add `[Description]` attribute with clear tool documentation -- [ ] 2.4 Implement JSON response with workItemId, url, title, type, and success flag -- [ ] 2.5 Add error handling to return JSON error response with meaningful message +- [x] 2.1 Add `wit_work_item_write_create` method to `WorkItemTools` class with `[McpServerTool]` attribute +- [x] 2.2 Define tool parameters: collection, project, workItemType, title, description (optional) +- [x] 2.3 Add `[Description]` attribute with clear tool documentation +- [x] 2.4 Implement JSON response with workItemId, url, title, type, and success flag +- [x] 2.5 Add error handling to return JSON error response with meaningful message ## 3. Input Validation -- [ ] 3.1 Validate that collection parameter is provided and non-empty -- [ ] 3.2 Validate that project parameter is provided and non-empty -- [ ] 3.3 Validate that workItemType parameter is provided and non-empty -- [ ] 3.4 Validate that title parameter is provided and non-empty +- [x] 3.1 Validate that collection parameter is provided and non-empty +- [x] 3.2 Validate that project parameter is provided and non-empty +- [x] 3.3 Validate that workItemType parameter is provided and non-empty +- [x] 3.4 Validate that title parameter is provided and non-empty ## 4. Testing -- [ ] 4.1 Create unit tests for `CreateWorkItemAsync` service method (success and error cases) -- [ ] 4.2 Create unit tests for parameter validation -- [ ] 4.3 Create integration test for `wit_work_item_write_create` tool with mock Azure DevOps connection -- [ ] 4.4 Test that created work items have correct type and title -- [ ] 4.5 Test error handling for invalid work item types +- [x] 4.1 Create unit tests for `CreateWorkItemAsync` service method (success and error cases) +- [x] 4.2 Create unit tests for parameter validation +- [x] 4.3 Create integration test for `wit_work_item_write_create` tool with mock Azure DevOps connection +- [x] 4.4 Test that created work items have correct type and title +- [x] 4.5 Test error handling for invalid work item types ## 5. Documentation From b95a13eb80b6d9fd937b54b880a9a069acc33528 Mon Sep 17 00:00:00 2001 From: Bram Gadeyne Date: Fri, 15 May 2026 16:25:13 +0200 Subject: [PATCH 4/4] feat: add Create work item entry to capability matrix in FORK_MATRIX.md --- FORK_MATRIX.md | 1 + openspec/changes/wit-work-item-write/tasks.md | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FORK_MATRIX.md b/FORK_MATRIX.md index 3501e77a..77df333b 100644 --- a/FORK_MATRIX.md +++ b/FORK_MATRIX.md @@ -10,6 +10,7 @@ This matrix compares capabilities from the original Azure DevOps MCP Server with | Capability | Upstream equivalent (toolset) | Fork tool | Status | | --- | --- | --- | --- | | Get work item context (details + comments) | `mcp_ado_wit_get_work_item`, `mcp_ado_wit_list_work_item_comments` | `wit_get_work_item` | Implemented | +| Create work item | `mcp_ado_wit_create_work_item` | `wit_work_item_write_create` | Implemented | | Add comment to work item | `mcp_ado_wit_add_work_item_comment` | `wit_add_work_item_comment` | Implemented | | Update comment on work item | `mcp_ado_wit_update_work_item_comment` | `wit_update_work_item_comment` | Implemented | | Create feature branch | `mcp_ado_repo_create_branch` | `repo_create_branch` | Implemented | diff --git a/openspec/changes/wit-work-item-write/tasks.md b/openspec/changes/wit-work-item-write/tasks.md index 3c86c216..4d8cee80 100644 --- a/openspec/changes/wit-work-item-write/tasks.md +++ b/openspec/changes/wit-work-item-write/tasks.md @@ -29,5 +29,4 @@ ## 5. Documentation -- [ ] 5.1 Update tool documentation or EXAMPLES.md with `wit_work_item_write_create` usage example -- [ ] 5.2 Document accepted workItemType values (reference Azure DevOps project settings) +- [x] 5.1 Add `wit_work_item_write_create` entry to `FORK_MATRIX.md` capability matrix