rootly-mcp-server 2.2.17__tar.gz → 2.2.19__tar.gz

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.
Files changed (81) hide show
  1. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/CHANGELOG.md +34 -0
  2. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/PKG-INFO +7 -3
  3. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/README.md +6 -2
  4. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/pyproject.toml +1 -1
  5. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/server_defaults.py +2 -0
  6. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/tools/incidents.py +102 -0
  7. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_server.py +13 -0
  8. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_server_defaults_module.py +2 -0
  9. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_tools.py +65 -0
  10. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/uv.lock +1 -1
  11. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.dockerignore +0 -0
  12. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.gitattributes +0 -0
  13. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.github/dependabot.yml +0 -0
  14. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.github/openapi-audit-remote-baseline.json +0 -0
  15. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.github/workflows/ci.yml +0 -0
  16. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.github/workflows/dependency-review.yml +0 -0
  17. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.github/workflows/openapi-audit.yml +0 -0
  18. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.github/workflows/pypi-release.yml +0 -0
  19. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.gitignore +0 -0
  20. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.semaphore/deploy-staging.yml +0 -0
  21. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.semaphore/deploy.yml +0 -0
  22. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.semaphore/semaphore.yml +0 -0
  23. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/.semaphore/update-task-definition.sh +0 -0
  24. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/CONTRIBUTING.md +0 -0
  25. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/Dockerfile +0 -0
  26. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/LICENSE +0 -0
  27. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/examples/skills/README.md +0 -0
  28. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/gemini-extension.json +0 -0
  29. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/rootly-mcp-server-demo.gif +0 -0
  30. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/rootly_openapi.json +0 -0
  31. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/scripts/audit_openapi.py +0 -0
  32. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/scripts/setup-hooks.sh +0 -0
  33. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/server.json +0 -0
  34. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/__init__.py +0 -0
  35. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/__main__.py +0 -0
  36. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/client.py +0 -0
  37. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/code_mode.py +0 -0
  38. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/data/__init__.py +0 -0
  39. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/exceptions.py +0 -0
  40. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/legacy_server.py +0 -0
  41. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/mcp_error.py +0 -0
  42. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/monitoring.py +0 -0
  43. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/och_client.py +0 -0
  44. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/pagination.py +0 -0
  45. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/payload_stripping.py +0 -0
  46. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/security.py +0 -0
  47. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/server.py +0 -0
  48. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/smart_utils.py +0 -0
  49. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/spec_transform.py +0 -0
  50. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/tools/__init__.py +0 -0
  51. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/tools/alerts.py +0 -0
  52. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/tools/oncall.py +0 -0
  53. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/tools/resources.py +0 -0
  54. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/transport.py +0 -0
  55. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/utils.py +0 -0
  56. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/src/rootly_mcp_server/validators.py +0 -0
  57. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/README.md +0 -0
  58. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/conftest.py +0 -0
  59. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/integration/local/test_basic.py +0 -0
  60. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/integration/local/test_smart_tools.py +0 -0
  61. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/integration/remote/test_essential.py +0 -0
  62. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/test_client.py +0 -0
  63. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_alert_stripping.py +0 -0
  64. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_authentication.py +0 -0
  65. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_code_mode_module.py +0 -0
  66. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_exceptions.py +0 -0
  67. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_http_headers.py +0 -0
  68. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_legacy_server_module.py +0 -0
  69. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_main_transport.py +0 -0
  70. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_mcp_error_module.py +0 -0
  71. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_och_client.py +0 -0
  72. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_oncall_handoff.py +0 -0
  73. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_oncall_metrics.py +0 -0
  74. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_oncall_new_tools.py +0 -0
  75. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_oncall_severity_normalization.py +0 -0
  76. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_payload_stripping_module.py +0 -0
  77. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_security.py +0 -0
  78. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_smart_utils.py +0 -0
  79. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_transport_module.py +0 -0
  80. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_utils.py +0 -0
  81. {rootly_mcp_server-2.2.17 → rootly_mcp_server-2.2.19}/tests/unit/test_validators.py +0 -0
