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