mle-kit-mcp 0.2.5__tar.gz → 1.0.0__tar.gz

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 (27) hide show
  1. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/PKG-INFO +1 -1
  2. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/server.py +18 -7
  3. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/tools/bash.py +37 -6
  4. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp.egg-info/PKG-INFO +1 -1
  5. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/pyproject.toml +1 -1
  6. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/tests/test_bash.py +13 -2
  7. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/tests/test_text_editor.py +2 -1
  8. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/LICENSE +0 -0
  9. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/README.md +0 -0
  10. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/__init__.py +0 -0
  11. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/__main__.py +0 -0
  12. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/files.py +0 -0
  13. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/llm_proxy.py +0 -0
  14. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/py.typed +0 -0
  15. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/tools/__init__.py +0 -0
  16. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/tools/llm_proxy.py +0 -0
  17. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/tools/remote_gpu.py +0 -0
  18. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/tools/text_editor.py +0 -0
  19. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp/utils.py +0 -0
  20. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp.egg-info/SOURCES.txt +0 -0
  21. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp.egg-info/dependency_links.txt +0 -0
  22. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp.egg-info/entry_points.txt +0 -0
  23. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp.egg-info/requires.txt +0 -0
  24. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/mle_kit_mcp.egg-info/top_level.txt +0 -0
  25. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/setup.cfg +0 -0
  26. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/tests/test_llm_proxy.py +0 -0
  27. {mle_kit_mcp-0.2.5 → mle_kit_mcp-1.0.0}/tests/test_truncate_context.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mle-kit-mcp
3
- Version: 0.2.5
3
+ Version: 1.0.0
4
4
  Summary: MCP server that provides different tools for MLE
5
5
  Author-email: Ilya Gusev <phoenixilya@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/IlyaGusev/mle_kit_mcp
@@ -1,9 +1,8 @@
1
1
  import os
2
2
  from pathlib import Path
3
- from typing import Optional
3
+ from typing import Optional, Literal
4
4
 
5
5
  import fire # type: ignore
6
- import uvicorn
7
6
  from mcp.server.fastmcp import FastMCP
8
7
  from dotenv import load_dotenv
9
8
 
