Skip to content

Commit e4fc1fc

Browse files
committed
Merge PR sooperset#726: [Feature] Update MCP with required field
2 parents 5090e91 + cd615d6 commit e4fc1fc

File tree

6 files changed

+1040
-870
lines changed

6 files changed

+1040
-870
lines changed

src/mcp_atlassian/models/jira/issue.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,10 @@ def from_api_response(cls, data: dict[str, Any], **kwargs: Any) -> "JiraIssue":
425425
custom_fields = {}
426426
fields_name_map = data.get("names", {})
427427
for orig_field_id, orig_field_value in fields.items():
428-
if orig_field_id.startswith("customfield_"):
428+
if (
429+
orig_field_id.startswith("customfield_")
430+
and orig_field_value is not None
431+
):
429432
value_obj_to_store = {"value": orig_field_value}
430433
human_readable_name = fields_name_map.get(orig_field_id)
431434
if human_readable_name:

src/mcp_atlassian/servers/jira.py

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from mcp_atlassian.jira.constants import DEFAULT_READ_JIRA_FIELDS
1313
from mcp_atlassian.models.jira.common import JiraUser
1414
from mcp_atlassian.servers.dependencies import get_jira_fetcher
15-
from mcp_atlassian.utils.decorators import check_write_access
15+
from mcp_atlassian.utils.decorators import check_write_access, handle_tool_errors
1616

1717
logger = logging.getLogger(__name__)
1818

@@ -23,6 +23,7 @@
2323

2424

