Skip to content

Commit 2b367a6

Browse files
committed
add ruff
1 parent da9bd0c commit 2b367a6

File tree

11 files changed

+444
-280
lines changed

11 files changed

+444
-280
lines changed

main.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import os
2+
13
from fastmcp import FastMCP
2-
import toolhive_client
4+
35
import mcp_client
6+
import toolhive_client
47
from shell_engine import ShellEngine
5-
import os
6-
78

89
mcp = FastMCP(
910
"model-context-shell",
@@ -15,7 +16,7 @@
1516
the entire workflow in one pipeline and use jq/grep/sed to transform data between stages.
1617
1718
Only inspect intermediate results to verify correctness, not to manually pass data around.
18-
"""
19+
""",
1920
)
2021

2122

@@ -108,6 +109,7 @@ async def _list_all_tools_impl() -> str:
108109
# Discover ToolHive connection to avoid assuming default ports
109110
try:
110111
from toolhive_client import discover_toolhive
112+
111113
host, port = discover_toolhive()
112114
tools_list = await mcp_client.list_tools(host=host, port=port)
113115
except Exception:
@@ -178,9 +180,11 @@ async def _get_tool_details_impl(server: str, tool_name: str) -> str:
178180

179181
result = []
180182
result.append(f"Tool: {details.get('name', 'unknown')}")
181-
result.append(f"\nDescription:\n{details.get('description', 'No description available')}")
182-
result.append(f"\nInput Schema:")
183-
result.append(json.dumps(details.get('inputSchema', {}), indent=2))
183+
result.append(
184+
f"\nDescription:\n{details.get('description', 'No description available')}"
185+
)
186+
result.append("\nInput Schema:")
187+
result.append(json.dumps(details.get("inputSchema", {}), indent=2))
184188

185189
return "\n".join(result)
186190

@@ -202,9 +206,9 @@ async def get_tool_details(server: str, tool_name: str) -> str:
202206

203207

204208
if __name__ == "__main__":
205-
import sys
206209
import os
207210
import shutil
211+
import sys
208212

209213
# Require bubblewrap (bwrap) for sandboxing child processes
210214
# Refuse to start the server if it's not available
@@ -226,14 +230,18 @@ async def get_tool_details(server: str, tool_name: str) -> str:
226230
else:
227231
# Container mode: Skip ToolHive discovery during startup
228232
# Tools will be discovered dynamically when execute_pipeline is called
229-
print("Running in container mode - ToolHive connection will be established on first tool use\n")
233+
print(
234+
"Running in container mode - ToolHive connection will be established on first tool use\n"
235+
)
230236

231237
# Run the MCP server with HTTP transport
232238
# Check if --transport argument is provided
233239
transport = "streamable-http" # Default to streamable-http for HTTP access
234240
# Defaults; may be overridden by CLI or env
235241
port = 8000
236-
host = "0.0.0.0" if in_container else "127.0.0.1" # Bind to 0.0.0.0 in container for external access
242+
host = (
243+
"0.0.0.0" if in_container else "127.0.0.1"
244+
) # Bind to 0.0.0.0 in container for external access
237245

238246
# CLI args take precedence over env
239247
for i, arg in enumerate(sys.argv):
@@ -251,7 +259,9 @@ async def get_tool_details(server: str, tool_name: str) -> str:
251259
try:
252260
port = int(mcp_port_env)
253261
except ValueError:
254-
sys.stderr.write(f"Warning: MCP_PORT must be an integer, got '{mcp_port_env}'. Using default/CLI value {port}.\n")
262+
sys.stderr.write(
263+
f"Warning: MCP_PORT must be an integer, got '{mcp_port_env}'. Using default/CLI value {port}.\n"
264+
)
255265

256266
if "--host" not in sys.argv:
257267
mcp_host_env = os.environ.get("MCP_HOST")

mcp_client.py

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import asyncio
2-
from typing import List, Dict, Any
2+
from typing import Any
3+
34
import httpx
45
from mcp import ClientSession
5-
from mcp.client.streamable_http import streamablehttp_client
66
from mcp.client.sse import sse_client
7-
7+
from mcp.client.streamable_http import streamablehttp_client
88

99
DEFAULT_HOST = "127.0.0.1"
1010
DEFAULT_PORT = 8080
1111

1212

