mcp-ticketer 0.3.2__py3-none-any.whl → 0.3.3__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.

@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from enum import Enum
6
- from typing import Any, Dict
6
+ from typing import Any
7
7
 
8
8
  from mcp_ticketer.core.models import Priority, TicketState
9
9
 
@@ -12,14 +12,14 @@ class LinearPriorityMapping:
12
12
  """Mapping between universal Priority and Linear priority values."""
13
13
 
14
14
  # Linear uses numeric priorities: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
15
- TO_LINEAR: Dict[Priority, int] = {
15
+ TO_LINEAR: dict[Priority, int] = {
16
16
  Priority.CRITICAL: 1, # Urgent
17
17
  Priority.HIGH: 2, # High
18
18
  Priority.MEDIUM: 3, # Medium
19
19
  Priority.LOW: 4, # Low
20
20
  }
21
21
 
22
- FROM_LINEAR: Dict[int, Priority] = {
22
+ FROM_LINEAR: dict[int, Priority] = {
23
23
  0: Priority.LOW, # No priority -> Low
24
24
  1: Priority.CRITICAL, # Urgent -> Critical
25
25
  2: Priority.HIGH, # High -> High
@@ -32,7 +32,7 @@ class LinearStateMapping:
32
32
  """Mapping between universal TicketState and Linear workflow state types."""
33
33
 
34
34
  # Linear workflow state types
35
- TO_LINEAR: Dict[TicketState, str] = {
35
+ TO_LINEAR: dict[TicketState, str] = {
36
36
  TicketState.OPEN: "unstarted",
37
37
  TicketState.IN_PROGRESS: "started",
38
38
  TicketState.READY: "unstarted", # No direct equivalent, use unstarted
@@ -43,7 +43,7 @@ class LinearStateMapping:
43
43
  TicketState.BLOCKED: "unstarted",
44
44
  }
45
45
 
46
- FROM_LINEAR: Dict[str, TicketState] = {
46
+ FROM_LINEAR: dict[str, TicketState] = {
47
47
  "backlog": TicketState.OPEN,
48
48
  "unstarted": TicketState.OPEN,
49
49
  "started": TicketState.IN_PROGRESS,
@@ -152,7 +152,7 @@ def build_issue_filter(
152
152
  updated_after: str | None = None,
153
153
  due_before: str | None = None,
154
154
  include_archived: bool = False,
155
- ) -> Dict[str, Any]:
155
+ ) -> dict[str, Any]:
156
156
  """Build a Linear issue filter from parameters.
157
157
 
158
158
  Args:
@@ -171,7 +171,7 @@ def build_issue_filter(
171
171
  Linear GraphQL filter object
172
172
 
173
173
  """
174
- issue_filter: Dict[str, Any] = {}
174
+ issue_filter: dict[str, Any] = {}
175
175
 
176
176
  # Team filter (required for most operations)
177
177
  if team_id:
@@ -218,7 +218,7 @@ def build_project_filter(
218
218
  state: str | None = None,
219
219
  team_id: str | None = None,
220
220
  include_completed: bool = True,
221
- ) -> Dict[str, Any]:
221
+ ) -> dict[str, Any]:
222
222
  """Build a Linear project filter from parameters.
223
223
 
224
224
  Args:
@@ -230,7 +230,7 @@ def build_project_filter(
230
230
  Linear GraphQL filter object
231
231
 
232
232
  """
233
- project_filter: Dict[str, Any] = {}
233
+ project_filter: dict[str, Any] = {}
234
234
 
235
235
  # Team filter
236
236
  if team_id:
@@ -246,7 +246,7 @@ def build_project_filter(
246
246
  return project_filter
247
247
 
248
248
 
249
- def extract_linear_metadata(issue_data: Dict[str, Any]) -> Dict[str, Any]:
249
+ def extract_linear_metadata(issue_data: dict[str, Any]) -> dict[str, Any]:
250
250
  """Extract Linear-specific metadata from issue data.
251
251
 
252
252
  Args:
@@ -1,7 +1,7 @@
1
1
  """Adapter diagnostics and configuration validation."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Any, Dict
4
+ from typing import Any
5
5
 
6
6
  from rich.console import Console
7
7
  from rich.table import Table
@@ -350,7 +350,7 @@ def _provide_recommendations(console: Console) -> None:
350
350
  console.print("• List tickets: [cyan]mcp-ticketer list[/cyan]")
351
351
 
352
352
 
353
- def get_adapter_status() -> Dict[str, Any]:
353
+ def get_adapter_status() -> dict[str, Any]:
354
354
  """Get current adapter status for programmatic use.
355
355
 
356
356
  Returns:
@@ -6,7 +6,7 @@ Unlike Claude Code and Gemini CLI, there is no project-level configuration suppo
6
6
 
7
7
  import sys
8
8
  from pathlib import Path
9
- from typing import Any, Dict, Optional
9
+ from typing import Any, Optional
10
10
 
11
11
  if sys.version_info >= (3, 11):
12
12
  import tomllib
@@ -36,7 +36,7 @@ def find_codex_config() -> Path:
36
36
  return config_path
37
37
 
38
38
 
39
- def load_codex_config(config_path: Path) -> Dict[str, Any]:
39
+ def load_codex_config(config_path: Path) -> dict[str, Any]:
40
40
  """Load existing Codex configuration or return empty structure.
41
41
 
42
42
  Args:
@@ -61,7 +61,7 @@ def load_codex_config(config_path: Path) -> Dict[str, Any]:
61
61
  return {"mcp_servers": {}}
62
62
 
63
63
 
64
- def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
64
+ def save_codex_config(config_path: Path, config: dict[str, Any]) -> None:
65
65
  """Save Codex configuration to TOML file.
66
66
 
67
67
  Args:
@@ -79,7 +79,7 @@ def save_codex_config(config_path: Path, config: Dict[str, Any]) -> None:
79
79
 
80
80
  def create_codex_server_config(
81
81
  binary_path: str, project_config: dict, cwd: Optional[str] = None
82
- ) -> Dict[str, Any]:
82
+ ) -> dict[str, Any]:
83
83
  """Create Codex MCP server configuration for mcp-ticketer.
84
84
 
85
85
  Args:
@@ -97,7 +97,7 @@ def create_codex_server_config(
97
97
  adapter_config = adapters_config.get(adapter, {})
98
98
 
99
99
  # Build environment variables
100
- env_vars: Dict[str, str] = {}
100
+ env_vars: dict[str, str] = {}
101
101
 
102
102
  # Add PYTHONPATH if running from development environment
103
103
  if cwd:
@@ -142,7 +142,7 @@ def create_codex_server_config(
142
142
 
143
143
  # Create server configuration with Codex-specific structure
144
144
  # NOTE: Codex uses nested dict structure for env vars
145
- config: Dict[str, Any] = {
145
+ config: dict[str, Any] = {
146
146
  "command": binary_path,
147
147
  "args": ["serve"],
148
148
  "env": env_vars,
@@ -5,7 +5,7 @@ import logging
5
5
  import sys
6
6
  from datetime import datetime, timedelta
7
7
  from pathlib import Path
8
- from typing import Any, Dict, List, Optional
8
+ from typing import Any, Optional
9
9
 
10
10
  import typer
11
11
  from rich.console import Console
@@ -108,7 +108,7 @@ class SystemDiagnostics:
108
108
  self.queue_available = False
109
109
  console.print(f"⚠️ Could not initialize worker manager: {e}")
110
110
 
111
- async def run_full_diagnosis(self) -> Dict[str, Any]:
111
+ async def run_full_diagnosis(self) -> dict[str, Any]:
112
112
  """Run complete system diagnosis and return detailed report."""
113
113
  console.print("\n🔍 [bold blue]MCP Ticketer System Diagnosis[/bold blue]")
114
114
  console.print("=" * 60)
@@ -137,7 +137,7 @@ class SystemDiagnostics:
137
137
  except ImportError:
138
138
  return "unknown"
139
139
 
140
- def _get_system_info(self) -> Dict[str, Any]:
140
+ def _get_system_info(self) -> dict[str, Any]:
141
141
  """Gather system information."""
142
142
  return {
143
143
  "python_version": sys.version,
@@ -150,7 +150,7 @@ class SystemDiagnostics:
150
150
  ),
151
151
  }
152
152
 
153
- async def _diagnose_configuration(self) -> Dict[str, Any]:
153
+ async def _diagnose_configuration(self) -> dict[str, Any]:
154
154
  """Diagnose configuration issues."""
155
155
  console.print("\n📋 [yellow]Configuration Analysis[/yellow]")
156
156
 
@@ -220,7 +220,7 @@ class SystemDiagnostics:
220
220
  console.print(f"✅ {len(adapters_config)} adapter(s) configured")
221
221
 
222
222
  # Check each adapter configuration
223
- for name, adapter_config in adapters_config.items():
223
+ for name, _adapter_config in adapters_config.items():
224
224
  try:
225
225
  # Use the same adapter creation approach as working commands
226
226
  adapter = CommonPatterns.get_adapter(override_adapter=name)
@@ -256,7 +256,7 @@ class SystemDiagnostics:
256
256
 
257
257
  return config_status
258
258
 
259
- async def _diagnose_adapters(self) -> Dict[str, Any]:
259
+ async def _diagnose_adapters(self) -> dict[str, Any]:
260
260
  """Diagnose adapter functionality."""
261
261
  console.print("\n🔌 [yellow]Adapter Diagnosis[/yellow]")
262
262
 
@@ -277,7 +277,6 @@ class SystemDiagnostics:
277
277
 
278
278
  for name, adapter_config in adapters_config.items():
279
279
  adapter_type = adapter_config.get("type", name)
280
- config_dict = adapter_config
281
280
 
282
281
  details = {
283
282
  "type": adapter_type,
@@ -327,7 +326,7 @@ class SystemDiagnostics:
327
326
 
328
327
  return adapter_status
329
328
 
330
- async def _diagnose_queue_system(self) -> Dict[str, Any]:
329
+ async def _diagnose_queue_system(self) -> dict[str, Any]:
331
330
  """Diagnose queue system health with active testing."""
332
331
  console.print("\n⚡ [yellow]Queue System Diagnosis[/yellow]")
333
332
 
@@ -454,7 +453,7 @@ class SystemDiagnostics:
454
453
 
455
454
  return queue_status
456
455
 
457
- async def _test_worker_startup(self) -> Dict[str, Any]:
456
+ async def _test_worker_startup(self) -> dict[str, Any]:
458
457
  """Test starting a queue worker."""
459
458
  test_result = {
460
459
  "attempted": True,
@@ -496,7 +495,7 @@ class SystemDiagnostics:
496
495
 
497
496
  return test_result
498
497
 
499
- async def _test_queue_operations(self) -> Dict[str, Any]:
498
+ async def _test_queue_operations(self) -> dict[str, Any]:
500
499
  """Test basic queue operations."""
501
500
  test_result = {
502
501
  "attempted": True,
@@ -531,7 +530,7 @@ class SystemDiagnostics:
531
530
 
532
531
  return test_result
533
532
 
534
- async def _test_basic_queue_functionality(self) -> Dict[str, Any]:
533
+ async def _test_basic_queue_functionality(self) -> dict[str, Any]:
535
534
  """Test basic queue functionality in fallback mode."""
536
535
  test_result = {
537
536
  "attempted": True,
@@ -572,7 +571,7 @@ class SystemDiagnostics:
572
571
 
573
572
  return test_result
574
573
 
575
- async def _analyze_recent_logs(self) -> Dict[str, Any]:
574
+ async def _analyze_recent_logs(self) -> dict[str, Any]:
576
575
  """Analyze recent log entries for issues."""
577
576
  console.print("\n📝 [yellow]Recent Log Analysis[/yellow]")
578
577
 
@@ -611,7 +610,7 @@ class SystemDiagnostics:
611
610
  return log_analysis
612
611
 
613
612
  async def _analyze_log_directory(
614
- self, log_path: Path, log_analysis: Dict[str, Any]
613
+ self, log_path: Path, log_analysis: dict[str, Any]
615
614
  ):
616
615
  """Analyze logs in a specific directory."""
617
616
  try:
@@ -624,7 +623,7 @@ class SystemDiagnostics:
624
623
  except Exception as e:
625
624
  self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
626
625
 
627
- async def _parse_log_file(self, log_file: Path, log_analysis: Dict[str, Any]):
626
+ async def _parse_log_file(self, log_file: Path, log_analysis: dict[str, Any]):
628
627
  """Parse individual log file for issues."""
629
628
  try:
630
629
  with open(log_file) as f:
@@ -639,7 +638,7 @@ class SystemDiagnostics:
639
638
  except Exception as e:
640
639
  self.warnings.append(f"Could not parse {log_file}: {str(e)}")
641
640
 
642
- async def _analyze_performance(self) -> Dict[str, Any]:
641
+ async def _analyze_performance(self) -> dict[str, Any]:
643
642
  """Analyze system performance metrics."""
644
643
  console.print("\n⚡ [yellow]Performance Analysis[/yellow]")
645
644
 
@@ -651,7 +650,7 @@ class SystemDiagnostics:
651
650
 
652
651
  try:
653
652
  # Test basic operations performance
654
- start_time = datetime.now()
653
+ datetime.now()
655
654
 
656
655
  # Test configuration loading
657
656
  config_start = datetime.now()
@@ -671,7 +670,7 @@ class SystemDiagnostics:
671
670
 
672
671
  return performance
673
672
 
674
- def _generate_recommendations(self) -> List[str]:
673
+ def _generate_recommendations(self) -> list[str]:
675
674
  """Generate actionable recommendations based on diagnosis."""
676
675
  recommendations = []
677
676
 
@@ -706,7 +705,7 @@ class SystemDiagnostics:
706
705
 
707
706
  return recommendations
708
707
 
709
- def _display_diagnosis_summary(self, report: Dict[str, Any]):
708
+ def _display_diagnosis_summary(self, report: dict[str, Any]):
710
709
  """Display a comprehensive diagnosis summary."""
711
710
  console.print("\n" + "=" * 60)
712
711
  console.print("📋 [bold green]DIAGNOSIS SUMMARY[/bold green]")
@@ -3,7 +3,7 @@
3
3
  import os
4
4
  import sys
5
5
  from pathlib import Path
6
- from typing import Any, Dict
6
+ from typing import Any
7
7
 
8
8
  from rich.console import Console
9
9
 
@@ -149,7 +149,7 @@ def simple_health_check() -> int:
149
149
  return 1
150
150
 
151
151
 
152
- def simple_diagnose() -> Dict[str, Any]:
152
+ def simple_diagnose() -> dict[str, Any]:
153
153
  """Simple diagnosis that works without full config system."""
154
154
  console.print("\n🔍 [bold blue]MCP Ticketer Simple Diagnosis[/bold blue]")
155
155
  console.print("=" * 60)
@@ -168,14 +168,9 @@ def simple_diagnose() -> Dict[str, Any]:
168
168
  console.print("\n📋 [yellow]Basic System Check[/yellow]")
169
169
 
170
170
  # Python version
171
- if sys.version_info < (3, 9):
172
- issue = f"Python {sys.version_info.major}.{sys.version_info.minor} is too old (requires 3.9+)"
173
- report["issues"].append(issue)
174
- console.print(f"❌ {issue}")
175
- else:
176
- console.print(
177
- f"✅ Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
178
- )
171
+ console.print(
172
+ f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
173
+ )
179
174
 
180
175
  # Installation check
181
176
  try:
@@ -12,7 +12,7 @@ import logging
12
12
  import os
13
13
  from dataclasses import dataclass
14
14
  from pathlib import Path
15
- from typing import Any, Dict, List, Optional
15
+ from typing import Any, Optional
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
@@ -22,7 +22,7 @@ class EnvKeyConfig:
22
22
  """Configuration for environment variable key aliases."""
23
23
 
24
24
  primary_key: str
25
- aliases: List[str]
25
+ aliases: list[str]
26
26
  description: str
27
27
  required: bool = False
28
28
  default: Optional[str] = None
@@ -113,7 +113,7 @@ class UnifiedEnvLoader:
113
113
 
114
114
  """
115
115
  self.project_root = project_root or self._find_project_root()
116
- self._env_cache: Dict[str, str] = {}
116
+ self._env_cache: dict[str, str] = {}
117
117
  self._load_env_files()
118
118
 
119
119
  def _find_project_root(self) -> Path:
@@ -148,7 +148,7 @@ class UnifiedEnvLoader:
148
148
  """Load variables from a single .env file."""
149
149
  try:
150
150
  with open(env_file) as f:
151
- for line_num, line in enumerate(f, 1):
151
+ for _line_num, line in enumerate(f, 1):
152
152
  line = line.strip()
153
153
 
154
154
  # Skip empty lines and comments
@@ -177,7 +177,7 @@ class UnifiedEnvLoader:
177
177
  logger.warning(f"Failed to load {env_file}: {e}")
178
178
 
179
179
  def get_value(
180
- self, config_key: str, config: Optional[Dict[str, Any]] = None
180
+ self, config_key: str, config: Optional[dict[str, Any]] = None
181
181
  ) -> Optional[str]:
182
182
  """Get a configuration value using the key alias system.
183
183
 
@@ -230,8 +230,8 @@ class UnifiedEnvLoader:
230
230
  return None
231
231
 
232
232
  def get_adapter_config(
233
- self, adapter_name: str, base_config: Optional[Dict[str, Any]] = None
234
- ) -> Dict[str, Any]:
233
+ self, adapter_name: str, base_config: Optional[dict[str, Any]] = None
234
+ ) -> dict[str, Any]:
235
235
  """Get complete configuration for an adapter with environment variable resolution.
236
236
 
237
237
  Args:
@@ -264,8 +264,8 @@ class UnifiedEnvLoader:
264
264
  return config
265
265
 
266
266
  def validate_adapter_config(
267
- self, adapter_name: str, config: Dict[str, Any]
268
- ) -> List[str]:
267
+ self, adapter_name: str, config: dict[str, Any]
268
+ ) -> list[str]:
269
269
  """Validate that all required configuration is present for an adapter.
270
270
 
271
271
  Args:
@@ -292,7 +292,7 @@ class UnifiedEnvLoader:
292
292
 
293
293
  return missing_keys
294
294
 
295
- def get_debug_info(self) -> Dict[str, Any]:
295
+ def get_debug_info(self) -> dict[str, Any]:
296
296
  """Get debug information about environment loading."""
297
297
  return {
298
298
  "project_root": str(self.project_root),
@@ -319,8 +319,8 @@ def get_env_loader() -> UnifiedEnvLoader:
319
319
 
320
320
 
321
321
  def load_adapter_config(
322
- adapter_name: str, base_config: Optional[Dict[str, Any]] = None
323
- ) -> Dict[str, Any]:
322
+ adapter_name: str, base_config: Optional[dict[str, Any]] = None
323
+ ) -> dict[str, Any]:
324
324
  """Convenience function to load adapter configuration with environment variables.
325
325
 
326
326
  Args:
@@ -334,7 +334,7 @@ def load_adapter_config(
334
334
  return get_env_loader().get_adapter_config(adapter_name, base_config)
335
335
 
336
336
 
337
- def validate_adapter_config(adapter_name: str, config: Dict[str, Any]) -> List[str]:
337
+ def validate_adapter_config(adapter_name: str, config: dict[str, Any]) -> list[str]:
338
338
  """Convenience function to validate adapter configuration.
339
339
 
340
340
  Args:
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Optional
5
+ from typing import Any
6
6
 
7
7
  from .models import TicketState
8
8
 
@@ -20,7 +20,7 @@ class AdapterError(MCPTicketerError):
20
20
  self,
21
21
  message: str,
22
22
  adapter_name: str,
23
- original_error: Optional[Exception] = None,
23
+ original_error: Exception | None = None,
24
24
  ):
25
25
  """Initialize adapter error.
26
26
 
@@ -55,8 +55,8 @@ class RateLimitError(AdapterError):
55
55
  self,
56
56
  message: str,
57
57
  adapter_name: str,
58
- retry_after: Optional[int] = None,
59
- original_error: Optional[Exception] = None,
58
+ retry_after: int | None = None,
59
+ original_error: Exception | None = None,
60
60
  ):
61
61
  """Initialize rate limit error.
62
62
 
@@ -74,7 +74,7 @@ class RateLimitError(AdapterError):
74
74
  class ValidationError(MCPTicketerError):
75
75
  """Data validation error."""
76
76
 
77
- def __init__(self, message: str, field: Optional[str] = None, value: Any = None):
77
+ def __init__(self, message: str, field: str | None = None, value: Any = None):
78
78
  """Initialize validation error.
79
79
 
80
80
  Args:
@@ -0,0 +1,58 @@
1
+ """MCP server constants and configuration."""
2
+
3
+ # JSON-RPC Protocol
4
+ JSONRPC_VERSION = "2.0"
5
+ MCP_PROTOCOL_VERSION = "2024-11-05"
6
+
7
+ # Server Info
8
+ SERVER_NAME = "mcp-ticketer"
9
+ SERVER_VERSION = "0.3.2"
10
+
11
+ # Status Values
12
+ STATUS_COMPLETED = "completed"
13
+ STATUS_ERROR = "error"
14
+ STATUS_NOT_IMPLEMENTED = "not_implemented"
15
+
16
+ # Error Codes
17
+ ERROR_PARSE = -32700
18
+ ERROR_INVALID_REQUEST = -32600
19
+ ERROR_METHOD_NOT_FOUND = -32601
20
+ ERROR_INVALID_PARAMS = -32602
21
+ ERROR_INTERNAL = -32603
22
+
23
+ # Default Values
24
+ DEFAULT_LIMIT = 10
25
+ DEFAULT_OFFSET = 0
26
+ DEFAULT_PRIORITY = "medium"
27
+ DEFAULT_MAX_DEPTH = 3
28
+ DEFAULT_BASE_PATH = ".aitrackdown"
29
+
30
+ # Response Messages
31
+ MSG_TICKET_NOT_FOUND = "Ticket {ticket_id} not found"
32
+ MSG_UPDATE_FAILED = "Ticket {ticket_id} not found or update failed"
33
+ MSG_TRANSITION_FAILED = "Ticket {ticket_id} not found or transition failed"
34
+ MSG_EPIC_NOT_FOUND = "Epic {epic_id} not found"
35
+ MSG_MISSING_PARENT_ID = "Tasks must have a parent_id (issue identifier)"
36
+ MSG_UNKNOWN_OPERATION = "Unknown comment operation: {operation}"
37
+ MSG_UNKNOWN_METHOD = "Method not found: {method}"
38
+ MSG_INTERNAL_ERROR = "Internal error: {error}"
39
+ MSG_NO_TICKETS_PROVIDED = "No tickets provided for bulk creation"
40
+ MSG_NO_UPDATES_PROVIDED = "No updates provided for bulk operation"
41
+ MSG_MISSING_TITLE = "Ticket {index} missing required 'title' field"
42
+ MSG_MISSING_TICKET_ID = "Update {index} missing required 'ticket_id' field"
43
+ MSG_TICKET_ID_REQUIRED = "ticket_id is required"
44
+ MSG_PR_URL_REQUIRED = "pr_url is required"
45
+ MSG_ATTACHMENT_NOT_IMPLEMENTED = "Attachment functionality not yet implemented"
46
+ MSG_PR_NOT_SUPPORTED = "PR creation not supported for adapter: {adapter}"
47
+ MSG_PR_LINK_NOT_SUPPORTED = "PR linking not supported for adapter: {adapter}"
48
+ MSG_UNKNOWN_TOOL = "Unknown tool: {tool}"
49
+ MSG_GITHUB_CONFIG_REQUIRED = "GitHub owner and repo are required for Linear PR creation"
50
+
51
+ # Attachment Alternative Messages
52
+ ATTACHMENT_ALTERNATIVES = [
53
+ "Add file URLs in comments",
54
+ "Use external file storage",
55
+ ]
56
+ ATTACHMENT_NOT_IMPLEMENTED_REASON = (
57
+ "File attachments require adapter-specific implementation"
58
+ )