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.
Files changed (81) hide show
  1. onetool/cli.py +63 -4
  2. onetool_mcp-1.0.0rc2.dist-info/METADATA +266 -0
  3. onetool_mcp-1.0.0rc2.dist-info/RECORD +129 -0
  4. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/LICENSE.txt +1 -1
  5. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/NOTICE.txt +54 -64
  6. ot/__main__.py +6 -6
  7. ot/config/__init__.py +48 -46
  8. ot/config/global_templates/__init__.py +2 -2
  9. ot/config/{defaults → global_templates}/diagram-templates/api-flow.mmd +33 -33
  10. ot/config/{defaults → global_templates}/diagram-templates/c4-context.puml +30 -30
  11. ot/config/{defaults → global_templates}/diagram-templates/class-diagram.mmd +87 -87
  12. ot/config/{defaults → global_templates}/diagram-templates/feature-mindmap.mmd +70 -70
  13. ot/config/{defaults → global_templates}/diagram-templates/microservices.d2 +81 -81
  14. ot/config/{defaults → global_templates}/diagram-templates/project-gantt.mmd +37 -37
  15. ot/config/{defaults → global_templates}/diagram-templates/state-machine.mmd +42 -42
  16. ot/config/global_templates/diagram.yaml +167 -0
  17. ot/config/global_templates/onetool.yaml +3 -1
  18. ot/config/{defaults → global_templates}/prompts.yaml +102 -97
  19. ot/config/global_templates/security.yaml +31 -0
  20. ot/config/global_templates/servers.yaml +93 -12
  21. ot/config/global_templates/snippets.yaml +5 -26
  22. ot/config/{defaults → global_templates}/tool_templates/__init__.py +7 -7
  23. ot/config/loader.py +221 -105
  24. ot/config/mcp.py +5 -1
  25. ot/config/secrets.py +192 -190
  26. ot/decorators.py +116 -116
  27. ot/executor/__init__.py +35 -35
  28. ot/executor/base.py +16 -16
  29. ot/executor/fence_processor.py +83 -83
  30. ot/executor/linter.py +142 -142
  31. ot/executor/pep723.py +288 -288
  32. ot/executor/runner.py +20 -6
  33. ot/executor/simple.py +163 -163
  34. ot/executor/validator.py +603 -164
  35. ot/http_client.py +145 -145
  36. ot/logging/__init__.py +37 -37
  37. ot/logging/entry.py +213 -213
  38. ot/logging/format.py +191 -188
  39. ot/logging/span.py +349 -349
  40. ot/meta.py +236 -14
  41. ot/paths.py +32 -49
  42. ot/prompts.py +218 -218
  43. ot/proxy/manager.py +14 -2
  44. ot/registry/__init__.py +189 -189
  45. ot/registry/parser.py +269 -269
  46. ot/server.py +330 -315
  47. ot/shortcuts/__init__.py +15 -15
  48. ot/shortcuts/aliases.py +87 -87
  49. ot/shortcuts/snippets.py +258 -258
  50. ot/stats/__init__.py +35 -35
  51. ot/stats/html.py +2 -2
  52. ot/stats/reader.py +354 -354
  53. ot/stats/timing.py +57 -57
  54. ot/support.py +63 -63
  55. ot/tools.py +1 -1
  56. ot/utils/batch.py +161 -161
  57. ot/utils/cache.py +120 -120
  58. ot/utils/exceptions.py +23 -23
  59. ot/utils/factory.py +178 -179
  60. ot/utils/format.py +65 -65
  61. ot/utils/http.py +202 -202
  62. ot/utils/platform.py +45 -45
  63. ot/utils/truncate.py +69 -69
  64. ot_tools/__init__.py +4 -4
  65. ot_tools/_convert/__init__.py +12 -12
  66. ot_tools/_convert/pdf.py +254 -254
  67. ot_tools/diagram.yaml +167 -167
  68. ot_tools/scaffold.py +2 -2
  69. ot_tools/transform.py +124 -19
  70. ot_tools/web_fetch.py +94 -43
  71. onetool_mcp-1.0.0b1.dist-info/METADATA +0 -163
  72. onetool_mcp-1.0.0b1.dist-info/RECORD +0 -132
  73. ot/config/defaults/bench.yaml +0 -4
  74. ot/config/defaults/onetool.yaml +0 -25
  75. ot/config/defaults/servers.yaml +0 -7
  76. ot/config/defaults/snippets.yaml +0 -4
  77. ot_tools/firecrawl.py +0 -732
  78. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/WHEEL +0 -0
  79. {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/entry_points.txt +0 -0
  80. /ot/config/{defaults → global_templates}/tool_templates/extension.py +0 -0
  81. /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
- Controls code validation with three-tier pattern system:
136
- - allow: Execute silently (highest priority)
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
- Priority order: allow > warned > blocked
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
- Pattern matching logic (handled automatically by validator):
148
- - Patterns WITHOUT dots (e.g., 'exec', 'subprocess') match:
149
- * Builtin function calls: exec(), eval()
150
- * Import statements: import subprocess, from os import system
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
- # Blocked patterns - EXTENDS built-in defaults (prevents accidental removal)
180
- # Use 'allow' to exempt specific patterns if needed
181
- blocked: list[str] = Field(
182
- default_factory=list,
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
- # Warned patterns - EXTENDS built-in defaults
187
- warned: list[str] = Field(
188
- default_factory=list,
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
- # Allow list - explicitly exempt patterns from defaults
193
- # Use this to remove a default pattern you need (e.g., allow 'open' for file tools)
194
- allow: list[str] = Field(
195
- default_factory=list,
196
- description="Patterns to exempt from blocking/warning (removes from defaults)",
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", "bundled", "none"] = Field(
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, then "
318
- "bundled defaults as fallback. Use for project configs that extend user prefs.\n"
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
- elif config_version > CURRENT_CONFIG_VERSION:
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 three-tier fallback.
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 three-tier order)
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
- # Tier 3: bundled (package defaults)
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 three-tier fallback:
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 three-tier resolution
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, bundled, none)
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
- # Try global first for 'global' mode
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
- else:
913
- try:
914
- raw_data = _load_yaml_file(global_config_path)
915
- # Process includes in global config - OT_DIR is ~/.onetool/
916
- global_ot_dir = get_global_dir()
917
- data = _load_includes(raw_data, global_ot_dir)
918
- logger.debug(
919
- f"Inherited base config from global: {global_config_path}"
920
- )
921
- return data
922
- except (FileNotFoundError, ValueError) as e:
923
- logger.warning(f"Failed to load global config for inheritance: {e}")
924
-
925
- # Fall back to bundled for both 'global' (when global missing) and 'bundled' modes
926
- try:
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
- return {}
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. Built-in defaults (bundled with package)
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 → bundled defaults (if global missing)
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 API keys from ~/.onetool/)
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
- logger.debug("No config file found, using defaults")
988
- config = OneToolConfig()
989
- config._config_dir = get_effective_cwd() / ".onetool" / CONFIG_SUBDIR
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", "bundled", "none"):
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