@@ -21,14 +20,26 @@ from .tools.llm_proxy import (
21
20
  from .files import get_workspace_dir, WorkspaceDirectory
22
21
 
23
22
 
24
- def run(host: str = "0.0.0.0", port: int = 5050, workspace: Optional[str] = None) -> None:
23
+ def run(
24
+ host: str = "0.0.0.0",
25
+ port: int = 5050,
26
+ mount_path: str = "/",
27
+ streamable_http_path: str = "/mcp",
28
+ workspace: Optional[str] = None,
29
+ transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http",
30
+ ) -> None:
25
31
  load_dotenv()
26
32
  if workspace:
27
33
  WorkspaceDirectory.set_dir(Path(workspace))
28
34
  workspace_path = get_workspace_dir()
29
35
  workspace_path.mkdir(parents=True, exist_ok=True)
30
36
 
31
- server = FastMCP("MLE kit MCP", stateless_http=True)
37
+ server = FastMCP(
38
+ "MLE kit MCP",
39
+ stateless_http=True,
40
+ streamable_http_path=streamable_http_path,
41
+ mount_path=mount_path,
42
+ )
32
43
 
33
44
  remote_text_editor = create_remote_text_editor(text_editor)
34
45
 
@@ -41,9 +52,9 @@ def run(host: str = "0.0.0.0", port: int = 5050, workspace: Optional[str] = None
41
52
  server.add_tool(llm_proxy_local)
42
53
  server.add_tool(llm_proxy_remote)
43
54
 
44
- http_app = server.streamable_http_app()
45
-
46
- uvicorn.run(http_app, host=host, port=port)
55
+ server.settings.port = port
56
+ server.settings.host = host
57
+ server.run(transport=transport)
47
58
 
48
59
 
49
60
  if __name__ == "__main__":
@@ -1,5 +1,6 @@
1
1
  import atexit
2
2
  import signal
3
+ import shlex
3
4
  from typing import Optional, Any
4
5
 
5
6
  from docker import from_env as docker_from_env # type: ignore
@@ -67,7 +68,11 @@ signal.signal(signal.SIGINT, cleanup_container)
67
68
  signal.signal(signal.SIGTERM, cleanup_container)
68
69
 
69
70
 
70
- def bash(command: str, cwd: Optional[str] = None) -> str:
71
+ def bash(
72
+ command: str,
73
+ cwd: Optional[str] = None,
74
+ timeout: int = 60,
75
+ ) -> str:
71
76
  """
72
77
  Run commands in a bash shell.
73
78
  When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
@@ -76,22 +81,48 @@ def bash(command: str, cwd: Optional[str] = None) -> str:
76
81
  State is persistent across command calls and discussions with the user.
77
82
  To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
78
83
  Please avoid commands that may produce a very large amount of output.
79
- Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
80
84
 
81
85
  Args:
82
86
  command: The bash command to run.
83
87
  cwd: The working directory to run the command in. Relative to the workspace directory.
88
+ timeout: Timeout for the command execution in seconds. Kills after this. 60 seconds by default.
84
89
  """
85
-
90
+ assert timeout and timeout > 0, "Timeout must be set and greater than 0"
86
91
  container = get_container()
87
92
  workdir = DOCKER_WORKSPACE_DIR_PATH
88
93
  if cwd:
89
- workdir = DOCKER_WORKSPACE_DIR_PATH + "/" + cwd
94
+ workdir += "/" + cwd
95
+
96
+ wrapped = f"bash -lc {shlex.quote(command)}"
97
+ final_command = f"timeout --signal=TERM --kill-after=5s {int(timeout)}s {wrapped}"
98
+
90
99
  result = container.exec_run(
91
- ["bash", "-c", command],
100
+ ["bash", "-lc", final_command],
92
101
  workdir=workdir,
93
102
  stdout=True,
94
103
  stderr=True,
104
+ demux=True,
95
105
  )
96
- output: str = result.output.decode("utf-8").strip()
106
+ stdout_bytes, stderr_bytes = (
107
+ result.output if isinstance(result.output, tuple) else (result.output, b"")
108
+ )
109
+
110
+ if result.exit_code in (124, 137):
111
+ timeout_msg = (
112
+ f"Command timed out after {int(timeout)} seconds: {command};\n"
113
+ f"You can increase the timeout by changing the parameter of the tool call."
114
+ )
115
+ stderr_bytes = (stderr_bytes or b"") + timeout_msg.encode("utf-8")
116
+
117
+ stdout_text = (stdout_bytes or b"").decode("utf-8", errors="replace").strip()
118
+ stderr_text = (stderr_bytes or b"").decode("utf-8", errors="replace").strip()
119
+
120
+ output_parts = []
121
+ if stdout_text:
122
+ output_parts.append("Command stdout: " + stdout_text)
123
+ if stderr_text:
124
+ output_parts.append("Command stderr: " + stderr_text)
125
+ output = ("\n".join(output_parts)).strip()
126
+ if not output:
127
+ output = "No output from the command"
97
128
  return output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mle-kit-mcp
3
- Version: 0.2.5
3
+ Version: 1.0.0
4
4
  Summary: MCP server that provides different tools for MLE
5
5
  Author-email: Ilya Gusev <phoenixilya@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/IlyaGusev/mle_kit_mcp
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mle-kit-mcp"
7
- version = "0.2.5"
7
+ version = "1.0.0"
8
8
  description = "MCP server that provides different tools for MLE"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -6,10 +6,10 @@ from mle_kit_mcp.files import get_workspace_dir
6
6
 
7
7
  def test_bash() -> None:
8
8
  result = bash('echo "Hello World"')
9
- assert result == "Hello World"
9
+ assert result == "Command stdout: Hello World"
10
10
 
11
11
  result = bash("pwd")
12
- assert result == "/workdir"
12
+ assert result == "Command stdout: /workdir"
13
13
 
14
14
  result = bash("touch dummy")
15
15
  assert os.path.exists(get_workspace_dir() / "dummy")
@@ -22,3 +22,14 @@ def test_bash_cwd() -> None:
22
22
  bash("mkdir -p dummy_dir")
23
23
  bash("touch dummy", cwd="dummy_dir")
24
24
  assert os.path.exists(get_workspace_dir() / "dummy_dir" / "dummy")
25
+
26
+
27
+ def test_bash_timeout_base() -> None:
28
+ result = bash("sleep 10", timeout=5)
29
+ assert "Command timed out" in result
30
+
31
+
32
+ def test_bash_timeout_with_output() -> None:
33
+ result = bash("echo 'hello' && sleep 100", timeout=5)
34
+ assert "hello" in result
35
+ assert "Command timed out" in result
@@ -5,6 +5,7 @@ import pytest
5
5
 
6
6
  from mle_kit_mcp.tools import text_editor
7
7
  from mle_kit_mcp.files import get_workspace_dir
8
+ from mle_kit_mcp.tools.text_editor import READ_MAX_OUTPUT_LENGTH
8
9
 
9
10
  DOCUMENT1 = """
10
11
  The dominant sequence transduction models are based on complex recurrent or convolutional
@@ -211,7 +212,7 @@ def test_text_editor_large_file_handling() -> None:
211
212
 
212
213
  result = text_editor("view", name)
213
214
  assert "This content has been truncated" in result
214
- assert len(result) <= 3100
215
+ assert len(result) <= READ_MAX_OUTPUT_LENGTH + 100
215
216
 
216
217
  result = text_editor("view", name, view_end_line=5)
217
218
  assert "<response clipped>" not in result
File without changes
File without changes
File without changes