@@ -5,6 +5,40 @@ All notable changes to the Rootly MCP Server will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.2.19] - Released 2026-04-17
9
+
10
+ ### Features
11
+
12
+ - **Scoped Incident Creation Tool**: Added a custom `createIncident` tool so agents can create incidents directly from MCP without exposing the full raw `/incidents` OpenAPI surface
13
+
14
+ ### Documentation
15
+
16
+ - **Custom Tool List Updated**: Added `createIncident` to the README custom tool section with its scoped workflow-oriented behavior
17
+
18
+ ## [2.2.18] - Released 2026-04-15
19
+
20
+ ### Features
21
+
22
+ - **Workflow Task Tools**: Added complete workflow task management tools to enable creation, listing, retrieval, and updating of workflow actions/tasks
23
+ - **Enhanced Workflow Functionality**: Users can now build complete functional workflows instead of just workflow shells
24
+
25
+ ### New Tools
26
+
27
+ - `createWorkflowTask` - Create new workflow actions (POST `/v1/workflows/{workflow_id}/workflow_tasks`)
28
+ - `listWorkflowTasks` - List all actions in a workflow (GET `/v1/workflows/{workflow_id}/workflow_tasks`)
29
+ - `getWorkflowTask` - Retrieve specific workflow action details (GET `/v1/workflow_tasks/{id}`)
30
+ - `updateWorkflowTask` - Modify existing workflow actions (PUT `/v1/workflow_tasks/{id}`)
31
+
32
+ ### Documentation
33
+
34
+ - **Tool Count Updated**: Increased from 105 to 109 tools reflecting new workflow task capabilities
35
+ - **Tool List Updated**: Added workflow task tools to OpenAPI-generated tools section
36
+ - **Badge Cleanup**: Removed broken Cursor install badge
37
+
38
+ ### Security
39
+
40
+ - **Delete Operations**: `deleteWorkflowTask` remains intentionally excluded following security policy for destructive operations
41
+
8
42
  ## [2.2.17] - Released 2026-04-14
9
43
 
10
44
  ### Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rootly-mcp-server
3
- Version: 2.2.17
3
+ Version: 2.2.19
4
4
  Summary: Secure Model Context Protocol server for Rootly APIs with AI SRE capabilities, comprehensive error handling, and input validation
5
5
  Project-URL: Homepage, https://github.com/Rootly-AI-Labs/Rootly-MCP-server
6
6
  Project-URL: Issues, https://github.com/Rootly-AI-Labs/Rootly-MCP-server/issues
