mcp-ticketer 0.12.0__py3-none-any.whl → 2.2.13__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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +507 -6
- mcp_ticketer/adapters/asana/adapter.py +229 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/adapter.py +2730 -139
- mcp_ticketer/adapters/linear/client.py +175 -3
- mcp_ticketer/adapters/linear/mappers.py +203 -8
- mcp_ticketer/adapters/linear/queries.py +280 -3
- mcp_ticketer/adapters/linear/types.py +120 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +1288 -105
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +267 -3175
- mcp_ticketer/cli/mcp_configure.py +821 -119
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +795 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +705 -103
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +56 -6
- mcp_ticketer/core/adapter.py +533 -2
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +480 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +625 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +33 -11
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/queue.py +68 -0
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1574
- mcp_ticketer/adapters/jira.py +0 -1258
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,6 +6,7 @@ import builtins
|
|
|
6
6
|
import logging
|
|
7
7
|
import mimetypes
|
|
8
8
|
import os
|
|
9
|
+
from datetime import datetime
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
@@ -59,6 +60,7 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
59
60
|
"""Initialize Asana adapter.
|
|
60
61
|
|
|
61
62
|
Args:
|
|
63
|
+
----
|
|
62
64
|
config: Configuration with:
|
|
63
65
|
- api_key: Asana Personal Access Token (or ASANA_PAT env var)
|
|
64
66
|
- workspace: Asana workspace name (optional, for resolution)
|
|
@@ -68,6 +70,7 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
68
70
|
- max_retries: Maximum retry attempts (default: 3)
|
|
69
71
|
|
|
70
72
|
Raises:
|
|
73
|
+
------
|
|
71
74
|
ValueError: If required configuration is missing
|
|
72
75
|
|
|
73
76
|
"""
|
|
@@ -120,6 +123,7 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
120
123
|
"""Validate Asana API credentials.
|
|
121
124
|
|
|
122
125
|
Returns:
|
|
126
|
+
-------
|
|
123
127
|
Tuple of (is_valid, error_message)
|
|
124
128
|
|
|
125
129
|
"""
|
|
@@ -244,9 +248,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
244
248
|
"""Load custom fields configured for a specific project.
|
|
245
249
|
|
|
246
250
|
Args:
|
|
251
|
+
----
|
|
247
252
|
project_gid: Project GID to load custom fields for
|
|
248
253
|
|
|
249
254
|
Returns:
|
|
255
|
+
-------
|
|
250
256
|
Dictionary mapping field name (lowercase) to field data
|
|
251
257
|
|
|
252
258
|
"""
|
|
@@ -277,9 +283,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
277
283
|
"""Get custom fields for a project, loading if not cached.
|
|
278
284
|
|
|
279
285
|
Args:
|
|
286
|
+
----
|
|
280
287
|
project_gid: Project GID
|
|
281
288
|
|
|
282
289
|
Returns:
|
|
290
|
+
-------
|
|
283
291
|
Dictionary mapping field name (lowercase) to field data
|
|
284
292
|
|
|
285
293
|
"""
|
|
@@ -295,10 +303,12 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
295
303
|
"""Map TicketState to Asana Status custom field option.
|
|
296
304
|
|
|
297
305
|
Args:
|
|
306
|
+
----
|
|
298
307
|
state: The TicketState to map
|
|
299
308
|
status_field: The Status custom field data with enum_options
|
|
300
309
|
|
|
301
310
|
Returns:
|
|
311
|
+
-------
|
|
302
312
|
Matching enum option or None
|
|
303
313
|
|
|
304
314
|
"""
|
|
@@ -339,6 +349,7 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
339
349
|
We return a mapping to "true"/"false" strings for compatibility.
|
|
340
350
|
|
|
341
351
|
Returns:
|
|
352
|
+
-------
|
|
342
353
|
Dictionary mapping TicketState to completion status string
|
|
343
354
|
|
|
344
355
|
"""
|
|
@@ -357,9 +368,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
357
368
|
"""Resolve project identifier (name or GID) to GID.
|
|
358
369
|
|
|
359
370
|
Args:
|
|
371
|
+
----
|
|
360
372
|
project_identifier: Project name or GID
|
|
361
373
|
|
|
362
374
|
Returns:
|
|
375
|
+
-------
|
|
363
376
|
Project GID or None if not found
|
|
364
377
|
|
|
365
378
|
"""
|
|
@@ -392,9 +405,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
392
405
|
"""Resolve user identifier (email, name, or GID) to GID.
|
|
393
406
|
|
|
394
407
|
Args:
|
|
408
|
+
----
|
|
395
409
|
user_identifier: User email, name, or GID
|
|
396
410
|
|
|
397
411
|
Returns:
|
|
412
|
+
-------
|
|
398
413
|
User GID or None if not found
|
|
399
414
|
|
|
400
415
|
"""
|
|
@@ -432,12 +447,15 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
432
447
|
"""Create a new Asana project or task.
|
|
433
448
|
|
|
434
449
|
Args:
|
|
450
|
+
----
|
|
435
451
|
ticket: Epic or Task to create
|
|
436
452
|
|
|
437
453
|
Returns:
|
|
454
|
+
-------
|
|
438
455
|
Created ticket with ID populated
|
|
439
456
|
|
|
440
457
|
Raises:
|
|
458
|
+
------
|
|
441
459
|
ValueError: If creation fails
|
|
442
460
|
|
|
443
461
|
"""
|
|
@@ -460,9 +478,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
460
478
|
"""Create an Asana project from an Epic.
|
|
461
479
|
|
|
462
480
|
Args:
|
|
481
|
+
----
|
|
463
482
|
epic: Epic to create
|
|
464
483
|
|
|
465
484
|
Returns:
|
|
485
|
+
-------
|
|
466
486
|
Created epic with Asana metadata
|
|
467
487
|
|
|
468
488
|
"""
|
|
@@ -491,9 +511,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
491
511
|
subtask (child of another task) when task.parent_issue is provided.
|
|
492
512
|
|
|
493
513
|
Args:
|
|
514
|
+
----
|
|
494
515
|
task: Task to create
|
|
495
516
|
|
|
496
517
|
Returns:
|
|
518
|
+
-------
|
|
497
519
|
Created task with Asana metadata
|
|
498
520
|
|
|
499
521
|
"""
|
|
@@ -564,6 +586,7 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
564
586
|
"""Add tags to an Asana task.
|
|
565
587
|
|
|
566
588
|
Args:
|
|
589
|
+
----
|
|
567
590
|
task_gid: Task GID
|
|
568
591
|
tags: List of tag names to add
|
|
569
592
|
|
|
@@ -602,9 +625,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
602
625
|
"""Read an Asana task by GID.
|
|
603
626
|
|
|
604
627
|
Args:
|
|
628
|
+
----
|
|
605
629
|
ticket_id: Asana task GID
|
|
606
630
|
|
|
607
631
|
Returns:
|
|
632
|
+
-------
|
|
608
633
|
Task if found, None otherwise
|
|
609
634
|
|
|
610
635
|
"""
|
|
@@ -632,10 +657,12 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
632
657
|
"""Update an Asana task.
|
|
633
658
|
|
|
634
659
|
Args:
|
|
660
|
+
----
|
|
635
661
|
ticket_id: Task GID
|
|
636
662
|
updates: Dictionary of fields to update
|
|
637
663
|
|
|
638
664
|
Returns:
|
|
665
|
+
-------
|
|
639
666
|
Updated task or None if not found
|
|
640
667
|
|
|
641
668
|
"""
|
|
@@ -776,9 +803,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
776
803
|
"""Delete an Asana task.
|
|
777
804
|
|
|
778
805
|
Args:
|
|
806
|
+
----
|
|
779
807
|
ticket_id: Task GID
|
|
780
808
|
|
|
781
809
|
Returns:
|
|
810
|
+
-------
|
|
782
811
|
True if successfully deleted
|
|
783
812
|
|
|
784
813
|
"""
|
|
@@ -795,11 +824,13 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
795
824
|
"""List Asana tasks with optional filtering.
|
|
796
825
|
|
|
797
826
|
Args:
|
|
827
|
+
----
|
|
798
828
|
limit: Maximum number of tasks to return
|
|
799
829
|
offset: Number of tasks to skip (Note: Asana uses offset tokens)
|
|
800
830
|
filters: Optional filters (state, assignee, project, etc.)
|
|
801
831
|
|
|
802
832
|
Returns:
|
|
833
|
+
-------
|
|
803
834
|
List of tasks matching the criteria
|
|
804
835
|
|
|
805
836
|
"""
|
|
@@ -896,9 +927,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
896
927
|
"""Search Asana tasks using filters.
|
|
897
928
|
|
|
898
929
|
Args:
|
|
930
|
+
----
|
|
899
931
|
query: Search query with filters
|
|
900
932
|
|
|
901
933
|
Returns:
|
|
934
|
+
-------
|
|
902
935
|
List of tasks matching the search criteria
|
|
903
936
|
|
|
904
937
|
"""
|
|
@@ -936,10 +969,12 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
936
969
|
"""Transition task to new state.
|
|
937
970
|
|
|
938
971
|
Args:
|
|
972
|
+
----
|
|
939
973
|
ticket_id: Task GID
|
|
940
974
|
target_state: Target state
|
|
941
975
|
|
|
942
976
|
Returns:
|
|
977
|
+
-------
|
|
943
978
|
Updated task or None if failed
|
|
944
979
|
|
|
945
980
|
"""
|
|
@@ -949,12 +984,15 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
949
984
|
"""Add a comment to an Asana task (as a story).
|
|
950
985
|
|
|
951
986
|
Args:
|
|
987
|
+
----
|
|
952
988
|
comment: Comment to add
|
|
953
989
|
|
|
954
990
|
Returns:
|
|
991
|
+
-------
|
|
955
992
|
Created comment with ID
|
|
956
993
|
|
|
957
994
|
Raises:
|
|
995
|
+
------
|
|
958
996
|
ValueError: If comment creation fails
|
|
959
997
|
|
|
960
998
|
"""
|
|
@@ -984,11 +1022,13 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
984
1022
|
Filters stories to only return comment type (not system events).
|
|
985
1023
|
|
|
986
1024
|
Args:
|
|
1025
|
+
----
|
|
987
1026
|
ticket_id: Task GID
|
|
988
1027
|
limit: Maximum number of comments to return
|
|
989
1028
|
offset: Number of comments to skip
|
|
990
1029
|
|
|
991
1030
|
Returns:
|
|
1031
|
+
-------
|
|
992
1032
|
List of comments for the task
|
|
993
1033
|
|
|
994
1034
|
"""
|
|
@@ -1019,11 +1059,13 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1019
1059
|
"""Create an Asana project (Epic).
|
|
1020
1060
|
|
|
1021
1061
|
Args:
|
|
1062
|
+
----
|
|
1022
1063
|
title: Epic title
|
|
1023
1064
|
description: Epic description
|
|
1024
1065
|
**kwargs: Additional fields
|
|
1025
1066
|
|
|
1026
1067
|
Returns:
|
|
1068
|
+
-------
|
|
1027
1069
|
Created epic or None if failed
|
|
1028
1070
|
|
|
1029
1071
|
"""
|
|
@@ -1041,9 +1083,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1041
1083
|
"""Get an Asana project (Epic) by GID.
|
|
1042
1084
|
|
|
1043
1085
|
Args:
|
|
1086
|
+
----
|
|
1044
1087
|
epic_id: Project GID
|
|
1045
1088
|
|
|
1046
1089
|
Returns:
|
|
1090
|
+
-------
|
|
1047
1091
|
Epic if found, None otherwise
|
|
1048
1092
|
|
|
1049
1093
|
"""
|
|
@@ -1065,10 +1109,12 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1065
1109
|
"""Update an Asana project (Epic).
|
|
1066
1110
|
|
|
1067
1111
|
Args:
|
|
1112
|
+
----
|
|
1068
1113
|
epic_id: Project GID
|
|
1069
1114
|
updates: Dictionary of fields to update
|
|
1070
1115
|
|
|
1071
1116
|
Returns:
|
|
1117
|
+
-------
|
|
1072
1118
|
Updated epic or None if failed
|
|
1073
1119
|
|
|
1074
1120
|
"""
|
|
@@ -1108,9 +1154,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1108
1154
|
"""List all Asana projects (Epics).
|
|
1109
1155
|
|
|
1110
1156
|
Args:
|
|
1157
|
+
----
|
|
1111
1158
|
**kwargs: Optional filter parameters
|
|
1112
1159
|
|
|
1113
1160
|
Returns:
|
|
1161
|
+
-------
|
|
1114
1162
|
List of epics
|
|
1115
1163
|
|
|
1116
1164
|
"""
|
|
@@ -1143,13 +1191,65 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1143
1191
|
logger.error(f"Failed to list projects: {e}")
|
|
1144
1192
|
return []
|
|
1145
1193
|
|
|
1194
|
+
async def delete_epic(self, epic_id: str) -> bool:
|
|
1195
|
+
"""Delete an Asana project (Epic).
|
|
1196
|
+
|
|
1197
|
+
Args:
|
|
1198
|
+
----
|
|
1199
|
+
epic_id: Project GID to delete
|
|
1200
|
+
|
|
1201
|
+
Returns:
|
|
1202
|
+
-------
|
|
1203
|
+
True if successfully deleted, False otherwise
|
|
1204
|
+
|
|
1205
|
+
Raises:
|
|
1206
|
+
------
|
|
1207
|
+
ValueError: If credentials are invalid or GID format is invalid
|
|
1208
|
+
|
|
1209
|
+
"""
|
|
1210
|
+
# Validate credentials
|
|
1211
|
+
is_valid, error_message = self.validate_credentials()
|
|
1212
|
+
if not is_valid:
|
|
1213
|
+
raise ValueError(error_message)
|
|
1214
|
+
|
|
1215
|
+
# Validate GID format (should be numeric)
|
|
1216
|
+
if not epic_id or not epic_id.isdigit():
|
|
1217
|
+
raise ValueError(
|
|
1218
|
+
f"Invalid project GID '{epic_id}'. Asana project GIDs must be numeric."
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
try:
|
|
1222
|
+
# Delete project using REST API
|
|
1223
|
+
await self.client.delete(f"/projects/{epic_id}")
|
|
1224
|
+
logger.info(f"Successfully deleted project {epic_id}")
|
|
1225
|
+
return True
|
|
1226
|
+
|
|
1227
|
+
except Exception as e:
|
|
1228
|
+
# Check if it's a 404 (not found) - return False
|
|
1229
|
+
if "404" in str(e) or "Not Found" in str(e):
|
|
1230
|
+
logger.warning(f"Project {epic_id} not found")
|
|
1231
|
+
return False
|
|
1232
|
+
|
|
1233
|
+
# Check for permissions errors
|
|
1234
|
+
if "403" in str(e) or "Forbidden" in str(e):
|
|
1235
|
+
logger.error(f"Permission denied to delete project {epic_id}")
|
|
1236
|
+
raise ValueError(
|
|
1237
|
+
f"Permission denied: You don't have permission to delete project {epic_id}"
|
|
1238
|
+
) from e
|
|
1239
|
+
|
|
1240
|
+
# Other errors - log and raise
|
|
1241
|
+
logger.error(f"Failed to delete project {epic_id}: {e}")
|
|
1242
|
+
raise ValueError(f"Failed to delete project: {e}") from e
|
|
1243
|
+
|
|
1146
1244
|
async def list_issues_by_epic(self, epic_id: str) -> builtins.list[Task]:
|
|
1147
1245
|
"""List all tasks in a project (Epic).
|
|
1148
1246
|
|
|
1149
1247
|
Args:
|
|
1248
|
+
----
|
|
1150
1249
|
epic_id: Project GID
|
|
1151
1250
|
|
|
1152
1251
|
Returns:
|
|
1252
|
+
-------
|
|
1153
1253
|
List of tasks in the project
|
|
1154
1254
|
|
|
1155
1255
|
"""
|
|
@@ -1161,9 +1261,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1161
1261
|
"""List all subtasks of a task (Issue).
|
|
1162
1262
|
|
|
1163
1263
|
Args:
|
|
1264
|
+
----
|
|
1164
1265
|
issue_id: Parent task GID
|
|
1165
1266
|
|
|
1166
1267
|
Returns:
|
|
1268
|
+
-------
|
|
1167
1269
|
List of subtasks
|
|
1168
1270
|
|
|
1169
1271
|
"""
|
|
@@ -1198,14 +1300,17 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1198
1300
|
"""Attach a file to an Asana task.
|
|
1199
1301
|
|
|
1200
1302
|
Args:
|
|
1303
|
+
----
|
|
1201
1304
|
ticket_id: Task GID
|
|
1202
1305
|
file_path: Local file path to upload
|
|
1203
1306
|
description: Optional attachment description (not used by Asana)
|
|
1204
1307
|
|
|
1205
1308
|
Returns:
|
|
1309
|
+
-------
|
|
1206
1310
|
Created Attachment with metadata
|
|
1207
1311
|
|
|
1208
1312
|
Raises:
|
|
1313
|
+
------
|
|
1209
1314
|
FileNotFoundError: If file doesn't exist
|
|
1210
1315
|
ValueError: If upload fails
|
|
1211
1316
|
|
|
@@ -1255,9 +1360,11 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1255
1360
|
"""Get all attachments for an Asana task.
|
|
1256
1361
|
|
|
1257
1362
|
Args:
|
|
1363
|
+
----
|
|
1258
1364
|
ticket_id: Task GID
|
|
1259
1365
|
|
|
1260
1366
|
Returns:
|
|
1367
|
+
-------
|
|
1261
1368
|
List of attachments
|
|
1262
1369
|
|
|
1263
1370
|
"""
|
|
@@ -1285,10 +1392,12 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1285
1392
|
"""Delete an attachment from an Asana task.
|
|
1286
1393
|
|
|
1287
1394
|
Args:
|
|
1395
|
+
----
|
|
1288
1396
|
ticket_id: Task GID (not used, kept for interface compatibility)
|
|
1289
1397
|
attachment_id: Attachment GID
|
|
1290
1398
|
|
|
1291
1399
|
Returns:
|
|
1400
|
+
-------
|
|
1292
1401
|
True if deleted successfully
|
|
1293
1402
|
|
|
1294
1403
|
"""
|
|
@@ -1303,6 +1412,126 @@ class AsanaAdapter(BaseAdapter[Task]):
|
|
|
1303
1412
|
"""Close adapter and cleanup resources."""
|
|
1304
1413
|
await self.client.close()
|
|
1305
1414
|
|
|
1415
|
+
# Milestone Methods (Not yet implemented)
|
|
1416
|
+
|
|
1417
|
+
async def milestone_create(
|
|
1418
|
+
self,
|
|
1419
|
+
name: str,
|
|
1420
|
+
target_date: datetime | None = None,
|
|
1421
|
+
labels: list[str] | None = None,
|
|
1422
|
+
description: str = "",
|
|
1423
|
+
project_id: str | None = None,
|
|
1424
|
+
) -> Any:
|
|
1425
|
+
"""Create milestone - not yet implemented for Asana.
|
|
1426
|
+
|
|
1427
|
+
Args:
|
|
1428
|
+
----
|
|
1429
|
+
name: Milestone name
|
|
1430
|
+
target_date: Target completion date
|
|
1431
|
+
labels: Labels that define this milestone
|
|
1432
|
+
description: Milestone description
|
|
1433
|
+
project_id: Associated project ID
|
|
1434
|
+
|
|
1435
|
+
Raises:
|
|
1436
|
+
------
|
|
1437
|
+
NotImplementedError: Milestone support coming in v2.1.0
|
|
1438
|
+
|
|
1439
|
+
"""
|
|
1440
|
+
raise NotImplementedError("Milestone support for Asana coming in v2.1.0")
|
|
1441
|
+
|
|
1442
|
+
async def milestone_get(self, milestone_id: str) -> Any:
|
|
1443
|
+
"""Get milestone - not yet implemented for Asana.
|
|
1444
|
+
|
|
1445
|
+
Args:
|
|
1446
|
+
----
|
|
1447
|
+
milestone_id: Milestone identifier
|
|
1448
|
+
|
|
1449
|
+
Raises:
|
|
1450
|
+
------
|
|
1451
|
+
NotImplementedError: Milestone support coming in v2.1.0
|
|
1452
|
+
|
|
1453
|
+
"""
|
|
1454
|
+
raise NotImplementedError("Milestone support for Asana coming in v2.1.0")
|
|
1455
|
+
|
|
1456
|
+
async def milestone_list(
|
|
1457
|
+
self,
|
|
1458
|
+
project_id: str | None = None,
|
|
1459
|
+
state: str | None = None,
|
|
1460
|
+
) -> list[Any]:
|
|
1461
|
+
"""List milestones - not yet implemented for Asana.
|
|
1462
|
+
|
|
1463
|
+
Args:
|
|
1464
|
+
----
|
|
1465
|
+
project_id: Filter by project
|
|
1466
|
+
state: Filter by state
|
|
1467
|
+
|
|
1468
|
+
Raises:
|
|
1469
|
+
------
|
|
1470
|
+
NotImplementedError: Milestone support coming in v2.1.0
|
|
1471
|
+
|
|
1472
|
+
"""
|
|
1473
|
+
raise NotImplementedError("Milestone support for Asana coming in v2.1.0")
|
|
1474
|
+
|
|
1475
|
+
async def milestone_update(
|
|
1476
|
+
self,
|
|
1477
|
+
milestone_id: str,
|
|
1478
|
+
name: str | None = None,
|
|
1479
|
+
target_date: datetime | None = None,
|
|
1480
|
+
state: str | None = None,
|
|
1481
|
+
labels: list[str] | None = None,
|
|
1482
|
+
description: str | None = None,
|
|
1483
|
+
) -> Any:
|
|
1484
|
+
"""Update milestone - not yet implemented for Asana.
|
|
1485
|
+
|
|
1486
|
+
Args:
|
|
1487
|
+
----
|
|
1488
|
+
milestone_id: Milestone identifier
|
|
1489
|
+
name: New name
|
|
1490
|
+
target_date: New target date
|
|
1491
|
+
state: New state
|
|
1492
|
+
labels: New labels
|
|
1493
|
+
description: New description
|
|
1494
|
+
|
|
1495
|
+
Raises:
|
|
1496
|
+
------
|
|
1497
|
+
NotImplementedError: Milestone support coming in v2.1.0
|
|
1498
|
+
|
|
1499
|
+
"""
|
|
1500
|
+
raise NotImplementedError("Milestone support for Asana coming in v2.1.0")
|
|
1501
|
+
|
|
1502
|
+
async def milestone_delete(self, milestone_id: str) -> bool:
|
|
1503
|
+
"""Delete milestone - not yet implemented for Asana.
|
|
1504
|
+
|
|
1505
|
+
Args:
|
|
1506
|
+
----
|
|
1507
|
+
milestone_id: Milestone identifier
|
|
1508
|
+
|
|
1509
|
+
Raises:
|
|
1510
|
+
------
|
|
1511
|
+
NotImplementedError: Milestone support coming in v2.1.0
|
|
1512
|
+
|
|
1513
|
+
"""
|
|
1514
|
+
raise NotImplementedError("Milestone support for Asana coming in v2.1.0")
|
|
1515
|
+
|
|
1516
|
+
async def milestone_get_issues(
|
|
1517
|
+
self,
|
|
1518
|
+
milestone_id: str,
|
|
1519
|
+
state: str | None = None,
|
|
1520
|
+
) -> list[Any]:
|
|
1521
|
+
"""Get milestone issues - not yet implemented for Asana.
|
|
1522
|
+
|
|
1523
|
+
Args:
|
|
1524
|
+
----
|
|
1525
|
+
milestone_id: Milestone identifier
|
|
1526
|
+
state: Filter by issue state
|
|
1527
|
+
|
|
1528
|
+
Raises:
|
|
1529
|
+
------
|
|
1530
|
+
NotImplementedError: Milestone support coming in v2.1.0
|
|
1531
|
+
|
|
1532
|
+
"""
|
|
1533
|
+
raise NotImplementedError("Milestone support for Asana coming in v2.1.0")
|
|
1534
|
+
|
|
1306
1535
|
|
|
1307
1536
|
# Register the adapter
|
|
1308
1537
|
AdapterRegistry.register("asana", AsanaAdapter)
|
|
@@ -22,9 +22,11 @@ def parse_asana_datetime(date_str: str | None) -> datetime | None:
|
|
|
22
22
|
"""Parse Asana datetime string to datetime object.
|
|
23
23
|
|
|
24
24
|
Args:
|
|
25
|
+
----
|
|
25
26
|
date_str: ISO 8601 datetime string or None
|
|
26
27
|
|
|
27
28
|
Returns:
|
|
29
|
+
-------
|
|
28
30
|
Parsed datetime or None
|
|
29
31
|
|
|
30
32
|
"""
|
|
@@ -43,9 +45,11 @@ def map_asana_project_to_epic(project: dict[str, Any]) -> Epic:
|
|
|
43
45
|
"""Map Asana project to Epic.
|
|
44
46
|
|
|
45
47
|
Args:
|
|
48
|
+
----
|
|
46
49
|
project: Asana project data
|
|
47
50
|
|
|
48
51
|
Returns:
|
|
52
|
+
-------
|
|
49
53
|
Epic model instance
|
|
50
54
|
|
|
51
55
|
"""
|
|
@@ -91,9 +95,11 @@ def map_asana_task_to_task(task: dict[str, Any]) -> Task:
|
|
|
91
95
|
- No parent task → ISSUE (standard task)
|
|
92
96
|
|
|
93
97
|
Args:
|
|
98
|
+
----
|
|
94
99
|
task: Asana task data
|
|
95
100
|
|
|
96
101
|
Returns:
|
|
102
|
+
-------
|
|
97
103
|
Task model instance
|
|
98
104
|
|
|
99
105
|
"""
|
|
@@ -181,11 +187,13 @@ def map_epic_to_asana_project(
|
|
|
181
187
|
"""Map Epic to Asana project create/update data.
|
|
182
188
|
|
|
183
189
|
Args:
|
|
190
|
+
----
|
|
184
191
|
epic: Epic model instance
|
|
185
192
|
workspace_gid: Asana workspace GID
|
|
186
193
|
team_gid: Asana team GID (optional, required for organization workspaces)
|
|
187
194
|
|
|
188
195
|
Returns:
|
|
196
|
+
-------
|
|
189
197
|
Asana project data for create/update
|
|
190
198
|
|
|
191
199
|
"""
|
|
@@ -216,11 +224,13 @@ def map_task_to_asana_task(
|
|
|
216
224
|
"""Map Task to Asana task create/update data.
|
|
217
225
|
|
|
218
226
|
Args:
|
|
227
|
+
----
|
|
219
228
|
task: Task model instance
|
|
220
229
|
workspace_gid: Asana workspace GID
|
|
221
230
|
project_gids: List of project GIDs to add task to (optional)
|
|
222
231
|
|
|
223
232
|
Returns:
|
|
233
|
+
-------
|
|
224
234
|
Asana task data for create/update
|
|
225
235
|
|
|
226
236
|
"""
|
|
@@ -262,10 +272,12 @@ def map_asana_story_to_comment(story: dict[str, Any], task_gid: str) -> Comment
|
|
|
262
272
|
Only maps stories of type 'comment'. Other story types (system events) are filtered out.
|
|
263
273
|
|
|
264
274
|
Args:
|
|
275
|
+
----
|
|
265
276
|
story: Asana story data
|
|
266
277
|
task_gid: Parent task GID
|
|
267
278
|
|
|
268
279
|
Returns:
|
|
280
|
+
-------
|
|
269
281
|
Comment model instance or None if not a comment type
|
|
270
282
|
|
|
271
283
|
"""
|
|
@@ -300,10 +312,12 @@ def map_asana_attachment_to_attachment(
|
|
|
300
312
|
IMPORTANT: Use permanent_url for reliable access, not download_url which expires.
|
|
301
313
|
|
|
302
314
|
Args:
|
|
315
|
+
----
|
|
303
316
|
attachment: Asana attachment data
|
|
304
317
|
task_gid: Parent task GID
|
|
305
318
|
|
|
306
319
|
Returns:
|
|
320
|
+
-------
|
|
307
321
|
Attachment model instance
|
|
308
322
|
|
|
309
323
|
"""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""GitHub adapter for MCP Ticketer.
|
|
2
|
+
|
|
3
|
+
This module provides integration with GitHub's REST and GraphQL APIs for universal ticket management.
|
|
4
|
+
The adapter is split into multiple modules for better organization:
|
|
5
|
+
|
|
6
|
+
- adapter.py: Main GitHubAdapter class with core functionality
|
|
7
|
+
- queries.py: GraphQL queries and fragments
|
|
8
|
+
- types.py: GitHub-specific types and mappings
|
|
9
|
+
- client.py: HTTP/GraphQL client management
|
|
10
|
+
- mappers.py: Data transformation between GitHub and universal models
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from mcp_ticketer.adapters.github import GitHubAdapter
|
|
14
|
+
|
|
15
|
+
config = {
|
|
16
|
+
"token": "your_github_token",
|
|
17
|
+
"owner": "repository_owner",
|
|
18
|
+
"repo": "repository_name"
|
|
19
|
+
}
|
|
20
|
+
adapter = GitHubAdapter(config)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .adapter import GitHubAdapter
|
|
24
|
+
from .types import GitHubStateMapping
|
|
25
|
+
|
|
26
|
+
__all__ = ["GitHubAdapter", "GitHubStateMapping"]
|