13-
async def get_workloads(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> List[Dict[str, Any]]:
13+
async def get_workloads(
14+
host: str = DEFAULT_HOST, port: int = DEFAULT_PORT
15+
) -> list[dict[str, Any]]:
1416
"""
1517
Get list of workloads from ToolHive API.
1618
@@ -46,7 +48,7 @@ async def get_workloads(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> L
4648
return workloads
4749

4850

49-
async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
51+
async def list_tools_from_server(workload: dict[str, Any]) -> dict[str, Any]:
5052
"""List tools from a single MCP server workload"""
5153
name = workload.get("name", "unknown")
5254

@@ -56,7 +58,7 @@ async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
5658
"list_available_shell_commands",
5759
"execute_pipeline",
5860
"list_all_tools",
59-
"get_tool_details"
61+
"get_tool_details",
6062
}
6163

6264
try:
@@ -72,15 +74,15 @@ async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
7274
"workload": name,
7375
"status": "skipped",
7476
"tools": [],
75-
"error": f"Workload status is '{status}', not running"
77+
"error": f"Workload status is '{status}', not running",
7678
}
7779

7880
if not url:
7981
return {
8082
"workload": name,
8183
"status": "error",
8284
"tools": [],
83-
"error": "No URL provided for workload"
85+
"error": "No URL provided for workload",
8486
}
8587

8688
# Determine which client to use based on proxy_mode or transport_type
@@ -92,10 +94,7 @@ async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
9294
await session.initialize()
9395
tools_response = await session.list_tools()
9496
tools_info = [
95-
{
96-
"name": tool.name,
97-
"description": tool.description or ""
98-
}
97+
{"name": tool.name, "description": tool.description or ""}
9998
for tool in tools_response.tools
10099
]
101100

@@ -107,14 +106,14 @@ async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
107106
"workload": name,
108107
"status": "skipped",
109108
"tools": [],
110-
"error": "Skipped: orchestrator workload (self)"
109+
"error": "Skipped: orchestrator workload (self)",
111110
}
112111

113112
return {
114113
"workload": name,
115114
"status": "success",
116115
"tools": tools_info,
117-
"error": None
116+
"error": None,
118117
}
119118
elif proxy_mode == "streamable-http" or transport_type == "streamable-http":
120119
# Use streamable HTTP client
@@ -123,10 +122,7 @@ async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
123122
await session.initialize()
124123
tools_response = await session.list_tools()
125124
tools_info = [
126-
{
127-
"name": tool.name,
128-
"description": tool.description or ""
129-
}
125+
{"name": tool.name, "description": tool.description or ""}
130126
for tool in tools_response.tools
131127
]
132128

@@ -138,54 +134,46 @@ async def list_tools_from_server(workload: Dict[str, Any]) -> Dict[str, Any]:
138134
"workload": name,
139135
"status": "skipped",
140136
"tools": [],
141-
"error": "Skipped: orchestrator workload (self)"
137+
"error": "Skipped: orchestrator workload (self)",
142138
}
143139

144140
return {
145141
"workload": name,
146142
"status": "success",
147143
"tools": tools_info,
148-
"error": None
144+
"error": None,
149145
}
150146
else:
151147
return {
152148
"workload": name,
153149
"status": "unsupported",
154150
"tools": [],
155-
"error": f"Transport/proxy mode '{proxy_mode or transport_type}' not yet supported"
151+
"error": f"Transport/proxy mode '{proxy_mode or transport_type}' not yet supported",
156152
}
157153

158154
except Exception as e:
159155
import traceback
156+
160157
error_msg = f"{str(e)}\n{traceback.format_exc()}"
161-
return {
162-
"workload": name,
163-
"status": "error",
164-
"tools": [],
165-
"error": error_msg
166-
}
158+
return {"workload": name, "status": "error", "tools": [], "error": error_msg}
167159

168160

169161
async def get_tool_details_from_server(
170-
workload_name: str,
171-
tool_name: str,
172-
host: str = None,
173-
port: int = None
174-
) -> Dict[str, Any]:
162+
workload_name: str, tool_name: str, host: str = None, port: int = None
163+
) -> dict[str, Any]:
175164
"""Get detailed information about a specific tool from a workload"""
176165
# Discover ToolHive if not already done
177166
if host is None or port is None:
178167
from toolhive_client import discover_toolhive
168+
179169
host, port = discover_toolhive(host, port)
180170

181171
# Get workload details
182172
workloads = await get_workloads(host, port)
183173
workload = next((w for w in workloads if w.get("name") == workload_name), None)
184174

185175
if not workload:
186-
return {
187-
"error": f"Workload '{workload_name}' not found"
188-
}
176+
return {"error": f"Workload '{workload_name}' not found"}
189177

190178
try:
191179
transport_type = workload.get("transport_type", "")
@@ -201,36 +189,47 @@ async def get_tool_details_from_server(
201189
async with ClientSession(read, write) as session:
202190
await session.initialize()
203191
tools_response = await session.list_tools()
204-
tool = next((t for t in tools_response.tools if t.name == tool_name), None)
192+
tool = next(
193+
(t for t in tools_response.tools if t.name == tool_name), None
194+
)
205195

206196
if not tool:
207-
return {"error": f"Tool '{tool_name}' not found in workload '{workload_name}'"}
197+
return {
198+
"error": f"Tool '{tool_name}' not found in workload '{workload_name}'"
199+
}
208200

209201
return {
210202
"name": tool.name,
211203
"description": tool.description or "",
212-
"inputSchema": tool.inputSchema
204+
"inputSchema": tool.inputSchema,
213205
}
214206
elif proxy_mode == "streamable-http" or transport_type == "streamable-http":
215207
async with streamablehttp_client(url) as (read, write, get_session_id):
216208
async with ClientSession(read, write) as session:
217209
await session.initialize()
218210
tools_response = await session.list_tools()
219-
tool = next((t for t in tools_response.tools if t.name == tool_name), None)
211+
tool = next(
212+
(t for t in tools_response.tools if t.name == tool_name), None
213+
)
220214

221215
if not tool:
222-
return {"error": f"Tool '{tool_name}' not found in workload '{workload_name}'"}
216+
return {
217+
"error": f"Tool '{tool_name}' not found in workload '{workload_name}'"
218+
}
223219

224220
return {
225221
"name": tool.name,
226222
"description": tool.description or "",
227-
"inputSchema": tool.inputSchema
223+
"inputSchema": tool.inputSchema,
228224
}
229225
else:
230-
return {"error": f"Transport/proxy mode '{proxy_mode or transport_type}' not supported"}
226+
return {
227+
"error": f"Transport/proxy mode '{proxy_mode or transport_type}' not supported"
228+
}
231229

232230
except Exception as e:
233231
import traceback
232+
234233
return {
235234
"error": f"Failed to get tool details: {str(e)}\n{traceback.format_exc()}"
236235
}
@@ -239,9 +238,9 @@ async def get_tool_details_from_server(
239238
async def call_tool(
240239
workload_name: str,
241240
tool_name: str,
242-
arguments: Dict[str, Any],
241+
arguments: dict[str, Any],
243242
host: str = DEFAULT_HOST,
244-
port: int = DEFAULT_PORT
243+
port: int = DEFAULT_PORT,
245244
) -> Any:
246245
"""
247246
Call a tool from a specific MCP server workload.
@@ -253,6 +252,7 @@ async def call_tool(
253252
try:
254253
if host == DEFAULT_HOST and port == DEFAULT_PORT:
255254
from toolhive_client import discover_toolhive
255+
256256
host, port = discover_toolhive(host=None, port=None)
257257
except Exception:
258258
# Fall back to provided/defaults if discovery fails
@@ -271,7 +271,9 @@ async def call_tool(
271271
transport_type = workload.get("transport_type", "")
272272

273273
if status != "running":
274-
raise RuntimeError(f"Workload '{workload_name}' is not running (status: {status})")
274+
raise RuntimeError(
275+
f"Workload '{workload_name}' is not running (status: {status})"
276+
)
275277

276278
if not url:
277279
raise ValueError(f"No URL provided for workload '{workload_name}'")
@@ -290,10 +292,14 @@ async def call_tool(
290292
result = await session.call_tool(tool_name, arguments=arguments)
291293
return result
292294
else:
293-
raise ValueError(f"Transport/proxy mode '{proxy_mode or transport_type}' not supported")
295+
raise ValueError(
296+
f"Transport/proxy mode '{proxy_mode or transport_type}' not supported"
297+
)
294298

295299

296-
async def list_tools(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> List[Dict[str, Any]]:
300+
async def list_tools(
301+
host: str = DEFAULT_HOST, port: int = DEFAULT_PORT
302+
) -> list[dict[str, Any]]:
297303
"""
298304
List tools from all MCP servers running through ToolHive.
299305
@@ -318,22 +324,26 @@ async def list_tools(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> List
318324
processed_results = []
319325
for i, result in enumerate(results):
320326
if isinstance(result, Exception):
321-
processed_results.append({
322-
"workload": workloads[i].get("name", f"workload_{i}"),
323-
"status": "error",
324-
"tools": [],
325-
"error": str(result)
326-
})
327+
processed_results.append(
328+
{
329+
"workload": workloads[i].get("name", f"workload_{i}"),
330+
"status": "error",
331+
"tools": [],
332+
"error": str(result),
333+
}
334+
)
327335
else:
328336
processed_results.append(result)
329337

330338
return processed_results
331339

332340
except Exception as e:
333341
# If we can't even get the workload list, return error
334-
return [{
335-
"workload": "toolhive",
336-
"status": "error",
337-
"tools": [],
338-
"error": f"Failed to get workload list: {str(e)}"
339-
}]
342+
return [
343+
{
344+
"workload": "toolhive",
345+
"status": "error",
346+
"tools": [],
347+
"error": f"Failed to get workload list: {str(e)}",
348+
}
349+
]

0 commit comments

Comments
 (0)