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/meta.py
CHANGED
|
@@ -102,6 +102,7 @@ __all__ = [
|
|
|
102
102
|
"packs",
|
|
103
103
|
"reload",
|
|
104
104
|
"result",
|
|
105
|
+
"security",
|
|
105
106
|
"snippets",
|
|
106
107
|
"stats",
|
|
107
108
|
"timed",
|
|
@@ -122,6 +123,45 @@ def version() -> str:
|
|
|
122
123
|
return __version__
|
|
123
124
|
|
|
124
125
|
|
|
126
|
+
def security(*, check: str = "") -> dict[str, Any]:
|
|
127
|
+
"""Check security rules for code validation.
|
|
128
|
+
|
|
129
|
+
OneTool uses an allowlist-based security model: everything is blocked
|
|
130
|
+
by default, and only explicitly allowed builtins, imports, and calls
|
|
131
|
+
are permitted. Tool namespaces (ot.*, brave.*, etc.) are auto-allowed.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
check: Pattern to check (e.g., "os", "json.loads", "pickle.*").
|
|
135
|
+
If empty, returns a summary of all security rules.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
If check is provided: Dict with 'pattern', 'status' (allowed/blocked/warned),
|
|
139
|
+
'category', and 'reason' explaining why.
|
|
140
|
+
If check is empty: Dict with summary of all security categories
|
|
141
|
+
(builtins, imports, calls, dunders, tool_namespaces).
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
ot.security() # Show all rules
|
|
145
|
+
ot.security(check="os") # "blocked: import"
|
|
146
|
+
ot.security(check="json") # "allowed: import"
|
|
147
|
+
ot.security(check="json.loads") # "allowed: module in imports"
|
|
148
|
+
ot.security(check="pickle.load") # "blocked: calls"
|
|
149
|
+
ot.security(check="brave.search") # "allowed: tool namespace"
|
|
150
|
+
"""
|
|
151
|
+
from ot.executor.validator import get_security_status, get_security_summary
|
|
152
|
+
|
|
153
|
+
with log(span="ot.security", check=check or None) as s:
|
|
154
|
+
if check:
|
|
155
|
+
result = get_security_status(check)
|
|
156
|
+
s.add("status", result["status"])
|
|
157
|
+
s.add("category", result["category"])
|
|
158
|
+
return result
|
|
159
|
+
else:
|
|
160
|
+
summary = get_security_summary()
|
|
161
|
+
s.add("status", summary.get("status", "unknown"))
|
|
162
|
+
return summary
|
|
163
|
+
|
|
164
|
+
|
|
125
165
|
def timed(func: _Callable[..., _T], **kwargs: Any) -> dict[str, Any]:
|
|
126
166
|
"""Execute a function and return result with timing info.
|
|
127
167
|
|
|
@@ -155,12 +195,14 @@ def get_ot_pack_functions() -> dict[str, Any]:
|
|
|
155
195
|
return {
|
|
156
196
|
"tools": tools,
|
|
157
197
|
"packs": packs,
|
|
198
|
+
"servers": servers,
|
|
158
199
|
"aliases": aliases,
|
|
159
200
|
"snippets": snippets,
|
|
160
201
|
"config": config,
|
|
161
202
|
"health": health,
|
|
162
203
|
"help": help,
|
|
163
204
|
"result": result,
|
|
205
|
+
"security": security,
|
|
164
206
|
"stats": stats,
|
|
165
207
|
"notify": notify,
|
|
166
208
|
"reload": reload,
|
|
@@ -178,7 +220,7 @@ def _get_doc_url(pack: str) -> str:
|
|
|
178
220
|
"""Get documentation URL for a pack.
|
|
179
221
|
|
|
180
222
|
Args:
|
|
181
|
-
pack: Pack name (e.g., "brave", "
|
|
223
|
+
pack: Pack name (e.g., "brave", "file")
|
|
182
224
|
|
|
183
225
|
Returns:
|
|
184
226
|
Documentation URL for the pack
|
|
@@ -227,7 +269,8 @@ def _format_general_help() -> str:
|
|
|
227
269
|
## Discovery
|
|
228
270
|
ot.tools() - List all tools
|
|
229
271
|
ot.tools(pattern="web") - Filter by pattern
|
|
230
|
-
ot.packs() - List all packs
|
|
272
|
+
ot.packs() - List all packs (local + MCP)
|
|
273
|
+
ot.servers() - List MCP proxy servers
|
|
231
274
|
ot.snippets() - List all snippets
|
|
232
275
|
ot.aliases() - List all aliases
|
|
233
276
|
ot.help(query="..") - Search for help
|
|
@@ -235,7 +278,7 @@ def _format_general_help() -> str:
|
|
|
235
278
|
## Info Levels
|
|
236
279
|
info="list" - Names only
|
|
237
280
|
info="min" - Name + description (default)
|
|
238
|
-
info="full" - Everything
|
|
281
|
+
info="full" - Everything (includes instructions)
|
|
239
282
|
|
|
240
283
|
## Quick Examples
|
|
241
284
|
brave.search(query="AI news")
|
|
@@ -448,7 +491,8 @@ def _format_search_results(
|
|
|
448
491
|
lines.append("")
|
|
449
492
|
lines.append("Try browsing with:")
|
|
450
493
|
lines.append(" ot.tools() - List all tools")
|
|
451
|
-
lines.append(" ot.packs() - List all packs")
|
|
494
|
+
lines.append(" ot.packs() - List all packs (local + MCP)")
|
|
495
|
+
lines.append(" ot.servers() - List MCP proxy servers")
|
|
452
496
|
lines.append(" ot.snippets() - List all snippets")
|
|
453
497
|
lines.append(" ot.aliases() - List all aliases")
|
|
454
498
|
|
|
@@ -504,7 +548,7 @@ def _build_tool_info(
|
|
|
504
548
|
Args:
|
|
505
549
|
full_name: Full tool name (e.g., "brave.search")
|
|
506
550
|
func: The function object
|
|
507
|
-
source: Source identifier (e.g., "local", "
|
|
551
|
+
source: Source identifier (e.g., "local", "mcp:github")
|
|
508
552
|
info: Output verbosity level ("list", "min", "full")
|
|
509
553
|
|
|
510
554
|
Returns:
|
|
@@ -577,7 +621,20 @@ def _schema_to_signature(full_name: str, schema: dict[str, Any]) -> str:
|
|
|
577
621
|
"array": "list",
|
|
578
622
|
"object": "dict",
|
|
579
623
|
}
|
|
580
|
-
|
|
624
|
+
|
|
625
|
+
# Handle JSON Schema union types (e.g., ["string", "null"])
|
|
626
|
+
if isinstance(prop_type, list):
|
|
627
|
+
# Filter out "null" and map remaining types
|
|
628
|
+
non_null = [t for t in prop_type if t != "null"]
|
|
629
|
+
if non_null:
|
|
630
|
+
mapped = [type_map.get(t, t) for t in non_null]
|
|
631
|
+
py_type = " | ".join(mapped)
|
|
632
|
+
if "null" in prop_type:
|
|
633
|
+
py_type = f"{py_type} | None"
|
|
634
|
+
else:
|
|
635
|
+
py_type = "None"
|
|
636
|
+
else:
|
|
637
|
+
py_type = type_map.get(prop_type, prop_type)
|
|
581
638
|
|
|
582
639
|
if prop_name in required:
|
|
583
640
|
params.append(f"{prop_name}: {py_type}")
|
|
@@ -626,7 +683,7 @@ def _build_proxy_tool_info(
|
|
|
626
683
|
full_name: Full tool name (e.g., "github.search")
|
|
627
684
|
description: Tool description from MCP server
|
|
628
685
|
input_schema: JSON Schema for tool input
|
|
629
|
-
source: Source identifier (e.g., "
|
|
686
|
+
source: Source identifier (e.g., "mcp:github")
|
|
630
687
|
info: Output verbosity level ("list", "min", "full")
|
|
631
688
|
|
|
632
689
|
Returns:
|
|
@@ -718,7 +775,7 @@ def tools(
|
|
|
718
775
|
tool_name,
|
|
719
776
|
proxy_tool.description or "",
|
|
720
777
|
proxy_tool.input_schema,
|
|
721
|
-
f"
|
|
778
|
+
f"mcp:{proxy_tool.server}",
|
|
722
779
|
info,
|
|
723
780
|
)
|
|
724
781
|
)
|
|
@@ -777,22 +834,44 @@ def packs(
|
|
|
777
834
|
# info="full" - detailed info for each matching pack
|
|
778
835
|
if info == "full":
|
|
779
836
|
results: list[dict[str, Any] | str] = []
|
|
837
|
+
cfg = get_config()
|
|
838
|
+
|
|
780
839
|
for pack_name in all_pack_names:
|
|
781
840
|
is_local = pack_name in local_packs
|
|
782
841
|
|
|
783
842
|
# Build detailed pack info
|
|
784
843
|
lines = [f"# {pack_name} pack", ""]
|
|
785
844
|
|
|
786
|
-
#
|
|
845
|
+
# Show source type
|
|
846
|
+
if is_local:
|
|
847
|
+
lines.append("**Type:** Local")
|
|
848
|
+
else:
|
|
849
|
+
lines.append("**Type:** MCP Proxy Server")
|
|
850
|
+
lines.append("")
|
|
851
|
+
|
|
852
|
+
# Get instructions from prompts.yaml
|
|
787
853
|
try:
|
|
788
854
|
prompts_config = get_prompts()
|
|
789
855
|
configured = get_pack_instructions(prompts_config, pack_name)
|
|
790
856
|
if configured:
|
|
857
|
+
lines.append("## Instructions")
|
|
858
|
+
lines.append("")
|
|
791
859
|
lines.append(configured)
|
|
792
860
|
lines.append("")
|
|
793
861
|
except PromptsError:
|
|
794
862
|
pass
|
|
795
863
|
|
|
864
|
+
# For proxy packs, also check server config for instructions
|
|
865
|
+
if not is_local and pack_name in cfg.servers:
|
|
866
|
+
server_cfg = cfg.servers[pack_name]
|
|
867
|
+
if server_cfg.instructions:
|
|
868
|
+
# Only add header if not already added from prompts
|
|
869
|
+
if "## Instructions" not in "\n".join(lines):
|
|
870
|
+
lines.append("## Instructions")
|
|
871
|
+
lines.append("")
|
|
872
|
+
lines.append(server_cfg.instructions.strip())
|
|
873
|
+
lines.append("")
|
|
874
|
+
|
|
796
875
|
# List tools in this pack
|
|
797
876
|
lines.append("## Tools")
|
|
798
877
|
lines.append("")
|
|
@@ -827,7 +906,7 @@ def packs(
|
|
|
827
906
|
|
|
828
907
|
for pack_name in all_pack_names:
|
|
829
908
|
is_local = pack_name in local_packs
|
|
830
|
-
source = "local" if is_local else "
|
|
909
|
+
source = "local" if is_local else "mcp"
|
|
831
910
|
|
|
832
911
|
# Count tools in pack
|
|
833
912
|
if is_local:
|
|
@@ -851,6 +930,127 @@ def packs(
|
|
|
851
930
|
return packs_list
|
|
852
931
|
|
|
853
932
|
|
|
933
|
+
def servers(
|
|
934
|
+
*,
|
|
935
|
+
pattern: str = "",
|
|
936
|
+
info: InfoLevel = "min",
|
|
937
|
+
) -> list[dict[str, Any] | str]:
|
|
938
|
+
"""List configured MCP proxy servers with optional filtering.
|
|
939
|
+
|
|
940
|
+
Shows all MCP servers configured in servers.yaml, including their
|
|
941
|
+
connection status, tool count, and instructions.
|
|
942
|
+
|
|
943
|
+
Args:
|
|
944
|
+
pattern: Filter servers by name pattern (case-insensitive substring)
|
|
945
|
+
info: Output verbosity level - "list" (names only), "min" (name + status + tool_count),
|
|
946
|
+
or "full" (detailed info with instructions and tools)
|
|
947
|
+
|
|
948
|
+
Returns:
|
|
949
|
+
List of server names (info="list") or server dicts/strings (info="min"/"full")
|
|
950
|
+
|
|
951
|
+
Example:
|
|
952
|
+
ot.servers()
|
|
953
|
+
ot.servers(pattern="github")
|
|
954
|
+
ot.servers(info="full")
|
|
955
|
+
ot.servers(pattern="devtools", info="full")
|
|
956
|
+
"""
|
|
957
|
+
proxy = get_proxy_manager()
|
|
958
|
+
cfg = get_config()
|
|
959
|
+
|
|
960
|
+
with log(span="ot.servers", pattern=pattern or None, info=info) as s:
|
|
961
|
+
# Get all configured servers
|
|
962
|
+
all_server_names = sorted(cfg.servers.keys())
|
|
963
|
+
|
|
964
|
+
# Filter by pattern
|
|
965
|
+
if pattern:
|
|
966
|
+
all_server_names = [
|
|
967
|
+
name for name in all_server_names if pattern.lower() in name.lower()
|
|
968
|
+
]
|
|
969
|
+
|
|
970
|
+
# info="list" - just names
|
|
971
|
+
if info == "list":
|
|
972
|
+
s.add("count", len(all_server_names))
|
|
973
|
+
return all_server_names # type: ignore[return-value]
|
|
974
|
+
|
|
975
|
+
# info="full" - detailed info for each server
|
|
976
|
+
if info == "full":
|
|
977
|
+
results: list[dict[str, Any] | str] = []
|
|
978
|
+
|
|
979
|
+
for server_name in all_server_names:
|
|
980
|
+
server_cfg = cfg.servers[server_name]
|
|
981
|
+
conn = proxy.get_connection(server_name)
|
|
982
|
+
status = "connected" if conn else "disconnected"
|
|
983
|
+
tool_count = len(proxy.list_tools(server=server_name)) if conn else 0
|
|
984
|
+
|
|
985
|
+
lines = [f"# {server_name} server", ""]
|
|
986
|
+
lines.append(f"**Type:** MCP Proxy Server ({server_cfg.type})")
|
|
987
|
+
lines.append(f"**Status:** {status}")
|
|
988
|
+
lines.append(f"**Enabled:** {server_cfg.enabled}")
|
|
989
|
+
if server_cfg.type == "http" and server_cfg.url:
|
|
990
|
+
lines.append(f"**URL:** {server_cfg.url}")
|
|
991
|
+
elif server_cfg.type == "stdio" and server_cfg.command:
|
|
992
|
+
cmd = f"{server_cfg.command} {' '.join(server_cfg.args)}"
|
|
993
|
+
lines.append(f"**Command:** {cmd}")
|
|
994
|
+
lines.append("")
|
|
995
|
+
|
|
996
|
+
# Show instructions if configured
|
|
997
|
+
if server_cfg.instructions:
|
|
998
|
+
lines.append("## Instructions")
|
|
999
|
+
lines.append("")
|
|
1000
|
+
lines.append(server_cfg.instructions.strip())
|
|
1001
|
+
lines.append("")
|
|
1002
|
+
|
|
1003
|
+
# List tools if connected
|
|
1004
|
+
if conn:
|
|
1005
|
+
lines.append(f"## Tools ({tool_count})")
|
|
1006
|
+
lines.append("")
|
|
1007
|
+
proxy_tools = proxy.list_tools(server=server_name)
|
|
1008
|
+
for tool in sorted(proxy_tools, key=lambda t: t.name):
|
|
1009
|
+
desc = tool.description or "(no description)"
|
|
1010
|
+
first_line = desc.split("\n")[0].strip()
|
|
1011
|
+
lines.append(f"- **{server_name}.{tool.name}**: {first_line}")
|
|
1012
|
+
elif server_cfg.enabled:
|
|
1013
|
+
lines.append("## Tools")
|
|
1014
|
+
lines.append("")
|
|
1015
|
+
lines.append("(not connected)")
|
|
1016
|
+
# Show error if available
|
|
1017
|
+
error = proxy.get_error(server_name)
|
|
1018
|
+
if error:
|
|
1019
|
+
lines.append("")
|
|
1020
|
+
lines.append(f"**Error:** {error}")
|
|
1021
|
+
|
|
1022
|
+
results.append("\n".join(lines))
|
|
1023
|
+
|
|
1024
|
+
s.add("count", len(results))
|
|
1025
|
+
return results
|
|
1026
|
+
|
|
1027
|
+
# info="min" (default) - summary for each server
|
|
1028
|
+
servers_list: list[dict[str, Any] | str] = []
|
|
1029
|
+
|
|
1030
|
+
for server_name in all_server_names:
|
|
1031
|
+
server_cfg = cfg.servers[server_name]
|
|
1032
|
+
conn = proxy.get_connection(server_name)
|
|
1033
|
+
status = "connected" if conn else "disconnected"
|
|
1034
|
+
tool_count = len(proxy.list_tools(server=server_name)) if conn else 0
|
|
1035
|
+
|
|
1036
|
+
server_info: dict[str, Any] = {
|
|
1037
|
+
"name": server_name,
|
|
1038
|
+
"type": server_cfg.type,
|
|
1039
|
+
"enabled": server_cfg.enabled,
|
|
1040
|
+
"status": status,
|
|
1041
|
+
"tool_count": tool_count,
|
|
1042
|
+
}
|
|
1043
|
+
# Include error if disconnected
|
|
1044
|
+
if not conn:
|
|
1045
|
+
error = proxy.get_error(server_name)
|
|
1046
|
+
if error:
|
|
1047
|
+
server_info["error"] = error
|
|
1048
|
+
servers_list.append(server_info)
|
|
1049
|
+
|
|
1050
|
+
s.add("count", len(servers_list))
|
|
1051
|
+
return servers_list
|
|
1052
|
+
|
|
1053
|
+
|
|
854
1054
|
# ============================================================================
|
|
855
1055
|
# Messaging Functions
|
|
856
1056
|
# ============================================================================
|
|
@@ -1035,14 +1235,21 @@ def health() -> dict[str, Any]:
|
|
|
1035
1235
|
"tool_count": tool_count,
|
|
1036
1236
|
}
|
|
1037
1237
|
|
|
1038
|
-
server_statuses: dict[str,
|
|
1238
|
+
server_statuses: dict[str, Any] = {}
|
|
1039
1239
|
for server_name in cfg.servers:
|
|
1040
1240
|
conn = proxy.get_connection(server_name)
|
|
1041
|
-
|
|
1241
|
+
if conn:
|
|
1242
|
+
server_statuses[server_name] = "connected"
|
|
1243
|
+
else:
|
|
1244
|
+
error = proxy.get_error(server_name)
|
|
1245
|
+
server_statuses[server_name] = {"status": "disconnected", "error": error} if error else "disconnected"
|
|
1042
1246
|
|
|
1043
1247
|
proxy_status: dict[str, Any] = {
|
|
1044
1248
|
"status": "ok"
|
|
1045
|
-
if all(
|
|
1249
|
+
if all(
|
|
1250
|
+
(s == "connected" if isinstance(s, str) else s.get("status") == "connected")
|
|
1251
|
+
for s in server_statuses.values()
|
|
1252
|
+
)
|
|
1046
1253
|
or not server_statuses
|
|
1047
1254
|
else "degraded",
|
|
1048
1255
|
"server_count": len(cfg.servers),
|
|
@@ -1074,6 +1281,7 @@ def reload() -> str:
|
|
|
1074
1281
|
- Prompts
|
|
1075
1282
|
- MCP proxy connections
|
|
1076
1283
|
- Parameter resolution caches
|
|
1284
|
+
- Security validation caches
|
|
1077
1285
|
|
|
1078
1286
|
Use after modifying config files, adding/removing tools, or
|
|
1079
1287
|
changing secrets during a session.
|
|
@@ -1121,6 +1329,11 @@ def reload() -> str:
|
|
|
1121
1329
|
ot.executor.param_resolver.get_tool_param_names.cache_clear()
|
|
1122
1330
|
ot.executor.param_resolver._mcp_param_cache.clear()
|
|
1123
1331
|
|
|
1332
|
+
# Clear security validator caches (depends on config and registry)
|
|
1333
|
+
import ot.executor.validator
|
|
1334
|
+
ot.executor.validator._get_tool_namespaces.cache_clear()
|
|
1335
|
+
ot.executor.validator._get_security_config.cache_clear()
|
|
1336
|
+
|
|
1124
1337
|
# Reload config to validate and report stats
|
|
1125
1338
|
cfg = get_config()
|
|
1126
1339
|
|
|
@@ -1471,7 +1684,7 @@ def help(*, query: str = "", info: InfoLevel = "min") -> str:
|
|
|
1471
1684
|
Example:
|
|
1472
1685
|
ot.help()
|
|
1473
1686
|
ot.help(query="brave.search")
|
|
1474
|
-
ot.help(query="
|
|
1687
|
+
ot.help(query="brave")
|
|
1475
1688
|
ot.help(query="$b_q")
|
|
1476
1689
|
ot.help(query="web fetch", info="list")
|
|
1477
1690
|
"""
|
|
@@ -1494,6 +1707,15 @@ def help(*, query: str = "", info: InfoLevel = "min") -> str:
|
|
|
1494
1707
|
s.add("match", query)
|
|
1495
1708
|
return _format_tool_help(tool, pack)
|
|
1496
1709
|
|
|
1710
|
+
# Check for exact server match (MCP proxy servers)
|
|
1711
|
+
server_names = servers(info="list")
|
|
1712
|
+
if query in server_names:
|
|
1713
|
+
server_results = servers(pattern=query, info="full")
|
|
1714
|
+
if server_results:
|
|
1715
|
+
s.add("type", "server")
|
|
1716
|
+
s.add("match", query)
|
|
1717
|
+
return str(server_results[0])
|
|
1718
|
+
|
|
1497
1719
|
# Check for exact pack match
|
|
1498
1720
|
pack_names = packs(info="list")
|
|
1499
1721
|
if query in pack_names:
|
ot/paths.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Path resolution for OneTool global and project directories.
|
|
2
2
|
|
|
3
|
-
OneTool uses a
|
|
4
|
-
- Bundled: package data in ot.config.defaults — read-only defaults
|
|
3
|
+
OneTool uses a two-tier directory structure:
|
|
5
4
|
- Global: ~/.onetool/ — user-wide settings, secrets
|
|
6
5
|
- Project: .onetool/ — project-specific config
|
|
7
6
|
|
|
@@ -9,10 +8,10 @@ Each .onetool/ directory uses subdirectories to organise files by purpose:
|
|
|
9
8
|
- config/ — YAML configuration files
|
|
10
9
|
- logs/ — Application log files
|
|
11
10
|
- stats/ — Statistics data (stats.jsonl)
|
|
12
|
-
- sessions/ — Browser session state
|
|
13
11
|
- tools/ — Reserved for installed tool packs
|
|
14
12
|
|
|
15
13
|
Directories are created lazily on first use, not on install.
|
|
14
|
+
Templates in ot.config.global_templates are copied to ~/.onetool/ on init.
|
|
16
15
|
"""
|
|
17
16
|
|
|
18
17
|
from __future__ import annotations
|
|
@@ -30,13 +29,9 @@ PROJECT_DIR_NAME = ".onetool"
|
|
|
30
29
|
CONFIG_SUBDIR = "config"
|
|
31
30
|
LOGS_SUBDIR = "logs"
|
|
32
31
|
STATS_SUBDIR = "stats"
|
|
33
|
-
SESSIONS_SUBDIR = "sessions"
|
|
34
32
|
TOOLS_SUBDIR = "tools"
|
|
35
33
|
|
|
36
|
-
# Package containing
|
|
37
|
-
BUNDLED_CONFIG_PACKAGE = "ot.config.defaults"
|
|
38
|
-
|
|
39
|
-
# Package containing global templates (copied to ~/.onetool/ on first run)
|
|
34
|
+
# Package containing global templates (copied to ~/.onetool/ on init)
|
|
40
35
|
GLOBAL_TEMPLATES_PACKAGE = "ot.config.global_templates"
|
|
41
36
|
|
|
42
37
|
|
|
@@ -49,7 +44,7 @@ def _resolve_package_dir(package_name: str, description: str) -> Path:
|
|
|
49
44
|
- Development mode
|
|
50
45
|
|
|
51
46
|
Args:
|
|
52
|
-
package_name: Dotted package name (e.g., "ot.config.
|
|
47
|
+
package_name: Dotted package name (e.g., "ot.config.global_templates")
|
|
53
48
|
description: Human-readable description for error messages
|
|
54
49
|
|
|
55
50
|
Returns:
|
|
@@ -105,29 +100,13 @@ def _resolve_package_dir(package_name: str, description: str) -> Path:
|
|
|
105
100
|
)
|
|
106
101
|
|
|
107
102
|
|
|
108
|
-
def get_bundled_config_dir() -> Path:
|
|
109
|
-
"""Get the bundled config defaults directory path.
|
|
110
|
-
|
|
111
|
-
Uses importlib.resources to access package data. Works correctly across:
|
|
112
|
-
- Regular pip/uv install (wheel)
|
|
113
|
-
- Editable install (uv tool install -e .)
|
|
114
|
-
- Development mode
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
Path to bundled defaults directory (read-only package data)
|
|
118
|
-
|
|
119
|
-
Raises:
|
|
120
|
-
FileNotFoundError: If bundled defaults package is not found or not on filesystem
|
|
121
|
-
"""
|
|
122
|
-
return _resolve_package_dir(BUNDLED_CONFIG_PACKAGE, "Bundled config")
|
|
123
|
-
|
|
124
|
-
|
|
125
103
|
def get_global_templates_dir() -> Path:
|
|
126
104
|
"""Get the global templates directory path.
|
|
127
105
|
|
|
128
106
|
Global templates are user-facing config files with commented examples,
|
|
129
|
-
copied to ~/.onetool/ on
|
|
130
|
-
|
|
107
|
+
copied to ~/.onetool/ on init. These provide documentation and examples
|
|
108
|
+
for configuration. Also contains subdirectories like diagram-templates/
|
|
109
|
+
and tool_templates/ for tool-specific resources.
|
|
131
110
|
|
|
132
111
|
Returns:
|
|
133
112
|
Path to global templates directory (read-only package data)
|
|
@@ -156,9 +135,14 @@ def get_effective_cwd() -> Path:
|
|
|
156
135
|
def get_global_dir() -> Path:
|
|
157
136
|
"""Get the global OneTool directory path.
|
|
158
137
|
|
|
138
|
+
Can be overridden via OT_GLOBAL_DIR environment variable.
|
|
139
|
+
|
|
159
140
|
Returns:
|
|
160
|
-
Path to ~/.onetool/ (not necessarily existing)
|
|
141
|
+
Path to ~/.onetool/ or OT_GLOBAL_DIR if set (not necessarily existing)
|
|
161
142
|
"""
|
|
143
|
+
env_global = os.getenv("OT_GLOBAL_DIR")
|
|
144
|
+
if env_global:
|
|
145
|
+
return Path(env_global).resolve()
|
|
162
146
|
return Path.home() / GLOBAL_DIR_NAME
|
|
163
147
|
|
|
164
148
|
|
|
@@ -232,11 +216,9 @@ def create_backup(file_path: Path) -> Path:
|
|
|
232
216
|
def ensure_global_dir(quiet: bool = False, force: bool = False) -> Path:
|
|
233
217
|
"""Ensure the global OneTool directory exists with subdirectory structure.
|
|
234
218
|
|
|
235
|
-
Creates ~/.onetool/ with subdirectories (config/, logs/, stats/,
|
|
219
|
+
Creates ~/.onetool/ with subdirectories (config/, logs/, stats/, tools/)
|
|
236
220
|
and copies template config files from global_templates to config/.
|
|
237
221
|
Templates are user-facing files with commented examples for customization.
|
|
238
|
-
Subdirectories (like diagram-templates/) are NOT copied - they remain in
|
|
239
|
-
bundled defaults and are accessed via config inheritance.
|
|
240
222
|
|
|
241
223
|
Args:
|
|
242
224
|
quiet: Suppress creation messages
|
|
@@ -255,12 +237,12 @@ def ensure_global_dir(quiet: bool = False, force: bool = False) -> Path:
|
|
|
255
237
|
|
|
256
238
|
# Create directory structure with subdirectories
|
|
257
239
|
global_dir.mkdir(parents=True, exist_ok=True)
|
|
258
|
-
subdirs = [CONFIG_SUBDIR, LOGS_SUBDIR, STATS_SUBDIR,
|
|
240
|
+
subdirs = [CONFIG_SUBDIR, LOGS_SUBDIR, STATS_SUBDIR, TOOLS_SUBDIR]
|
|
259
241
|
for subdir in subdirs:
|
|
260
242
|
(global_dir / subdir).mkdir(exist_ok=True)
|
|
261
243
|
|
|
262
244
|
# Copy template config files to config/ subdirectory
|
|
263
|
-
# Only YAML files are copied
|
|
245
|
+
# Only YAML files are copied from global_templates/
|
|
264
246
|
# Files named *-template.yaml are copied without the -template suffix
|
|
265
247
|
# (to avoid gitignore patterns on secrets.yaml)
|
|
266
248
|
config_dir = global_dir / CONFIG_SUBDIR
|
|
@@ -275,6 +257,20 @@ def ensure_global_dir(quiet: bool = False, force: bool = False) -> Path:
|
|
|
275
257
|
if not dest.exists() or force:
|
|
276
258
|
shutil.copy(config_file, dest)
|
|
277
259
|
copied_items.append(f"config/{dest_name}")
|
|
260
|
+
|
|
261
|
+
# Copy resource subdirectories (e.g., diagram-templates/)
|
|
262
|
+
# These contain user-customizable template files
|
|
263
|
+
for template_subdir in templates_dir.iterdir():
|
|
264
|
+
if template_subdir.is_dir() and not template_subdir.name.startswith("_"):
|
|
265
|
+
# Skip tool_templates - those are code templates accessed via get_global_templates_dir()
|
|
266
|
+
if template_subdir.name == "tool_templates":
|
|
267
|
+
continue
|
|
268
|
+
dest_subdir = config_dir / template_subdir.name
|
|
269
|
+
if not dest_subdir.exists() or force:
|
|
270
|
+
if dest_subdir.exists():
|
|
271
|
+
shutil.rmtree(dest_subdir)
|
|
272
|
+
shutil.copytree(template_subdir, dest_subdir)
|
|
273
|
+
copied_items.append(f"config/{template_subdir.name}/")
|
|
278
274
|
except FileNotFoundError:
|
|
279
275
|
# Global templates not available (dev environment without package install)
|
|
280
276
|
pass
|
|
@@ -295,7 +291,7 @@ def ensure_project_dir(path: Path | None = None, quiet: bool = False) -> Path:
|
|
|
295
291
|
"""Ensure the project OneTool directory exists with subdirectory structure.
|
|
296
292
|
|
|
297
293
|
Creates .onetool/ in the specified directory or effective cwd,
|
|
298
|
-
including subdirectories (config/, logs/, stats/,
|
|
294
|
+
including subdirectories (config/, logs/, stats/, tools/).
|
|
299
295
|
|
|
300
296
|
Args:
|
|
301
297
|
path: Project root (default: get_effective_cwd())
|
|
@@ -312,7 +308,7 @@ def ensure_project_dir(path: Path | None = None, quiet: bool = False) -> Path:
|
|
|
312
308
|
|
|
313
309
|
# Create directory structure with subdirectories
|
|
314
310
|
project_dir.mkdir(parents=True, exist_ok=True)
|
|
315
|
-
subdirs = [CONFIG_SUBDIR, LOGS_SUBDIR, STATS_SUBDIR,
|
|
311
|
+
subdirs = [CONFIG_SUBDIR, LOGS_SUBDIR, STATS_SUBDIR, TOOLS_SUBDIR]
|
|
316
312
|
for subdir in subdirs:
|
|
317
313
|
(project_dir / subdir).mkdir(exist_ok=True)
|
|
318
314
|
|
|
@@ -409,19 +405,6 @@ def get_stats_dir(base_dir: Path | None = None) -> Path:
|
|
|
409
405
|
return base / STATS_SUBDIR
|
|
410
406
|
|
|
411
407
|
|
|
412
|
-
def get_sessions_dir(base_dir: Path | None = None) -> Path:
|
|
413
|
-
"""Get the sessions directory path within a .onetool directory.
|
|
414
|
-
|
|
415
|
-
Args:
|
|
416
|
-
base_dir: Base .onetool directory (default: global dir)
|
|
417
|
-
|
|
418
|
-
Returns:
|
|
419
|
-
Path to sessions/ subdirectory
|
|
420
|
-
"""
|
|
421
|
-
base = base_dir or get_global_dir()
|
|
422
|
-
return base / SESSIONS_SUBDIR
|
|
423
|
-
|
|
424
|
-
|
|
425
408
|
def resolve_cwd_path(path: str) -> Path:
|
|
426
409
|
"""Resolve a path relative to the project working directory (OT_CWD).
|
|
427
410
|
|