mcp-ticketer 0.3.5__py3-none-any.whl → 0.12.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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +263 -14
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1308 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +334 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +326 -109
- mcp_ticketer/adapters/hybrid.py +11 -11
- mcp_ticketer/adapters/jira.py +271 -25
- mcp_ticketer/adapters/linear/adapter.py +693 -39
- mcp_ticketer/adapters/linear/client.py +61 -9
- mcp_ticketer/adapters/linear/mappers.py +9 -3
- mcp_ticketer/adapters/linear/queries.py +9 -7
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +1 -1
- mcp_ticketer/cli/auggie_configure.py +104 -15
- mcp_ticketer/cli/codex_configure.py +188 -32
- mcp_ticketer/cli/configure.py +37 -48
- mcp_ticketer/cli/diagnostics.py +20 -18
- mcp_ticketer/cli/discover.py +292 -26
- mcp_ticketer/cli/gemini_configure.py +107 -26
- mcp_ticketer/cli/instruction_commands.py +429 -0
- mcp_ticketer/cli/linear_commands.py +105 -22
- mcp_ticketer/cli/main.py +1830 -435
- mcp_ticketer/cli/mcp_configure.py +296 -89
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +412 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/simple_health.py +1 -1
- mcp_ticketer/cli/ticket_commands.py +773 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +67 -62
- mcp_ticketer/core/__init__.py +14 -1
- mcp_ticketer/core/adapter.py +84 -15
- mcp_ticketer/core/config.py +44 -39
- mcp_ticketer/core/env_discovery.py +42 -12
- mcp_ticketer/core/env_loader.py +15 -14
- mcp_ticketer/core/exceptions.py +3 -3
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/mappers.py +11 -11
- mcp_ticketer/core/models.py +50 -20
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/project_config.py +57 -35
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/{dto.py → server/dto.py} +32 -32
- mcp_ticketer/mcp/{server.py → server/main.py} +127 -74
- mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +2 -2
- mcp_ticketer/mcp/server/server_sdk.py +93 -0
- mcp_ticketer/mcp/server/tools/__init__.py +47 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +90 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +532 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +154 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +206 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +430 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +5 -4
- mcp_ticketer/queue/manager.py +15 -51
- mcp_ticketer/queue/queue.py +19 -19
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +14 -14
- mcp_ticketer/queue/worker.py +16 -14
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +168 -32
- mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
- mcp_ticketer-0.3.5.dist-info/RECORD +0 -62
- /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Comment management tools for tickets.
|
|
2
|
+
|
|
3
|
+
This module implements tools for adding and retrieving comments on tickets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ....core.models import Comment
|
|
9
|
+
from ..server_sdk import get_adapter, mcp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
async def ticket_comment(
|
|
14
|
+
ticket_id: str,
|
|
15
|
+
operation: str,
|
|
16
|
+
text: str | None = None,
|
|
17
|
+
limit: int = 10,
|
|
18
|
+
offset: int = 0,
|
|
19
|
+
) -> dict[str, Any]:
|
|
20
|
+
"""Add or list comments on a ticket.
|
|
21
|
+
|
|
22
|
+
This tool supports two operations:
|
|
23
|
+
- 'add': Add a new comment to a ticket (requires 'text' parameter)
|
|
24
|
+
- 'list': Retrieve comments from a ticket (supports pagination)
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
ticket_id: Unique identifier of the ticket
|
|
28
|
+
operation: Operation to perform - must be 'add' or 'list'
|
|
29
|
+
text: Comment text (required when operation='add')
|
|
30
|
+
limit: Maximum number of comments to return (used when operation='list', default: 10)
|
|
31
|
+
offset: Number of comments to skip for pagination (used when operation='list', default: 0)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Comment data or list of comments, or error information
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
adapter = get_adapter()
|
|
39
|
+
|
|
40
|
+
# Validate operation
|
|
41
|
+
if operation not in ["add", "list"]:
|
|
42
|
+
return {
|
|
43
|
+
"status": "error",
|
|
44
|
+
"error": f"Invalid operation '{operation}'. Must be 'add' or 'list'",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if operation == "add":
|
|
48
|
+
# Add comment operation
|
|
49
|
+
if not text:
|
|
50
|
+
return {
|
|
51
|
+
"status": "error",
|
|
52
|
+
"error": "Parameter 'text' is required when operation='add'",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Create comment object
|
|
56
|
+
comment = Comment(
|
|
57
|
+
ticket_id=ticket_id,
|
|
58
|
+
content=text,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Add comment via adapter
|
|
62
|
+
created = await adapter.add_comment(comment)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"status": "completed",
|
|
66
|
+
"operation": "add",
|
|
67
|
+
"comment": created.model_dump(),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
else: # operation == "list"
|
|
71
|
+
# List comments operation
|
|
72
|
+
comments = await adapter.get_comments(
|
|
73
|
+
ticket_id=ticket_id, limit=limit, offset=offset
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"status": "completed",
|
|
78
|
+
"operation": "list",
|
|
79
|
+
"ticket_id": ticket_id,
|
|
80
|
+
"comments": [comment.model_dump() for comment in comments],
|
|
81
|
+
"count": len(comments),
|
|
82
|
+
"limit": limit,
|
|
83
|
+
"offset": offset,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return {
|
|
88
|
+
"status": "error",
|
|
89
|
+
"error": f"Comment operation failed: {str(e)}",
|
|
90
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""Configuration management tools for MCP ticketer.
|
|
2
|
+
|
|
3
|
+
This module provides tools for managing project-local configuration including
|
|
4
|
+
default adapter, project, and user settings. All configuration is stored in
|
|
5
|
+
.mcp-ticketer/config.json within the project root.
|
|
6
|
+
|
|
7
|
+
Design Decision: Project-Local Configuration Only
|
|
8
|
+
-------------------------------------------------
|
|
9
|
+
For security and isolation, this module ONLY manages project-local configuration
|
|
10
|
+
stored in .mcp-ticketer/config.json. It never reads from or writes to user home
|
|
11
|
+
directory or system-wide locations to prevent configuration leakage across projects.
|
|
12
|
+
|
|
13
|
+
Configuration stored:
|
|
14
|
+
- default_adapter: Primary adapter to use for ticket operations
|
|
15
|
+
- default_project: Default epic/project ID for new tickets
|
|
16
|
+
- default_user: Default assignee for new tickets (user_id or email)
|
|
17
|
+
- default_epic: Alias for default_project (backward compatibility)
|
|
18
|
+
|
|
19
|
+
Error Handling:
|
|
20
|
+
- All tools validate input before modifying configuration
|
|
21
|
+
- Adapter names are validated against AdapterRegistry
|
|
22
|
+
- Configuration file is created atomically to prevent corruption
|
|
23
|
+
- Detailed error messages for invalid configurations
|
|
24
|
+
|
|
25
|
+
Performance: Configuration is cached in memory by ConfigResolver,
|
|
26
|
+
so repeated reads are fast (O(1) after first load).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from ....core.project_config import AdapterType, ConfigResolver, TicketerConfig
|
|
33
|
+
from ..server_sdk import mcp
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_resolver() -> ConfigResolver:
|
|
37
|
+
"""Get or create the configuration resolver.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
ConfigResolver instance for current working directory
|
|
41
|
+
|
|
42
|
+
Design Decision: Uses CWD as project root, assuming MCP server
|
|
43
|
+
is started from project directory. This matches user expectations
|
|
44
|
+
and aligns with how other development tools operate.
|
|
45
|
+
|
|
46
|
+
Note: Creates a new resolver each time to avoid caching issues
|
|
47
|
+
in tests and ensure current working directory is always used.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
return ConfigResolver(project_path=Path.cwd())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
async def config_set_primary_adapter(adapter: str) -> dict[str, Any]:
|
|
55
|
+
"""Set the default adapter for ticket operations.
|
|
56
|
+
|
|
57
|
+
Updates the project-local configuration (.mcp-ticketer/config.json)
|
|
58
|
+
to use the specified adapter as the default for all ticket operations.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
adapter: Adapter name to set as primary. Must be one of:
|
|
62
|
+
- "aitrackdown" (file-based tracking)
|
|
63
|
+
- "linear" (Linear.app)
|
|
64
|
+
- "github" (GitHub Issues)
|
|
65
|
+
- "jira" (Atlassian JIRA)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dictionary containing:
|
|
69
|
+
- status: "completed" or "error"
|
|
70
|
+
- message: Success or error message
|
|
71
|
+
- previous_adapter: Previous default adapter (if successful)
|
|
72
|
+
- new_adapter: New default adapter (if successful)
|
|
73
|
+
- error: Error details (if failed)
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> result = await config_set_primary_adapter("linear")
|
|
77
|
+
>>> print(result)
|
|
78
|
+
{
|
|
79
|
+
"status": "completed",
|
|
80
|
+
"message": "Default adapter set to 'linear'",
|
|
81
|
+
"previous_adapter": "aitrackdown",
|
|
82
|
+
"new_adapter": "linear"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Error Conditions:
|
|
86
|
+
- Invalid adapter name: Returns error with valid options
|
|
87
|
+
- Configuration file write failure: Returns error with file path
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
# Validate adapter name against registry
|
|
92
|
+
valid_adapters = [adapter_type.value for adapter_type in AdapterType]
|
|
93
|
+
if adapter.lower() not in valid_adapters:
|
|
94
|
+
return {
|
|
95
|
+
"status": "error",
|
|
96
|
+
"error": f"Invalid adapter '{adapter}'. Must be one of: {', '.join(valid_adapters)}",
|
|
97
|
+
"valid_adapters": valid_adapters,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Load current configuration
|
|
101
|
+
resolver = get_resolver()
|
|
102
|
+
config = resolver.load_project_config() or TicketerConfig()
|
|
103
|
+
|
|
104
|
+
# Store previous adapter for response
|
|
105
|
+
previous_adapter = config.default_adapter
|
|
106
|
+
|
|
107
|
+
# Update default adapter
|
|
108
|
+
config.default_adapter = adapter.lower()
|
|
109
|
+
|
|
110
|
+
# Save configuration
|
|
111
|
+
resolver.save_project_config(config)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"status": "completed",
|
|
115
|
+
"message": f"Default adapter set to '{adapter.lower()}'",
|
|
116
|
+
"previous_adapter": previous_adapter,
|
|
117
|
+
"new_adapter": adapter.lower(),
|
|
118
|
+
"config_path": str(resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH),
|
|
119
|
+
}
|
|
120
|
+
except Exception as e:
|
|
121
|
+
return {
|
|
122
|
+
"status": "error",
|
|
123
|
+
"error": f"Failed to set default adapter: {str(e)}",
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@mcp.tool()
|
|
128
|
+
async def config_set_default_project(
|
|
129
|
+
project_id: str,
|
|
130
|
+
project_key: str | None = None,
|
|
131
|
+
) -> dict[str, Any]:
|
|
132
|
+
"""Set the default project/epic for new tickets.
|
|
133
|
+
|
|
134
|
+
Updates the project-local configuration to automatically assign new tickets
|
|
135
|
+
to the specified project or epic. This is useful for teams working primarily
|
|
136
|
+
on a single project or feature area.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
project_id: Project or epic ID to set as default (required)
|
|
140
|
+
project_key: Optional project key (for adapters that use keys vs IDs)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Dictionary containing:
|
|
144
|
+
- status: "completed" or "error"
|
|
145
|
+
- message: Success or error message
|
|
146
|
+
- previous_project: Previous default project (if any)
|
|
147
|
+
- new_project: New default project ID
|
|
148
|
+
- error: Error details (if failed)
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> result = await config_set_default_project("PROJ-123")
|
|
152
|
+
>>> print(result)
|
|
153
|
+
{
|
|
154
|
+
"status": "completed",
|
|
155
|
+
"message": "Default project set to 'PROJ-123'",
|
|
156
|
+
"previous_project": None,
|
|
157
|
+
"new_project": "PROJ-123"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Usage Notes:
|
|
161
|
+
- This sets both default_project and default_epic (for backward compatibility)
|
|
162
|
+
- Empty string or null clears the default project
|
|
163
|
+
- Project ID is not validated (allows flexibility across adapters)
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
# Load current configuration
|
|
168
|
+
resolver = get_resolver()
|
|
169
|
+
config = resolver.load_project_config() or TicketerConfig()
|
|
170
|
+
|
|
171
|
+
# Store previous project for response
|
|
172
|
+
previous_project = config.default_project or config.default_epic
|
|
173
|
+
|
|
174
|
+
# Update default project (and epic for backward compat)
|
|
175
|
+
config.default_project = project_id if project_id else None
|
|
176
|
+
config.default_epic = project_id if project_id else None
|
|
177
|
+
|
|
178
|
+
# Save configuration
|
|
179
|
+
resolver.save_project_config(config)
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"status": "completed",
|
|
183
|
+
"message": (
|
|
184
|
+
f"Default project set to '{project_id}'"
|
|
185
|
+
if project_id
|
|
186
|
+
else "Default project cleared"
|
|
187
|
+
),
|
|
188
|
+
"previous_project": previous_project,
|
|
189
|
+
"new_project": project_id,
|
|
190
|
+
"config_path": str(resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH),
|
|
191
|
+
}
|
|
192
|
+
except Exception as e:
|
|
193
|
+
return {
|
|
194
|
+
"status": "error",
|
|
195
|
+
"error": f"Failed to set default project: {str(e)}",
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@mcp.tool()
|
|
200
|
+
async def config_set_default_user(
|
|
201
|
+
user_id: str,
|
|
202
|
+
user_email: str | None = None,
|
|
203
|
+
) -> dict[str, Any]:
|
|
204
|
+
"""Set the default assignee for new tickets.
|
|
205
|
+
|
|
206
|
+
Updates the project-local configuration to automatically assign new tickets
|
|
207
|
+
to the specified user. Supports both user IDs and email addresses depending
|
|
208
|
+
on adapter requirements.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
user_id: User identifier or email to set as default assignee (required)
|
|
212
|
+
user_email: Optional email (for adapters that require separate email field)
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Dictionary containing:
|
|
216
|
+
- status: "completed" or "error"
|
|
217
|
+
- message: Success or error message
|
|
218
|
+
- previous_user: Previous default user (if any)
|
|
219
|
+
- new_user: New default user ID
|
|
220
|
+
- error: Error details (if failed)
|
|
221
|
+
|
|
222
|
+
Example:
|
|
223
|
+
>>> result = await config_set_default_user("user123")
|
|
224
|
+
>>> print(result)
|
|
225
|
+
{
|
|
226
|
+
"status": "completed",
|
|
227
|
+
"message": "Default user set to 'user123'",
|
|
228
|
+
"previous_user": None,
|
|
229
|
+
"new_user": "user123"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Example with email:
|
|
233
|
+
>>> result = await config_set_default_user("user@example.com")
|
|
234
|
+
>>> print(result)
|
|
235
|
+
{
|
|
236
|
+
"status": "completed",
|
|
237
|
+
"message": "Default user set to 'user@example.com'",
|
|
238
|
+
"previous_user": "old_user@example.com",
|
|
239
|
+
"new_user": "user@example.com"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
Usage Notes:
|
|
243
|
+
- User ID/email is not validated (allows flexibility across adapters)
|
|
244
|
+
- Empty string or null clears the default user
|
|
245
|
+
- Some adapters prefer email, others prefer user UUID
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
try:
|
|
249
|
+
# Load current configuration
|
|
250
|
+
resolver = get_resolver()
|
|
251
|
+
config = resolver.load_project_config() or TicketerConfig()
|
|
252
|
+
|
|
253
|
+
# Store previous user for response
|
|
254
|
+
previous_user = config.default_user
|
|
255
|
+
|
|
256
|
+
# Update default user
|
|
257
|
+
config.default_user = user_id if user_id else None
|
|
258
|
+
|
|
259
|
+
# Save configuration
|
|
260
|
+
resolver.save_project_config(config)
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
"status": "completed",
|
|
264
|
+
"message": (
|
|
265
|
+
f"Default user set to '{user_id}'"
|
|
266
|
+
if user_id
|
|
267
|
+
else "Default user cleared"
|
|
268
|
+
),
|
|
269
|
+
"previous_user": previous_user,
|
|
270
|
+
"new_user": user_id,
|
|
271
|
+
"config_path": str(resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH),
|
|
272
|
+
}
|
|
273
|
+
except Exception as e:
|
|
274
|
+
return {
|
|
275
|
+
"status": "error",
|
|
276
|
+
"error": f"Failed to set default user: {str(e)}",
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@mcp.tool()
|
|
281
|
+
async def config_get() -> dict[str, Any]:
|
|
282
|
+
"""Get current configuration settings.
|
|
283
|
+
|
|
284
|
+
Retrieves the current project-local configuration including default adapter,
|
|
285
|
+
project, user, and all adapter-specific settings.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Dictionary containing:
|
|
289
|
+
- status: "completed" or "error"
|
|
290
|
+
- config: Complete configuration dictionary including:
|
|
291
|
+
- default_adapter: Primary adapter name
|
|
292
|
+
- default_project: Default project/epic ID (if set)
|
|
293
|
+
- default_user: Default assignee (if set)
|
|
294
|
+
- adapters: All adapter configurations
|
|
295
|
+
- hybrid_mode: Hybrid mode settings (if enabled)
|
|
296
|
+
- config_path: Path to configuration file
|
|
297
|
+
- error: Error details (if failed)
|
|
298
|
+
|
|
299
|
+
Example:
|
|
300
|
+
>>> result = await config_get()
|
|
301
|
+
>>> print(result)
|
|
302
|
+
{
|
|
303
|
+
"status": "completed",
|
|
304
|
+
"config": {
|
|
305
|
+
"default_adapter": "linear",
|
|
306
|
+
"default_project": "PROJ-123",
|
|
307
|
+
"default_user": "user@example.com",
|
|
308
|
+
"adapters": {
|
|
309
|
+
"linear": {"api_key": "***", "team_id": "..."}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"config_path": "/project/.mcp-ticketer/config.json"
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
Usage Notes:
|
|
316
|
+
- Sensitive values (API keys) are masked in the response
|
|
317
|
+
- Returns default values if no configuration file exists
|
|
318
|
+
- Configuration is merged from multiple sources (env vars, .env files, config.json)
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
try:
|
|
322
|
+
# Load current configuration
|
|
323
|
+
resolver = get_resolver()
|
|
324
|
+
config = resolver.load_project_config() or TicketerConfig()
|
|
325
|
+
|
|
326
|
+
# Convert to dictionary
|
|
327
|
+
config_dict = config.to_dict()
|
|
328
|
+
|
|
329
|
+
# Mask sensitive values (API keys, tokens)
|
|
330
|
+
masked_config = _mask_sensitive_values(config_dict)
|
|
331
|
+
|
|
332
|
+
config_path = resolver.project_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
333
|
+
config_exists = config_path.exists()
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
"status": "completed",
|
|
337
|
+
"config": masked_config,
|
|
338
|
+
"config_path": str(config_path),
|
|
339
|
+
"config_exists": config_exists,
|
|
340
|
+
"message": (
|
|
341
|
+
"Configuration retrieved successfully"
|
|
342
|
+
if config_exists
|
|
343
|
+
else "No configuration file found, showing defaults"
|
|
344
|
+
),
|
|
345
|
+
}
|
|
346
|
+
except Exception as e:
|
|
347
|
+
return {
|
|
348
|
+
"status": "error",
|
|
349
|
+
"error": f"Failed to retrieve configuration: {str(e)}",
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _mask_sensitive_values(config: dict[str, Any]) -> dict[str, Any]:
|
|
354
|
+
"""Mask sensitive values in configuration dictionary.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
config: Configuration dictionary
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Configuration dictionary with sensitive values masked
|
|
361
|
+
|
|
362
|
+
Implementation Details:
|
|
363
|
+
- Recursively processes nested dictionaries
|
|
364
|
+
- Masks any field containing: key, token, password, secret
|
|
365
|
+
- Preserves structure for debugging while protecting credentials
|
|
366
|
+
|
|
367
|
+
"""
|
|
368
|
+
masked = {}
|
|
369
|
+
sensitive_keys = {"api_key", "token", "password", "secret", "api_token"}
|
|
370
|
+
|
|
371
|
+
for key, value in config.items():
|
|
372
|
+
if isinstance(value, dict):
|
|
373
|
+
# Recursively mask nested dictionaries
|
|
374
|
+
masked[key] = _mask_sensitive_values(value)
|
|
375
|
+
elif any(sensitive in key.lower() for sensitive in sensitive_keys):
|
|
376
|
+
# Mask sensitive values
|
|
377
|
+
masked[key] = "***" if value else None
|
|
378
|
+
else:
|
|
379
|
+
masked[key] = value
|
|
380
|
+
|
|
381
|
+
return masked
|