mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.23__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 +7 -7
- mcp_ticketer/__version__.py +4 -2
- mcp_ticketer/adapters/__init__.py +4 -4
- mcp_ticketer/adapters/aitrackdown.py +66 -49
- mcp_ticketer/adapters/github.py +192 -125
- mcp_ticketer/adapters/hybrid.py +99 -53
- mcp_ticketer/adapters/jira.py +161 -151
- mcp_ticketer/adapters/linear.py +396 -246
- mcp_ticketer/cache/__init__.py +1 -1
- mcp_ticketer/cache/memory.py +15 -16
- mcp_ticketer/cli/__init__.py +1 -1
- mcp_ticketer/cli/configure.py +69 -93
- mcp_ticketer/cli/discover.py +43 -35
- mcp_ticketer/cli/main.py +283 -298
- mcp_ticketer/cli/mcp_configure.py +39 -15
- mcp_ticketer/cli/migrate_config.py +11 -13
- mcp_ticketer/cli/queue_commands.py +21 -58
- mcp_ticketer/cli/utils.py +121 -66
- mcp_ticketer/core/__init__.py +2 -2
- mcp_ticketer/core/adapter.py +46 -39
- mcp_ticketer/core/config.py +128 -92
- mcp_ticketer/core/env_discovery.py +69 -37
- mcp_ticketer/core/http_client.py +57 -40
- mcp_ticketer/core/mappers.py +98 -54
- mcp_ticketer/core/models.py +38 -24
- mcp_ticketer/core/project_config.py +145 -80
- mcp_ticketer/core/registry.py +16 -16
- mcp_ticketer/mcp/__init__.py +1 -1
- mcp_ticketer/mcp/server.py +199 -145
- mcp_ticketer/queue/__init__.py +2 -2
- mcp_ticketer/queue/__main__.py +1 -1
- mcp_ticketer/queue/manager.py +30 -26
- mcp_ticketer/queue/queue.py +147 -85
- mcp_ticketer/queue/run_worker.py +2 -3
- mcp_ticketer/queue/worker.py +55 -40
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/METADATA +1 -1
- mcp_ticketer-0.1.23.dist-info/RECORD +42 -0
- mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/top_level.txt +0 -0
mcp_ticketer/core/config.py
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
"""Centralized configuration management with caching and validation."""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import json
|
|
5
4
|
import logging
|
|
6
|
-
|
|
7
|
-
from
|
|
5
|
+
import os
|
|
6
|
+
from enum import Enum
|
|
8
7
|
from functools import lru_cache
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Optional, Union
|
|
10
|
+
|
|
9
11
|
import yaml
|
|
10
|
-
from pydantic import BaseModel, Field,
|
|
11
|
-
from enum import Enum
|
|
12
|
+
from pydantic import BaseModel, Field, root_validator, validator
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class AdapterType(str, Enum):
|
|
17
18
|
"""Supported adapter types."""
|
|
19
|
+
|
|
18
20
|
GITHUB = "github"
|
|
19
21
|
JIRA = "jira"
|
|
20
22
|
LINEAR = "linear"
|
|
@@ -23,109 +25,115 @@ class AdapterType(str, Enum):
|
|
|
23
25
|
|
|
24
26
|
class BaseAdapterConfig(BaseModel):
|
|
25
27
|
"""Base configuration for all adapters."""
|
|
28
|
+
|
|
26
29
|
type: AdapterType
|
|
27
30
|
name: Optional[str] = None
|
|
28
31
|
enabled: bool = True
|
|
29
32
|
timeout: float = 30.0
|
|
30
33
|
max_retries: int = 3
|
|
31
|
-
rate_limit: Optional[
|
|
34
|
+
rate_limit: Optional[dict[str, Any]] = None
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
class GitHubConfig(BaseAdapterConfig):
|
|
35
38
|
"""GitHub adapter configuration."""
|
|
39
|
+
|
|
36
40
|
type: AdapterType = AdapterType.GITHUB
|
|
37
|
-
token: Optional[str] = Field(None, env=
|
|
38
|
-
owner: Optional[str] = Field(None, env=
|
|
39
|
-
repo: Optional[str] = Field(None, env=
|
|
41
|
+
token: Optional[str] = Field(None, env="GITHUB_TOKEN")
|
|
42
|
+
owner: Optional[str] = Field(None, env="GITHUB_OWNER")
|
|
43
|
+
repo: Optional[str] = Field(None, env="GITHUB_REPO")
|
|
40
44
|
api_url: str = "https://api.github.com"
|
|
41
45
|
use_projects_v2: bool = False
|
|
42
|
-
custom_priority_scheme: Optional[
|
|
46
|
+
custom_priority_scheme: Optional[dict[str, list[str]]] = None
|
|
43
47
|
|
|
44
|
-
@validator(
|
|
45
|
-
def validate_token(
|
|
48
|
+
@validator("token", pre=True, always=True)
|
|
49
|
+
def validate_token(self, v):
|
|
46
50
|
if not v:
|
|
47
|
-
v = os.getenv(
|
|
51
|
+
v = os.getenv("GITHUB_TOKEN")
|
|
48
52
|
if not v:
|
|
49
|
-
raise ValueError(
|
|
53
|
+
raise ValueError("GitHub token is required")
|
|
50
54
|
return v
|
|
51
55
|
|
|
52
|
-
@validator(
|
|
53
|
-
def validate_owner(
|
|
56
|
+
@validator("owner", pre=True, always=True)
|
|
57
|
+
def validate_owner(self, v):
|
|
54
58
|
if not v:
|
|
55
|
-
v = os.getenv(
|
|
59
|
+
v = os.getenv("GITHUB_OWNER")
|
|
56
60
|
if not v:
|
|
57
|
-
raise ValueError(
|
|
61
|
+
raise ValueError("GitHub owner is required")
|
|
58
62
|
return v
|
|
59
63
|
|
|
60
|
-
@validator(
|
|
61
|
-
def validate_repo(
|
|
64
|
+
@validator("repo", pre=True, always=True)
|
|
65
|
+
def validate_repo(self, v):
|
|
62
66
|
if not v:
|
|
63
|
-
v = os.getenv(
|
|
67
|
+
v = os.getenv("GITHUB_REPO")
|
|
64
68
|
if not v:
|
|
65
|
-
raise ValueError(
|
|
69
|
+
raise ValueError("GitHub repo is required")
|
|
66
70
|
return v
|
|
67
71
|
|
|
68
72
|
|
|
69
73
|
class JiraConfig(BaseAdapterConfig):
|
|
70
74
|
"""JIRA adapter configuration."""
|
|
75
|
+
|
|
71
76
|
type: AdapterType = AdapterType.JIRA
|
|
72
|
-
server: Optional[str] = Field(None, env=
|
|
73
|
-
email: Optional[str] = Field(None, env=
|
|
74
|
-
api_token: Optional[str] = Field(None, env=
|
|
75
|
-
project_key: Optional[str] = Field(None, env=
|
|
77
|
+
server: Optional[str] = Field(None, env="JIRA_SERVER")
|
|
78
|
+
email: Optional[str] = Field(None, env="JIRA_EMAIL")
|
|
79
|
+
api_token: Optional[str] = Field(None, env="JIRA_API_TOKEN")
|
|
80
|
+
project_key: Optional[str] = Field(None, env="JIRA_PROJECT_KEY")
|
|
76
81
|
cloud: bool = True
|
|
77
82
|
verify_ssl: bool = True
|
|
78
83
|
|
|
79
|
-
@validator(
|
|
80
|
-
def validate_server(
|
|
84
|
+
@validator("server", pre=True, always=True)
|
|
85
|
+
def validate_server(self, v):
|
|
81
86
|
if not v:
|
|
82
|
-
v = os.getenv(
|
|
87
|
+
v = os.getenv("JIRA_SERVER")
|
|
83
88
|
if not v:
|
|
84
|
-
raise ValueError(
|
|
85
|
-
return v.rstrip(
|
|
89
|
+
raise ValueError("JIRA server URL is required")
|
|
90
|
+
return v.rstrip("/")
|
|
86
91
|
|
|
87
|
-
@validator(
|
|
88
|
-
def validate_email(
|
|
92
|
+
@validator("email", pre=True, always=True)
|
|
93
|
+
def validate_email(self, v):
|
|
89
94
|
if not v:
|
|
90
|
-
v = os.getenv(
|
|
95
|
+
v = os.getenv("JIRA_EMAIL")
|
|
91
96
|
if not v:
|
|
92
|
-
raise ValueError(
|
|
97
|
+
raise ValueError("JIRA email is required")
|
|
93
98
|
return v
|
|
94
99
|
|
|
95
|
-
@validator(
|
|
96
|
-
def validate_api_token(
|
|
100
|
+
@validator("api_token", pre=True, always=True)
|
|
101
|
+
def validate_api_token(self, v):
|
|
97
102
|
if not v:
|
|
98
|
-
v = os.getenv(
|
|
103
|
+
v = os.getenv("JIRA_API_TOKEN")
|
|
99
104
|
if not v:
|
|
100
|
-
raise ValueError(
|
|
105
|
+
raise ValueError("JIRA API token is required")
|
|
101
106
|
return v
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
class LinearConfig(BaseAdapterConfig):
|
|
105
110
|
"""Linear adapter configuration."""
|
|
111
|
+
|
|
106
112
|
type: AdapterType = AdapterType.LINEAR
|
|
107
|
-
api_key: Optional[str] = Field(None, env=
|
|
113
|
+
api_key: Optional[str] = Field(None, env="LINEAR_API_KEY")
|
|
108
114
|
workspace: Optional[str] = None
|
|
109
115
|
team_key: str
|
|
110
116
|
api_url: str = "https://api.linear.app/graphql"
|
|
111
117
|
|
|
112
|
-
@validator(
|
|
113
|
-
def validate_api_key(
|
|
118
|
+
@validator("api_key", pre=True, always=True)
|
|
119
|
+
def validate_api_key(self, v):
|
|
114
120
|
if not v:
|
|
115
|
-
v = os.getenv(
|
|
121
|
+
v = os.getenv("LINEAR_API_KEY")
|
|
116
122
|
if not v:
|
|
117
|
-
raise ValueError(
|
|
123
|
+
raise ValueError("Linear API key is required")
|
|
118
124
|
return v
|
|
119
125
|
|
|
120
126
|
|
|
121
127
|
class AITrackdownConfig(BaseAdapterConfig):
|
|
122
128
|
"""AITrackdown adapter configuration."""
|
|
129
|
+
|
|
123
130
|
type: AdapterType = AdapterType.AITRACKDOWN
|
|
124
131
|
# AITrackdown uses local storage, minimal config needed
|
|
125
132
|
|
|
126
133
|
|
|
127
134
|
class QueueConfig(BaseModel):
|
|
128
135
|
"""Queue configuration."""
|
|
136
|
+
|
|
129
137
|
provider: str = "sqlite"
|
|
130
138
|
connection_string: Optional[str] = None
|
|
131
139
|
batch_size: int = 10
|
|
@@ -136,6 +144,7 @@ class QueueConfig(BaseModel):
|
|
|
136
144
|
|
|
137
145
|
class LoggingConfig(BaseModel):
|
|
138
146
|
"""Logging configuration."""
|
|
147
|
+
|
|
139
148
|
level: str = "INFO"
|
|
140
149
|
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
141
150
|
file: Optional[str] = None
|
|
@@ -145,25 +154,30 @@ class LoggingConfig(BaseModel):
|
|
|
145
154
|
|
|
146
155
|
class AppConfig(BaseModel):
|
|
147
156
|
"""Main application configuration."""
|
|
148
|
-
|
|
157
|
+
|
|
158
|
+
adapters: dict[
|
|
159
|
+
str, Union[GitHubConfig, JiraConfig, LinearConfig, AITrackdownConfig]
|
|
160
|
+
] = {}
|
|
149
161
|
queue: QueueConfig = QueueConfig()
|
|
150
162
|
logging: LoggingConfig = LoggingConfig()
|
|
151
163
|
cache_ttl: int = 300 # Cache TTL in seconds
|
|
152
164
|
default_adapter: Optional[str] = None
|
|
153
165
|
|
|
154
|
-
@root_validator
|
|
155
|
-
def validate_adapters(
|
|
166
|
+
@root_validator(skip_on_failure=True)
|
|
167
|
+
def validate_adapters(self, values):
|
|
156
168
|
"""Validate adapter configurations."""
|
|
157
|
-
adapters = values.get(
|
|
169
|
+
adapters = values.get("adapters", {})
|
|
158
170
|
|
|
159
171
|
if not adapters:
|
|
160
172
|
logger.warning("No adapters configured")
|
|
161
173
|
return values
|
|
162
174
|
|
|
163
175
|
# Validate default adapter
|
|
164
|
-
default_adapter = values.get(
|
|
176
|
+
default_adapter = values.get("default_adapter")
|
|
165
177
|
if default_adapter and default_adapter not in adapters:
|
|
166
|
-
raise ValueError(
|
|
178
|
+
raise ValueError(
|
|
179
|
+
f"Default adapter '{default_adapter}' not found in adapters"
|
|
180
|
+
)
|
|
167
181
|
|
|
168
182
|
return values
|
|
169
183
|
|
|
@@ -171,19 +185,21 @@ class AppConfig(BaseModel):
|
|
|
171
185
|
"""Get configuration for a specific adapter."""
|
|
172
186
|
return self.adapters.get(adapter_name)
|
|
173
187
|
|
|
174
|
-
def get_enabled_adapters(self) ->
|
|
188
|
+
def get_enabled_adapters(self) -> dict[str, BaseAdapterConfig]:
|
|
175
189
|
"""Get all enabled adapters."""
|
|
176
|
-
return {
|
|
190
|
+
return {
|
|
191
|
+
name: config for name, config in self.adapters.items() if config.enabled
|
|
192
|
+
}
|
|
177
193
|
|
|
178
194
|
|
|
179
195
|
class ConfigurationManager:
|
|
180
196
|
"""Centralized configuration management with caching and validation."""
|
|
181
197
|
|
|
182
|
-
_instance: Optional[
|
|
198
|
+
_instance: Optional["ConfigurationManager"] = None
|
|
183
199
|
_config: Optional[AppConfig] = None
|
|
184
|
-
_config_file_paths:
|
|
200
|
+
_config_file_paths: list[Path] = []
|
|
185
201
|
|
|
186
|
-
def __new__(cls) ->
|
|
202
|
+
def __new__(cls) -> "ConfigurationManager":
|
|
187
203
|
"""Singleton pattern for global config access."""
|
|
188
204
|
if cls._instance is None:
|
|
189
205
|
cls._instance = super().__new__(cls)
|
|
@@ -191,26 +207,48 @@ class ConfigurationManager:
|
|
|
191
207
|
|
|
192
208
|
def __init__(self):
|
|
193
209
|
"""Initialize configuration manager."""
|
|
194
|
-
if not hasattr(self,
|
|
210
|
+
if not hasattr(self, "_initialized"):
|
|
195
211
|
self._initialized = True
|
|
196
|
-
self._config_cache:
|
|
212
|
+
self._config_cache: dict[str, Any] = {}
|
|
197
213
|
self._find_config_files()
|
|
198
214
|
|
|
199
215
|
def _find_config_files(self) -> None:
|
|
200
|
-
"""Find configuration files in
|
|
216
|
+
"""Find configuration files in project-local directory ONLY.
|
|
217
|
+
|
|
218
|
+
SECURITY: This method ONLY searches in the current project directory
|
|
219
|
+
to prevent configuration leakage across projects. It will NEVER read
|
|
220
|
+
from user home directory or system-wide locations.
|
|
221
|
+
"""
|
|
222
|
+
# ONLY search in current project directory, never external locations
|
|
201
223
|
possible_paths = [
|
|
202
|
-
Path.cwd() / "mcp-ticketer.
|
|
203
|
-
Path.cwd() / "mcp-ticketer.
|
|
204
|
-
Path.cwd() / "
|
|
205
|
-
Path.cwd() / "config.
|
|
206
|
-
Path.
|
|
207
|
-
Path.home() / ".mcp-ticketer.yml",
|
|
208
|
-
Path("/etc/mcp-ticketer/config.yaml"),
|
|
209
|
-
Path("/etc/mcp-ticketer/config.yml"),
|
|
224
|
+
Path.cwd() / ".mcp-ticketer" / "config.json", # Primary JSON config
|
|
225
|
+
Path.cwd() / "mcp-ticketer.yaml", # Alternative YAML
|
|
226
|
+
Path.cwd() / "mcp-ticketer.yml", # Alternative YML
|
|
227
|
+
Path.cwd() / "config.yaml", # Generic YAML
|
|
228
|
+
Path.cwd() / "config.yml", # Generic YML
|
|
210
229
|
]
|
|
211
230
|
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
# Validate all paths are within project (security check)
|
|
232
|
+
validated_paths = []
|
|
233
|
+
for path in possible_paths:
|
|
234
|
+
if path.exists():
|
|
235
|
+
try:
|
|
236
|
+
if path.resolve().is_relative_to(Path.cwd().resolve()):
|
|
237
|
+
validated_paths.append(path)
|
|
238
|
+
else:
|
|
239
|
+
logger.warning(
|
|
240
|
+
f"Security: Ignoring config file outside project: {path}"
|
|
241
|
+
)
|
|
242
|
+
except (ValueError, RuntimeError):
|
|
243
|
+
# is_relative_to may raise ValueError in some cases
|
|
244
|
+
# Skip this file if we can't validate it
|
|
245
|
+
logger.warning(f"Could not validate config file path: {path}")
|
|
246
|
+
|
|
247
|
+
self._config_file_paths = validated_paths
|
|
248
|
+
if self._config_file_paths:
|
|
249
|
+
logger.debug(f"Found project-local config files: {self._config_file_paths}")
|
|
250
|
+
else:
|
|
251
|
+
logger.debug("No project-local config files found, will use defaults")
|
|
214
252
|
|
|
215
253
|
@lru_cache(maxsize=1)
|
|
216
254
|
def load_config(self, config_file: Optional[Union[str, Path]] = None) -> AppConfig:
|
|
@@ -221,6 +259,7 @@ class ConfigurationManager:
|
|
|
221
259
|
|
|
222
260
|
Returns:
|
|
223
261
|
Validated application configuration
|
|
262
|
+
|
|
224
263
|
"""
|
|
225
264
|
if self._config is not None and config_file is None:
|
|
226
265
|
return self._config
|
|
@@ -253,7 +292,9 @@ class ConfigurationManager:
|
|
|
253
292
|
elif adapter_type == "aitrackdown":
|
|
254
293
|
parsed_adapters[name] = AITrackdownConfig(**adapter_config)
|
|
255
294
|
else:
|
|
256
|
-
logger.warning(
|
|
295
|
+
logger.warning(
|
|
296
|
+
f"Unknown adapter type: {adapter_type} for adapter: {name}"
|
|
297
|
+
)
|
|
257
298
|
|
|
258
299
|
config_data["adapters"] = parsed_adapters
|
|
259
300
|
|
|
@@ -261,13 +302,13 @@ class ConfigurationManager:
|
|
|
261
302
|
self._config = AppConfig(**config_data)
|
|
262
303
|
return self._config
|
|
263
304
|
|
|
264
|
-
def _load_config_file(self, config_path: Path) ->
|
|
305
|
+
def _load_config_file(self, config_path: Path) -> dict[str, Any]:
|
|
265
306
|
"""Load configuration from YAML or JSON file."""
|
|
266
307
|
try:
|
|
267
|
-
with open(config_path,
|
|
268
|
-
if config_path.suffix.lower() in [
|
|
308
|
+
with open(config_path, encoding="utf-8") as file:
|
|
309
|
+
if config_path.suffix.lower() in [".yaml", ".yml"]:
|
|
269
310
|
return yaml.safe_load(file) or {}
|
|
270
|
-
elif config_path.suffix.lower() ==
|
|
311
|
+
elif config_path.suffix.lower() == ".json":
|
|
271
312
|
return json.load(file)
|
|
272
313
|
else:
|
|
273
314
|
# Try YAML first, then JSON
|
|
@@ -291,7 +332,7 @@ class ConfigurationManager:
|
|
|
291
332
|
config = self.get_config()
|
|
292
333
|
return config.get_adapter_config(adapter_name)
|
|
293
334
|
|
|
294
|
-
def get_enabled_adapters(self) ->
|
|
335
|
+
def get_enabled_adapters(self) -> dict[str, BaseAdapterConfig]:
|
|
295
336
|
"""Get all enabled adapter configurations."""
|
|
296
337
|
config = self.get_config()
|
|
297
338
|
return config.get_enabled_adapters()
|
|
@@ -306,7 +347,9 @@ class ConfigurationManager:
|
|
|
306
347
|
config = self.get_config()
|
|
307
348
|
return config.logging
|
|
308
349
|
|
|
309
|
-
def reload_config(
|
|
350
|
+
def reload_config(
|
|
351
|
+
self, config_file: Optional[Union[str, Path]] = None
|
|
352
|
+
) -> AppConfig:
|
|
310
353
|
"""Reload configuration from file."""
|
|
311
354
|
# Clear cache
|
|
312
355
|
self.load_config.cache_clear()
|
|
@@ -327,7 +370,7 @@ class ConfigurationManager:
|
|
|
327
370
|
|
|
328
371
|
# Parse nested keys like "queue.batch_size"
|
|
329
372
|
config = self.get_config()
|
|
330
|
-
parts = key.split(
|
|
373
|
+
parts = key.split(".")
|
|
331
374
|
value = config.dict()
|
|
332
375
|
|
|
333
376
|
for part in parts:
|
|
@@ -348,13 +391,13 @@ class ConfigurationManager:
|
|
|
348
391
|
"token": "${GITHUB_TOKEN}",
|
|
349
392
|
"owner": "your-org",
|
|
350
393
|
"repo": "your-repo",
|
|
351
|
-
"enabled": True
|
|
394
|
+
"enabled": True,
|
|
352
395
|
},
|
|
353
396
|
"linear-dev": {
|
|
354
397
|
"type": "linear",
|
|
355
398
|
"api_key": "${LINEAR_API_KEY}",
|
|
356
399
|
"team_key": "DEV",
|
|
357
|
-
"enabled": True
|
|
400
|
+
"enabled": True,
|
|
358
401
|
},
|
|
359
402
|
"jira-support": {
|
|
360
403
|
"type": "jira",
|
|
@@ -362,23 +405,16 @@ class ConfigurationManager:
|
|
|
362
405
|
"email": "${JIRA_EMAIL}",
|
|
363
406
|
"api_token": "${JIRA_API_TOKEN}",
|
|
364
407
|
"project_key": "SUPPORT",
|
|
365
|
-
"enabled": False
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
"queue": {
|
|
369
|
-
"provider": "sqlite",
|
|
370
|
-
"batch_size": 10,
|
|
371
|
-
"max_concurrent": 5
|
|
372
|
-
},
|
|
373
|
-
"logging": {
|
|
374
|
-
"level": "INFO",
|
|
375
|
-
"file": "mcp-ticketer.log"
|
|
408
|
+
"enabled": False,
|
|
409
|
+
},
|
|
376
410
|
},
|
|
377
|
-
"
|
|
411
|
+
"queue": {"provider": "sqlite", "batch_size": 10, "max_concurrent": 5},
|
|
412
|
+
"logging": {"level": "INFO", "file": "mcp-ticketer.log"},
|
|
413
|
+
"default_adapter": "github-main",
|
|
378
414
|
}
|
|
379
415
|
|
|
380
416
|
output_path = Path(output_path)
|
|
381
|
-
with open(output_path,
|
|
417
|
+
with open(output_path, "w", encoding="utf-8") as file:
|
|
382
418
|
yaml.dump(sample_config, file, default_flow_style=False, indent=2)
|
|
383
419
|
|
|
384
420
|
logger.info(f"Sample configuration created at: {output_path}")
|
|
@@ -400,4 +436,4 @@ def get_adapter_config(adapter_name: str) -> Optional[BaseAdapterConfig]:
|
|
|
400
436
|
|
|
401
437
|
def reload_config(config_file: Optional[Union[str, Path]] = None) -> AppConfig:
|
|
402
438
|
"""Reload the global configuration."""
|
|
403
|
-
return config_manager.reload_config(config_file)
|
|
439
|
+
return config_manager.reload_config(config_file)
|