Skip to main content
Glama

create-work-item

Create new work items in Azure DevOps, including tasks, bugs, and user stories, with options for assignment, tagging, and sprint planning.

Instructions

Create a new work item in Azure DevOps

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
typeYesWork item type (e.g., Task, Bug, User Story)
titleYesWork item title
descriptionNoWork item description
assignedToNoEmail of the person to assign the work item to
tagsNoSemicolon-separated tags
parentNoParent work item ID for establishing hierarchy during creation
iterationPathNoIteration path for sprint assignment (e.g., ProjectName\Sprint 1)
stateNoInitial work item state (e.g., New, Active)

Implementation Reference

  • Full implementation of the create-work-item handler. Creates work items via Azure DevOps WIT API, supports parent hierarchy, iteration path normalization/validation, state validation, generic fields, and detailed response with relations.
    private async createWorkItem(args: any): Promise<any> {
      if (!args.type || !args.title) {
        throw new Error('Work item type and title are required');
      }
    
      try {
        const operations = [
          {
            op: 'add',
            path: '/fields/System.Title',
            value: args.title
          }
        ];
    
        if (args.description) {
          operations.push({
            op: 'add',
            path: '/fields/System.Description',
            value: args.description
          });
        }
    
        if (args.assignedTo) {
          operations.push({
            op: 'add',
            path: '/fields/System.AssignedTo',
            value: args.assignedTo
          });
        }
    
        if (args.tags) {
          operations.push({
            op: 'add',
            path: '/fields/System.Tags',
            value: args.tags
          });
        }
    
        // Support parent relationship during creation using relations API
        if (args.parent) {
          // Validate parent ID is a number
          const parentId = parseInt(args.parent, 10);
          if (isNaN(parentId) || parentId <= 0) {
            throw new Error(`Invalid parent work item ID: ${args.parent}. Must be a positive integer.`);
          }
          
          const parentUrl = `${this.currentConfig!.organizationUrl}/${this.currentConfig!.project}/_apis/wit/workItems/${parentId}`;
          console.log(`[DEBUG] Setting parent relationship to work item ${parentId} using URL: ${parentUrl}`);
          
          operations.push({
            op: 'add',
            path: '/relations/-',
            value: {
              rel: 'System.LinkTypes.Hierarchy-Reverse',
              url: parentUrl,
              attributes: {
                comment: `Parent relationship set via MCP create-work-item command`
              }
            }
          });
        }
    
        // Enhanced iteration path handling with normalization and validation
        let iterationPathHandled = false;
        let iterationPathError = null;
        let finalIterationPath = null;
    
        if (args.iterationPath) {
          try {
            // Validate and normalize the iteration path
            finalIterationPath = await this.validateIterationPath(args.iterationPath);
            
            // Add normalized path to the creation operations
            operations.push({
              op: 'add',
              path: '/fields/System.IterationPath',
              value: finalIterationPath
            });
            iterationPathHandled = true;
            console.log(`[DEBUG] Iteration path normalized to '${finalIterationPath}' and will be set during creation`);
          } catch (validationError) {
            iterationPathError = validationError;
            finalIterationPath = this.normalizeIterationPath(args.iterationPath);
            console.log(`[DEBUG] Iteration path validation failed: ${validationError instanceof Error ? validationError.message : 'Unknown error'}`);
            console.log(`[DEBUG] Will attempt to set normalized path '${finalIterationPath}' after work item creation`);
          }
        }
    
        // Support state during creation with validation
        if (args.state) {
          // Validate state for work item type to prevent TF401347-like errors
          const validatedState = await this.validateWorkItemState(args.type, args.state);
          operations.push({
            op: 'add',
            path: '/fields/System.State',
            value: validatedState
          });
        }
    
        // Handle generic field creation with intelligent field name resolution
        if (args.fields && typeof args.fields === 'object') {
          Object.entries(args.fields).forEach(([fieldName, fieldValue]) => {
            // CRITICAL FIX: Implement proper field name resolution as specified in GitHub issue #53
            let normalizedFieldName = fieldName;
            
            // CRITICAL: Microsoft.VSTS.* fields must NEVER be prefixed with System.
            // Azure DevOps field categories:
            // - System fields: Always prefixed with "System." (e.g., System.Title, System.State)
            // - Microsoft fields: Never prefixed, use full name (e.g., Microsoft.VSTS.Common.Priority)
            // - Custom fields: May have organization-specific prefixes
            
            // Apply System. prefix ONLY to fields that don't already have System. or Microsoft. prefixes
            if (!fieldName.startsWith('System.') && !fieldName.startsWith('Microsoft.')) {
              // Only add System. prefix for known system fields without namespaces
              const knownSystemFields = ['Title', 'Description', 'State', 'AssignedTo', 'Tags', 'IterationPath', 'AreaPath'];
              if (knownSystemFields.includes(fieldName)) {
                normalizedFieldName = `System.${fieldName}`;
              }
              // All other fields (including BusinessValue, Priority, Effort) remain unchanged
              // This preserves custom fields and Microsoft.VSTS.* fields correctly
            }
            // System.* and Microsoft.* fields are preserved exactly as-is
    
            console.log(`[DEBUG] Field resolution: "${fieldName}" → "${normalizedFieldName}"`);
            
            operations.push({
              op: 'add',
              path: `/fields/${normalizedFieldName}`,
              value: fieldValue
            });
          });
        }
    
        // Debug logging to validate the endpoint construction
        const endpoint = `/wit/workitems/$${args.type}?api-version=7.1`;
        console.log(`[DEBUG] Creating work item with endpoint: ${endpoint}`);
        console.log(`[DEBUG] Full URL will be: ${this.currentConfig!.organizationUrl}/${this.currentConfig!.project}/_apis${endpoint}`);
        
        // Create the work item
        const result = await this.makeApiRequest(
          endpoint,
          'PATCH',
          operations
        );
    
        // Handle iteration path post-creation if it wasn't set during creation
        if (args.iterationPath && !iterationPathHandled && finalIterationPath) {
          try {
            console.log(`[DEBUG] Attempting to set normalized iteration path '${finalIterationPath}' post-creation for work item ${result.id}`);
            await this.updateWorkItemIterationPath(result.id, finalIterationPath);
            
            // Refresh the work item to get updated fields
            const updatedResult = await this.makeApiRequest(`/wit/workitems/${result.id}?api-version=7.1`);
            Object.assign(result, updatedResult);
            
            console.log(`[DEBUG] Successfully set iteration path post-creation`);
          } catch (postCreationError) {
            console.error(`[WARNING] Failed to set iteration path post-creation: ${postCreationError instanceof Error ? postCreationError.message : 'Unknown error'}`);
            // Don't fail the entire operation, just log the warning
          }
        }
    
        // Extract parent information from relations
        let parentInfo = null;
        if (result.relations && result.relations.length > 0) {
          const parentRelation = result.relations.find((rel: any) =>
            rel.rel === 'System.LinkTypes.Hierarchy-Reverse'
          );
          if (parentRelation) {
            // Extract parent ID from URL (e.g., .../workItems/1562 -> 1562)
            const match = parentRelation.url.match(/workItems\/(\d+)$/);
            parentInfo = {
              id: match ? parseInt(match[1], 10) : null,
              url: parentRelation.url,
              comment: parentRelation.attributes?.comment
            };
          }
        }
    
        // Prepare response with enhanced error reporting
        const response: any = {
          success: true,
          workItem: {
            id: result.id,
            title: result.fields['System.Title'],
            type: result.fields['System.WorkItemType'],
            state: result.fields['System.State'],
            parent: result.fields['System.Parent'] || parentInfo?.id || null,
            parentRelation: parentInfo,
            iterationPath: result.fields['System.IterationPath'],
            assignedTo: result.fields['System.AssignedTo']?.displayName || result.fields['System.AssignedTo'],
            url: result._links.html.href,
            relations: result.relations?.length || 0
          },
          message: args.parent ? `Work item created with parent relationship to work item ${args.parent}` : 'Work item created successfully'
        };
    
        // Add iteration path handling details to response
        if (args.iterationPath) {
          response.iterationPathHandling = {
            requested: args.iterationPath,
            normalized: finalIterationPath,
            setDuringCreation: iterationPathHandled,
            finalValue: result.fields['System.IterationPath']
          };
          
          if (iterationPathError) {
            response.iterationPathHandling.validationError = iterationPathError instanceof Error ? iterationPathError.message : 'Unknown validation error';
          }
        }
    
        return {
          content: [{
            type: 'text',
            text: JSON.stringify(response, null, 2),
          }],
        };
      } catch (error) {
        throw new Error(`Failed to create work item: ${error instanceof Error ? error.message : 'Unknown error'}`);
      }
    }
  • src/index.ts:123-164 (registration)
    Tool registration in MCP server. Defines the 'create-work-item' tool name, description, and input schema in the list returned by ListToolsRequest.
    {
      name: 'create-work-item',
      description: 'Create a new work item in Azure DevOps',
      inputSchema: {
        type: 'object',
        properties: {
          type: {
            type: 'string',
            description: 'Work item type (e.g., Task, Bug, User Story)',
          },
          title: {
            type: 'string',
            description: 'Work item title',
          },
          description: {
            type: 'string',
            description: 'Work item description',
          },
          assignedTo: {
            type: 'string',
            description: 'Email of the person to assign the work item to',
          },
          tags: {
            type: 'string',
            description: 'Semicolon-separated tags',
          },
          parent: {
            type: 'number',
            description: 'Parent work item ID for establishing hierarchy during creation',
          },
          iterationPath: {
            type: 'string',
            description: 'Iteration path for sprint assignment (e.g., ProjectName\\Sprint 1)',
          },
          state: {
            type: 'string',
            description: 'Initial work item state (e.g., New, Active)',
          },
        },
        required: ['type', 'title'],
      },
    },
  • Input schema for create-work-item tool, defining properties like type, title (required), description, assignedTo, tags, parent, iterationPath, state.
    inputSchema: {
      type: 'object',
      properties: {
        type: {
          type: 'string',
          description: 'Work item type (e.g., Task, Bug, User Story)',
        },
        title: {
          type: 'string',
          description: 'Work item title',
        },
        description: {
          type: 'string',
          description: 'Work item description',
        },
        assignedTo: {
          type: 'string',
          description: 'Email of the person to assign the work item to',
        },
        tags: {
          type: 'string',
          description: 'Semicolon-separated tags',
        },
        parent: {
          type: 'number',
          description: 'Parent work item ID for establishing hierarchy during creation',
        },
        iterationPath: {
          type: 'string',
          description: 'Iteration path for sprint assignment (e.g., ProjectName\\Sprint 1)',
        },
        state: {
          type: 'string',
          description: 'Initial work item state (e.g., New, Active)',
        },
      },
      required: ['type', 'title'],
  • Dispatch case in handleToolCall method that routes 'create-work-item' calls to the createWorkItem implementation.
    case 'create-work-item':
      return await this.createWorkItem(args || {});
  • Helper function for normalizing iteration paths, crucial for create-work-item to handle sprint assignments correctly and avoid TF401347 errors.
    private normalizeIterationPath(iterationPath: string): string {
      // Remove leading/trailing whitespace
      let normalized = iterationPath.trim();
      
      // Convert forward slashes to backslashes for consistency with Azure DevOps
      normalized = normalized.replace(/\//g, '\\');
      
      // Remove leading backslash if present
      if (normalized.startsWith('\\')) {
        normalized = normalized.substring(1);
      }
      
      // Handle different input scenarios
      const projectName = this.currentConfig!.project;
      
      // Case 1: Path starts with project name and has proper Iteration prefix
      if (normalized.startsWith(`${projectName}\\Iteration\\`)) {
        console.log(`[DEBUG] Path already in correct format with Iteration prefix: ${normalized}`);
        return normalized;
      }
      
      // Case 2: Path starts with project but missing Iteration component
      if (normalized.startsWith(`${projectName}\\`) && !normalized.includes('\\Iteration\\')) {
        // Insert Iteration component after project name
        const pathParts = normalized.split('\\');
        if (pathParts.length >= 2) {
          pathParts.splice(1, 0, 'Iteration');
          normalized = pathParts.join('\\');
          console.log(`[DEBUG] Added Iteration component to path: ${normalized}`);
          return normalized;
        }
      }
      
      // Case 3: Has Iteration prefix but missing project name (Iteration\SprintName)
      if (normalized.startsWith('Iteration\\')) {
        normalized = `${projectName}\\${normalized}`;
        console.log(`[DEBUG] Added project name prefix to Iteration path: ${normalized}`);
        return normalized;
      }
      
      // Case 4: Just the sprint name (SprintName or Sprint 3)
      if (!normalized.includes('\\')) {
        normalized = `${projectName}\\Iteration\\${normalized}`;
        console.log(`[DEBUG] Added full project and Iteration prefix to sprint: ${normalized}`);
        return normalized;
      }
      
      // Case 5: Starts with something else - ensure proper format
      if (!normalized.startsWith(projectName)) {
        // Check if it already has an Iteration component
        if (normalized.includes('\\Iteration\\')) {
          normalized = `${projectName}\\${normalized}`;
        } else {
          // Add both project name and Iteration component
          normalized = `${projectName}\\Iteration\\${normalized}`;
        }
        console.log(`[DEBUG] Added project name prefix with Iteration: ${normalized}`);
      }
      
      console.log(`[DEBUG] Normalized iteration path from '${iterationPath}' to '${normalized}'`);
      return normalized;
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wangkanai/devops-enhanced-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server