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/server.py
CHANGED
|
@@ -1,315 +1,330 @@
|
|
|
1
|
-
"""FastMCP server implementation with a single 'run' tool.
|
|
2
|
-
|
|
3
|
-
The agent generates function call syntax with __ot prefix:
|
|
4
|
-
__ot context7.search(query="next.js")
|
|
5
|
-
__ot context7.doc(library_key="vercel/next.js", topic="routing")
|
|
6
|
-
__ot `demo.upper(text="hello")`
|
|
7
|
-
|
|
8
|
-
Or Python code blocks:
|
|
9
|
-
__ot
|
|
10
|
-
```python
|
|
11
|
-
metals = ["Gold", "Silver", "Bronze"]
|
|
12
|
-
results = {}
|
|
13
|
-
for metal in metals:
|
|
14
|
-
results[metal] = brave.web_search(query=f"{metal} price", count=3)
|
|
15
|
-
return results
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Or direct MCP calls:
|
|
19
|
-
mcp__onetool__run(command='brave.web_search(query="test")')
|
|
20
|
-
|
|
21
|
-
Supported prefixes: __ot, __ot__run, __onetool, __onetool__run, mcp__onetool__run
|
|
22
|
-
Note: mcp__ot__run is NOT valid.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import time
|
|
28
|
-
from contextlib import asynccontextmanager
|
|
29
|
-
from typing import TYPE_CHECKING, Any
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import AsyncIterator
|
|
33
|
-
|
|
34
|
-
from fastmcp import Context, FastMCP
|
|
35
|
-
from loguru import logger
|
|
36
|
-
|
|
37
|
-
from ot.config.loader import get_config
|
|
38
|
-
from ot.executor import SimpleExecutor, execute_command
|
|
39
|
-
from ot.executor.runner import prepare_command
|
|
40
|
-
|
|
41
|
-
# Import logging first to remove Loguru's default console handler
|
|
42
|
-
from ot.logging import LogSpan, configure_logging
|
|
43
|
-
from ot.prompts import get_prompts, get_tool_description, get_tool_examples
|
|
44
|
-
from ot.proxy import get_proxy_manager
|
|
45
|
-
from ot.registry import get_registry
|
|
46
|
-
from ot.stats import (
|
|
47
|
-
JsonlStatsWriter,
|
|
48
|
-
get_client_name,
|
|
49
|
-
set_stats_writer,
|
|
50
|
-
)
|
|
51
|
-
from ot.support import get_startup_message
|
|
52
|
-
|
|
53
|
-
_config = get_config()
|
|
54
|
-
|
|
55
|
-
# Initialize logging to serve.log
|
|
56
|
-
configure_logging(log_name="serve")
|
|
57
|
-
|
|
58
|
-
# Global stats writer (unified JSONL for both run and tool stats)
|
|
59
|
-
_stats_writer: JsonlStatsWriter | None = None
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _get_instructions() -> str:
|
|
63
|
-
"""Generate MCP server instructions.
|
|
64
|
-
|
|
65
|
-
Note: Tool descriptions are NOT included here - they come through
|
|
66
|
-
the MCP tool definitions which the client converts to function calling format.
|
|
67
|
-
"""
|
|
68
|
-
# Load prompts from config (loaded via include: or inline prompts:)
|
|
69
|
-
prompts = get_prompts(inline_prompts=_config.prompts)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"
|
|
234
|
-
"
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
#
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
1
|
+
"""FastMCP server implementation with a single 'run' tool.
|
|
2
|
+
|
|
3
|
+
The agent generates function call syntax with __ot prefix:
|
|
4
|
+
__ot context7.search(query="next.js")
|
|
5
|
+
__ot context7.doc(library_key="vercel/next.js", topic="routing")
|
|
6
|
+
__ot `demo.upper(text="hello")`
|
|
7
|
+
|
|
8
|
+
Or Python code blocks:
|
|
9
|
+
__ot
|
|
10
|
+
```python
|
|
11
|
+
metals = ["Gold", "Silver", "Bronze"]
|
|
12
|
+
results = {}
|
|
13
|
+
for metal in metals:
|
|
14
|
+
results[metal] = brave.web_search(query=f"{metal} price", count=3)
|
|
15
|
+
return results
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or direct MCP calls:
|
|
19
|
+
mcp__onetool__run(command='brave.web_search(query="test")')
|
|
20
|
+
|
|
21
|
+
Supported prefixes: __ot, __ot__run, __onetool, __onetool__run, mcp__onetool__run
|
|
22
|
+
Note: mcp__ot__run is NOT valid.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import time
|
|
28
|
+
from contextlib import asynccontextmanager
|
|
29
|
+
from typing import TYPE_CHECKING, Any
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import AsyncIterator
|
|
33
|
+
|
|
34
|
+
from fastmcp import Context, FastMCP
|
|
35
|
+
from loguru import logger
|
|
36
|
+
|
|
37
|
+
from ot.config.loader import get_config
|
|
38
|
+
from ot.executor import SimpleExecutor, execute_command
|
|
39
|
+
from ot.executor.runner import prepare_command
|
|
40
|
+
|
|
41
|
+
# Import logging first to remove Loguru's default console handler
|
|
42
|
+
from ot.logging import LogSpan, configure_logging
|
|
43
|
+
from ot.prompts import get_prompts, get_tool_description, get_tool_examples
|
|
44
|
+
from ot.proxy import get_proxy_manager
|
|
45
|
+
from ot.registry import get_registry
|
|
46
|
+
from ot.stats import (
|
|
47
|
+
JsonlStatsWriter,
|
|
48
|
+
get_client_name,
|
|
49
|
+
set_stats_writer,
|
|
50
|
+
)
|
|
51
|
+
from ot.support import get_startup_message
|
|
52
|
+
|
|
53
|
+
_config = get_config()
|
|
54
|
+
|
|
55
|
+
# Initialize logging to serve.log
|
|
56
|
+
configure_logging(log_name="serve")
|
|
57
|
+
|
|
58
|
+
# Global stats writer (unified JSONL for both run and tool stats)
|
|
59
|
+
_stats_writer: JsonlStatsWriter | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_instructions() -> str:
|
|
63
|
+
"""Generate MCP server instructions.
|
|
64
|
+
|
|
65
|
+
Note: Tool descriptions are NOT included here - they come through
|
|
66
|
+
the MCP tool definitions which the client converts to function calling format.
|
|
67
|
+
"""
|
|
68
|
+
# Load prompts from config (loaded via include: or inline prompts:)
|
|
69
|
+
prompts = get_prompts(inline_prompts=_config.prompts)
|
|
70
|
+
|
|
71
|
+
parts = [prompts.instructions.strip()]
|
|
72
|
+
|
|
73
|
+
# Append server-specific instructions from enabled servers
|
|
74
|
+
if _config.servers:
|
|
75
|
+
server_instructions = []
|
|
76
|
+
for name, cfg in _config.servers.items():
|
|
77
|
+
if cfg.enabled and cfg.instructions:
|
|
78
|
+
server_instructions.append(f"## {name}\n{cfg.instructions.strip()}")
|
|
79
|
+
|
|
80
|
+
if server_instructions:
|
|
81
|
+
parts.append("\n# MCP Server Instructions\n")
|
|
82
|
+
parts.append(
|
|
83
|
+
"The following MCP servers have provided instructions for how to use their tools and resources:\n"
|
|
84
|
+
)
|
|
85
|
+
parts.append("\n\n".join(server_instructions))
|
|
86
|
+
|
|
87
|
+
return "\n".join(parts)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@asynccontextmanager
|
|
91
|
+
async def _lifespan(_server: FastMCP) -> AsyncIterator[None]:
|
|
92
|
+
"""Manage server lifecycle - startup and shutdown."""
|
|
93
|
+
global _stats_writer
|
|
94
|
+
|
|
95
|
+
with LogSpan(span="mcp.server.start") as start_span:
|
|
96
|
+
# Startup: connect to proxy MCP servers
|
|
97
|
+
proxy = get_proxy_manager()
|
|
98
|
+
if _config.servers:
|
|
99
|
+
with LogSpan(span="server.startup.proxy", serverCount=len(_config.servers)):
|
|
100
|
+
await proxy.connect(_config.servers)
|
|
101
|
+
start_span.add("proxyCount", len(_config.servers))
|
|
102
|
+
|
|
103
|
+
# Log tool count from registry
|
|
104
|
+
registry = get_registry()
|
|
105
|
+
start_span.add("toolCount", len(registry.tools))
|
|
106
|
+
|
|
107
|
+
# Startup: initialize unified JSONL stats writer if enabled
|
|
108
|
+
if _config.stats.enabled:
|
|
109
|
+
stats_path = _config.get_stats_file_path()
|
|
110
|
+
flush_interval = _config.stats.flush_interval_seconds
|
|
111
|
+
|
|
112
|
+
_stats_writer = JsonlStatsWriter(
|
|
113
|
+
path=stats_path,
|
|
114
|
+
flush_interval=flush_interval,
|
|
115
|
+
)
|
|
116
|
+
await _stats_writer.start()
|
|
117
|
+
set_stats_writer(_stats_writer)
|
|
118
|
+
|
|
119
|
+
start_span.add("statsEnabled", True)
|
|
120
|
+
start_span.add("statsPath", str(stats_path))
|
|
121
|
+
|
|
122
|
+
# Log support message
|
|
123
|
+
logger.info(get_startup_message())
|
|
124
|
+
|
|
125
|
+
yield
|
|
126
|
+
|
|
127
|
+
with LogSpan(span="mcp.server.stop") as stop_span:
|
|
128
|
+
# Shutdown: stop stats writer
|
|
129
|
+
if _stats_writer is not None:
|
|
130
|
+
await _stats_writer.stop()
|
|
131
|
+
set_stats_writer(None)
|
|
132
|
+
stop_span.add("statsStopped", True)
|
|
133
|
+
|
|
134
|
+
# Shutdown: disconnect from proxy MCP servers
|
|
135
|
+
if proxy.servers:
|
|
136
|
+
with LogSpan(span="server.shutdown.proxy", serverCount=len(proxy.servers)):
|
|
137
|
+
await proxy.shutdown()
|
|
138
|
+
stop_span.add("proxyCount", len(proxy.servers))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
mcp = FastMCP(
|
|
142
|
+
name="ot",
|
|
143
|
+
instructions=_get_instructions(),
|
|
144
|
+
lifespan=_lifespan,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# =============================================================================
|
|
149
|
+
# MCP Logging - Dynamic log level control
|
|
150
|
+
# =============================================================================
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@mcp._mcp_server.set_logging_level() # type: ignore[no-untyped-call,untyped-decorator]
|
|
154
|
+
async def handle_set_logging_level(level: str) -> None:
|
|
155
|
+
"""Handle logging/setLevel requests from MCP clients.
|
|
156
|
+
|
|
157
|
+
Allows clients to dynamically change the server's log level.
|
|
158
|
+
"""
|
|
159
|
+
# Map MCP LoggingLevel to Python logging levels
|
|
160
|
+
level_map = {
|
|
161
|
+
"debug": "DEBUG",
|
|
162
|
+
"info": "INFO",
|
|
163
|
+
"notice": "INFO", # MCP notice -> INFO
|
|
164
|
+
"warning": "WARNING",
|
|
165
|
+
"error": "ERROR",
|
|
166
|
+
"critical": "CRITICAL",
|
|
167
|
+
"alert": "CRITICAL", # MCP alert -> CRITICAL
|
|
168
|
+
"emergency": "CRITICAL", # MCP emergency -> CRITICAL
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
log_level = level_map.get(str(level).lower(), "INFO")
|
|
172
|
+
logger.info(f"Log level change requested: {level} -> {log_level}")
|
|
173
|
+
|
|
174
|
+
# Reconfigure logging with new level
|
|
175
|
+
configure_logging(log_name="serve", level=log_level)
|
|
176
|
+
logger.info(f"Logging reconfigured at level {log_level}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# =============================================================================
|
|
180
|
+
# MCP Resources - Tool discoverability
|
|
181
|
+
# =============================================================================
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@mcp.resource("ot://tools")
|
|
185
|
+
def list_tools_resource() -> list[dict[str, str]]:
|
|
186
|
+
"""List all available tools with signatures and descriptions."""
|
|
187
|
+
registry = get_registry()
|
|
188
|
+
prompts = get_prompts(inline_prompts=_config.prompts)
|
|
189
|
+
|
|
190
|
+
tools_list = []
|
|
191
|
+
|
|
192
|
+
# Add local tools
|
|
193
|
+
for tool in registry.tools.values():
|
|
194
|
+
desc = get_tool_description(prompts, tool.name, tool.description)
|
|
195
|
+
tools_list.append(
|
|
196
|
+
{
|
|
197
|
+
"name": tool.name,
|
|
198
|
+
"signature": tool.signature,
|
|
199
|
+
"description": desc,
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Add proxied tools
|
|
204
|
+
proxy = get_proxy_manager()
|
|
205
|
+
for proxy_tool in proxy.list_tools():
|
|
206
|
+
tools_list.append(
|
|
207
|
+
{
|
|
208
|
+
"name": f"{proxy_tool.server}.{proxy_tool.name}",
|
|
209
|
+
"signature": f"{proxy_tool.server}.{proxy_tool.name}(...)",
|
|
210
|
+
"description": f"[proxy] {proxy_tool.description}",
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return tools_list
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@mcp.resource("ot://tool/{name}")
|
|
218
|
+
def get_tool_resource(name: str) -> dict[str, Any]:
|
|
219
|
+
"""Get detailed information about a specific tool."""
|
|
220
|
+
registry = get_registry()
|
|
221
|
+
prompts = get_prompts(inline_prompts=_config.prompts)
|
|
222
|
+
|
|
223
|
+
tool = registry.tools.get(name)
|
|
224
|
+
if not tool:
|
|
225
|
+
return {"error": f"Tool '{name}' not found"}
|
|
226
|
+
|
|
227
|
+
desc = get_tool_description(prompts, tool.name, tool.description)
|
|
228
|
+
examples = get_tool_examples(prompts, tool.name)
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"name": tool.name,
|
|
232
|
+
"module": tool.module,
|
|
233
|
+
"signature": tool.signature,
|
|
234
|
+
"description": desc,
|
|
235
|
+
"args": [
|
|
236
|
+
{
|
|
237
|
+
"name": arg.name,
|
|
238
|
+
"type": arg.type,
|
|
239
|
+
"default": arg.default,
|
|
240
|
+
"description": arg.description,
|
|
241
|
+
}
|
|
242
|
+
for arg in tool.args
|
|
243
|
+
],
|
|
244
|
+
"returns": tool.returns,
|
|
245
|
+
"examples": examples or tool.examples,
|
|
246
|
+
"tags": tool.tags,
|
|
247
|
+
"enabled": tool.enabled,
|
|
248
|
+
"deprecated": tool.deprecated,
|
|
249
|
+
"deprecated_message": tool.deprecated_message,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Global executor instance
|
|
254
|
+
_executor: SimpleExecutor | None = None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _get_executor() -> SimpleExecutor:
|
|
258
|
+
"""Get or create the executor."""
|
|
259
|
+
global _executor
|
|
260
|
+
|
|
261
|
+
if _executor is None:
|
|
262
|
+
_executor = SimpleExecutor()
|
|
263
|
+
|
|
264
|
+
return _executor
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _get_run_description() -> str:
|
|
268
|
+
"""Get run tool description from prompts config.
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
ValueError: If run tool description not found in prompts.yaml
|
|
272
|
+
"""
|
|
273
|
+
prompts = get_prompts(inline_prompts=_config.prompts)
|
|
274
|
+
desc = get_tool_description(prompts, "run", "")
|
|
275
|
+
if not desc:
|
|
276
|
+
raise ValueError("Missing 'run' tool description in prompts.yaml")
|
|
277
|
+
return desc
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@mcp.tool(
|
|
281
|
+
description=_get_run_description(),
|
|
282
|
+
annotations={
|
|
283
|
+
"title": "Execute OneTool Command",
|
|
284
|
+
"readOnlyHint": False,
|
|
285
|
+
"destructiveHint": False,
|
|
286
|
+
"idempotentHint": False,
|
|
287
|
+
"openWorldHint": True,
|
|
288
|
+
},
|
|
289
|
+
)
|
|
290
|
+
async def run(command: str, ctx: Context) -> str: # noqa: ARG001
|
|
291
|
+
# Get registry (cached, no rescan per request) and executor
|
|
292
|
+
registry = get_registry()
|
|
293
|
+
executor = _get_executor()
|
|
294
|
+
|
|
295
|
+
# Record start time for stats
|
|
296
|
+
start_time = time.monotonic()
|
|
297
|
+
|
|
298
|
+
# Step 1: Prepare and validate command
|
|
299
|
+
prepared = prepare_command(command)
|
|
300
|
+
|
|
301
|
+
if prepared.error:
|
|
302
|
+
return f"Error: {prepared.error}"
|
|
303
|
+
|
|
304
|
+
# Step 2: Execute through unified runner (skip validation since already done)
|
|
305
|
+
result = await execute_command(
|
|
306
|
+
command,
|
|
307
|
+
registry,
|
|
308
|
+
executor,
|
|
309
|
+
prepared_code=prepared.code,
|
|
310
|
+
skip_validation=True,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Record run-level stats if enabled
|
|
314
|
+
if _stats_writer is not None:
|
|
315
|
+
duration_ms = int((time.monotonic() - start_time) * 1000)
|
|
316
|
+
_stats_writer.record_run(
|
|
317
|
+
client=get_client_name(),
|
|
318
|
+
chars_in=len(command),
|
|
319
|
+
chars_out=len(result.result),
|
|
320
|
+
duration_ms=duration_ms,
|
|
321
|
+
success=result.success,
|
|
322
|
+
error_type=result.error_type,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return result.result
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def main() -> None:
|
|
329
|
+
"""Run the MCP server over stdio transport."""
|
|
330
|
+
mcp.run(show_banner=False)
|