Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/miroflow-agent/conf/llm/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ top_p: 1.0
min_p: 0.0
top_k: -1
max_tokens: 4096
max_context_length: 65536
openai_base_url: null
keep_tool_result: -1
anthropic_base_url: https://api.anthropic.com
2 changes: 1 addition & 1 deletion apps/miroflow-agent/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies = [
"mcp",
"fastmcp",
"anthropic",
"e2b-code-interpreter>=1.2.1",
# "e2b-code-interpreter>=1.2.1",
"jsonlines>=4.0.0",
"mammoth>=1.9.0",
"numpy>=2.2.5",
Expand Down
29 changes: 19 additions & 10 deletions apps/miroflow-agent/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
set dotenv-load := true
set dotenv-path := "{{justfile_directory()}}/.env"

default:
just --list

Expand Down Expand Up @@ -34,3 +37,8 @@ format-md:
# run precommit before PR
[group('precommit')]
precommit: lint sort-imports format-md format

# run mcp server under test
[group('mcp-test')]
mcp-inspect service:
uv run --directory libs/miroflow-tools mcp dev src/miroflow_tools/mcp_servers/{{service}}
5 changes: 3 additions & 2 deletions libs/miroflow-tools/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies = [
"fastmcp>=0.1.0",
"playwright>=1.40.0",
"requests>=2.32.0",
"e2b-code-interpreter>=1.2.1",
"e2b-code-interpreter==2.0.0",
"wikipedia",
"mutagen",
"markitdown-mcp>=0.0.1a3",
Expand All @@ -37,6 +37,7 @@ dev = [
"pytest-mock>=3.10.0",
"pytest-timeout>=2.1.0",
"inline-snapshot>=0.23.2",
"mcp[cli]>=1.14.1",
]

[tool.pytest.ini_options]
Expand All @@ -59,4 +60,4 @@ markers = [
"unit: marks tests as unit tests",
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"requires_api_key: marks tests that require real API credentials",
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import os

from e2b_code_interpreter import Sandbox
from fastmcp import FastMCP
from mcp.server.fastmcp import FastMCP

# Initialize FastMCP server
mcp = FastMCP("e2b-python-interpreter")
Expand All @@ -30,6 +30,9 @@
# DEFAULT TEMPLATE ID
DEFAULT_TEMPLATE_ID = "all_pip_apt_pkg"

# DEFAULT METADATA
DEFAULT_METADATA = {"env": "miro-thinker"}

# DEFAULT CONFS
DEFAULT_TIMEOUT = 1800 # seconds

Expand All @@ -45,15 +48,17 @@ async def create_sandbox() -> str:
The id of the newly created sandbox. You should use this sandbox_id to run other tools in the sandbox.
"""
max_retries = 3
sandbox = None
for attempt in range(1, max_retries + 1):
sandbox = None
try:
sandbox = Sandbox(
sandbox = Sandbox.create(
template=DEFAULT_TEMPLATE_ID,
timeout=DEFAULT_TIMEOUT,
api_key=E2B_API_KEY,
metadata=DEFAULT_METADATA,
)
info = sandbox.get_info()
# sandbox.beta_pause()

tmpfiles_dir = os.path.join(LOGS_DIR, "tmpfiles")
os.makedirs(tmpfiles_dir, exist_ok=True)
Expand All @@ -66,7 +71,9 @@ async def create_sandbox() -> str:
finally:
# Set timeout before exit to prevent timeout after function exits
try:
sandbox.set_timeout(DEFAULT_TIMEOUT)
if sandbox is not None:
sandbox.beta_pause()
# sandbox.set_timeout(DEFAULT_TIMEOUT)
except Exception:
pass # Ignore timeout setting errors

Expand All @@ -91,10 +98,10 @@ async def run_command(command: str, sandbox_id: str) -> str:
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
sandbox.set_timeout(
DEFAULT_TIMEOUT
) # refresh the timeout for each command execution
result = sandbox.commands.run(command)
# sandbox.set_timeout(
# DEFAULT_TIMEOUT
# ) # refresh the timeout for each command execution
result = sandbox.commands.run(command, timeout=DEFAULT_TIMEOUT)

# Check if command contains package installation commands
result_str = str(result)
Expand All @@ -115,7 +122,8 @@ async def run_command(command: str, sandbox_id: str) -> str:
finally:
# Set timeout before exit to prevent timeout after function exits
try:
sandbox.set_timeout(DEFAULT_TIMEOUT)
sandbox.beta_pause()
# sandbox.set_timeout(DEFAULT_TIMEOUT)
except Exception:
pass # Ignore timeout setting errors

Expand All @@ -140,11 +148,11 @@ async def run_python_code(code_block: str, sandbox_id: str) -> str:
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
sandbox.set_timeout(
DEFAULT_TIMEOUT
) # refresh the timeout for each command execution
# sandbox.set_timeout(
# DEFAULT_TIMEOUT
# ) # refresh the timeout for each command execution

execution = sandbox.run_code(code_block)
execution = sandbox.run_code(code_block, timeout=DEFAULT_TIMEOUT)
return str(execution)
except Exception as e:
if attempt == max_retries:
Expand All @@ -153,7 +161,8 @@ async def run_python_code(code_block: str, sandbox_id: str) -> str:
finally:
# Set timeout before exit to prevent timeout after function exits
try:
sandbox.set_timeout(DEFAULT_TIMEOUT)
sandbox.beta_pause()
# sandbox.set_timeout(DEFAULT_TIMEOUT)
except Exception:
pass # Ignore timeout setting errors

Expand All @@ -179,9 +188,9 @@ async def upload_file_from_local_to_sandbox(
return f"[ERROR]: Failed to connect to sandbox {sandbox_id}, retry later. Make sure the sandbox is created and the id is correct."

try:
sandbox.set_timeout(
DEFAULT_TIMEOUT
) # refresh the timeout for each command execution
# sandbox.set_timeout(
# DEFAULT_TIMEOUT
# ) # refresh the timeout for each command execution

# Get the uploaded file path
uploaded_file_path = os.path.join(
Expand All @@ -190,15 +199,16 @@ async def upload_file_from_local_to_sandbox(

# Upload the file
with open(local_file_path, "rb") as f:
sandbox.files.write(uploaded_file_path, f)
sandbox.files.write(uploaded_file_path, f, request_timeout=DEFAULT_TIMEOUT)

return f"File uploaded to {uploaded_file_path}\n\n[INFO]: For directly reading local files without uploading to sandbox, consider using the `convert_to_markdown` tool which can read various file types (Doc, PPT, PDF, Excel, CSV, ZIP, etc.) directly from local paths or URLs. Note that `convert_to_markdown` doesn't support files already in the sandbox."
except Exception as e:
return f"[ERROR]: Failed to upload file {local_file_path} to sandbox {sandbox_id}: {e}\n\n[INFO]: This tool is for uploading local files to the sandbox. For security reasons, downloading files from sandbox to local system is not supported. Alternatively, consider using the `convert_to_markdown` tool which can directly read various file types (Doc, PPT, PDF, Excel, CSV, ZIP, etc.) from local paths or URLs without uploading to sandbox."
finally:
# Set timeout before exit to prevent timeout after function exits
try:
sandbox.set_timeout(DEFAULT_TIMEOUT)
sandbox.beta_pause()
# sandbox.set_timeout(DEFAULT_TIMEOUT)
except Exception:
pass # Ignore timeout setting errors

Expand All @@ -224,16 +234,18 @@ async def download_file_from_internet_to_sandbox(
return f"[ERROR]: Failed to connect to sandbox {sandbox_id}, retry later. Make sure the sandbox is created and the id is correct."

try:
sandbox.set_timeout(
DEFAULT_TIMEOUT
) # refresh the timeout for each command execution
# sandbox.set_timeout(
# DEFAULT_TIMEOUT
# ) # refresh the timeout for each command execution

downloaded_file_path = os.path.join(sandbox_file_path, os.path.basename(url))

# Download the file with retry logic
max_retries = 3
for attempt in range(1, max_retries + 1):
result = sandbox.commands.run(f"wget {url} -O {downloaded_file_path}")
result = sandbox.commands.run(
f"wget {url} -O {downloaded_file_path}", timeout=DEFAULT_TIMEOUT
)
if result.exit_code == 0:
return f"File downloaded to {downloaded_file_path}\n\n[INFO]: For directly reading files from internet URLs without downloading to sandbox, consider using the `convert_to_markdown` tool which can read various file types (Doc, PPT, PDF, Excel, CSV, ZIP, etc.) directly from URLs. Note that `convert_to_markdown` doesn't support files already in the sandbox."
elif attempt < max_retries:
Expand All @@ -246,7 +258,8 @@ async def download_file_from_internet_to_sandbox(
finally:
# Set timeout before exit to prevent timeout after function exits
try:
sandbox.set_timeout(DEFAULT_TIMEOUT)
sandbox.beta_pause()
# sandbox.set_timeout(DEFAULT_TIMEOUT)
except Exception:
pass # Ignore timeout setting errors

Expand All @@ -272,9 +285,9 @@ async def download_file_from_sandbox_to_local(
return f"[ERROR]: Failed to connect to sandbox {sandbox_id}, retry later. Make sure the sandbox is created and the id is correct."

try:
sandbox.set_timeout(
DEFAULT_TIMEOUT
) # refresh the timeout for each command execution
# sandbox.set_timeout(
# DEFAULT_TIMEOUT
# ) # refresh the timeout for each command execution

# Create tmpfiles directory if it doesn't exist
if not LOGS_DIR:
Expand All @@ -293,7 +306,9 @@ async def download_file_from_sandbox_to_local(

# Download the file
with open(local_file_path, "wb") as f:
content = sandbox.files.read(sandbox_file_path, format="bytes")
content = sandbox.files.read(
sandbox_file_path, format="bytes", request_timeout=DEFAULT_TIMEOUT
)
f.write(content)

return f"File downloaded successfully to: {local_file_path}\n\n[INFO]: The file can now be accessed by other tools (reading, question-answering, etc.) which only support local files and internet URLs, not sandbox files."
Expand All @@ -302,7 +317,8 @@ async def download_file_from_sandbox_to_local(
finally:
# Set timeout before exit to prevent timeout after function exits
try:
sandbox.set_timeout(DEFAULT_TIMEOUT)
sandbox.beta_pause()
# sandbox.set_timeout(DEFAULT_TIMEOUT)
except Exception:
pass # Ignore timeout setting errors

Expand Down
Loading