onetool-mcp 1.0.0b1__py3-none-any.whl → 1.0.0rc2__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.
- onetool/cli.py +63 -4
- onetool_mcp-1.0.0rc2.dist-info/METADATA +266 -0
- onetool_mcp-1.0.0rc2.dist-info/RECORD +129 -0
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/LICENSE.txt +1 -1
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/NOTICE.txt +54 -64
- ot/__main__.py +6 -6
- ot/config/__init__.py +48 -46
- ot/config/global_templates/__init__.py +2 -2
- ot/config/{defaults → global_templates}/diagram-templates/api-flow.mmd +33 -33
- ot/config/{defaults → global_templates}/diagram-templates/c4-context.puml +30 -30
- ot/config/{defaults → global_templates}/diagram-templates/class-diagram.mmd +87 -87
- ot/config/{defaults → global_templates}/diagram-templates/feature-mindmap.mmd +70 -70
- ot/config/{defaults → global_templates}/diagram-templates/microservices.d2 +81 -81
- ot/config/{defaults → global_templates}/diagram-templates/project-gantt.mmd +37 -37
- ot/config/{defaults → global_templates}/diagram-templates/state-machine.mmd +42 -42
- ot/config/global_templates/diagram.yaml +167 -0
- ot/config/global_templates/onetool.yaml +3 -1
- ot/config/{defaults → global_templates}/prompts.yaml +102 -97
- ot/config/global_templates/security.yaml +31 -0
- ot/config/global_templates/servers.yaml +93 -12
- ot/config/global_templates/snippets.yaml +5 -26
- ot/config/{defaults → global_templates}/tool_templates/__init__.py +7 -7
- ot/config/loader.py +221 -105
- ot/config/mcp.py +5 -1
- ot/config/secrets.py +192 -190
- ot/decorators.py +116 -116
- ot/executor/__init__.py +35 -35
- ot/executor/base.py +16 -16
- ot/executor/fence_processor.py +83 -83
- ot/executor/linter.py +142 -142
- ot/executor/pep723.py +288 -288
- ot/executor/runner.py +20 -6
- ot/executor/simple.py +163 -163
- ot/executor/validator.py +603 -164
- ot/http_client.py +145 -145
- ot/logging/__init__.py +37 -37
- ot/logging/entry.py +213 -213
- ot/logging/format.py +191 -188
- ot/logging/span.py +349 -349
- ot/meta.py +236 -14
- ot/paths.py +32 -49
- ot/prompts.py +218 -218
- ot/proxy/manager.py +14 -2
- ot/registry/__init__.py +189 -189
- ot/registry/parser.py +269 -269
- ot/server.py +330 -315
- ot/shortcuts/__init__.py +15 -15
- ot/shortcuts/aliases.py +87 -87
- ot/shortcuts/snippets.py +258 -258
- ot/stats/__init__.py +35 -35
- ot/stats/html.py +2 -2
- ot/stats/reader.py +354 -354
- ot/stats/timing.py +57 -57
- ot/support.py +63 -63
- ot/tools.py +1 -1
- ot/utils/batch.py +161 -161
- ot/utils/cache.py +120 -120
- ot/utils/exceptions.py +23 -23
- ot/utils/factory.py +178 -179
- ot/utils/format.py +65 -65
- ot/utils/http.py +202 -202
- ot/utils/platform.py +45 -45
- ot/utils/truncate.py +69 -69
- ot_tools/__init__.py +4 -4
- ot_tools/_convert/__init__.py +12 -12
- ot_tools/_convert/pdf.py +254 -254
- ot_tools/diagram.yaml +167 -167
- ot_tools/scaffold.py +2 -2
- ot_tools/transform.py +124 -19
- ot_tools/web_fetch.py +94 -43
- onetool_mcp-1.0.0b1.dist-info/METADATA +0 -163
- onetool_mcp-1.0.0b1.dist-info/RECORD +0 -132
- ot/config/defaults/bench.yaml +0 -4
- ot/config/defaults/onetool.yaml +0 -25
- ot/config/defaults/servers.yaml +0 -7
- ot/config/defaults/snippets.yaml +0 -4
- ot_tools/firecrawl.py +0 -732
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/WHEEL +0 -0
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/entry_points.txt +0 -0
- /ot/config/{defaults → global_templates}/tool_templates/extension.py +0 -0
- /ot/config/{defaults → global_templates}/tool_templates/isolated.py +0 -0
ot/config/loader.py
CHANGED
|
@@ -41,7 +41,6 @@ from pydantic import (
|
|
|
41
41
|
from ot.config.mcp import McpServerConfig, expand_secrets
|
|
42
42
|
from ot.paths import (
|
|
43
43
|
CONFIG_SUBDIR,
|
|
44
|
-
get_bundled_config_dir,
|
|
45
44
|
get_config_dir,
|
|
46
45
|
get_effective_cwd,
|
|
47
46
|
get_global_dir,
|
|
@@ -51,6 +50,14 @@ from ot.paths import (
|
|
|
51
50
|
CURRENT_CONFIG_VERSION = 1
|
|
52
51
|
|
|
53
52
|
|
|
53
|
+
class ConfigNotFoundError(Exception):
|
|
54
|
+
"""Raised when configuration file is not found and no fallback is available.
|
|
55
|
+
|
|
56
|
+
This error indicates that OneTool has not been initialized. Users should run
|
|
57
|
+
`onetool init` to create the global configuration directory.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
54
61
|
class TransformConfig(BaseModel):
|
|
55
62
|
"""Configuration for the transform() tool."""
|
|
56
63
|
|
|
@@ -129,41 +136,137 @@ class OutputSanitizationConfig(BaseModel):
|
|
|
129
136
|
)
|
|
130
137
|
|
|
131
138
|
|
|
139
|
+
def _flatten_nested_list(items: list[Any]) -> list[str]:
|
|
140
|
+
"""Flatten nested lists/arrays into a single list of strings.
|
|
141
|
+
|
|
142
|
+
Supports the compact array format in security.yaml:
|
|
143
|
+
allow:
|
|
144
|
+
- [str, int, float] # Grouped for readability
|
|
145
|
+
- print # Single item
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
items: List that may contain strings or nested lists
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Flattened list of strings
|
|
152
|
+
"""
|
|
153
|
+
result: list[str] = []
|
|
154
|
+
for item in items:
|
|
155
|
+
if isinstance(item, list):
|
|
156
|
+
result.extend(str(x) for x in item)
|
|
157
|
+
else:
|
|
158
|
+
result.append(str(item))
|
|
159
|
+
return result
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class BuiltinsConfig(BaseModel):
|
|
163
|
+
"""Builtins allowlist configuration."""
|
|
164
|
+
|
|
165
|
+
allow: list[str] = Field(
|
|
166
|
+
default_factory=list,
|
|
167
|
+
description="Allowed builtin functions and types",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@field_validator("allow", mode="before")
|
|
171
|
+
@classmethod
|
|
172
|
+
def flatten_allow(cls, v: Any) -> list[str]:
|
|
173
|
+
"""Flatten nested arrays in allow list."""
|
|
174
|
+
if isinstance(v, list):
|
|
175
|
+
return _flatten_nested_list(v)
|
|
176
|
+
return v if v else []
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class ImportsConfig(BaseModel):
|
|
180
|
+
"""Imports allowlist configuration."""
|
|
181
|
+
|
|
182
|
+
allow: list[str] = Field(
|
|
183
|
+
default_factory=list,
|
|
184
|
+
description="Allowed import modules",
|
|
185
|
+
)
|
|
186
|
+
warn: list[str] = Field(
|
|
187
|
+
default_factory=list,
|
|
188
|
+
description="Imports that trigger warnings but are allowed",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@field_validator("allow", "warn", mode="before")
|
|
192
|
+
@classmethod
|
|
193
|
+
def flatten_lists(cls, v: Any) -> list[str]:
|
|
194
|
+
"""Flatten nested arrays in lists."""
|
|
195
|
+
if isinstance(v, list):
|
|
196
|
+
return _flatten_nested_list(v)
|
|
197
|
+
return v if v else []
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class CallsConfig(BaseModel):
|
|
201
|
+
"""Qualified calls configuration."""
|
|
202
|
+
|
|
203
|
+
allow: list[str] = Field(
|
|
204
|
+
default_factory=list,
|
|
205
|
+
description="Allowed qualified function calls (e.g., 'json.loads')",
|
|
206
|
+
)
|
|
207
|
+
block: list[str] = Field(
|
|
208
|
+
default_factory=list,
|
|
209
|
+
description="Blocked qualified function calls (e.g., 'pickle.*')",
|
|
210
|
+
)
|
|
211
|
+
warn: list[str] = Field(
|
|
212
|
+
default_factory=list,
|
|
213
|
+
description="Qualified calls that trigger warnings",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@field_validator("allow", "block", "warn", mode="before")
|
|
217
|
+
@classmethod
|
|
218
|
+
def flatten_lists(cls, v: Any) -> list[str]:
|
|
219
|
+
"""Flatten nested arrays in lists."""
|
|
220
|
+
if isinstance(v, list):
|
|
221
|
+
return _flatten_nested_list(v)
|
|
222
|
+
return v if v else []
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class DundersConfig(BaseModel):
|
|
226
|
+
"""Magic variable (dunder) configuration."""
|
|
227
|
+
|
|
228
|
+
allow: list[str] = Field(
|
|
229
|
+
default_factory=list,
|
|
230
|
+
description="Allowed magic variables (e.g., '__format__')",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
@field_validator("allow", mode="before")
|
|
234
|
+
@classmethod
|
|
235
|
+
def flatten_allow(cls, v: Any) -> list[str]:
|
|
236
|
+
"""Flatten nested arrays in allow list."""
|
|
237
|
+
if isinstance(v, list):
|
|
238
|
+
return _flatten_nested_list(v)
|
|
239
|
+
return v if v else []
|
|
240
|
+
|
|
241
|
+
|
|
132
242
|
class SecurityConfig(BaseModel):
|
|
133
243
|
"""Code validation security configuration.
|
|
134
244
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
- warned: Log warning but allow execution
|
|
138
|
-
- blocked: Reject with error (lowest priority)
|
|
245
|
+
Allowlist-based security model: block everything by default, explicitly
|
|
246
|
+
allow what's safe. Tool namespaces (ot.*, brave.*, etc.) are auto-allowed.
|
|
139
247
|
|
|
140
|
-
|
|
248
|
+
Configuration structure:
|
|
249
|
+
security:
|
|
250
|
+
builtins:
|
|
251
|
+
allow: [str, int, list, ...]
|
|
252
|
+
imports:
|
|
253
|
+
allow: [json, re, math, ...]
|
|
254
|
+
warn: [yaml]
|
|
255
|
+
calls:
|
|
256
|
+
block: [pickle.*, yaml.load]
|
|
257
|
+
warn: [random.seed]
|
|
258
|
+
dunders:
|
|
259
|
+
allow: [__format__, __sanitize__]
|
|
141
260
|
|
|
142
261
|
Patterns support fnmatch wildcards:
|
|
143
262
|
- '*' matches any characters (e.g., 'subprocess.*' matches 'subprocess.run')
|
|
144
263
|
- '?' matches a single character
|
|
145
264
|
- '[seq]' matches any character in seq
|
|
146
265
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- Patterns WITH dots (e.g., 'subprocess.*', 'os.system') match:
|
|
152
|
-
* Qualified function calls: subprocess.run(), os.system()
|
|
153
|
-
|
|
154
|
-
Configuration behavior:
|
|
155
|
-
- blocked/warned lists EXTEND the built-in defaults (additive)
|
|
156
|
-
- Adding a pattern to 'warned' downgrades it from blocked (if in defaults)
|
|
157
|
-
- Use 'allow' list to exempt specific patterns entirely (no warning)
|
|
158
|
-
|
|
159
|
-
Example configurations:
|
|
160
|
-
# Air-gapped mode - block network tools
|
|
161
|
-
security:
|
|
162
|
-
block: [brave.*, web_fetch.*, context7.*]
|
|
163
|
-
|
|
164
|
-
# Trust file ops
|
|
165
|
-
security:
|
|
166
|
-
allow: [file.*]
|
|
266
|
+
Compact array format for readability:
|
|
267
|
+
allow:
|
|
268
|
+
- [str, int, float] # Grouped items
|
|
269
|
+
- print # Single item
|
|
167
270
|
"""
|
|
168
271
|
|
|
169
272
|
validate_code: bool = Field(
|
|
@@ -176,24 +279,25 @@ class SecurityConfig(BaseModel):
|
|
|
176
279
|
description="Enable security pattern checking (requires validate_code)",
|
|
177
280
|
)
|
|
178
281
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
description="Additional patterns to block (extends defaults, not replaces)",
|
|
282
|
+
# New category-based allowlist configuration
|
|
283
|
+
builtins: BuiltinsConfig = Field(
|
|
284
|
+
default_factory=BuiltinsConfig,
|
|
285
|
+
description="Builtins allowlist configuration",
|
|
184
286
|
)
|
|
185
287
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
description="Additional patterns to warn on (extends defaults, not replaces)",
|
|
288
|
+
imports: ImportsConfig = Field(
|
|
289
|
+
default_factory=ImportsConfig,
|
|
290
|
+
description="Imports allowlist configuration",
|
|
190
291
|
)
|
|
191
292
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
293
|
+
calls: CallsConfig = Field(
|
|
294
|
+
default_factory=CallsConfig,
|
|
295
|
+
description="Qualified calls configuration",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
dunders: DundersConfig = Field(
|
|
299
|
+
default_factory=DundersConfig,
|
|
300
|
+
description="Magic variable (dunder) configuration",
|
|
197
301
|
)
|
|
198
302
|
|
|
199
303
|
# Output sanitization configuration
|
|
@@ -202,6 +306,34 @@ class SecurityConfig(BaseModel):
|
|
|
202
306
|
description="Output sanitization for prompt injection protection",
|
|
203
307
|
)
|
|
204
308
|
|
|
309
|
+
def get_allowed_builtins(self) -> frozenset[str]:
|
|
310
|
+
"""Get the set of allowed builtins."""
|
|
311
|
+
return frozenset(self.builtins.allow)
|
|
312
|
+
|
|
313
|
+
def get_allowed_imports(self) -> frozenset[str]:
|
|
314
|
+
"""Get the set of allowed imports."""
|
|
315
|
+
return frozenset(self.imports.allow)
|
|
316
|
+
|
|
317
|
+
def get_warned_imports(self) -> frozenset[str]:
|
|
318
|
+
"""Get the set of imports that trigger warnings."""
|
|
319
|
+
return frozenset(self.imports.warn)
|
|
320
|
+
|
|
321
|
+
def get_blocked_calls(self) -> frozenset[str]:
|
|
322
|
+
"""Get the set of blocked qualified calls."""
|
|
323
|
+
return frozenset(self.calls.block)
|
|
324
|
+
|
|
325
|
+
def get_warned_calls(self) -> frozenset[str]:
|
|
326
|
+
"""Get the set of qualified calls that trigger warnings."""
|
|
327
|
+
return frozenset(self.calls.warn)
|
|
328
|
+
|
|
329
|
+
def get_allowed_calls(self) -> frozenset[str]:
|
|
330
|
+
"""Get the set of explicitly allowed qualified calls."""
|
|
331
|
+
return frozenset(self.calls.allow)
|
|
332
|
+
|
|
333
|
+
def get_allowed_dunders(self) -> frozenset[str]:
|
|
334
|
+
"""Get the set of allowed magic variables."""
|
|
335
|
+
return frozenset(self.dunders.allow)
|
|
336
|
+
|
|
205
337
|
|
|
206
338
|
class OutputConfig(BaseModel):
|
|
207
339
|
"""Large output handling configuration.
|
|
@@ -310,14 +442,12 @@ class OneToolConfig(BaseModel):
|
|
|
310
442
|
description="Config schema version for migration support",
|
|
311
443
|
)
|
|
312
444
|
|
|
313
|
-
inherit: Literal["global", "
|
|
445
|
+
inherit: Literal["global", "none"] = Field(
|
|
314
446
|
default="global",
|
|
315
447
|
description=(
|
|
316
448
|
"Config inheritance mode:\n"
|
|
317
|
-
" - 'global' (default): Merge ~/.onetool/onetool.yaml first
|
|
318
|
-
"
|
|
319
|
-
" - 'bundled': Merge package defaults only, skip global config. "
|
|
320
|
-
"Use for reproducible configs that shouldn't depend on user settings.\n"
|
|
449
|
+
" - 'global' (default): Merge ~/.onetool/onetool.yaml first. "
|
|
450
|
+
"Use for project configs that extend user prefs.\n"
|
|
321
451
|
" - 'none': Standalone config with no inheritance. "
|
|
322
452
|
"Use for fully self-contained configs."
|
|
323
453
|
),
|
|
@@ -699,12 +829,19 @@ def _validate_version(data: dict[str, Any], config_path: Path) -> None:
|
|
|
699
829
|
f"Add 'version: {CURRENT_CONFIG_VERSION}' to {config_path}"
|
|
700
830
|
)
|
|
701
831
|
data["version"] = 1
|
|
702
|
-
|
|
832
|
+
config_version = 1
|
|
833
|
+
|
|
834
|
+
if config_version > CURRENT_CONFIG_VERSION:
|
|
703
835
|
raise ValueError(
|
|
704
836
|
f"Config version {config_version} is not supported. "
|
|
705
837
|
f"Maximum supported version is {CURRENT_CONFIG_VERSION}. "
|
|
706
838
|
f"Please upgrade OneTool: uv tool upgrade onetool"
|
|
707
839
|
)
|
|
840
|
+
elif config_version < CURRENT_CONFIG_VERSION:
|
|
841
|
+
logger.warning(
|
|
842
|
+
f"Config version {config_version} is outdated (current: {CURRENT_CONFIG_VERSION}). "
|
|
843
|
+
f"Run 'onetool init reset' to update config templates."
|
|
844
|
+
)
|
|
708
845
|
|
|
709
846
|
|
|
710
847
|
def _remove_legacy_fields(data: dict[str, Any]) -> None:
|
|
@@ -720,17 +857,16 @@ def _remove_legacy_fields(data: dict[str, Any]) -> None:
|
|
|
720
857
|
|
|
721
858
|
|
|
722
859
|
def _resolve_include_path(include_path_str: str, ot_dir: Path) -> Path | None:
|
|
723
|
-
"""Resolve an include path using
|
|
860
|
+
"""Resolve an include path using two-tier fallback.
|
|
724
861
|
|
|
725
862
|
Search order:
|
|
726
863
|
1. ot_dir (project .onetool/ or wherever the config's OT_DIR is)
|
|
727
864
|
2. global (~/.onetool/)
|
|
728
|
-
3. bundled (package defaults)
|
|
729
865
|
|
|
730
866
|
Supports:
|
|
731
867
|
- Absolute paths (used as-is)
|
|
732
868
|
- ~ expansion (expands to home directory)
|
|
733
|
-
- Relative paths (searched in
|
|
869
|
+
- Relative paths (searched in two-tier order)
|
|
734
870
|
|
|
735
871
|
Args:
|
|
736
872
|
include_path_str: Path string from include directive
|
|
@@ -761,18 +897,7 @@ def _resolve_include_path(include_path_str: str, ot_dir: Path) -> Path | None:
|
|
|
761
897
|
logger.debug(f"Include resolved (global): {tier2}")
|
|
762
898
|
return tier2
|
|
763
899
|
|
|
764
|
-
|
|
765
|
-
try:
|
|
766
|
-
bundled_dir = get_bundled_config_dir()
|
|
767
|
-
tier3 = (bundled_dir / include_path).resolve()
|
|
768
|
-
if tier3.exists():
|
|
769
|
-
logger.debug(f"Include resolved (bundled): {tier3}")
|
|
770
|
-
return tier3
|
|
771
|
-
except FileNotFoundError:
|
|
772
|
-
# Bundled defaults not available
|
|
773
|
-
pass
|
|
774
|
-
|
|
775
|
-
logger.debug(f"Include not found in any tier: {include_path_str}")
|
|
900
|
+
logger.debug(f"Include not found: {include_path_str}")
|
|
776
901
|
return None
|
|
777
902
|
|
|
778
903
|
|
|
@@ -782,6 +907,7 @@ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any
|
|
|
782
907
|
- Nested dicts are recursively merged
|
|
783
908
|
- Non-dict values (lists, scalars) are replaced entirely
|
|
784
909
|
- Keys in override not in base are added
|
|
910
|
+
- None values in override are skipped (won't override existing values)
|
|
785
911
|
|
|
786
912
|
Args:
|
|
787
913
|
base: Base dictionary (inputs not mutated)
|
|
@@ -793,6 +919,11 @@ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any
|
|
|
793
919
|
result = base.copy()
|
|
794
920
|
|
|
795
921
|
for key, override_value in override.items():
|
|
922
|
+
# Skip None values - they shouldn't override existing values
|
|
923
|
+
# This handles YAML files with keys but no values (e.g., "security:" with comments)
|
|
924
|
+
if override_value is None:
|
|
925
|
+
continue
|
|
926
|
+
|
|
796
927
|
if key in result:
|
|
797
928
|
base_value = result[key]
|
|
798
929
|
# Only deep merge if both are dicts
|
|
@@ -816,10 +947,9 @@ def _load_includes(
|
|
|
816
947
|
Files are merged left-to-right (later files override earlier).
|
|
817
948
|
Inline content in the main file overrides everything.
|
|
818
949
|
|
|
819
|
-
Include resolution uses
|
|
950
|
+
Include resolution uses two-tier fallback:
|
|
820
951
|
1. ot_dir (project .onetool/ or current OT_DIR)
|
|
821
952
|
2. global (~/.onetool/)
|
|
822
|
-
3. bundled (package defaults)
|
|
823
953
|
|
|
824
954
|
Args:
|
|
825
955
|
data: Config data dict containing optional 'include' key
|
|
@@ -840,7 +970,7 @@ def _load_includes(
|
|
|
840
970
|
merged: dict[str, Any] = {}
|
|
841
971
|
|
|
842
972
|
for include_path_str in include_list:
|
|
843
|
-
# Use
|
|
973
|
+
# Use two-tier resolution (ot_dir -> global)
|
|
844
974
|
include_path = _resolve_include_path(include_path_str, ot_dir)
|
|
845
975
|
|
|
846
976
|
if include_path is None:
|
|
@@ -888,7 +1018,7 @@ def _load_base_config(inherit: str, current_config_path: Path | None) -> dict[st
|
|
|
888
1018
|
"""Load base configuration for inheritance.
|
|
889
1019
|
|
|
890
1020
|
Args:
|
|
891
|
-
inherit: Inheritance mode (global
|
|
1021
|
+
inherit: Inheritance mode (global or none)
|
|
892
1022
|
current_config_path: Path to the current config file (to avoid self-include)
|
|
893
1023
|
|
|
894
1024
|
Returns:
|
|
@@ -897,7 +1027,7 @@ def _load_base_config(inherit: str, current_config_path: Path | None) -> dict[st
|
|
|
897
1027
|
if inherit == "none":
|
|
898
1028
|
return {}
|
|
899
1029
|
|
|
900
|
-
#
|
|
1030
|
+
# 'global' mode - inherit from global config
|
|
901
1031
|
if inherit == "global":
|
|
902
1032
|
global_config_path = get_config_dir(get_global_dir()) / "onetool.yaml"
|
|
903
1033
|
if global_config_path.exists():
|
|
@@ -909,33 +1039,24 @@ def _load_base_config(inherit: str, current_config_path: Path | None) -> dict[st
|
|
|
909
1039
|
logger.debug(
|
|
910
1040
|
"Skipping global inheritance (loading global config itself)"
|
|
911
1041
|
)
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
bundled_dir = get_bundled_config_dir()
|
|
928
|
-
bundled_config_path = bundled_dir / "onetool.yaml"
|
|
929
|
-
if bundled_config_path.exists():
|
|
930
|
-
raw_data = _load_yaml_file(bundled_config_path)
|
|
931
|
-
# Process includes in bundled config - bundled dir is flat (no config/ subdir)
|
|
932
|
-
data = _load_includes(raw_data, bundled_dir)
|
|
933
|
-
logger.debug(f"Inherited base config from bundled: {bundled_config_path}")
|
|
934
|
-
return data
|
|
935
|
-
except FileNotFoundError:
|
|
936
|
-
logger.debug("Bundled config not available for inheritance")
|
|
1042
|
+
return {}
|
|
1043
|
+
try:
|
|
1044
|
+
raw_data = _load_yaml_file(global_config_path)
|
|
1045
|
+
# Process includes in global config - OT_DIR is ~/.onetool/
|
|
1046
|
+
global_ot_dir = get_global_dir()
|
|
1047
|
+
data = _load_includes(raw_data, global_ot_dir)
|
|
1048
|
+
logger.debug(
|
|
1049
|
+
f"Inherited base config from global: {global_config_path}"
|
|
1050
|
+
)
|
|
1051
|
+
return data
|
|
1052
|
+
except (FileNotFoundError, ValueError) as e:
|
|
1053
|
+
logger.warning(f"Failed to load global config for inheritance: {e}")
|
|
1054
|
+
# Global config not available - no fallback (user must run 'onetool init')
|
|
1055
|
+
logger.debug("Global config not found for 'inherit: global' mode")
|
|
1056
|
+
return {}
|
|
937
1057
|
|
|
938
|
-
|
|
1058
|
+
# Invalid inherit value - should never reach here due to validation
|
|
1059
|
+
return {} # pragma: no cover
|
|
939
1060
|
|
|
940
1061
|
|
|
941
1062
|
def load_config(config_path: Path | str | None = None) -> OneToolConfig:
|
|
@@ -945,20 +1066,15 @@ def load_config(config_path: Path | str | None = None) -> OneToolConfig:
|
|
|
945
1066
|
1. ONETOOL_CONFIG env var
|
|
946
1067
|
2. cwd/.onetool/onetool.yaml (project config)
|
|
947
1068
|
3. ~/.onetool/onetool.yaml (global config)
|
|
948
|
-
4.
|
|
1069
|
+
4. ConfigNotFoundError (requires 'onetool init')
|
|
949
1070
|
|
|
950
1071
|
Inheritance (controlled by 'inherit' field in your config):
|
|
951
1072
|
|
|
952
1073
|
'global' (default):
|
|
953
|
-
Base: ~/.onetool/onetool.yaml
|
|
1074
|
+
Base: ~/.onetool/onetool.yaml
|
|
954
1075
|
Your config overrides the base. Use for project configs that
|
|
955
1076
|
extend user preferences (API keys, timeouts, etc.).
|
|
956
1077
|
|
|
957
|
-
'bundled':
|
|
958
|
-
Base: bundled defaults only (ignores ~/.onetool/)
|
|
959
|
-
Your config overrides bundled. Use for reproducible configs
|
|
960
|
-
that shouldn't depend on user-specific settings.
|
|
961
|
-
|
|
962
1078
|
'none':
|
|
963
1079
|
No base config. Your config is standalone.
|
|
964
1080
|
Use for fully self-contained configurations.
|
|
@@ -967,7 +1083,7 @@ def load_config(config_path: Path | str | None = None) -> OneToolConfig:
|
|
|
967
1083
|
|
|
968
1084
|
# .onetool/onetool.yaml
|
|
969
1085
|
version: 1
|
|
970
|
-
# inherit: global (implicit default - gets
|
|
1086
|
+
# inherit: global (implicit default - gets settings from ~/.onetool/)
|
|
971
1087
|
tools_dir:
|
|
972
1088
|
- ./tools/*.py
|
|
973
1089
|
|
|
@@ -978,16 +1094,16 @@ def load_config(config_path: Path | str | None = None) -> OneToolConfig:
|
|
|
978
1094
|
Validated OneToolConfig
|
|
979
1095
|
|
|
980
1096
|
Raises:
|
|
1097
|
+
ConfigNotFoundError: If no config found and OneTool not initialized
|
|
981
1098
|
FileNotFoundError: If explicit config path doesn't exist
|
|
982
1099
|
ValueError: If YAML is invalid or validation fails
|
|
983
1100
|
"""
|
|
984
1101
|
resolved_path = _resolve_config_path(config_path)
|
|
985
1102
|
|
|
986
1103
|
if resolved_path is None:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
return config
|
|
1104
|
+
raise ConfigNotFoundError(
|
|
1105
|
+
"No configuration file found. Run 'onetool init' to initialize."
|
|
1106
|
+
)
|
|
991
1107
|
|
|
992
1108
|
logger.debug(f"Loading config from {resolved_path}")
|
|
993
1109
|
|
|
@@ -1002,7 +1118,7 @@ def load_config(config_path: Path | str | None = None) -> OneToolConfig:
|
|
|
1002
1118
|
|
|
1003
1119
|
# Determine inheritance mode (default: global)
|
|
1004
1120
|
inherit = merged_data.get("inherit", "global")
|
|
1005
|
-
if inherit not in ("global", "
|
|
1121
|
+
if inherit not in ("global", "none"):
|
|
1006
1122
|
logger.warning(f"Invalid inherit value '{inherit}', using 'global'")
|
|
1007
1123
|
inherit = "global"
|
|
1008
1124
|
|
ot/config/mcp.py
CHANGED
|
@@ -60,7 +60,7 @@ def expand_secrets(value: str) -> str:
|
|
|
60
60
|
if missing_vars:
|
|
61
61
|
raise ValueError(
|
|
62
62
|
f"Missing variables in secrets.yaml: {', '.join(missing_vars)}. "
|
|
63
|
-
f"Add them to .onetool/secrets.yaml or use ${{VAR:-default}} syntax."
|
|
63
|
+
f"Add them to .onetool/config/secrets.yaml or use ${{VAR:-default}} syntax."
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
return result
|
|
@@ -135,6 +135,10 @@ class McpServerConfig(BaseModel):
|
|
|
135
135
|
default_factory=dict, description="Environment variables for stdio servers"
|
|
136
136
|
)
|
|
137
137
|
timeout: int = Field(default=30, description="Connection timeout in seconds")
|
|
138
|
+
instructions: str | None = Field(
|
|
139
|
+
default=None,
|
|
140
|
+
description="Agent instructions for using this server's tools (surfaced in MCP instructions)",
|
|
141
|
+
)
|
|
138
142
|
|
|
139
143
|
@field_validator("url", "command", mode="before")
|
|
140
144
|
@classmethod
|