devops-mcp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
devops_mcp/models.py ADDED
@@ -0,0 +1,954 @@
1
+ """Pydantic input models for all Azure DevOps MCP tools."""
2
+
3
+ import uuid
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
6
+
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # GUID validation helpers
10
+ # ---------------------------------------------------------------------------
11
+
12
+
13
+ def _validate_guid(value: str, field: str) -> str:
14
+ """Validate that *value* is a well-formed UUID (8-4-4-4-12 hex, case-insensitive).
15
+
16
+ Returns the original value unchanged on success so the field stores whatever
17
+ case the caller provided. Raises ``ValueError`` with an actionable message on
18
+ failure so Pydantic wraps it into a ``ValidationError``.
19
+ """
20
+ try:
21
+ uuid.UUID(value)
22
+ except ValueError:
23
+ raise ValueError(
24
+ f"'{field}' must be a valid GUID (format: 8-4-4-4-12 hex, "
25
+ f"e.g. 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'); got: {value!r}"
26
+ )
27
+ return value
28
+
29
+
30
+ def _validate_guid_list(values: list[str], field: str) -> list[str]:
31
+ """Validate every element in *values* as a GUID. Returns the list unchanged."""
32
+ for v in values:
33
+ _validate_guid(v, field)
34
+ return values
35
+
36
+
37
+ class AzDoBaseInput(BaseModel):
38
+ """Shared organization and project selection for all Azure DevOps tools."""
39
+
40
+ model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
41
+
42
+ organization: str | None = Field(
43
+ default=None,
44
+ description=(
45
+ "Azure DevOps organization name (e.g., 'myorg'). "
46
+ "If omitted, falls back to the AZDO_ORGANIZATION environment variable."
47
+ ),
48
+ )
49
+ project: str | None = Field(
50
+ default=None,
51
+ description=(
52
+ "Azure DevOps project name or ID. "
53
+ "If omitted, falls back to the AZDO_PROJECT environment variable."
54
+ ),
55
+ )
56
+
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # Pipelines
60
+ # ---------------------------------------------------------------------------
61
+
62
+
63
+ class ListPipelinesInput(AzDoBaseInput):
64
+ """Input for listing pipelines in a project."""
65
+
66
+ top: int = Field(
67
+ default=100,
68
+ description="Maximum number of pipelines to return (max 1000).",
69
+ ge=1,
70
+ le=1000,
71
+ )
72
+ continuation_token: str | None = Field(
73
+ default=None,
74
+ description="Pagination token from a previous response.",
75
+ )
76
+ order_by: str | None = Field(
77
+ default=None,
78
+ description="Sort expression (e.g., 'name asc').",
79
+ )
80
+
81
+
82
+ class ListPipelineRunsInput(AzDoBaseInput):
83
+ """Input for listing runs of a specific pipeline."""
84
+
85
+ pipeline_id: int = Field(description="The pipeline ID.", ge=1)
86
+ top: int = Field(
87
+ default=100,
88
+ description="Maximum number of runs to return — client-side limit (max 10000, Azure DevOps server-side cap).",
89
+ ge=1,
90
+ le=10000,
91
+ )
92
+
93
+
94
+ class GetPipelineRunInput(AzDoBaseInput):
95
+ """Input for getting a specific pipeline run."""
96
+
97
+ pipeline_id: int = Field(description="The pipeline ID.", ge=1)
98
+ run_id: int = Field(description="The run ID.", ge=1)
99
+
100
+
101
+ class ListRunLogsInput(AzDoBaseInput):
102
+ """Input for listing log entries for a pipeline run."""
103
+
104
+ build_id: int = Field(
105
+ description=(
106
+ "The build/run ID. This is the 'buildId' value from the Azure DevOps "
107
+ "build URL (e.g., ?buildId=12345). Identical to run_id."
108
+ ),
109
+ ge=1,
110
+ )
111
+
112
+
113
+ class GetBuildInput(AzDoBaseInput):
114
+ """Input for retrieving details of a specific build by build ID."""
115
+
116
+ build_id: int = Field(
117
+ description=(
118
+ "The build ID. This is the 'buildId' value from the Azure DevOps "
119
+ "build URL (e.g., ?buildId=12345). Identical to run_id."
120
+ ),
121
+ ge=1,
122
+ )
123
+
124
+
125
+ class GetRunLogContentInput(AzDoBaseInput):
126
+ """Input for retrieving the plain-text content of a specific log."""
127
+
128
+ build_id: int = Field(
129
+ description="The build/run ID (run_id and build_id are the same value).",
130
+ ge=1,
131
+ )
132
+ log_id: int = Field(
133
+ description="The log ID (obtain from devops_list_run_logs).",
134
+ ge=1,
135
+ )
136
+ start_line: int | None = Field(
137
+ default=None,
138
+ description="Start line for partial log retrieval (1-based, inclusive).",
139
+ ge=1,
140
+ )
141
+ end_line: int | None = Field(
142
+ default=None,
143
+ description="End line for partial log retrieval (inclusive).",
144
+ ge=1,
145
+ )
146
+
147
+
148
+ class ListBuildArtifactsInput(AzDoBaseInput):
149
+ """Input for listing artifacts produced by a pipeline build."""
150
+
151
+ build_id: int = Field(
152
+ description="The build/run ID.",
153
+ ge=1,
154
+ )
155
+ artifact_name: str | None = Field(
156
+ default=None,
157
+ description="Filter to a specific artifact by name. Omit to list all artifacts.",
158
+ )
159
+
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # Repositories
163
+ # ---------------------------------------------------------------------------
164
+
165
+
166
+ class ListRepositoriesInput(AzDoBaseInput):
167
+ """Input for listing Git repositories in a project."""
168
+
169
+ include_links: bool = Field(
170
+ default=False,
171
+ description="Include _links in the response.",
172
+ )
173
+ include_all_urls: bool = Field(
174
+ default=False,
175
+ description="Include all remote URLs (HTTPS and SSH).",
176
+ )
177
+ include_hidden: bool = Field(
178
+ default=False,
179
+ description="Include hidden repositories.",
180
+ )
181
+
182
+
183
+ class GetRepositoryInput(AzDoBaseInput):
184
+ """Input for getting a specific Git repository."""
185
+
186
+ repository_id: str = Field(
187
+ description="Repository ID (UUID) or repository name.",
188
+ )
189
+
190
+
191
+ class ListBranchesInput(AzDoBaseInput):
192
+ """Input for listing branches in a Git repository."""
193
+
194
+ repository_id: str = Field(
195
+ description="Repository ID (UUID) or repository name.",
196
+ )
197
+ filter_contains: str | None = Field(
198
+ default=None,
199
+ description="Filter branches by a substring (e.g., 'feature').",
200
+ )
201
+ top: int = Field(
202
+ default=100,
203
+ description="Maximum number of branches to return (max 1000).",
204
+ ge=1,
205
+ le=1000,
206
+ )
207
+
208
+
209
+ # ---------------------------------------------------------------------------
210
+ # Pull Requests
211
+ # ---------------------------------------------------------------------------
212
+
213
+
214
+ class GetPullRequestInput(AzDoBaseInput):
215
+ """Input for retrieving a single pull request by ID."""
216
+
217
+ repository_id: str = Field(
218
+ description="Repository ID (UUID) or repository name.",
219
+ )
220
+ pull_request_id: int = Field(
221
+ description="The pull request ID.",
222
+ ge=1,
223
+ )
224
+ include_commits: bool = Field(
225
+ default=False,
226
+ description="Include the commits associated with the pull request.",
227
+ )
228
+ include_work_item_refs: bool = Field(
229
+ default=False,
230
+ description="Include work item references associated with the pull request.",
231
+ )
232
+
233
+
234
+ class ListPullRequestsInput(AzDoBaseInput):
235
+ """Input for listing pull requests in a repository."""
236
+
237
+ repository_id: str = Field(
238
+ description="Repository ID (UUID) or repository name.",
239
+ )
240
+ status: str | None = Field(
241
+ default="active",
242
+ description=(
243
+ "Filter by PR status: 'active', 'abandoned', 'completed', 'all'. "
244
+ "Defaults to 'active'."
245
+ ),
246
+ )
247
+ source_ref_name: str | None = Field(
248
+ default=None,
249
+ description="Filter by source branch (e.g., 'refs/heads/feature/my-branch').",
250
+ )
251
+ target_ref_name: str | None = Field(
252
+ default=None,
253
+ description="Filter by target branch (e.g., 'refs/heads/main').",
254
+ )
255
+ creator_id: str | None = Field(
256
+ default=None,
257
+ description="Filter by creator identity ID (UUID).",
258
+ )
259
+ reviewer_id: str | None = Field(
260
+ default=None,
261
+ description="Filter by reviewer identity ID (UUID).",
262
+ )
263
+ labels: list[str] | None = Field(
264
+ default=None,
265
+ description="Filter by label names. All specified labels must match (AND).",
266
+ )
267
+ title: str | None = Field(
268
+ default=None,
269
+ description="Filter by title substring.",
270
+ )
271
+ top: int = Field(
272
+ default=100,
273
+ description="Maximum number of pull requests to return (max 1000).",
274
+ ge=1,
275
+ le=1000,
276
+ )
277
+ skip: int | None = Field(
278
+ default=None,
279
+ description="Number of pull requests to skip (for pagination).",
280
+ ge=0,
281
+ )
282
+
283
+ @field_validator("creator_id", mode="after")
284
+ @classmethod
285
+ def validate_creator_id(cls, v: str | None) -> str | None:
286
+ if v is None:
287
+ return v
288
+ return _validate_guid(v, "creator_id")
289
+
290
+ @field_validator("reviewer_id", mode="after")
291
+ @classmethod
292
+ def validate_reviewer_id(cls, v: str | None) -> str | None:
293
+ if v is None:
294
+ return v
295
+ return _validate_guid(v, "reviewer_id")
296
+
297
+
298
+ class CreatePullRequestInput(AzDoBaseInput):
299
+ """Input for creating a new pull request."""
300
+
301
+ repository_id: str = Field(
302
+ description="Repository ID (UUID) or repository name.",
303
+ )
304
+ source_ref_name: str = Field(
305
+ description="Source branch ref name (e.g., 'refs/heads/feature/my-branch').",
306
+ )
307
+ target_ref_name: str = Field(
308
+ description="Target branch ref name (e.g., 'refs/heads/main').",
309
+ )
310
+ title: str = Field(
311
+ description="Title of the pull request.",
312
+ )
313
+ description: str | None = Field(
314
+ default=None,
315
+ description="Description of the pull request (up to 4000 characters).",
316
+ )
317
+ is_draft: bool = Field(
318
+ default=False,
319
+ description="Create as a draft (WIP) pull request.",
320
+ )
321
+ reviewers: list[str] | None = Field(
322
+ default=None,
323
+ description="List of reviewer identity IDs (UUIDs) to add.",
324
+ )
325
+ labels: list[str] | None = Field(
326
+ default=None,
327
+ description="List of label names to attach to the pull request.",
328
+ )
329
+ work_item_ids: list[int] | None = Field(
330
+ default=None,
331
+ description="List of work item IDs to associate with the pull request.",
332
+ )
333
+ delete_source_branch: bool = Field(
334
+ default=False,
335
+ description="Delete the source branch after the pull request is completed.",
336
+ )
337
+ merge_strategy: str | None = Field(
338
+ default=None,
339
+ description=(
340
+ "Merge strategy on completion: 'noFastForward', 'squash', "
341
+ "'rebase', or 'rebaseMerge'."
342
+ ),
343
+ )
344
+
345
+ @field_validator("reviewers", mode="after")
346
+ @classmethod
347
+ def validate_reviewers(cls, v: list[str] | None) -> list[str] | None:
348
+ if v is None:
349
+ return v
350
+ return _validate_guid_list(v, "reviewers")
351
+
352
+
353
+ class UpdatePullRequestInput(AzDoBaseInput):
354
+ """Input for updating an existing pull request.
355
+
356
+ Only supply the fields you want to change. Updatable fields: title,
357
+ description, status, isDraft, targetRefName, autoCompleteSetBy, and
358
+ completionOptions (deleteSourceBranch, mergeStrategy, mergeCommitMessage,
359
+ transitionWorkItems).
360
+ """
361
+
362
+ repository_id: str = Field(
363
+ description="Repository ID (UUID) or repository name.",
364
+ )
365
+ pull_request_id: int = Field(
366
+ description="The pull request ID to update.",
367
+ ge=1,
368
+ )
369
+ title: str | None = Field(
370
+ default=None,
371
+ description="New title for the pull request.",
372
+ )
373
+ description: str | None = Field(
374
+ default=None,
375
+ description="New description (up to 4000 characters).",
376
+ )
377
+ status: str | None = Field(
378
+ default=None,
379
+ description="New status: 'active', 'abandoned', or 'completed'.",
380
+ )
381
+ is_draft: bool | None = Field(
382
+ default=None,
383
+ description="Set draft status (True = draft/WIP, False = ready for review).",
384
+ )
385
+ target_ref_name: str | None = Field(
386
+ default=None,
387
+ description="Retarget the PR to a different branch (requires retargeting policy).",
388
+ )
389
+ auto_complete_identity_id: str | None = Field(
390
+ default=None,
391
+ description=(
392
+ "Identity ID (UUID) to enable auto-complete. "
393
+ "Set to the current user's ID to enable auto-complete."
394
+ ),
395
+ )
396
+ delete_source_branch: bool | None = Field(
397
+ default=None,
398
+ description="Whether to delete the source branch on completion.",
399
+ )
400
+ merge_strategy: str | None = Field(
401
+ default=None,
402
+ description=(
403
+ "Merge strategy on completion: 'noFastForward', 'squash', "
404
+ "'rebase', or 'rebaseMerge'."
405
+ ),
406
+ )
407
+ merge_commit_message: str | None = Field(
408
+ default=None,
409
+ description="Custom commit message for the merge commit.",
410
+ )
411
+ transition_work_items: bool | None = Field(
412
+ default=None,
413
+ description=(
414
+ "Transition linked work items to the next logical state on completion."
415
+ ),
416
+ )
417
+
418
+ @field_validator("auto_complete_identity_id", mode="after")
419
+ @classmethod
420
+ def validate_auto_complete_identity_id(cls, v: str | None) -> str | None:
421
+ if v is None:
422
+ return v
423
+ return _validate_guid(v, "auto_complete_identity_id")
424
+
425
+
426
+ class TagPullRequestInput(AzDoBaseInput):
427
+ """Input for adding labels (tags) to a pull request."""
428
+
429
+ repository_id: str = Field(
430
+ description="Repository ID (UUID) or repository name.",
431
+ )
432
+ pull_request_id: int = Field(
433
+ description="The pull request ID.",
434
+ ge=1,
435
+ )
436
+ labels: list[str] = Field(
437
+ description=(
438
+ "One or more label names to add. Labels are created automatically "
439
+ "if they do not already exist."
440
+ ),
441
+ min_length=1,
442
+ )
443
+
444
+
445
+ class LinkWorkItemsToPullRequestInput(AzDoBaseInput):
446
+ """Input for associating work items with a pull request."""
447
+
448
+ repository_id: str = Field(
449
+ description="Repository ID (UUID) or repository name.",
450
+ )
451
+ pull_request_id: int = Field(
452
+ description="The pull request ID.",
453
+ ge=1,
454
+ )
455
+ work_item_ids: list[int] = Field(
456
+ description="List of work item IDs to associate with the pull request.",
457
+ min_length=1,
458
+ )
459
+
460
+
461
+ class ListPullRequestThreadsInput(AzDoBaseInput):
462
+ """Input for listing all comment threads on a pull request."""
463
+
464
+ repository_id: str = Field(
465
+ description="Repository ID (UUID) or repository name.",
466
+ )
467
+ pull_request_id: int = Field(
468
+ description="The pull request ID.",
469
+ ge=1,
470
+ )
471
+
472
+
473
+ class GetPullRequestThreadInput(AzDoBaseInput):
474
+ """Input for retrieving a single comment thread on a pull request."""
475
+
476
+ repository_id: str = Field(
477
+ description="Repository ID (UUID) or repository name.",
478
+ )
479
+ pull_request_id: int = Field(
480
+ description="The pull request ID.",
481
+ ge=1,
482
+ )
483
+ thread_id: int = Field(
484
+ description="The thread ID.",
485
+ ge=1,
486
+ )
487
+
488
+
489
+ _VALID_THREAD_STATUSES = {"active", "fixed", "wontFix", "closed", "byDesign", "pending"}
490
+
491
+
492
+ class CreatePullRequestThreadInput(AzDoBaseInput):
493
+ """Input for creating a new comment thread on a pull request.
494
+
495
+ When file_path is provided, the thread is anchored to a specific code line
496
+ (inline comment); right_file_start_line and right_file_end_line are required
497
+ in that case. When file_path is omitted, the thread is a general PR-level
498
+ comment and line fields are ignored.
499
+ """
500
+
501
+ repository_id: str = Field(
502
+ description="Repository ID (UUID) or repository name.",
503
+ )
504
+ pull_request_id: int = Field(
505
+ description="The pull request ID.",
506
+ ge=1,
507
+ )
508
+ content: str = Field(
509
+ description="Text of the initial comment on the thread.",
510
+ min_length=1,
511
+ )
512
+ status: str = Field(
513
+ default="active",
514
+ description=(
515
+ "Initial thread status. Valid values: 'active', 'fixed', 'wontFix', "
516
+ "'closed', 'byDesign', 'pending'. Defaults to 'active'."
517
+ ),
518
+ )
519
+ file_path: str | None = Field(
520
+ default=None,
521
+ description=(
522
+ "File path to anchor the thread to a specific code line "
523
+ "(e.g., '/src/main.py'). Omit to create a general PR-level comment."
524
+ ),
525
+ )
526
+ right_file_start_line: int | None = Field(
527
+ default=None,
528
+ description=(
529
+ "Start line in the file (1-based, inclusive). "
530
+ "Required when file_path is provided."
531
+ ),
532
+ ge=1,
533
+ )
534
+ right_file_end_line: int | None = Field(
535
+ default=None,
536
+ description=(
537
+ "End line in the file (1-based, inclusive). "
538
+ "Required when file_path is provided."
539
+ ),
540
+ ge=1,
541
+ )
542
+ right_file_start_offset: int = Field(
543
+ default=1,
544
+ description="Column offset of the start position (1-based). Defaults to 1.",
545
+ ge=1,
546
+ )
547
+ right_file_end_offset: int = Field(
548
+ default=1,
549
+ description="Column offset of the end position (1-based). Defaults to 1.",
550
+ ge=1,
551
+ )
552
+
553
+ @field_validator("status", mode="after")
554
+ @classmethod
555
+ def validate_status(cls, v: str) -> str:
556
+ if v not in _VALID_THREAD_STATUSES:
557
+ raise ValueError(
558
+ f"'status' must be one of {sorted(_VALID_THREAD_STATUSES)}; got: {v!r}"
559
+ )
560
+ return v
561
+
562
+ @model_validator(mode="after")
563
+ def validate_inline_line_fields(self) -> "CreatePullRequestThreadInput":
564
+ if self.file_path is not None:
565
+ if self.right_file_start_line is None or self.right_file_end_line is None:
566
+ raise ValueError(
567
+ "right_file_start_line and right_file_end_line are required when "
568
+ "file_path is set (inline comment on a code line)."
569
+ )
570
+ return self
571
+
572
+
573
+ class SetPullRequestThreadStatusInput(AzDoBaseInput):
574
+ """Input for updating the status of an existing pull request comment thread."""
575
+
576
+ repository_id: str = Field(
577
+ description="Repository ID (UUID) or repository name.",
578
+ )
579
+ pull_request_id: int = Field(
580
+ description="The pull request ID.",
581
+ ge=1,
582
+ )
583
+ thread_id: int = Field(
584
+ description="The thread ID to update.",
585
+ ge=1,
586
+ )
587
+ status: str = Field(
588
+ description=(
589
+ "New thread status. Valid values: 'active', 'fixed', 'wontFix', "
590
+ "'closed', 'byDesign', 'pending'."
591
+ ),
592
+ )
593
+
594
+ @field_validator("status", mode="after")
595
+ @classmethod
596
+ def validate_status(cls, v: str) -> str:
597
+ if v not in _VALID_THREAD_STATUSES:
598
+ raise ValueError(
599
+ f"'status' must be one of {sorted(_VALID_THREAD_STATUSES)}; got: {v!r}"
600
+ )
601
+ return v
602
+
603
+
604
+ class AddPullRequestCommentInput(AzDoBaseInput):
605
+ """Input for adding a reply comment to an existing pull request thread."""
606
+
607
+ repository_id: str = Field(
608
+ description="Repository ID (UUID) or repository name.",
609
+ )
610
+ pull_request_id: int = Field(
611
+ description="The pull request ID.",
612
+ ge=1,
613
+ )
614
+ thread_id: int = Field(
615
+ description="The thread ID to reply to.",
616
+ ge=1,
617
+ )
618
+ content: str = Field(
619
+ description="Text of the comment.",
620
+ min_length=1,
621
+ )
622
+ parent_comment_id: int = Field(
623
+ default=0,
624
+ description=(
625
+ "ID of the parent comment to reply to within the thread. "
626
+ "Use 0 (default) for a top-level reply on the thread."
627
+ ),
628
+ ge=0,
629
+ )
630
+
631
+
632
+ class UpdatePullRequestCommentInput(AzDoBaseInput):
633
+ """Input for updating the content of an existing comment in a pull request thread."""
634
+
635
+ repository_id: str = Field(
636
+ description="Repository ID (UUID) or repository name.",
637
+ )
638
+ pull_request_id: int = Field(
639
+ description="The pull request ID.",
640
+ ge=1,
641
+ )
642
+ thread_id: int = Field(
643
+ description="The thread ID that owns the comment.",
644
+ ge=1,
645
+ )
646
+ comment_id: int = Field(
647
+ description="The comment ID to update.",
648
+ ge=1,
649
+ )
650
+ content: str = Field(
651
+ description="The updated text for the comment.",
652
+ min_length=1,
653
+ )
654
+
655
+
656
+ class ListPullRequestIterationsInput(AzDoBaseInput):
657
+ """Input for listing push iterations on a pull request."""
658
+
659
+ repository_id: str = Field(
660
+ description="Repository ID (UUID) or repository name.",
661
+ )
662
+ pull_request_id: int = Field(
663
+ description="The pull request ID.",
664
+ ge=1,
665
+ )
666
+ include_commits: bool = Field(
667
+ default=False,
668
+ description=(
669
+ "When True, includes the commits associated with each iteration "
670
+ "in the response."
671
+ ),
672
+ )
673
+
674
+
675
+ class GetPullRequestChangesInput(AzDoBaseInput):
676
+ """Input for retrieving the file-change entries for a pull request iteration."""
677
+
678
+ repository_id: str = Field(
679
+ description="Repository ID (UUID) or repository name.",
680
+ )
681
+ pull_request_id: int = Field(
682
+ description="The pull request ID.",
683
+ ge=1,
684
+ )
685
+ iteration_id: int = Field(
686
+ description=(
687
+ "The iteration ID to retrieve changes for. "
688
+ "Obtain from devops_list_pull_request_iterations."
689
+ ),
690
+ ge=1,
691
+ )
692
+ compare_to: int | None = Field(
693
+ default=None,
694
+ description=(
695
+ "Iteration ID to diff against. When supplied, the response contains "
696
+ "only the incremental changes between compare_to and iteration_id. "
697
+ "When omitted, changes are relative to the PR target branch."
698
+ ),
699
+ ge=1,
700
+ )
701
+ top: int | None = Field(
702
+ default=None,
703
+ description="Maximum number of change entries to return (for pagination).",
704
+ ge=1,
705
+ )
706
+ skip: int | None = Field(
707
+ default=None,
708
+ description="Number of change entries to skip (for pagination).",
709
+ ge=0,
710
+ )
711
+
712
+
713
+ # ---------------------------------------------------------------------------
714
+ # Work Items
715
+ # ---------------------------------------------------------------------------
716
+
717
+
718
+ class GetWorkItemInput(AzDoBaseInput):
719
+ """Input for getting a single work item by ID."""
720
+
721
+ work_item_id: int = Field(description="The work item ID.", ge=1)
722
+ fields: list[str] | None = Field(
723
+ default=None,
724
+ description=(
725
+ "Specific field reference names to return "
726
+ "(e.g., ['System.Id', 'System.Title', 'System.State']). "
727
+ "Omit to return all default fields."
728
+ ),
729
+ )
730
+ expand: str | None = Field(
731
+ default=None,
732
+ description="Expand options: 'none', 'relations', 'fields', 'links', 'all'.",
733
+ )
734
+
735
+
736
+ class ListWorkItemsInput(AzDoBaseInput):
737
+ """Input for bulk-fetching work items by their IDs."""
738
+
739
+ ids: list[int] = Field(
740
+ description="Work item IDs to fetch (max 200 per call).",
741
+ min_length=1,
742
+ max_length=200,
743
+ )
744
+ fields: list[str] | None = Field(
745
+ default=None,
746
+ description="Specific field reference names to return. Omit for all default fields.",
747
+ )
748
+ expand: str | None = Field(
749
+ default=None,
750
+ description="Expand options: 'none', 'relations', 'fields', 'links', 'all'.",
751
+ )
752
+ error_policy: str | None = Field(
753
+ default="omit",
754
+ description="How to handle invalid IDs: 'fail' raises an error, 'omit' skips them silently.",
755
+ )
756
+
757
+
758
+ class QueryWorkItemsInput(AzDoBaseInput):
759
+ """Input for querying work items using WIQL."""
760
+
761
+ wiql: str = Field(
762
+ description=(
763
+ "WIQL query string. Example: "
764
+ "\"SELECT [System.Id], [System.Title] FROM WorkItems "
765
+ "WHERE [System.TeamProject] = @project AND [System.State] <> 'Closed'\""
766
+ ),
767
+ )
768
+ top: int | None = Field(
769
+ default=50,
770
+ description="Maximum number of results to return (max 200).",
771
+ ge=1,
772
+ le=200,
773
+ )
774
+ fetch_details: bool = Field(
775
+ default=True,
776
+ description=(
777
+ "When True (default), automatically fetches full work item field values "
778
+ "for all returned IDs. When False, returns only IDs and URLs."
779
+ ),
780
+ )
781
+ fields: list[str] | None = Field(
782
+ default=None,
783
+ description="Specific field reference names to return when fetch_details=True.",
784
+ )
785
+
786
+
787
+ class CreateWorkItemInput(AzDoBaseInput):
788
+ """Input for creating a new work item.
789
+
790
+ Common field values are exposed as named parameters. Use additional_fields
791
+ for any other field reference name not listed here (e.g.,
792
+ 'Microsoft.VSTS.Common.Priority', 'System.Tags').
793
+ """
794
+
795
+ work_item_type: str = Field(
796
+ description=(
797
+ "The work item type to create (e.g., 'Bug', 'Task', 'User Story', "
798
+ "'Feature', 'Epic')."
799
+ ),
800
+ )
801
+ title: str = Field(
802
+ description="Title of the work item (System.Title).",
803
+ )
804
+ description: str | None = Field(
805
+ default=None,
806
+ description="Description of the work item (System.Description).",
807
+ )
808
+ assigned_to: str | None = Field(
809
+ default=None,
810
+ description=(
811
+ "Assign to a user by display name or email "
812
+ "(e.g., 'Jane Doe' or 'jane@example.com')."
813
+ ),
814
+ )
815
+ state: str | None = Field(
816
+ default=None,
817
+ description=(
818
+ "Initial state (e.g., 'New', 'Active', 'To Do'). "
819
+ "Defaults to the work item type's initial state if omitted."
820
+ ),
821
+ )
822
+ area_path: str | None = Field(
823
+ default=None,
824
+ description="Area path (System.AreaPath), e.g., 'MyProject\\\\Team'.",
825
+ )
826
+ iteration_path: str | None = Field(
827
+ default=None,
828
+ description=(
829
+ "Iteration path (System.IterationPath), "
830
+ "e.g., 'MyProject\\\\Sprint 1'."
831
+ ),
832
+ )
833
+ priority: int | None = Field(
834
+ default=None,
835
+ description="Priority (Microsoft.VSTS.Common.Priority): 1 (high) to 4 (low).",
836
+ ge=1,
837
+ le=4,
838
+ )
839
+ tags: str | None = Field(
840
+ default=None,
841
+ description=(
842
+ "Semicolon-separated tags (System.Tags), "
843
+ "e.g., 'backend; needs-review'."
844
+ ),
845
+ )
846
+ parent_id: int | None = Field(
847
+ default=None,
848
+ description=(
849
+ "Work item ID of the parent to set a child-parent hierarchy link."
850
+ ),
851
+ ge=1,
852
+ )
853
+ additional_fields: dict | None = Field(
854
+ default=None,
855
+ description=(
856
+ "Additional field values as a dict mapping field reference name to value "
857
+ "(e.g., {'Microsoft.VSTS.Scheduling.StoryPoints': 5, "
858
+ "'Custom.MyField': 'value'})."
859
+ ),
860
+ )
861
+
862
+
863
+ class UpdateWorkItemInput(AzDoBaseInput):
864
+ """Input for updating an existing work item.
865
+
866
+ Only supply the fields you want to change. Common fields are exposed as
867
+ named parameters. Use additional_fields for any other field reference name.
868
+ """
869
+
870
+ work_item_id: int = Field(
871
+ description="The ID of the work item to update.",
872
+ ge=1,
873
+ )
874
+ title: str | None = Field(
875
+ default=None,
876
+ description="New title (System.Title).",
877
+ )
878
+ description: str | None = Field(
879
+ default=None,
880
+ description="New description (System.Description).",
881
+ )
882
+ assigned_to: str | None = Field(
883
+ default=None,
884
+ description=(
885
+ "Assign to a user by display name or email. "
886
+ "Pass an empty string to unassign."
887
+ ),
888
+ )
889
+ state: str | None = Field(
890
+ default=None,
891
+ description="New state (e.g., 'Active', 'Resolved', 'Closed', 'Done').",
892
+ )
893
+ area_path: str | None = Field(
894
+ default=None,
895
+ description="New area path (System.AreaPath).",
896
+ )
897
+ iteration_path: str | None = Field(
898
+ default=None,
899
+ description="New iteration path (System.IterationPath).",
900
+ )
901
+ priority: int | None = Field(
902
+ default=None,
903
+ description="New priority (Microsoft.VSTS.Common.Priority): 1 (high) to 4 (low).",
904
+ ge=1,
905
+ le=4,
906
+ )
907
+ tags: str | None = Field(
908
+ default=None,
909
+ description=(
910
+ "Replace all tags with this semicolon-separated string (System.Tags). "
911
+ "Pass an empty string to clear all tags."
912
+ ),
913
+ )
914
+ comment: str | None = Field(
915
+ default=None,
916
+ description="Add a discussion comment (System.History).",
917
+ )
918
+ additional_fields: dict | None = Field(
919
+ default=None,
920
+ description=(
921
+ "Additional field updates as a dict mapping field reference name to value "
922
+ "(e.g., {'Microsoft.VSTS.Scheduling.StoryPoints': 8})."
923
+ ),
924
+ )
925
+
926
+
927
+ class AddWorkItemCommentInput(AzDoBaseInput):
928
+ """Input for adding a comment to a work item."""
929
+
930
+ work_item_id: int = Field(
931
+ description="The ID of the work item to add a comment to.",
932
+ ge=1,
933
+ )
934
+ text: str = Field(
935
+ description="Text of the comment. Supports markdown.",
936
+ min_length=1,
937
+ )
938
+
939
+
940
+ class UpdateWorkItemCommentInput(AzDoBaseInput):
941
+ """Input for updating an existing comment on a work item."""
942
+
943
+ work_item_id: int = Field(
944
+ description="The ID of the work item that owns the comment.",
945
+ ge=1,
946
+ )
947
+ comment_id: int = Field(
948
+ description="The ID of the comment to update.",
949
+ ge=1,
950
+ )
951
+ text: str = Field(
952
+ description="The updated text of the comment. Supports markdown.",
953
+ min_length=1,
954
+ )