@@ -46,7 +46,6 @@ Description-Content-Type: text/markdown
46
46
  [![PyPI version](https://badge.fury.io/py/rootly-mcp-server.svg)](https://pypi.org/project/rootly-mcp-server/)
47
47
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/rootly-mcp-server)](https://pypi.org/project/rootly-mcp-server/)
48
48
  [![Python Version](https://img.shields.io/pypi/pyversions/rootly-mcp-server.svg)](https://pypi.org/project/rootly-mcp-server/)
49
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=rootly&config=eyJ1cmwiOiJodHRwczovL21jcC5yb290bHkuY29tL21jcCIsImhlYWRlcnMiOnsiQXV0aG9yaXphdGlvbiI6IkJlYXJlciA8WU9VUl9ST09UTFlfQVBJX1RPS0VOPiJ9fQo=)
50
49
 
51
50
  An MCP server for the [Rootly API](https://docs.rootly.com/api-reference/overview) for Cursor, Windsurf, Claude, and other MCP clients.
52
51
 
@@ -409,13 +408,14 @@ docker run -p 8000:8000 \
409
408
 
410
409
  ## Supported Tools
411
410
 
412
- The default server configuration exposes **105 tools**.
411
+ The default server configuration exposes **110 tools**.
413
412
 
414
413
  ### Custom Agentic Tools
415
414
 
416
415
  - `check_oncall_health_risk`
417
416
  - `check_responder_availability`
418
417
  - `collect_incidents`
418
+ - `createIncident` - create a new incident with a scoped set of fields for agent workflows
419
419
  - `create_override_recommendation`
420
420
  - `find_related_incidents`
421
421
  - `getIncident` - retrieve a single incident for direct verification, including PIR-related fields
@@ -457,6 +457,7 @@ createService
457
457
  createSeverity
458
458
  createTeam
459
459
  createWorkflow
460
+ createWorkflowTask
460
461
  deleteEscalationLevel
461
462
  deleteEscalationPath
462
463
  deleteEscalationPolicy
@@ -482,6 +483,7 @@ getSeverity
482
483
  getTeam
483
484
  getUser
484
485
  getWorkflow
486
+ getWorkflowTask
485
487
  listAlerts
486
488
  listEnvironments
487
489
  listEscalationLevels
@@ -506,6 +508,7 @@ listShifts
506
508
  listTeams
507
509
  listUsers
508
510
  listWorkflows
511
+ listWorkflowTasks
509
512
  updateAlert
510
513
  updateEnvironment
511
514
  updateEscalationLevel
@@ -524,6 +527,7 @@ updateSeverity
524
527
  updateTeam
525
528
  updateUser
526
529
  updateWorkflow
530
+ updateWorkflowTask
527
531
  ```
528
532
 
529
533
  Delete operations are intentionally scoped to screenshot coverage paths:
@@ -4,7 +4,6 @@
4
4
  [![PyPI version](https://badge.fury.io/py/rootly-mcp-server.svg)](https://pypi.org/project/rootly-mcp-server/)
5
5
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/rootly-mcp-server)](https://pypi.org/project/rootly-mcp-server/)
6
6
  [![Python Version](https://img.shields.io/pypi/pyversions/rootly-mcp-server.svg)](https://pypi.org/project/rootly-mcp-server/)
7
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=rootly&config=eyJ1cmwiOiJodHRwczovL21jcC5yb290bHkuY29tL21jcCIsImhlYWRlcnMiOnsiQXV0aG9yaXphdGlvbiI6IkJlYXJlciA8WU9VUl9ST09UTFlfQVBJX1RPS0VOPiJ9fQo=)
8
7
 
9
8
  An MCP server for the [Rootly API](https://docs.rootly.com/api-reference/overview) for Cursor, Windsurf, Claude, and other MCP clients.
10
9
 
@@ -367,13 +366,14 @@ docker run -p 8000:8000 \
367
366
 
368
367
  ## Supported Tools
369
368
 
370
- The default server configuration exposes **105 tools**.
369
+ The default server configuration exposes **110 tools**.
371
370
 
372
371
  ### Custom Agentic Tools
373
372
 
374
373
  - `check_oncall_health_risk`
375
374
  - `check_responder_availability`
376
375
  - `collect_incidents`
376
+ - `createIncident` - create a new incident with a scoped set of fields for agent workflows
377
377
  - `create_override_recommendation`
378
378
  - `find_related_incidents`
379
379
  - `getIncident` - retrieve a single incident for direct verification, including PIR-related fields
@@ -415,6 +415,7 @@ createService
415
415
  createSeverity
416
416
  createTeam
417
417
  createWorkflow
418
+ createWorkflowTask
418
419
  deleteEscalationLevel
419
420
  deleteEscalationPath
420
421
  deleteEscalationPolicy
@@ -440,6 +441,7 @@ getSeverity
440
441
  getTeam
441
442
  getUser
442
443
  getWorkflow
444
+ getWorkflowTask
443
445
  listAlerts
444
446
  listEnvironments
445
447
  listEscalationLevels
@@ -464,6 +466,7 @@ listShifts
464
466
  listTeams
465
467
  listUsers
466
468
  listWorkflows
469
+ listWorkflowTasks
467
470
  updateAlert
468
471
  updateEnvironment
469
472
  updateEscalationLevel
@@ -482,6 +485,7 @@ updateSeverity
482
485
  updateTeam
483
486
  updateUser
484
487
  updateWorkflow
488
+ updateWorkflowTask
485
489
  ```
486
490
 
487
491
  Delete operations are intentionally scoped to screenshot coverage paths:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rootly-mcp-server"
3
- version = "2.2.17"
3
+ version = "2.2.19"
4
4
  description = "Secure Model Context Protocol server for Rootly APIs with AI SRE capabilities, comprehensive error handling, and input validation"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -67,6 +67,8 @@ DEFAULT_ALLOWED_PATHS = [
67
67
  # Workflows
68
68
  "/workflows",
69
69
  "/workflows/{workflow_id}",
70
+ "/workflows/{workflow_id}/workflow_tasks",
71
+ "/workflow_tasks/{id}",
70
72
  # Workflow runs
71
73
  "/workflow_runs",
72
74
  "/workflow_runs/{workflow_run_id}",
@@ -29,6 +29,14 @@ def _split_csv_values(value: str) -> list[str]:
29
29
  return [part.strip() for part in value.split(",") if part.strip()]
30
30
 
31
31
 
32
+ def _normalize_optional_text(value: str | None) -> str | None:
33
+ """Normalize optional text inputs by trimming whitespace and empty values."""
34
+ if value is None:
35
+ return None
36
+ normalized = value.strip()
37
+ return normalized or None
38
+
39
+
32
40
  def _extract_incident_severity(severity_value: Any) -> str | None:
33
41
  """Normalize severity values from Rootly API responses into a compact string."""
34
42
  if severity_value is None:
@@ -641,6 +649,100 @@ def register_incident_tools(
641
649
  ),
642
650
  )
643
651
 
652
+ @mcp.tool(name="createIncident")
653
+ async def create_incident(
654
+ title: Annotated[
655
+ str | None,
656
+ Field(description="Incident title. If omitted, Rootly may autogenerate one."),
657
+ ] = None,
658
+ summary: Annotated[
659
+ str | None,
660
+ Field(description="Incident summary or short description."),
661
+ ] = None,
662
+ severity_id: Annotated[
663
+ str | None,
664
+ Field(description="Optional severity ID to attach to the incident."),
665
+ ] = None,
666
+ service_ids: Annotated[
667
+ str | None,
668
+ Field(description="Comma-separated service IDs to attach to the incident."),
669
+ ] = None,
670
+ team_ids: Annotated[
671
+ str | None,
672
+ Field(description="Comma-separated team IDs to attach to the incident."),
673
+ ] = None,
674
+ environment_ids: Annotated[
675
+ str | None,
676
+ Field(description="Comma-separated environment IDs to attach to the incident."),
677
+ ] = None,
678
+ incident_type_ids: Annotated[
679
+ str | None,
680
+ Field(description="Comma-separated incident type IDs to attach to the incident."),
681
+ ] = None,
682
+ ) -> JsonDict:
683
+ """Create an incident with a scoped set of fields for agent-driven workflows."""
684
+ normalized_title = _normalize_optional_text(title)
685
+ normalized_summary = _normalize_optional_text(summary)
686
+
687
+ if normalized_title is None and normalized_summary is None:
688
+ return cast(
689
+ JsonDict,
690
+ mcp_error.tool_error(
691
+ "Must provide at least one of title or summary",
692
+ "validation_error",
693
+ ),
694
+ )
695
+
696
+ attributes: dict[str, Any] = {}
697
+
698
+ if normalized_title is not None:
699
+ attributes["title"] = normalized_title
700
+ if normalized_summary is not None:
701
+ attributes["summary"] = normalized_summary
702
+
703
+ normalized_severity_id = _normalize_optional_text(severity_id)
704
+ if normalized_severity_id is not None:
705
+ attributes["severity_id"] = normalized_severity_id
706
+
707
+ csv_attribute_map = (
708
+ ("service_ids", service_ids),
709
+ ("group_ids", team_ids),
710
+ ("environment_ids", environment_ids),
711
+ ("incident_type_ids", incident_type_ids),
712
+ )
713
+ for attribute_name, raw_value in csv_attribute_map:
714
+ if raw_value is None:
715
+ continue
716
+ values = _split_csv_values(raw_value)
717
+ if values:
718
+ attributes[attribute_name] = values
719
+
720
+ payload = {
721
+ "data": {
722
+ "type": "incidents",
723
+ "attributes": attributes,
724
+ }
725
+ }
726
+
727
+ try:
728
+ response = await make_authenticated_request("POST", "/v1/incidents", json=payload)
729
+ response.raise_for_status()
730
+
731
+ response_data = response.json()
732
+ if isinstance(response_data.get("data"), dict):
733
+ stripped = strip_heavy_nested_data({"data": [response_data["data"]]})
734
+ response_data["data"] = stripped["data"][0]
735
+ return cast(JsonDict, response_data)
736
+ except Exception as e:
737
+ error_type, error_message = mcp_error.categorize_error(e)
738
+ return cast(
739
+ JsonDict,
740
+ mcp_error.tool_error(
741
+ f"Failed to create incident: {error_message}",
742
+ error_type,
743
+ ),
744
+ )
745
+
644
746
  @mcp.tool(name="updateIncident")
645
747
  async def update_incident(
646
748
  incident_id: Annotated[str, Field(description="Incident ID to update")],
@@ -215,6 +215,19 @@ class TestBundledIncidentFormFieldSelectionTools:
215
215
 
216
216
  assert "deleteIncidentFormFieldSelection" not in tool_names
217
217
 
218
+ async def test_workflow_task_tools_are_available_without_delete(self, mock_environment_token):
219
+ server = create_rootly_mcp_server(hosted=False)
220
+
221
+ tools = await server.list_tools()
222
+ tool_names = {tool.name for tool in tools}
223
+
224
+ assert "createWorkflowTask" in tool_names
225
+ assert "listWorkflowTasks" in tool_names
226
+ assert "getWorkflowTask" in tool_names
227
+ assert "updateWorkflowTask" in tool_names
228
+
229
+ assert "deleteWorkflowTask" not in tool_names
230
+
218
231
 
219
232
  @pytest.mark.unit
220
233
  class TestAuthenticatedHTTPXClient:
@@ -11,6 +11,8 @@ class TestServerDefaultsModule:
11
11
  assert "/incidents/{incident_id}/alerts" in DEFAULT_ALLOWED_PATHS
12
12
  assert "/incidents/{incident_id}/form_field_selections" in DEFAULT_ALLOWED_PATHS
13
13
  assert "/incident_form_field_selections/{id}" in DEFAULT_ALLOWED_PATHS
14
+ assert "/workflows/{workflow_id}/workflow_tasks" in DEFAULT_ALLOWED_PATHS
15
+ assert "/workflow_tasks/{id}" in DEFAULT_ALLOWED_PATHS
14
16
  assert "/shifts" in DEFAULT_ALLOWED_PATHS
15
17
  assert "/on_call_roles" in DEFAULT_ALLOWED_PATHS
16
18
 
@@ -143,9 +143,74 @@ class TestScopedIncidentUpdateTool:
143
143
  async def test_update_incident_tool_is_registered_with_customer_facing_name(self):
144
144
  tools, _ = self._register_tools()
145
145
 
146
+ assert "createIncident" in tools
146
147
  assert "updateIncident" in tools
147
148
  assert "getIncident" in tools
148
149
 
150
+ @pytest.mark.asyncio
151
+ async def test_create_incident_sends_only_allowed_fields(self):
152
+ tools, request = self._register_tools()
153
+ response = Mock()
154
+ response.raise_for_status.return_value = None
155
+ response.json.return_value = {
156
+ "data": {
157
+ "id": "inc-123",
158
+ "type": "incidents",
159
+ "attributes": {
160
+ "title": "Database latency spike",
161
+ "summary": "Primary API requests timing out",
162
+ "severity_id": "sev-1",
163
+ "service_ids": ["svc-1", "svc-2"],
164
+ "group_ids": ["team-1", "team-2"],
165
+ "environment_ids": ["env-1"],
166
+ "incident_type_ids": ["type-1"],
167
+ },
168
+ }
169
+ }
170
+ request.return_value = response
171
+
172
+ result = await tools["createIncident"](
173
+ title=" Database latency spike ",
174
+ summary=" Primary API requests timing out ",
175
+ severity_id=" sev-1 ",
176
+ service_ids="svc-1, svc-2",
177
+ team_ids="team-1, team-2",
178
+ environment_ids="env-1",
179
+ incident_type_ids="type-1",
180
+ )
181
+
182
+ request.assert_awaited_once_with(
183
+ "POST",
184
+ "/v1/incidents",
185
+ json={
186
+ "data": {
187
+ "type": "incidents",
188
+ "attributes": {
189
+ "title": "Database latency spike",
190
+ "summary": "Primary API requests timing out",
191
+ "severity_id": "sev-1",
192
+ "service_ids": ["svc-1", "svc-2"],
193
+ "group_ids": ["team-1", "team-2"],
194
+ "environment_ids": ["env-1"],
195
+ "incident_type_ids": ["type-1"],
196
+ },
197
+ }
198
+ },
199
+ )
200
+ assert result["data"]["id"] == "inc-123"
201
+ assert result["data"]["attributes"]["title"] == "Database latency spike"
202
+
203
+ @pytest.mark.asyncio
204
+ async def test_create_incident_requires_title_or_summary(self):
205
+ tools, request = self._register_tools()
206
+
207
+ result = await tools["createIncident"](title=" ", summary=None)
208
+
209
+ request.assert_not_called()
210
+ assert result["error"] is True
211
+ assert result["error_type"] == "validation_error"
212
+ assert "Must provide at least one of title or summary" in result["message"]
213
+
149
214
  @pytest.mark.asyncio
150
215
  async def test_get_incident_fetches_single_incident(self):
151
216
  tools, request = self._register_tools()
@@ -1702,7 +1702,7 @@ wheels = [
1702
1702
 
1703
1703
  [[package]]
1704
1704
  name = "rootly-mcp-server"
1705
- version = "2.2.16"
1705
+ version = "2.2.19"
1706
1706
  source = { editable = "." }
1707
1707
  dependencies = [
1708
1708
  { name = "authlib" },