onetool-mcp 1.0.0rc2__py3-none-any.whl → 1.0.0rc3__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 (34) hide show
  1. onetool/cli.py +2 -0
  2. {onetool_mcp-1.0.0rc2.dist-info → onetool_mcp-1.0.0rc3.dist-info}/METADATA +26 -33
  3. {onetool_mcp-1.0.0rc2.dist-info → onetool_mcp-1.0.0rc3.dist-info}/RECORD +31 -33
  4. ot/config/__init__.py +90 -48
  5. ot/config/global_templates/__init__.py +2 -2
  6. ot/config/global_templates/diagram-templates/api-flow.mmd +33 -33
  7. ot/config/global_templates/diagram-templates/c4-context.puml +30 -30
  8. ot/config/global_templates/diagram-templates/class-diagram.mmd +87 -87
  9. ot/config/global_templates/diagram-templates/feature-mindmap.mmd +70 -70
  10. ot/config/global_templates/diagram-templates/microservices.d2 +81 -81
  11. ot/config/global_templates/diagram-templates/project-gantt.mmd +37 -37
  12. ot/config/global_templates/diagram-templates/state-machine.mmd +42 -42
  13. ot/config/global_templates/diagram.yaml +167 -167
  14. ot/config/global_templates/onetool.yaml +2 -0
  15. ot/config/global_templates/prompts.yaml +102 -102
  16. ot/config/global_templates/security.yaml +1 -4
  17. ot/config/global_templates/servers.yaml +1 -1
  18. ot/config/global_templates/tool_templates/__init__.py +7 -7
  19. ot/config/loader.py +226 -869
  20. ot/config/models.py +735 -0
  21. ot/config/secrets.py +243 -192
  22. ot/executor/tool_loader.py +10 -1
  23. ot/executor/validator.py +11 -1
  24. ot/meta.py +338 -33
  25. ot/prompts.py +228 -218
  26. ot/proxy/manager.py +168 -8
  27. ot/registry/__init__.py +199 -189
  28. ot/config/dynamic.py +0 -121
  29. ot/config/mcp.py +0 -149
  30. ot/config/tool_config.py +0 -125
  31. {onetool_mcp-1.0.0rc2.dist-info → onetool_mcp-1.0.0rc3.dist-info}/WHEEL +0 -0
  32. {onetool_mcp-1.0.0rc2.dist-info → onetool_mcp-1.0.0rc3.dist-info}/entry_points.txt +0 -0
  33. {onetool_mcp-1.0.0rc2.dist-info → onetool_mcp-1.0.0rc3.dist-info}/licenses/LICENSE.txt +0 -0
  34. {onetool_mcp-1.0.0rc2.dist-info → onetool_mcp-1.0.0rc3.dist-info}/licenses/NOTICE.txt +0 -0
ot/meta.py CHANGED
@@ -21,9 +21,12 @@ from __future__ import annotations
21
21
 
22
22
  import asyncio
23
23
  import fnmatch
24
+ import getpass
24
25
  import inspect
26
+ import os
27
+ import platform
25
28
  import sys
26
- import time as _time
29
+ import time
27
30
  from collections.abc import (
28
31
  Callable as _Callable, # noqa: TC003 - used at runtime in timed()
29
32
  )
@@ -43,6 +46,9 @@ from ot.proxy import get_proxy_manager
43
46
 
44
47
  _T = _TypeVar("_T")
45
48
 
49
+ # Track when module was first loaded (OneTool start time)
50
+ _MODULE_LOAD_TIME = time.time()
51
+
46
52
  # Alias for cleaner logging calls in this module
47
53
  log = LogSpan
48
54
 
@@ -75,6 +81,7 @@ def resolve_ot_path(path: str) -> Path:
75
81
 
76
82
  # Info level type for discovery functions
77
83
  InfoLevel = Literal["list", "min", "full"]
84
+ ServerInfoLevel = Literal["list", "min", "full", "resources", "prompts"]
78
85
 
79
86
  # Pack name for dot notation: ot.tools(), ot.packs(), etc.
80
87
  PACK_NAME = "ot"