2525
@jira_mcp.tool(tags={"jira", "read"})
26+
@handle_tool_errors(default_return_key="user", service_name="Jira")
2627
async def get_user_profile(
2728
ctx: Context,
2829
user_identifier: Annotated[
@@ -81,6 +82,7 @@ async def get_user_profile(
8182

8283

8384
@jira_mcp.tool(tags={"jira", "read"})
85+
@handle_tool_errors(default_return_key="issue", service_name="Jira")
8486
async def get_issue(
8587
ctx: Context,
8688
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -164,6 +166,7 @@ async def get_issue(
164166

165167

166168
@jira_mcp.tool(tags={"jira", "read"})
169+
@handle_tool_errors(default_return_key="issues", service_name="Jira")
167170
async def search(
168171
ctx: Context,
169172
jql: Annotated[
@@ -251,6 +254,7 @@ async def search(
251254

252255

253256
@jira_mcp.tool(tags={"jira", "read"})
257+
@handle_tool_errors(default_return_key="fields", service_name="Jira")
254258
async def search_fields(
255259
ctx: Context,
256260
keyword: Annotated[
@@ -285,6 +289,7 @@ async def search_fields(
285289

286290

287291
@jira_mcp.tool(tags={"jira", "read"})
292+
@handle_tool_errors(default_return_key="issues", service_name="Jira")
288293
async def get_project_issues(
289294
ctx: Context,
290295
project_key: Annotated[str, Field(description="The project key")],
@@ -317,6 +322,7 @@ async def get_project_issues(
317322

318323

319324
@jira_mcp.tool(tags={"jira", "read"})
325+
@handle_tool_errors(default_return_key="transitions", service_name="Jira")
320326
async def get_transitions(
321327
ctx: Context,
322328
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -337,6 +343,7 @@ async def get_transitions(
337343

338344

339345
@jira_mcp.tool(tags={"jira", "read"})
346+
@handle_tool_errors(default_return_key="worklogs", service_name="Jira")
340347
async def get_worklog(
341348
ctx: Context,
342349
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -357,6 +364,7 @@ async def get_worklog(
357364

358365

359366
@jira_mcp.tool(tags={"jira", "read"})
367+
@handle_tool_errors(default_return_key="result", service_name="Jira")
360368
async def download_attachments(
361369
ctx: Context,
362370
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -380,6 +388,7 @@ async def download_attachments(
380388

381389

382390
@jira_mcp.tool(tags={"jira", "read"})
391+
@handle_tool_errors(default_return_key="boards", service_name="Jira")
383392
async def get_agile_boards(
384393
ctx: Context,
385394
board_name: Annotated[
@@ -430,6 +439,7 @@ async def get_agile_boards(
430439

431440

432441
@jira_mcp.tool(tags={"jira", "read"})
442+
@handle_tool_errors(default_return_key="issues", service_name="Jira")
433443
async def get_board_issues(
434444
ctx: Context,
435445
board_id: Annotated[str, Field(description="The id of the board (e.g., '1001')")],
@@ -507,6 +517,7 @@ async def get_board_issues(
507517

508518

509519
@jira_mcp.tool(tags={"jira", "read"})
520+
@handle_tool_errors(default_return_key="sprints", service_name="Jira")
510521
async def get_sprints_from_board(
511522
ctx: Context,
512523
board_id: Annotated[str, Field(description="The id of board (e.g., '1000')")],
@@ -544,6 +555,7 @@ async def get_sprints_from_board(
544555

545556

546557
@jira_mcp.tool(tags={"jira", "read"})
558+
@handle_tool_errors(default_return_key="sprint_issues", service_name="Jira")
547559
async def get_sprint_issues(
548560
ctx: Context,
549561
sprint_id: Annotated[str, Field(description="The id of sprint (e.g., '10001')")],
@@ -592,6 +604,7 @@ async def get_sprint_issues(
592604

593605

594606
@jira_mcp.tool(tags={"jira", "read"})
607+
@handle_tool_errors(default_return_key="link_types", service_name="Jira")
595608
async def get_link_types(ctx: Context) -> str:
596609
"""Get all available issue link types.
597610
@@ -609,6 +622,7 @@ async def get_link_types(ctx: Context) -> str:
609622

610623
@jira_mcp.tool(tags={"jira", "write"})
611624
@check_write_access
625+
@handle_tool_errors(default_return_key="issue", service_name="Jira")
612626
async def create_issue(
613627
ctx: Context,
614628
project_key: Annotated[
@@ -714,6 +728,7 @@ async def create_issue(
714728

715729
@jira_mcp.tool(tags={"jira", "write"})
716730
@check_write_access
731+
@handle_tool_errors(default_return_key="issues", service_name="Jira")
717732
async def batch_create_issues(
718733
ctx: Context,
719734
issues: Annotated[
@@ -782,6 +797,7 @@ async def batch_create_issues(
782797

783798

784799
@jira_mcp.tool(tags={"jira", "read"})
800+
@handle_tool_errors(default_return_key="changelogs", service_name="Jira")
785801
async def batch_get_changelogs(
786802
ctx: Context,
787803
issue_ids_or_keys: Annotated[
@@ -855,6 +871,7 @@ async def batch_get_changelogs(
855871

856872
@jira_mcp.tool(tags={"jira", "write"})
857873
@check_write_access
874+
@handle_tool_errors(default_return_key="issue", service_name="Jira")
858875
async def update_issue(
859876
ctx: Context,
860877
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -864,16 +881,9 @@ async def update_issue(
864881
description=(
865882
"Dictionary of fields to update. For 'assignee', provide a string identifier (email, name, or accountId). "
866883
"Example: `{'assignee': '[email protected]', 'summary': 'New Summary'}`"
867-
)
884+
),
868885
),
869886
],
870-
additional_fields: Annotated[
871-
dict[str, Any] | None,
872-
Field(
873-
description="(Optional) Dictionary of additional fields to update. Use this for custom fields or more complex updates.",
874-
default=None,
875-
),
876-
] = None,
877887
attachments: Annotated[
878888
str | None,
879889
Field(
@@ -890,8 +900,7 @@ async def update_issue(
890900
Args:
891901
ctx: The FastMCP context.
892902
issue_key: Jira issue key.
893-
fields: Dictionary of fields to update.
894-
additional_fields: Optional dictionary of additional fields.
903+
fields: (Required) Dictionary of fields to update.
895904
attachments: Optional JSON array string or comma-separated list of file paths.
896905
897906
Returns:
@@ -906,11 +915,6 @@ async def update_issue(
906915
raise ValueError("fields must be a dictionary.")
907916
update_fields = fields
908917

909-
# Use additional_fields directly as dict
910-
extra_fields = additional_fields or {}
911-
if not isinstance(extra_fields, dict):
912-
raise ValueError("additional_fields must be a dictionary.")
913-
914918
# Parse attachments
915919
attachment_paths = []
916920
if attachments:
@@ -932,7 +936,7 @@ async def update_issue(
932936
)
933937

934938
# Combine fields and additional_fields
935-
all_updates = {**update_fields, **extra_fields}
939+
all_updates = {**update_fields}
936940
if attachment_paths:
937941
all_updates["attachments"] = attachment_paths
938942

@@ -956,6 +960,7 @@ async def update_issue(
956960

957961
@jira_mcp.tool(tags={"jira", "write"})
958962
@check_write_access
963+
@handle_tool_errors(default_return_key="result", service_name="Jira")
959964
async def delete_issue(
960965
ctx: Context,
961966
issue_key: Annotated[str, Field(description="Jira issue key (e.g. PROJ-123)")],
@@ -981,6 +986,7 @@ async def delete_issue(
981986

982987
@jira_mcp.tool(tags={"jira", "write"})
983988
@check_write_access
989+
@handle_tool_errors(default_return_key="comment", service_name="Jira")
984990
async def add_comment(
985991
ctx: Context,
986992
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -1007,6 +1013,7 @@ async def add_comment(
10071013

10081014
@jira_mcp.tool(tags={"jira", "write"})
10091015
@check_write_access
1016+
@handle_tool_errors(default_return_key="worklog", service_name="Jira")
10101017
async def add_worklog(
10111018
ctx: Context,
10121019
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -1074,6 +1081,7 @@ async def add_worklog(
10741081

10751082
@jira_mcp.tool(tags={"jira", "write"})
10761083
@check_write_access
1084+
@handle_tool_errors(default_return_key="issue", service_name="Jira")
10771085
async def link_to_epic(
10781086
ctx: Context,
10791087
issue_key: Annotated[
@@ -1107,6 +1115,7 @@ async def link_to_epic(
11071115

11081116
@jira_mcp.tool(tags={"jira", "write"})
11091117
@check_write_access
1118+
@handle_tool_errors(default_return_key="result", service_name="Jira")
11101119
async def create_issue_link(
11111120
ctx: Context,
11121121
link_type: Annotated[
@@ -1175,6 +1184,7 @@ async def create_issue_link(
11751184

11761185
@jira_mcp.tool(tags={"jira", "write"})
11771186
@check_write_access
1187+
@handle_tool_errors(default_return_key="result", service_name="Jira")
11781188
async def create_remote_issue_link(
11791189
ctx: Context,
11801190
issue_key: Annotated[
@@ -1257,6 +1267,7 @@ async def create_remote_issue_link(
12571267

12581268
@jira_mcp.tool(tags={"jira", "write"})
12591269
@check_write_access
1270+
@handle_tool_errors(default_return_key="result", service_name="Jira")
12601271
async def remove_issue_link(
12611272
ctx: Context,
12621273
link_id: Annotated[str, Field(description="The ID of the link to remove")],
@@ -1283,6 +1294,7 @@ async def remove_issue_link(
12831294

12841295
@jira_mcp.tool(tags={"jira", "write"})
12851296
@check_write_access
1297+
@handle_tool_errors(default_return_key="issue", service_name="Jira")
12861298
async def transition_issue(
12871299
ctx: Context,
12881300
issue_key: Annotated[str, Field(description="Jira issue key (e.g., 'PROJ-123')")],
@@ -1356,6 +1368,7 @@ async def transition_issue(
13561368

13571369
@jira_mcp.tool(tags={"jira", "write"})
13581370
@check_write_access
1371+
@handle_tool_errors(default_return_key="sprint", service_name="Jira")
13591372
async def create_sprint(
13601373
ctx: Context,
13611374
board_id: Annotated[str, Field(description="The id of board (e.g., '1000')")],
@@ -1401,6 +1414,7 @@ async def create_sprint(
14011414

14021415
@jira_mcp.tool(tags={"jira", "write"})
14031416
@check_write_access
1417+
@handle_tool_errors(default_return_key="sprint", service_name="Jira")
14041418
async def update_sprint(
14051419
ctx: Context,
14061420
sprint_id: Annotated[str, Field(description="The id of sprint (e.g., '10001')")],
@@ -1458,6 +1472,7 @@ async def update_sprint(
14581472

14591473

14601474
@jira_mcp.tool(tags={"jira", "read"})
1475+
@handle_tool_errors(default_return_key="versions", service_name="Jira")
14611476
async def get_project_versions(
14621477
ctx: Context,
14631478
project_key: Annotated[str, Field(description="Jira project key (e.g., 'PROJ')")],
@@ -1469,6 +1484,7 @@ async def get_project_versions(
14691484

14701485

14711486
@jira_mcp.tool(tags={"jira", "read"})
1487+
@handle_tool_errors(default_return_key="projects", service_name="Jira")
14721488
async def get_all_projects(
14731489
ctx: Context,
14741490
include_archived: Annotated[
@@ -1533,8 +1549,60 @@ async def get_all_projects(
15331549
return json.dumps(projects, indent=2, ensure_ascii=False)
15341550

15351551

1552+
@jira_mcp.tool(tags={"jira", "read"})
1553+
@handle_tool_errors(default_return_key="issue_types", service_name="Jira")
1554+
async def get_all_issue_types(
1555+
ctx: Context,
1556+
project_key: Annotated[str, Field(description="Jira project key (e.g., 'PROJ')")],
1557+
) -> str:
1558+
"""Get all issue types for a specific Jira project.
1559+
Args:
1560+
ctx: The FastMCP context.
1561+
project_key: The project key.
1562+
1563+
Returns:
1564+
JSON string representing a list of issue types.
1565+
1566+
Raises:
1567+
ValueError: If the Jira client is not configured or available.
1568+
"""
1569+
jira = await get_jira_fetcher(ctx)
1570+
issue_types = jira.get_project_issue_types(project_key)
1571+
return json.dumps(issue_types, indent=2, ensure_ascii=False)
1572+
1573+
1574+
@jira_mcp.tool(tags={"jira", "read"})
1575+
@handle_tool_errors(default_return_key="required_fields", service_name="Jira")
1576+
async def get_required_fields(
1577+
ctx: Context,
1578+
issue_type: Annotated[
1579+
str,
1580+
Field(
1581+
description="Jira issue type (e.g., 'Task', 'Bug', 'Story', 'Epic', 'Subtask')"
1582+
),
1583+
],
1584+
project_key: Annotated[str, Field(description="Jira project key (e.g., 'PROJ')")],
1585+
) -> str:
1586+
"""Get required fields for a specific Jira issue type in a project.
1587+
Args:
1588+
ctx: The FastMCP context.
1589+
issue_type: The issue type.
1590+
project_key: The project key.
1591+
1592+
Returns:
1593+
JSON string representing a list of required fields.
1594+
1595+
Raises:
1596+
ValueError: If the Jira client is not configured or available.
1597+
"""
1598+
jira = await get_jira_fetcher(ctx)
1599+
required_fields = jira.get_required_fields(issue_type, project_key)
1600+
return json.dumps(required_fields, indent=2, ensure_ascii=False)
1601+
1602+
15361603
@jira_mcp.tool(tags={"jira", "write"})
15371604
@check_write_access
1605+
@handle_tool_errors(default_return_key="version", service_name="Jira")
15381606
async def create_version(
15391607
ctx: Context,
15401608
project_key: Annotated[str, Field(description="Jira project key (e.g., 'PROJ')")],
@@ -1583,6 +1651,7 @@ async def create_version(
15831651

15841652
@jira_mcp.tool(name="batch_create_versions", tags={"jira", "write"})
15851653
@check_write_access
1654+
@handle_tool_errors(default_return_key="results", service_name="Jira")
15861655
async def batch_create_versions(
15871656
ctx: Context,
15881657
project_key: Annotated[str, Field(description="Jira project key (e.g., 'PROJ')")],

0 commit comments

Comments
 (0)