@@ -95,6 +102,7 @@ __all__ = [
95
102
  "PACK_NAME",
96
103
  "aliases",
97
104
  "config",
105
+ "debug",
98
106
  "get_ot_pack_functions",
99
107
  "health",
100
108
  "help",
@@ -123,6 +131,203 @@ def version() -> str:
123
131
  return __version__
124
132
 
125
133
 
134
+ def _get_version_info() -> dict[str, Any]:
135
+ """Get version information from package metadata."""
136
+ return {"version": __version__}
137
+
138
+
139
+ def _get_paths_info() -> dict[str, Any]:
140
+ """Get all relevant path information."""
141
+ install_path = Path(__file__).parent.parent.resolve()
142
+ global_dir = get_global_dir()
143
+ cfg = get_config()
144
+
145
+ paths: dict[str, Any] = {
146
+ "install": str(install_path),
147
+ "global_dir": str(global_dir),
148
+ "cwd": str(resolve_cwd_path(".")),
149
+ "python": sys.executable,
150
+ }
151
+
152
+ if cfg._config_dir:
153
+ paths["config_file"] = str(cfg._config_dir / "onetool.yaml")
154
+ paths["config_dir"] = str(cfg._config_dir)
155
+
156
+ paths["log_dir"] = str(cfg.get_log_dir_path())
157
+
158
+ if cfg.stats.enabled:
159
+ paths["stats_file"] = str(cfg.get_stats_file_path())
160
+
161
+ paths["result_store"] = str(cfg.get_result_store_path())
162
+
163
+ return paths
164
+
165
+
166
+ def _get_config_info(verbose: bool = False) -> dict[str, Any]:
167
+ """Get configuration summary."""
168
+ from ot.executor.tool_loader import load_tool_registry
169
+
170
+ cfg = get_config()
171
+ registry = load_tool_registry()
172
+
173
+ info: dict[str, Any] = {
174
+ "version": cfg.version,
175
+ "servers": list(cfg.servers.keys()),
176
+ "packs_loaded": len(registry.packs),
177
+ "aliases": len(cfg.alias) if cfg.alias else 0,
178
+ "snippets": len(cfg.snippets) if cfg.snippets else 0,
179
+ }
180
+
181
+ if verbose:
182
+ info["includes"] = cfg.include
183
+ info["tools_dir"] = cfg.tools_dir
184
+ info["stats_enabled"] = cfg.stats.enabled
185
+ info["log_verbose"] = cfg.log_verbose
186
+
187
+ return info
188
+
189
+
190
+ def _get_python_info() -> dict[str, Any]:
191
+ """Get Python environment information."""
192
+ return {
193
+ "version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
194
+ "implementation": sys.implementation.name,
195
+ "platform": sys.platform,
196
+ "executable": sys.executable,
197
+ }
198
+
199
+
200
+ def _get_system_info() -> dict[str, Any]:
201
+ """Get OS/system information."""
202
+ info = {
203
+ "platform": platform.system(),
204
+ "machine": platform.machine(),
205
+ "user": getpass.getuser(),
206
+ "pid": os.getpid(),
207
+ }
208
+
209
+ # Add memory usage if psutil available
210
+ try:
211
+ import psutil # type: ignore[import-untyped]
212
+
213
+ process = psutil.Process(os.getpid())
214
+ mem_info = process.memory_info()
215
+ info["memory"] = {
216
+ "rss_mb": round(mem_info.rss / 1024 / 1024, 2),
217
+ "vms_mb": round(mem_info.vms / 1024 / 1024, 2),
218
+ "percent": round(process.memory_percent(), 2),
219
+ }
220
+ except ImportError:
221
+ pass
222
+
223
+ return info
224
+
225
+
226
+ def _get_runtime_info() -> dict[str, Any]:
227
+ """Get current runtime state."""
228
+ from ot.executor.tool_loader import load_tool_registry
229
+ from ot.executor.worker_proxy import WorkerPackProxy
230
+ from ot.proxy import get_proxy_manager
231
+
232
+ registry = load_tool_registry()
233
+ proxy = get_proxy_manager()
234
+ cfg = get_config()
235
+
236
+ # Count local tools
237
+ tool_count = 0
238
+ for funcs in registry.packs.values():
239
+ if isinstance(funcs, WorkerPackProxy):
240
+ tool_count += len(funcs.functions)
241
+ else:
242
+ tool_count += len(funcs)
243
+
244
+ # Count proxy connections
245
+ connected = sum(1 for name in cfg.servers if proxy.get_connection(name))
246
+ disconnected = len(cfg.servers) - connected
247
+
248
+ # Timing information
249
+ current_time = time.time()
250
+ uptime_seconds = current_time - _MODULE_LOAD_TIME
251
+ start_time = datetime.fromtimestamp(_MODULE_LOAD_TIME, tz=UTC)
252
+
253
+ return {
254
+ "packs_loaded": len(registry.packs),
255
+ "tools_local": tool_count,
256
+ "tools_proxied": proxy.tool_count,
257
+ "servers_configured": len(cfg.servers),
258
+ "servers_connected": connected,
259
+ "servers_disconnected": disconnected,
260
+ "start_time": start_time.isoformat(),
261
+ "uptime_seconds": round(uptime_seconds, 2),
262
+ }
263
+
264
+
265
+ def debug(
266
+ *,
267
+ verbose: bool = False,
268
+ env_vars: bool = False,
269
+ dependencies: bool = False,
270
+ ) -> dict[str, Any]:
271
+ """Get comprehensive debug information about this OneTool installation.
272
+
273
+ Essential for multi-version development - clearly identifies which
274
+ version is running and where it's configured.
275
+
276
+ Args:
277
+ verbose: Include detailed configuration information
278
+ env_vars: Include relevant environment variables
279
+ dependencies: Include dependency versions
280
+
281
+ Returns:
282
+ Structured debug information with sections:
283
+ - version: Package version
284
+ - paths: All relevant file paths
285
+ - config: Configuration summary
286
+ - python: Python environment details
287
+ - system: OS/platform information
288
+ - runtime: Current runtime state (packs, servers, tools, timing)
289
+
290
+ Example:
291
+ ot.debug()
292
+ ot.debug(verbose=True, env_vars=True)
293
+ """
294
+ with log(span="ot.debug", verbose=verbose) as s:
295
+ result: dict[str, Any] = {
296
+ "version": _get_version_info(),
297
+ "paths": _get_paths_info(),
298
+ "config": _get_config_info(verbose=verbose),
299
+ "python": _get_python_info(),
300
+ "system": _get_system_info(),
301
+ "runtime": _get_runtime_info(),
302
+ }
303
+
304
+ if env_vars:
305
+ # Include relevant environment variables
306
+ result["env"] = {
307
+ "OT_GLOBAL_DIR": os.getenv("OT_GLOBAL_DIR"),
308
+ "ONETOOL_CONFIG": os.getenv("ONETOOL_CONFIG"),
309
+ "OT_CWD": os.getenv("OT_CWD"),
310
+ }
311
+
312
+ if dependencies:
313
+ # Include dependency versions
314
+ from importlib.metadata import PackageNotFoundError
315
+ from importlib.metadata import version as get_version
316
+
317
+ deps = {}
318
+ for pkg in ["fastmcp", "pydantic", "pyyaml", "loguru", "requests", "openai"]:
319
+ try:
320
+ deps[pkg] = get_version(pkg)
321
+ except PackageNotFoundError:
322
+ deps[pkg] = "not installed"
323
+ result["dependencies"] = deps
324
+
325
+ version_str = result["version"].get("version", "unknown")
326
+ s.add("version", version_str)
327
+
328
+ return result
329
+
330
+
126
331
  def security(*, check: str = "") -> dict[str, Any]:
127
332
  """Check security rules for code validation.
128
333
 
@@ -176,9 +381,9 @@ def timed(func: _Callable[..., _T], **kwargs: Any) -> dict[str, Any]:
176
381
  ot.timed(brave.search, query="AI news")
177
382
  # Returns: {"ms": 234, "result": {...}}
178
383
  """
179
- start = _time.perf_counter()
384
+ start = time.perf_counter()
180
385
  result = func(**kwargs)
181
- elapsed = _time.perf_counter() - start
386
+ elapsed = time.perf_counter() - start
182
387
 
183
388
  return {
184
389
  "ms": round(elapsed * 1000),
@@ -199,6 +404,7 @@ def get_ot_pack_functions() -> dict[str, Any]:
199
404
  "aliases": aliases,
200
405
  "snippets": snippets,
201
406
  "config": config,
407
+ "debug": debug,
202
408
  "health": health,
203
409
  "help": help,
204
410
  "result": result,
@@ -472,7 +678,8 @@ def _format_search_results(
472
678
  for snippet in snippets_results:
473
679
  if isinstance(snippet, str):
474
680
  # For info="min", format is "name: description"
475
- lines.append(f"- ${snippet.split(':')[0]}" if ":" not in snippet else f"- ${snippet}")
681
+ snippet_name = snippet.split(":")[0] if ":" in snippet else snippet
682
+ lines.append(f"- ${snippet_name}")
476
683
  else:
477
684
  lines.append(f"- ${snippet}")
478
685
  lines.append("")
@@ -933,7 +1140,7 @@ def packs(
933
1140
  def servers(
934
1141
  *,
935
1142
  pattern: str = "",
936
- info: InfoLevel = "min",
1143
+ info: ServerInfoLevel = "min",
937
1144
  ) -> list[dict[str, Any] | str]:
938
1145
  """List configured MCP proxy servers with optional filtering.
939
1146
 
@@ -942,17 +1149,22 @@ def servers(
942
1149
 
943
1150
  Args:
944
1151
  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)
1152
+ info: Output verbosity level:
1153
+ - "list": names only
1154
+ - "min": name + status + tool_count (default)
1155
+ - "full": detailed info with instructions and tools
1156
+ - "resources": list resources per server
1157
+ - "prompts": list prompts per server
947
1158
 
948
1159
  Returns:
949
- List of server names (info="list") or server dicts/strings (info="min"/"full")
1160
+ List of server names (info="list") or server dicts/strings (info="min"/"full"/"resources"/"prompts")
950
1161
 
951
1162
  Example:
952
1163
  ot.servers()
953
1164
  ot.servers(pattern="github")
954
1165
  ot.servers(info="full")
955
- ot.servers(pattern="devtools", info="full")
1166
+ ot.servers(info="resources")
1167
+ ot.servers(pattern="devtools", info="prompts")
956
1168
  """
957
1169
  proxy = get_proxy_manager()
958
1170
  cfg = get_config()
@@ -991,6 +1203,25 @@ def servers(
991
1203
  elif server_cfg.type == "stdio" and server_cfg.command:
992
1204
  cmd = f"{server_cfg.command} {' '.join(server_cfg.args)}"
993
1205
  lines.append(f"**Command:** {cmd}")
1206
+
1207
+ # Add resource and prompt counts if connected
1208
+ if conn:
1209
+ try:
1210
+ if proxy._loop and proxy._loop.is_running():
1211
+ future_res = asyncio.run_coroutine_threadsafe(
1212
+ proxy.list_resources(server_name), proxy._loop
1213
+ )
1214
+ future_pmt = asyncio.run_coroutine_threadsafe(
1215
+ proxy.list_prompts(server_name), proxy._loop
1216
+ )
1217
+ resource_count = len(future_res.result(timeout=5))
1218
+ prompt_count = len(future_pmt.result(timeout=5))
1219
+ lines.append(f"**Resources:** {resource_count}")
1220
+ lines.append(f"**Prompts:** {prompt_count}")
1221
+ except Exception:
1222
+ # Silently skip if resources/prompts not supported
1223
+ pass
1224
+
994
1225
  lines.append("")
995
1226
 
996
1227
  # Show instructions if configured
@@ -1024,6 +1255,92 @@ def servers(
1024
1255
  s.add("count", len(results))
1025
1256
  return results
1026
1257
 
1258
+ # info="resources" - list resources per server
1259
+ if info == "resources":
1260
+ results_resources: list[dict[str, Any] | str] = []
1261
+
1262
+ for server_name in all_server_names:
1263
+ conn = proxy.get_connection(server_name)
1264
+ if not conn:
1265
+ results_resources.append({
1266
+ "server": server_name,
1267
+ "status": "disconnected",
1268
+ "resources": [],
1269
+ })
1270
+ continue
1271
+
1272
+ try:
1273
+ # Run async list_resources using ProxyManager's event loop
1274
+ if proxy._loop and proxy._loop.is_running():
1275
+ future = asyncio.run_coroutine_threadsafe(
1276
+ proxy.list_resources(server_name),
1277
+ proxy._loop,
1278
+ )
1279
+ resources = future.result(timeout=10)
1280
+ else:
1281
+ # No event loop available - server not initialized
1282
+ resources = []
1283
+
1284
+ results_resources.append({
1285
+ "server": server_name,
1286
+ "status": "connected",
1287
+ "resource_count": len(resources),
1288
+ "resources": resources,
1289
+ })
1290
+ except Exception as e:
1291
+ results_resources.append({
1292
+ "server": server_name,
1293
+ "status": "error",
1294
+ "error": str(e),
1295
+ "resources": [],
1296
+ })
1297
+
1298
+ s.add("count", len(results_resources))
1299
+ return results_resources
1300
+
1301
+ # info="prompts" - list prompts per server
1302
+ if info == "prompts":
1303
+ results_prompts: list[dict[str, Any] | str] = []
1304
+
1305
+ for server_name in all_server_names:
1306
+ conn = proxy.get_connection(server_name)
1307
+ if not conn:
1308
+ results_prompts.append({
1309
+ "server": server_name,
1310
+ "status": "disconnected",
1311
+ "prompts": [],
1312
+ })
1313
+ continue
1314
+
1315
+ try:
1316
+ # Run async list_prompts using ProxyManager's event loop
1317
+ if proxy._loop and proxy._loop.is_running():
1318
+ future = asyncio.run_coroutine_threadsafe(
1319
+ proxy.list_prompts(server_name),
1320
+ proxy._loop,
1321
+ )
1322
+ prompts = future.result(timeout=10)
1323
+ else:
1324
+ # No event loop available - server not initialized
1325
+ prompts = []
1326
+
1327
+ results_prompts.append({
1328
+ "server": server_name,
1329
+ "status": "connected",
1330
+ "prompt_count": len(prompts),
1331
+ "prompts": prompts,
1332
+ })
1333
+ except Exception as e:
1334
+ results_prompts.append({
1335
+ "server": server_name,
1336
+ "status": "error",
1337
+ "error": str(e),
1338
+ "prompts": [],
1339
+ })
1340
+
1341
+ s.add("count", len(results_prompts))
1342
+ return results_prompts
1343
+
1027
1344
  # info="min" (default) - summary for each server
1028
1345
  servers_list: list[dict[str, Any] | str] = []
1029
1346
 
@@ -1295,25 +1612,25 @@ def reload() -> str:
1295
1612
  import sys
1296
1613
 
1297
1614
  with log(span="ot.reload") as s:
1298
- import ot.config.loader
1299
- import ot.config.secrets
1615
+ # Import modules
1616
+ import ot.config
1300
1617
  import ot.executor.param_resolver
1301
1618
  import ot.executor.tool_loader
1619
+ import ot.executor.validator
1302
1620
  import ot.prompts
1303
1621
  import ot.proxy
1304
1622
  import ot.registry
1305
1623
 
1306
- # Clear config cache (must be first - other caches depend on it)
1307
- ot.config.loader._config = None
1308
-
1309
- # Clear secrets cache
1310
- ot.config.secrets._secrets = None
1624
+ # Clear in dependency order (config first, others depend on it)
1625
+ ot.config.reset() # Clears both config and secrets
1626
+ ot.prompts.reset()
1627
+ ot.registry.reset()
1628
+ ot.executor.tool_loader.reset()
1629
+ ot.executor.validator.reset()
1311
1630
 
1312
- # Clear prompts cache
1313
- ot.prompts._prompts = None
1314
-
1315
- # Clear tool loader module cache
1316
- ot.executor.tool_loader._module_cache.clear()
1631
+ # Clear param resolver cache
1632
+ ot.executor.param_resolver.get_tool_param_names.cache_clear()
1633
+ ot.executor.param_resolver._mcp_param_cache.clear()
1317
1634
 
1318
1635
  # Clean up dynamically loaded tool modules from sys.modules
1319
1636
  # Tool loader uses "tools.{stem}" naming pattern
@@ -1322,18 +1639,6 @@ def reload() -> str:
1322
1639
  del sys.modules[mod_name]
1323
1640
  s.add("toolModulesCleared", len(tool_modules))
1324
1641
 
1325
- # Clear tool registry cache (will rescan on next access)
1326
- ot.registry._registry = None
1327
-
1328
- # Clear param resolver cache (depends on registry)
1329
- ot.executor.param_resolver.get_tool_param_names.cache_clear()
1330
- ot.executor.param_resolver._mcp_param_cache.clear()
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
-
1337
1642
  # Reload config to validate and report stats
1338
1643
  cfg = get_config()
1339
1644