Spaces:
Paused
Paused
Update tests.py
Browse files
tests.py
CHANGED
|
@@ -1,1046 +1,465 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
from
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
| 6 |
import openpyxl
|
|
|
|
|
|
|
| 7 |
import pexpect
|
| 8 |
-
import requests
|
| 9 |
-
from bs4 import BeautifulSoup
|
| 10 |
-
from google import genai # Assuming genai handles API key internally via env or client init
|
| 11 |
-
from litellm import completion
|
| 12 |
-
from mcp.server.fastmcp import FastMCP
|
| 13 |
-
from requests.exceptions import RequestException
|
| 14 |
-
|
| 15 |
-
# --- Configuration ---
|
| 16 |
-
|
| 17 |
-
# Load API Keys from Environment Variables (Recommended)
|
| 18 |
-
# Ensure these are set in your deployment environment
|
| 19 |
-
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
| 20 |
-
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
| 21 |
-
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
|
| 22 |
-
RAPIDAPI_KEY = os.environ.get("RAPIDAPI_KEY") # Added for RapidAPI calls
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
# if not OPENROUTER_API_KEY: raise ValueError("OPENROUTER_API_KEY not set")
|
| 30 |
-
# if not RAPIDAPI_KEY: raise ValueError("RAPIDAPI_KEY not set")
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
if GROQ_API_KEY:
|
| 35 |
-
os.environ["GROQ_API_KEY"] = GROQ_API_KEY
|
| 36 |
-
if GEMINI_API_KEY:
|
| 37 |
-
# Note: genai client might use its own way, but litellm might need this
|
| 38 |
-
os.environ["GEMINI_API_KEY"] = GEMINI_API_KEY
|
| 39 |
-
if OPENROUTER_API_KEY:
|
| 40 |
-
os.environ["OPENROUTER_API_KEY"] = OPENROUTER_API_KEY
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
# --- Constants ---
|
| 44 |
-
CODE_DIR = Path("/app/code_interpreter")
|
| 45 |
-
TEMP_UPLOAD_DIR = Path("/app/uploads/temp") # Source for transfer_files
|
| 46 |
-
SERVER_BASE_URL = "https://opengpt-4ik5.onrender.com"
|
| 47 |
-
FILES_ENDPOINT = "/upload" # Endpoint to list files
|
| 48 |
-
UPLOAD_ENDPOINT = "/upload" # Endpoint to upload files
|
| 49 |
-
SERVER_FILES_URL = f"{SERVER_BASE_URL}{FILES_ENDPOINT}"
|
| 50 |
-
SERVER_UPLOAD_URL = f"{SERVER_BASE_URL}{UPLOAD_ENDPOINT}"
|
| 51 |
-
SERVER_STATIC_URL_PREFIX = f"{SERVER_BASE_URL}/static/"
|
| 52 |
-
|
| 53 |
-
# RapidAPI Endpoints
|
| 54 |
-
YOUTUBE_TRANSCRIPT_API = "youtube-transcript3.p.rapidapi.com"
|
| 55 |
-
SCRAPE_NINJA_API = "scrapeninja.p.rapidapi.com"
|
| 56 |
-
|
| 57 |
-
# --- Global State (Use Sparingly) ---
|
| 58 |
-
# Keep track of files present in the CODE_DIR to identify newly created ones
|
| 59 |
-
# This state persists across tool calls within a single mcp run
|
| 60 |
-
tracked_files_in_codedir: set[Path] = set(CODE_DIR.glob("*"))
|
| 61 |
-
# Keep track of files downloaded from the server to avoid re-downloading
|
| 62 |
-
server_downloaded_files: set[str] = set()
|
| 63 |
-
|
| 64 |
-
# --- Logging Setup ---
|
| 65 |
-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 66 |
-
|
| 67 |
-
# --- Clients ---
|
| 68 |
-
try:
|
| 69 |
-
# Initialize Gemini Client (Ensure API key is handled, ideally via env var)
|
| 70 |
-
# If the env var GEMINI_API_KEY is set, genai might pick it up automatically.
|
| 71 |
-
# If not, you might need to pass it explicitly if the env var method above isn't enough:
|
| 72 |
-
# client = genai.Client(api_key=GEMINI_API_KEY)
|
| 73 |
-
# Or rely on application default credentials if configured.
|
| 74 |
-
if GEMINI_API_KEY:
|
| 75 |
-
client = genai.Client(api_key=GEMINI_API_KEY)
|
| 76 |
-
logging.info("Gemini Client initialized using API Key.")
|
| 77 |
-
else:
|
| 78 |
-
# Attempt to initialize without explicit key (might use ADC or other methods)
|
| 79 |
-
client = genai.Client()
|
| 80 |
-
logging.info("Gemini Client initialized (attempting default credentials).")
|
| 81 |
-
|
| 82 |
-
except Exception as e:
|
| 83 |
-
logging.error(f"Failed to initialize Gemini client: {e}")
|
| 84 |
-
client = None # Indicate client is unavailable
|
| 85 |
|
| 86 |
mcp = FastMCP("code_sandbox")
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
|
| 90 |
|
| 91 |
-
def download_server_files(
|
| 92 |
-
base_url: str,
|
| 93 |
-
files_endpoint: str,
|
| 94 |
-
download_directory: Path,
|
| 95 |
-
already_downloaded: set[str]
|
| 96 |
-
) -> set[str]:
|
| 97 |
-
"""
|
| 98 |
-
Downloads all files listed on the server's file listing page
|
| 99 |
-
that haven't been downloaded yet in this session.
|
| 100 |
|
| 101 |
-
Args:
|
| 102 |
-
base_url: The base URL of the server (e.g., "https://example.com").
|
| 103 |
-
files_endpoint: The path to the page listing files (e.g., "/uploads").
|
| 104 |
-
download_directory: The local directory (Path object) to save files.
|
| 105 |
-
already_downloaded: A set of filenames already downloaded.
|
| 106 |
-
|
| 107 |
-
Returns:
|
| 108 |
-
The updated set of downloaded filenames.
|
| 109 |
-
"""
|
| 110 |
-
download_directory.mkdir(parents=True, exist_ok=True)
|
| 111 |
-
files_url = f"{base_url}{files_endpoint}"
|
| 112 |
-
newly_downloaded_count = 0
|
| 113 |
|
|
|
|
| 114 |
try:
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
if not file_href.startswith(("http://", "https://")):
|
| 127 |
-
file_url = f"{base_url}{file_href}"
|
| 128 |
-
else:
|
| 129 |
-
file_url = file_href
|
| 130 |
-
|
| 131 |
-
filename = Path(file_url).name
|
| 132 |
-
if not filename:
|
| 133 |
-
logging.warning(f"Could not extract filename from URL: {file_url}")
|
| 134 |
-
continue
|
| 135 |
-
|
| 136 |
-
# Skip if already downloaded in this session
|
| 137 |
-
if filename in already_downloaded:
|
| 138 |
-
continue
|
| 139 |
-
|
| 140 |
-
file_path = download_directory / filename
|
| 141 |
-
logging.info(f"Downloading: {filename} from {file_url}")
|
| 142 |
-
|
| 143 |
-
try:
|
| 144 |
-
file_response = requests_session.get(file_url, stream=True, timeout=60)
|
| 145 |
-
file_response.raise_for_status()
|
| 146 |
-
|
| 147 |
-
with open(file_path, "wb") as f:
|
| 148 |
-
for chunk in file_response.iter_content(chunk_size=8192):
|
| 149 |
-
if chunk:
|
| 150 |
-
f.write(chunk)
|
| 151 |
-
|
| 152 |
-
logging.info(f"Downloaded: {filename} to {file_path}")
|
| 153 |
-
already_downloaded.add(filename)
|
| 154 |
-
newly_downloaded_count += 1
|
| 155 |
-
|
| 156 |
-
except RequestException as e:
|
| 157 |
-
logging.error(f"Error downloading {filename}: {e}")
|
| 158 |
-
except OSError as e:
|
| 159 |
-
logging.error(f"Error saving {filename}: {e}")
|
| 160 |
-
except Exception as e:
|
| 161 |
-
logging.error(f"Unexpected error downloading/saving {filename}: {e}")
|
| 162 |
-
|
| 163 |
-
except RequestException as e:
|
| 164 |
-
logging.error(f"Error getting file list from {files_url}: {e}")
|
| 165 |
-
except Exception as e:
|
| 166 |
-
logging.error(f"An unexpected error occurred during file download process: {e}")
|
| 167 |
-
|
| 168 |
-
logging.info(f"Downloaded {newly_downloaded_count} new files from server.")
|
| 169 |
-
return already_downloaded
|
| 170 |
-
|
| 171 |
-
def transfer_temp_files(source_dir: Path, destination_dir: Path):
|
| 172 |
-
"""Moves files from temp upload subdirectories to the main code directory."""
|
| 173 |
-
destination_dir.mkdir(parents=True, exist_ok=True)
|
| 174 |
-
moved_count = 0
|
| 175 |
-
if not source_dir.exists():
|
| 176 |
-
logging.warning(f"Source directory for transfer does not exist: {source_dir}")
|
| 177 |
-
return
|
| 178 |
-
|
| 179 |
-
for item in source_dir.iterdir():
|
| 180 |
-
if item.is_dir(): # Check if it's a directory (e.g., session-specific temp folder)
|
| 181 |
-
for source_file_path in item.iterdir():
|
| 182 |
-
if source_file_path.is_file():
|
| 183 |
-
destination_file_path = destination_dir / source_file_path.name
|
| 184 |
-
try:
|
| 185 |
-
shutil.move(str(source_file_path), str(destination_file_path))
|
| 186 |
-
logging.info(f"Moved {source_file_path.name} to {destination_dir}")
|
| 187 |
-
moved_count += 1
|
| 188 |
-
except OSError as e:
|
| 189 |
-
logging.error(f"Error moving {source_file_path.name}: {e}")
|
| 190 |
-
elif item.is_file(): # Also handle files directly in source_dir if any
|
| 191 |
-
destination_file_path = destination_dir / item.name
|
| 192 |
-
try:
|
| 193 |
-
shutil.move(str(item), str(destination_file_path))
|
| 194 |
-
logging.info(f"Moved {item.name} directly to {destination_dir}")
|
| 195 |
-
moved_count += 1
|
| 196 |
-
except OSError as e:
|
| 197 |
-
logging.error(f"Error moving {item.name}: {e}")
|
| 198 |
-
if moved_count > 0:
|
| 199 |
-
logging.info(f"Transferred {moved_count} files from {source_dir} area.")
|
| 200 |
-
|
| 201 |
-
def upload_file_to_server(file_path: Path, upload_url: str) -> Optional[str]:
|
| 202 |
-
"""
|
| 203 |
-
Uploads a single file to the specified server endpoint.
|
| 204 |
-
|
| 205 |
-
Args:
|
| 206 |
-
file_path: Path object of the file to upload.
|
| 207 |
-
upload_url: The URL to upload the file to.
|
| 208 |
-
|
| 209 |
-
Returns:
|
| 210 |
-
The filename returned by the server upon successful upload, or None on failure.
|
| 211 |
-
"""
|
| 212 |
-
if not file_path.is_file():
|
| 213 |
-
logging.error(f"File not found or is not a file: {file_path}")
|
| 214 |
-
return None
|
| 215 |
-
|
| 216 |
try:
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
except RequestException as e:
|
| 231 |
-
logging.error(f"Upload failed for {file_path.name}. Network/Server error: {e}")
|
| 232 |
-
if hasattr(e, 'response') and e.response is not None:
|
| 233 |
-
logging.error(f"Server response: {e.response.status_code} - {e.response.text}")
|
| 234 |
-
return None
|
| 235 |
-
except Exception as e:
|
| 236 |
-
logging.error(f"An unexpected error occurred during upload of {file_path.name}: {e}")
|
| 237 |
-
return None
|
| 238 |
-
|
| 239 |
-
def run_command_in_sandbox(
|
| 240 |
-
command: str,
|
| 241 |
-
timeout_sec: int,
|
| 242 |
-
run_forever: bool = False,
|
| 243 |
-
cwd: Path = CODE_DIR
|
| 244 |
-
) -> str:
|
| 245 |
-
"""
|
| 246 |
-
Runs a shell command using pexpect in a specific directory.
|
| 247 |
-
|
| 248 |
-
Args:
|
| 249 |
-
command: The command string to execute.
|
| 250 |
-
timeout_sec: Timeout in seconds. Ignored if run_forever is True.
|
| 251 |
-
run_forever: If True, does not enforce timeout (use with caution).
|
| 252 |
-
cwd: The working directory (Path object) for the command.
|
| 253 |
-
|
| 254 |
-
Returns:
|
| 255 |
-
The captured stdout/stderr output of the command.
|
| 256 |
-
"""
|
| 257 |
-
output = ""
|
| 258 |
-
full_command = f"cd {shlex.quote(str(cwd))} && {command}"
|
| 259 |
-
logging.info(f"Running command: {full_command}")
|
| 260 |
|
| 261 |
try:
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
child.sendline(f'export PS1="{prompt_marker}"')
|
| 266 |
-
child.expect_exact(prompt_marker, timeout=10) # Wait for prompt change
|
| 267 |
-
|
| 268 |
-
child.sendline(full_command)
|
| 269 |
-
|
| 270 |
-
if run_forever:
|
| 271 |
-
# For forever commands, we might just return after sending,
|
| 272 |
-
# or wait for initial output, depending on requirements.
|
| 273 |
-
# Here, we'll just log and return an indication it started.
|
| 274 |
-
logging.info(f"Command '{command}' started in 'run_forever' mode.")
|
| 275 |
-
# Optionally, capture some initial output if needed:
|
| 276 |
-
# try:
|
| 277 |
-
# output = child.read_nonblocking(size=1024, timeout=5).decode(errors='ignore')
|
| 278 |
-
# except pexpect.TIMEOUT:
|
| 279 |
-
# pass # No initial output quickly
|
| 280 |
-
# child.close(force=True) # Or keep it running? Depends on MCP lifecycle.
|
| 281 |
-
# For now, assume we detach:
|
| 282 |
-
# NOTE: Pexpect might not be ideal for true 'daemonizing'.
|
| 283 |
-
# A better approach for 'forever' might be `subprocess.Popen` without waiting.
|
| 284 |
-
# However, sticking to the original tool's apparent intent with pexpect:
|
| 285 |
-
# We can't easily get continuous output AND return control without threads.
|
| 286 |
-
# Returning immediately after sending the command for 'forever' mode.
|
| 287 |
-
return f"Command '{command}' started in background (output streaming not captured)."
|
| 288 |
-
|
| 289 |
-
# For commands with timeout:
|
| 290 |
-
start_time = time.time()
|
| 291 |
-
while True:
|
| 292 |
-
if time.time() - start_time > timeout_sec:
|
| 293 |
-
raise TimeoutExpired(command, timeout_sec)
|
| 294 |
-
try:
|
| 295 |
-
# Expect the specific prompt marker
|
| 296 |
-
index = child.expect([prompt_marker, pexpect.EOF, pexpect.TIMEOUT], timeout=max(1, timeout_sec - (time.time() - start_time)))
|
| 297 |
-
line = child.before.decode(errors='ignore')
|
| 298 |
-
output += line
|
| 299 |
-
# logging.debug(f"Shell output: {line.strip()}") # Log intermediate output if needed
|
| 300 |
-
|
| 301 |
-
if index == 0: # Prompt marker found, command finished
|
| 302 |
-
logging.info(f"Command '{command}' finished.")
|
| 303 |
-
break
|
| 304 |
-
elif index == 1: # EOF
|
| 305 |
-
logging.warning(f"Command '{command}' resulted in EOF.")
|
| 306 |
-
break
|
| 307 |
-
# index == 2 (TIMEOUT) is handled by the outer loop's timeout check
|
| 308 |
-
|
| 309 |
-
except pexpect.TIMEOUT:
|
| 310 |
-
logging.warning(f"Pexpect read timed out waiting for output or prompt for command: {command}")
|
| 311 |
-
# Check outer loop timeout condition
|
| 312 |
-
if time.time() - start_time > timeout_sec:
|
| 313 |
-
raise TimeoutExpired(command, timeout_sec)
|
| 314 |
-
# Otherwise, continue waiting if overall time not exceeded
|
| 315 |
-
continue
|
| 316 |
-
except Exception as e:
|
| 317 |
-
logging.error(f"Pexpect error during command '{command}': {e}")
|
| 318 |
-
output += f"\nPexpect Error: {e}"
|
| 319 |
-
break
|
| 320 |
-
|
| 321 |
-
except TimeoutExpired:
|
| 322 |
-
output += f"\n--- TimeoutError: Command '{command}' exceeded {timeout_sec} seconds ---"
|
| 323 |
-
logging.error(f"Command '{command}' timed out after {timeout_sec} seconds.")
|
| 324 |
-
except pexpect.ExceptionPexpect as e:
|
| 325 |
-
output += f"\n--- Pexpect Error: {e} ---"
|
| 326 |
-
logging.error(f"Pexpect execution failed for command '{command}': {e}")
|
| 327 |
-
except Exception as e:
|
| 328 |
-
output += f"\n--- Unexpected Error: {e} ---"
|
| 329 |
-
logging.error(f"Unexpected error running command '{command}': {e}")
|
| 330 |
-
finally:
|
| 331 |
-
if 'child' in locals() and child.isalive():
|
| 332 |
-
child.close(force=True)
|
| 333 |
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
|
|
|
|
|
|
|
| 338 |
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
global server_downloaded_files
|
| 342 |
-
logging.info("Ensuring local file system is synchronized...")
|
| 343 |
-
# 1. Transfer files moved to the temp upload area
|
| 344 |
-
transfer_temp_files(temp_dir, code_dir)
|
| 345 |
-
# 2. Download missing files from the server
|
| 346 |
-
server_downloaded_files = download_server_files(
|
| 347 |
-
SERVER_BASE_URL, FILES_ENDPOINT, code_dir, server_downloaded_files
|
| 348 |
-
)
|
| 349 |
-
# 3. Update the set of tracked files *after* syncing
|
| 350 |
-
global tracked_files_in_codedir
|
| 351 |
-
tracked_files_in_codedir = set(code_dir.glob("*"))
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
def _upload_new_files(code_dir: Path, known_files_before: set[Path]) -> Tuple[List[str], set[Path]]:
|
| 355 |
-
"""Finds new files in code_dir, uploads them, returns URLs and updated file set."""
|
| 356 |
-
current_files = set(code_dir.glob("*"))
|
| 357 |
-
new_files = current_files - known_files_before
|
| 358 |
-
uploaded_file_urls = []
|
| 359 |
-
|
| 360 |
-
if not new_files:
|
| 361 |
-
logging.info("No new files detected for upload.")
|
| 362 |
-
return [], current_files # Return empty list and the latest set
|
| 363 |
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
server_filename = upload_file_to_server(file_path, SERVER_UPLOAD_URL)
|
| 369 |
-
if server_filename:
|
| 370 |
-
# Construct the download URL based on the server's static path convention
|
| 371 |
-
download_url = f"{SERVER_STATIC_URL_PREFIX}{server_filename}"
|
| 372 |
-
uploaded_file_urls.append(download_url)
|
| 373 |
else:
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
|
|
|
|
|
|
| 381 |
|
| 382 |
|
| 383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
|
| 385 |
@mcp.tool()
|
| 386 |
-
def analyse_audio(audiopath
|
| 387 |
-
"""
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
Returns:
|
| 397 |
-
A dictionary containing the AI's response under the key "Output".
|
| 398 |
-
Returns an error message if the client or file processing fails.
|
| 399 |
-
"""
|
| 400 |
-
_ensure_files_synced(CODE_DIR, TEMP_UPLOAD_DIR)
|
| 401 |
-
if not client:
|
| 402 |
-
return {"Output": "Error: Gemini client not initialized."}
|
| 403 |
-
|
| 404 |
-
audio_file_path = Path(audiopath)
|
| 405 |
-
if not audio_file_path.is_absolute(): # Assume relative to CODE_DIR if not absolute
|
| 406 |
-
audio_file_path = CODE_DIR / audiopath
|
| 407 |
-
|
| 408 |
-
if not audio_file_path.exists():
|
| 409 |
-
return {"Output": f"Error: Audio file not found at {audio_file_path}"}
|
| 410 |
-
|
| 411 |
-
logging.info(f"Analysing audio: {audio_file_path.name} with query: '{query}'")
|
| 412 |
-
try:
|
| 413 |
-
# Upload file to Gemini API
|
| 414 |
-
audio_file_ref = client.files.upload(file=str(audio_file_path))
|
| 415 |
-
logging.info(f"Uploaded {audio_file_path.name} to Gemini API. File ref: {audio_file_ref.name}, State: {audio_file_ref.state.name}")
|
| 416 |
-
|
| 417 |
-
# Wait for processing (with timeout)
|
| 418 |
-
start_time = time.time()
|
| 419 |
-
timeout_seconds = 120 # Adjust as needed
|
| 420 |
-
while audio_file_ref.state.name == "PROCESSING":
|
| 421 |
-
if time.time() - start_time > timeout_seconds:
|
| 422 |
-
logging.error(f"Gemini file processing timed out for {audio_file_ref.name}")
|
| 423 |
-
return {"Output": f"Error: Gemini file processing timed out for {audio_file_path.name}."}
|
| 424 |
-
print('.', end='', flush=True) # Keep original progress indicator
|
| 425 |
-
time.sleep(2)
|
| 426 |
-
audio_file_ref = client.files.get(name=audio_file_ref.name)
|
| 427 |
-
|
| 428 |
-
print() # Newline after progress dots
|
| 429 |
-
|
| 430 |
-
if audio_file_ref.state.name == "FAILED":
|
| 431 |
-
logging.error(f"Gemini file processing failed for {audio_file_ref.name}. State: {audio_file_ref.state.name}")
|
| 432 |
-
return {"Output": f"Error: Gemini failed to process the audio file {audio_file_path.name}."}
|
| 433 |
-
|
| 434 |
-
if audio_file_ref.state.name != "ACTIVE":
|
| 435 |
-
logging.warning(f"Gemini file {audio_file_ref.name} ended in unexpected state: {audio_file_ref.state.name}")
|
| 436 |
-
# Proceed anyway, but log warning
|
| 437 |
-
|
| 438 |
-
# Generate content
|
| 439 |
-
response = client.models.generate_content(
|
| 440 |
-
model='gemini-1.5-flash', # Use appropriate model
|
| 441 |
-
contents=[query, audio_file_ref]
|
| 442 |
-
)
|
| 443 |
-
logging.info(f"Gemini analysis complete for {audio_file_path.name}.")
|
| 444 |
-
return {"Output": response.text}
|
| 445 |
-
|
| 446 |
-
except Exception as e:
|
| 447 |
-
logging.error(f"Error during Gemini audio analysis for {audio_file_path.name}: {e}", exc_info=True)
|
| 448 |
-
return {"Output": f"An error occurred during audio analysis: {e}"}
|
| 449 |
-
|
| 450 |
-
# Note: analyse_video and analyse_images follow the same pattern as analyse_audio
|
| 451 |
-
# Refactoring them similarly:
|
| 452 |
|
| 453 |
@mcp.tool()
|
| 454 |
-
def analyse_video(videopath
|
| 455 |
-
"""
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
video_file_path = Path(videopath)
|
| 473 |
-
if not video_file_path.is_absolute():
|
| 474 |
-
video_file_path = CODE_DIR / videopath
|
| 475 |
-
|
| 476 |
-
if not video_file_path.exists():
|
| 477 |
-
return {"Output": f"Error: Video file not found at {video_file_path}"}
|
| 478 |
-
|
| 479 |
-
logging.info(f"Analysing video: {video_file_path.name} with query: '{query}'")
|
| 480 |
-
try:
|
| 481 |
-
video_file_ref = client.files.upload(file=str(video_file_path))
|
| 482 |
-
logging.info(f"Uploaded {video_file_path.name} to Gemini API. File ref: {video_file_ref.name}, State: {video_file_ref.state.name}")
|
| 483 |
-
|
| 484 |
-
start_time = time.time()
|
| 485 |
-
timeout_seconds = 300 # Videos might take longer
|
| 486 |
-
while video_file_ref.state.name == "PROCESSING":
|
| 487 |
-
if time.time() - start_time > timeout_seconds:
|
| 488 |
-
logging.error(f"Gemini file processing timed out for {video_file_ref.name}")
|
| 489 |
-
return {"Output": f"Error: Gemini file processing timed out for {video_file_path.name}."}
|
| 490 |
-
print('.', end='', flush=True)
|
| 491 |
-
time.sleep(5) # Longer sleep for video
|
| 492 |
-
video_file_ref = client.files.get(name=video_file_ref.name)
|
| 493 |
-
print()
|
| 494 |
-
|
| 495 |
-
if video_file_ref.state.name == "FAILED":
|
| 496 |
-
logging.error(f"Gemini file processing failed for {video_file_ref.name}")
|
| 497 |
-
return {"Output": f"Error: Gemini failed to process the video file {video_file_path.name}."}
|
| 498 |
-
|
| 499 |
-
if video_file_ref.state.name != "ACTIVE":
|
| 500 |
-
logging.warning(f"Gemini file {video_file_ref.name} ended in unexpected state: {video_file_ref.state.name}")
|
| 501 |
-
|
| 502 |
-
response = client.models.generate_content(
|
| 503 |
-
model='gemini-1.5-flash',
|
| 504 |
-
contents=[query, video_file_ref]
|
| 505 |
-
)
|
| 506 |
-
logging.info(f"Gemini analysis complete for {video_file_path.name}.")
|
| 507 |
-
return {"Output": response.text}
|
| 508 |
-
|
| 509 |
-
except Exception as e:
|
| 510 |
-
logging.error(f"Error during Gemini video analysis for {video_file_path.name}: {e}", exc_info=True)
|
| 511 |
-
return {"Output": f"An error occurred during video analysis: {e}"}
|
| 512 |
|
| 513 |
|
| 514 |
@mcp.tool()
|
| 515 |
-
def analyse_images(imagepath
|
| 516 |
-
"""
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
Args:
|
| 521 |
-
imagepath: Path to the image file within '/app/code_interpreter'
|
| 522 |
-
(e.g., '/app/code_interpreter/diagram.png').
|
| 523 |
-
query: The question to ask about the image content.
|
| 524 |
-
|
| 525 |
-
Returns:
|
| 526 |
-
A dictionary containing the AI's response under the key "Output".
|
| 527 |
-
Returns an error message if the client or file processing fails.
|
| 528 |
-
"""
|
| 529 |
-
_ensure_files_synced(CODE_DIR, TEMP_UPLOAD_DIR)
|
| 530 |
-
if not client:
|
| 531 |
-
return {"Output": "Error: Gemini client not initialized."}
|
| 532 |
-
|
| 533 |
-
image_file_path = Path(imagepath)
|
| 534 |
-
if not image_file_path.is_absolute():
|
| 535 |
-
image_file_path = CODE_DIR / imagepath
|
| 536 |
-
|
| 537 |
-
if not image_file_path.exists():
|
| 538 |
-
return {"Output": f"Error: Image file not found at {image_file_path}"}
|
| 539 |
-
|
| 540 |
-
logging.info(f"Analysing image: {image_file_path.name} with query: '{query}'")
|
| 541 |
-
try:
|
| 542 |
-
# Note: For Gemini Flash/Pro Vision, direct image data might be preferred over file API
|
| 543 |
-
# Check Gemini API docs for best practices. Using File API for consistency here.
|
| 544 |
-
image_file_ref = client.files.upload(file=str(image_file_path))
|
| 545 |
-
logging.info(f"Uploaded {image_file_path.name} to Gemini API. File ref: {image_file_ref.name}, State: {image_file_ref.state.name}")
|
| 546 |
-
|
| 547 |
-
start_time = time.time()
|
| 548 |
-
timeout_seconds = 60
|
| 549 |
-
while image_file_ref.state.name == "PROCESSING":
|
| 550 |
-
if time.time() - start_time > timeout_seconds:
|
| 551 |
-
logging.error(f"Gemini file processing timed out for {image_file_ref.name}")
|
| 552 |
-
return {"Output": f"Error: Gemini file processing timed out for {image_file_path.name}."}
|
| 553 |
-
print('.', end='', flush=True)
|
| 554 |
-
time.sleep(1)
|
| 555 |
-
image_file_ref = client.files.get(name=image_file_ref.name)
|
| 556 |
-
print()
|
| 557 |
|
| 558 |
-
if image_file_ref.state.name == "FAILED":
|
| 559 |
-
logging.error(f"Gemini file processing failed for {image_file_ref.name}")
|
| 560 |
-
return {"Output": f"Error: Gemini failed to process the image file {image_file_path.name}."}
|
| 561 |
|
| 562 |
-
|
| 563 |
-
|
|
|
|
|
|
|
|
|
|
| 564 |
|
| 565 |
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
return {"Output": response.text}
|
| 572 |
|
| 573 |
-
except Exception as e:
|
| 574 |
-
logging.error(f"Error during Gemini image analysis for {image_file_path.name}: {e}", exc_info=True)
|
| 575 |
-
return {"Output": f"An error occurred during image analysis: {e}"}
|
| 576 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
|
| 578 |
@mcp.tool()
|
| 579 |
-
def
|
| 580 |
-
"""
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
filename: The name of the file to create (e.g., 'script.py', 'data.txt').
|
| 586 |
-
The file is created in '/app/code_interpreter/'.
|
| 587 |
-
code: The string content to write into the file.
|
| 588 |
-
|
| 589 |
-
Returns:
|
| 590 |
-
A dictionary indicating the task outcome.
|
| 591 |
-
"""
|
| 592 |
-
_ensure_files_synced(CODE_DIR, TEMP_UPLOAD_DIR) # Ensure dir exists, sync base files
|
| 593 |
-
|
| 594 |
-
if not filename:
|
| 595 |
-
return {"info": "Error: Filename cannot be empty."}
|
| 596 |
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
# Update tracked files immediately after creation
|
| 607 |
-
global tracked_files_in_codedir
|
| 608 |
-
tracked_files_in_codedir.add(file_path)
|
| 609 |
-
|
| 610 |
-
logging.info(f"Successfully wrote {len(code)} characters to {file_path}")
|
| 611 |
-
return {"info": f"File '{filename}' created/updated successfully in {CODE_DIR}."}
|
| 612 |
-
except OSError as e:
|
| 613 |
-
logging.error(f"Failed to write file {file_path}: {e}")
|
| 614 |
-
return {"info": f"Error: Could not write file '{filename}'. Reason: {e}"}
|
| 615 |
-
except Exception as e:
|
| 616 |
-
logging.error(f"Unexpected error writing file {file_path}: {e}", exc_info=True)
|
| 617 |
-
return {"info": f"Error: An unexpected error occurred while writing '{filename}'. Reason: {e}"}
|
| 618 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
|
|
|
|
| 620 |
@mcp.tool()
|
| 621 |
-
def
|
| 622 |
-
"""
|
| 623 |
-
|
| 624 |
-
|
| 625 |
Args:
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
"""
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
|
|
|
|
|
|
| 651 |
else:
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 656 |
|
| 657 |
|
| 658 |
@mcp.tool()
|
| 659 |
-
def
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
python_packages: str = "",
|
| 664 |
-
timeout_seconds: int = 300,
|
| 665 |
-
run_forever: bool = False
|
| 666 |
-
) -> Dict[str, Any]:
|
| 667 |
-
"""
|
| 668 |
-
Creates a code file, optionally installs Python packages, executes the code
|
| 669 |
-
using the provided start command, and uploads any newly created files.
|
| 670 |
-
|
| 671 |
Args:
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
timeout_seconds: Maximum execution time in seconds (default 300). Ignored if run_forever is True.
|
| 679 |
-
run_forever: If True, the command attempts to run indefinitely (e.g., for servers).
|
| 680 |
-
Output capture might be limited, and timeout is ignored.
|
| 681 |
-
|
| 682 |
Returns:
|
| 683 |
-
A dictionary containing:
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
return {
|
| 706 |
-
"output": "Aborted due to file creation error.",
|
| 707 |
-
"info": "\n".join(info_messages),
|
| 708 |
-
"files_download_links": []
|
| 709 |
-
}
|
| 710 |
-
|
| 711 |
-
# Refresh known files *after* creating the target file
|
| 712 |
-
known_files_before_run = set(CODE_DIR.glob("*"))
|
| 713 |
-
tracked_files_in_codedir = known_files_before_run # Update global state
|
| 714 |
-
|
| 715 |
-
# 3. Execute the command
|
| 716 |
-
logging.info(f"Executing start command: {start_cmd}")
|
| 717 |
-
exec_output = run_command_in_sandbox(start_cmd, timeout_sec=timeout_seconds, run_forever=run_forever, cwd=CODE_DIR)
|
| 718 |
-
|
| 719 |
-
# 4. Upload any new files created by the execution
|
| 720 |
-
new_file_urls, tracked_files_in_codedir = _upload_new_files(CODE_DIR, known_files_before_run)
|
| 721 |
-
|
| 722 |
-
return {
|
| 723 |
-
"output": exec_output,
|
| 724 |
-
"info": "\n".join(info_messages),
|
| 725 |
-
"files_download_links": new_file_urls
|
| 726 |
-
}
|
| 727 |
|
| 728 |
|
| 729 |
@mcp.tool()
|
| 730 |
-
def
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
)
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
|
|
|
|
|
|
| 739 |
Args:
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
Returns:
|
| 746 |
-
A dictionary containing:
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
"""
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
# Execute the command
|
| 756 |
-
logging.info(f"Executing command on existing files: {start_cmd}")
|
| 757 |
-
exec_output = run_command_in_sandbox(start_cmd, timeout_sec=timeout_seconds, run_forever=run_forever, cwd=CODE_DIR)
|
| 758 |
|
| 759 |
-
# Upload any new files created by the execution
|
| 760 |
-
new_file_urls, tracked_files_in_codedir = _upload_new_files(CODE_DIR, known_files_before_run)
|
| 761 |
-
|
| 762 |
-
return {
|
| 763 |
-
"output": exec_output,
|
| 764 |
-
"files_download_links": new_file_urls
|
| 765 |
-
}
|
| 766 |
|
| 767 |
|
| 768 |
@mcp.tool()
|
| 769 |
-
def
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
Useful for file manipulation, setup, or simple tasks. Executes on Alpine Linux.
|
| 777 |
-
Avoid commands requiring sudo. Uploads any newly created files.
|
| 778 |
-
|
| 779 |
-
Args:
|
| 780 |
-
cmd: The shell command to execute (e.g., "mkdir output_data", "ls -l").
|
| 781 |
-
timeout_seconds: Maximum execution time in seconds (default 300). Ignored if run_forever is True.
|
| 782 |
-
run_forever: If True, the command attempts to run indefinitely. Output capture might be limited.
|
| 783 |
-
|
| 784 |
-
Returns:
|
| 785 |
-
A dictionary containing:
|
| 786 |
-
- "output": The stdout/stderr from the command execution.
|
| 787 |
-
- "files_download_links": A list of URLs for any new files created by the command.
|
| 788 |
-
"""
|
| 789 |
-
global tracked_files_in_codedir
|
| 790 |
-
# Syncing might be relevant if the command interacts with downloaded/transferred files
|
| 791 |
-
_ensure_files_synced(CODE_DIR, TEMP_UPLOAD_DIR)
|
| 792 |
-
|
| 793 |
-
known_files_before_run = tracked_files_in_codedir
|
| 794 |
-
|
| 795 |
-
# Execute the command
|
| 796 |
-
logging.info(f"Executing shell command: {cmd}")
|
| 797 |
-
exec_output = run_command_in_sandbox(cmd, timeout_sec=timeout_seconds, run_forever=run_forever, cwd=CODE_DIR)
|
| 798 |
-
|
| 799 |
-
# Upload any new files created by the execution (e.g., if cmd was `tar czf archive.tar.gz data/`)
|
| 800 |
-
new_file_urls, tracked_files_in_codedir = _upload_new_files(CODE_DIR, known_files_before_run)
|
| 801 |
|
| 802 |
-
|
| 803 |
-
"
|
| 804 |
-
|
| 805 |
-
}
|
| 806 |
|
|
|
|
| 807 |
|
| 808 |
@mcp.tool()
|
| 809 |
-
def get_youtube_transcript(
|
| 810 |
-
"""
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
Args:
|
| 814 |
-
video_id: The unique ID of the YouTube video (e.g., "ZacjOVVgoLY").
|
| 815 |
-
|
| 816 |
-
Returns:
|
| 817 |
-
A dictionary containing the transcript data or an error message.
|
| 818 |
-
"""
|
| 819 |
-
if not RAPIDAPI_KEY:
|
| 820 |
-
return {"error": "RapidAPI key is not configured."}
|
| 821 |
-
|
| 822 |
-
url = f"https://{YOUTUBE_TRANSCRIPT_API}/api/transcript"
|
| 823 |
-
params = {"videoId": video_id}
|
| 824 |
headers = {
|
| 825 |
-
'x-rapidapi-key':
|
| 826 |
-
'x-rapidapi-host':
|
| 827 |
}
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
try:
|
| 831 |
-
response = requests_session.get(url, headers=headers, params=params, timeout=30)
|
| 832 |
-
response.raise_for_status()
|
| 833 |
-
data = response.json()
|
| 834 |
-
logging.info(f"Successfully fetched transcript for {video_id}.")
|
| 835 |
-
return data
|
| 836 |
-
except RequestException as e:
|
| 837 |
-
logging.error(f"Error fetching YouTube transcript for {video_id}: {e}")
|
| 838 |
-
error_msg = f"Failed to fetch transcript: {e}"
|
| 839 |
-
if hasattr(e, 'response') and e.response is not None:
|
| 840 |
-
error_msg += f" (Status: {e.response.status_code}, Body: {e.response.text[:200]})" # Include snippet of response
|
| 841 |
-
return {"error": error_msg}
|
| 842 |
-
except json.JSONDecodeError as e:
|
| 843 |
-
logging.error(f"Error decoding JSON response for youtube transcript {video_id}: {e}")
|
| 844 |
-
return {"error": f"Failed to parse transcript response: {e}"}
|
| 845 |
-
except Exception as e:
|
| 846 |
-
logging.error(f"Unexpected error fetching YouTube transcript {video_id}: {e}", exc_info=True)
|
| 847 |
-
return {"error": f"An unexpected error occurred: {e}"}
|
| 848 |
|
|
|
|
|
|
|
|
|
|
| 849 |
|
| 850 |
@mcp.tool()
|
| 851 |
-
def read_excel_file(filename
|
| 852 |
-
"""
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
filename: The name of the Excel file (e.g., 'report.xlsx').
|
| 857 |
|
| 858 |
-
|
| 859 |
-
A dictionary where keys are cell coordinates (e.g., 'Sheet1!A1')
|
| 860 |
-
and values are the corresponding cell contents (converted to string).
|
| 861 |
-
Returns an error message if the file cannot be read.
|
| 862 |
-
"""
|
| 863 |
-
_ensure_files_synced(CODE_DIR, TEMP_UPLOAD_DIR) # Make sure file is present
|
| 864 |
-
|
| 865 |
-
file_path = CODE_DIR / Path(filename).name # Sanitize name
|
| 866 |
-
|
| 867 |
-
if not file_path.exists():
|
| 868 |
-
logging.error(f"Excel file not found: {file_path}")
|
| 869 |
-
return {"error": f"File not found: {filename}"}
|
| 870 |
|
| 871 |
-
|
| 872 |
excel_data_dict = {}
|
| 873 |
-
try:
|
| 874 |
-
workbook = openpyxl.load_workbook(file_path, data_only=True) # Read values, not formulas
|
| 875 |
-
for sheet_name in workbook.sheetnames:
|
| 876 |
-
sheet = workbook[sheet_name]
|
| 877 |
-
for row in sheet.iter_rows():
|
| 878 |
-
for cell in row:
|
| 879 |
-
if cell.value is not None:
|
| 880 |
-
# Use sheet name in key for clarity if multiple sheets exist
|
| 881 |
-
cell_coordinate = f"{sheet_name}!{cell.coordinate}"
|
| 882 |
-
# Keep original type if simple, else convert complex types to string
|
| 883 |
-
cell_value = cell.value
|
| 884 |
-
if not isinstance(cell_value, (str, int, float, bool)):
|
| 885 |
-
cell_value = str(cell_value)
|
| 886 |
-
excel_data_dict[cell_coordinate] = cell_value
|
| 887 |
-
logging.info(f"Successfully read {len(excel_data_dict)} cells from {filename}.")
|
| 888 |
-
return excel_data_dict
|
| 889 |
-
except Exception as e:
|
| 890 |
-
logging.error(f"Failed to read Excel file {file_path}: {e}", exc_info=True)
|
| 891 |
-
return {"error": f"Could not read Excel file '{filename}'. Reason: {e}"}
|
| 892 |
-
|
| 893 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 894 |
@mcp.tool()
|
| 895 |
-
def
|
| 896 |
-
"""
|
| 897 |
-
Scrapes the textual content of a single website URL using ScrapeNinja via RapidAPI
|
| 898 |
-
and optionally asks a question about the content using an AI model.
|
| 899 |
|
| 900 |
-
|
| 901 |
-
url: The URL of the website to scrape.
|
| 902 |
-
query: An optional question to ask the AI about the scraped content.
|
| 903 |
|
| 904 |
-
Returns:
|
| 905 |
-
A dictionary containing the scraped content ("content") and,
|
| 906 |
-
if a query was provided, the AI's answer ("ai_answer").
|
| 907 |
-
Returns an error message on failure.
|
| 908 |
-
"""
|
| 909 |
-
if not RAPIDAPI_KEY:
|
| 910 |
-
return {"error": "RapidAPI key is not configured."}
|
| 911 |
|
| 912 |
-
scrape_url = f"https://{SCRAPE_NINJA_API}/scrape"
|
| 913 |
headers = {
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
}
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
content = scraped_data.get("body", "") # Or another relevant key like 'text'
|
| 929 |
-
if not content:
|
| 930 |
-
content = str(scraped_data) # Fallback to string representation if body is empty
|
| 931 |
-
|
| 932 |
-
# Basic cleaning (optional, enhance as needed)
|
| 933 |
-
soup = BeautifulSoup(content, "html.parser")
|
| 934 |
-
cleaned_content = soup.get_text(separator=' ', strip=True)
|
| 935 |
-
result["content"] = cleaned_content
|
| 936 |
-
logging.info(f"Successfully scraped content from {url} (length: {len(cleaned_content)}).")
|
| 937 |
-
|
| 938 |
-
if query:
|
| 939 |
-
logging.info(f"Asking AI query about scraped content: '{query}'")
|
| 940 |
-
try:
|
| 941 |
-
ai_response = completion(
|
| 942 |
-
model="gemini/gemini-1.5-flash", # Use a suitable model
|
| 943 |
-
messages=[
|
| 944 |
-
{"role": "system", "content": "You are an AI assistant analyzing website content."},
|
| 945 |
-
{"role": "user", "content": f"Based on the following website content, please answer this question: {query}\n\nWebsite Content:\n{cleaned_content[:15000]}"} # Limit context size
|
| 946 |
-
],
|
| 947 |
-
max_tokens=500,
|
| 948 |
-
temperature=0.5,
|
| 949 |
-
)
|
| 950 |
-
ai_answer = ai_response.choices[0].message.content
|
| 951 |
-
result["ai_answer"] = ai_answer
|
| 952 |
-
logging.info(f"Received AI answer for query on {url}.")
|
| 953 |
-
except Exception as e:
|
| 954 |
-
logging.error(f"AI query failed for {url}: {e}", exc_info=True)
|
| 955 |
-
result["ai_answer"] = f"Error during AI analysis: {e}"
|
| 956 |
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
except RequestException as e:
|
| 960 |
-
logging.error(f"Error scraping {url}: {e}")
|
| 961 |
-
error_msg = f"Failed to scrape {url}: {e}"
|
| 962 |
-
if hasattr(e, 'response') and e.response is not None:
|
| 963 |
-
error_msg += f" (Status: {e.response.status_code}, Body: {e.response.text[:200]})"
|
| 964 |
-
return {"error": error_msg}
|
| 965 |
-
except json.JSONDecodeError as e:
|
| 966 |
-
logging.error(f"Error decoding JSON response for scrape {url}: {e}")
|
| 967 |
-
return {"error": f"Failed to parse scrape response: {e}"}
|
| 968 |
-
except Exception as e:
|
| 969 |
-
logging.error(f"Unexpected error scraping {url}: {e}", exc_info=True)
|
| 970 |
-
return {"error": f"An unexpected error occurred during scraping: {e}"}
|
| 971 |
-
|
| 972 |
-
# Consolidated Deep Thinking Tool
|
| 973 |
-
@mcp.tool()
|
| 974 |
-
def ask_advanced_ai(model_provider: str, query: str, context_info: str) -> Dict[str, str]:
|
| 975 |
-
"""
|
| 976 |
-
Leverages a powerful external AI model for complex reasoning or generation tasks.
|
| 977 |
-
|
| 978 |
-
Args:
|
| 979 |
-
model_provider: The provider/model to use. Supported: 'groq', 'openrouter', 'gemini'.
|
| 980 |
-
query: The main question or task for the AI.
|
| 981 |
-
context_info: Additional context, background information, or previous findings
|
| 982 |
-
relevant to the query.
|
| 983 |
-
|
| 984 |
-
Returns:
|
| 985 |
-
A dictionary containing the AI's response under the key "response".
|
| 986 |
-
Returns an error message on failure.
|
| 987 |
-
"""
|
| 988 |
-
logging.info(f"Sending query to advanced AI ({model_provider}): '{query[:100]}...'")
|
| 989 |
-
|
| 990 |
-
model_map = {
|
| 991 |
-
# Using specific model names known to litellm
|
| 992 |
-
'groq': "groq/llama3-70b-8192", # Example: Use a powerful Groq model
|
| 993 |
-
'openrouter': "openrouter/meta-llama/llama-3-70b-instruct", # Example: Use a powerful OpenRouter model
|
| 994 |
-
'gemini': "gemini/gemini-1.5-pro-latest" # Example: Use a powerful Gemini model
|
| 995 |
-
}
|
| 996 |
-
|
| 997 |
-
model_name = model_map.get(model_provider.lower())
|
| 998 |
-
|
| 999 |
-
if not model_name:
|
| 1000 |
-
logging.error(f"Unsupported model provider specified: {model_provider}")
|
| 1001 |
-
return {"response": f"Error: Unsupported model provider '{model_provider}'. Use 'groq', 'openrouter', or 'gemini'."}
|
| 1002 |
-
|
| 1003 |
-
# Check for required API key for the selected provider
|
| 1004 |
-
key_missing = False
|
| 1005 |
-
if model_provider == 'groq' and not GROQ_API_KEY: key_missing = True
|
| 1006 |
-
if model_provider == 'openrouter' and not OPENROUTER_API_KEY: key_missing = True
|
| 1007 |
-
if model_provider == 'gemini' and not GEMINI_API_KEY: key_missing = True # litellm might need env var
|
| 1008 |
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1012 |
|
| 1013 |
|
| 1014 |
-
messages = [
|
| 1015 |
-
{"role": "system", "content": "You are a highly capable AI assistant performing advanced reasoning or generation."},
|
| 1016 |
-
{"role": "user", "content": f"Based on the following information, please address the query.\n\nContext/Information Provided:\n{context_info}\n\nQuery:\n{query}"}
|
| 1017 |
-
]
|
| 1018 |
|
| 1019 |
-
try:
|
| 1020 |
-
response = completion(
|
| 1021 |
-
model=model_name,
|
| 1022 |
-
messages=messages,
|
| 1023 |
-
# stream=False # Already default
|
| 1024 |
-
# Add other parameters like temperature, max_tokens if needed
|
| 1025 |
-
)
|
| 1026 |
-
ai_response = response.choices[0].message.content
|
| 1027 |
-
logging.info(f"Received response from {model_provider} AI.")
|
| 1028 |
-
return {"response": ai_response}
|
| 1029 |
-
except Exception as e:
|
| 1030 |
-
logging.error(f"Error calling {model_provider} AI ({model_name}): {e}", exc_info=True)
|
| 1031 |
-
# Attempt to extract more detail from the exception if possible (litellm might provide specifics)
|
| 1032 |
-
return {"response": f"Error interacting with {model_provider} AI: {e}"}
|
| 1033 |
|
| 1034 |
-
|
| 1035 |
-
# --- Main Execution ---
|
| 1036 |
if __name__ == "__main__":
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
logging.info(f"Initial tracked files in {CODE_DIR}: {[f.name for f in tracked_files_in_codedir]}")
|
| 1043 |
-
|
| 1044 |
-
# Initialize and run the server using standard I/O transport
|
| 1045 |
-
mcp.run(transport='stdio')
|
| 1046 |
-
|
|
|
|
| 1 |
+
from mcp.server.fastmcp import FastMCP
|
| 2 |
+
import time
|
| 3 |
+
from litellm import completion
|
| 4 |
+
import os
|
| 5 |
+
import glob
|
| 6 |
+
import http.client
|
| 7 |
+
import json
|
| 8 |
import openpyxl
|
| 9 |
+
import shutil
|
| 10 |
+
from google import genai
|
| 11 |
import pexpect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
client = genai.Client(api_key="AIzaSyDtP05TyoIy9j0uPL7_wLEhgQEE75AZQSc")
|
| 14 |
+
source_dir = "/app/uploads/temp"
|
| 15 |
+
destination_dir = "/app/code_interpreter"
|
| 16 |
+
files_list=[]
|
| 17 |
+
downloaded_files=[]
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
from openai import OpenAI
|
| 20 |
+
clienty = OpenAI(api_key="xyz", base_url="https://akiko19191-better-tool-calling.hf.space/")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
mcp = FastMCP("code_sandbox")
|
| 23 |
+
data={}
|
| 24 |
+
result=""
|
| 25 |
+
import requests
|
| 26 |
+
import os
|
| 27 |
+
from bs4 import BeautifulSoup # For parsing HTML
|
| 28 |
|
| 29 |
+
Parent=pexpect.spawn('bash')
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
def transfer_files():
|
| 34 |
try:
|
| 35 |
+
for item in os.listdir(source_dir):
|
| 36 |
+
item_path = os.path.join(source_dir, item)
|
| 37 |
+
if os.path.isdir(item_path): # Check if it's a directory
|
| 38 |
+
for filename in os.listdir(item_path):
|
| 39 |
+
source_file_path = os.path.join(item_path, filename)
|
| 40 |
+
destination_file_path = os.path.join(destination_dir, filename)
|
| 41 |
+
if not os.path.exists(destination_file_path):
|
| 42 |
+
shutil.move(source_file_path, destination_file_path)
|
| 43 |
+
except:
|
| 44 |
+
pass
|
| 45 |
+
def transfer_files2():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
try:
|
| 47 |
+
for item in os.listdir("/app/uploads"):
|
| 48 |
+
if "temp" not in item:
|
| 49 |
+
item_path = os.path.join(source_dir, item)
|
| 50 |
+
if os.path.isdir(item_path): # Check if it's a directory
|
| 51 |
+
for filename in os.listdir(item_path):
|
| 52 |
+
source_file_path = os.path.join(item_path, filename)
|
| 53 |
+
destination_file_path = os.path.join(destination_dir, filename.split("__")[1])
|
| 54 |
+
if not os.path.exists(destination_file_path):
|
| 55 |
+
shutil.move(source_file_path, destination_file_path)
|
| 56 |
+
except:
|
| 57 |
+
pass
|
| 58 |
+
def upload_file(file_path, upload_url):
|
| 59 |
+
"""Uploads a file to the specified server endpoint."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
try:
|
| 62 |
+
# Check if the file exists
|
| 63 |
+
if not os.path.exists(file_path):
|
| 64 |
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
# Prepare the file for upload
|
| 67 |
+
with open(file_path, "rb") as file:
|
| 68 |
+
files = {"file": (os.path.basename(file_path), file)} # Important: Provide filename
|
| 69 |
|
| 70 |
+
# Send the POST request
|
| 71 |
+
response = requests.post(upload_url, files=files)
|
| 72 |
|
| 73 |
+
# Check the response status code
|
| 74 |
+
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
+
# Parse and print the response
|
| 77 |
+
if response.status_code == 200:
|
| 78 |
+
print(f"File uploaded successfully. Filename returned by server: {response.text}")
|
| 79 |
+
return response.text # Return the filename returned by the server
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
else:
|
| 81 |
+
print(f"Upload failed. Status code: {response.status_code}, Response: {response.text}")
|
| 82 |
+
return None
|
| 83 |
+
|
| 84 |
+
except FileNotFoundError as e:
|
| 85 |
+
print(e)
|
| 86 |
+
return None # or re-raise the exception if you want the program to halt
|
| 87 |
+
except requests.exceptions.RequestException as e:
|
| 88 |
+
print(f"Upload failed. Network error: {e}")
|
| 89 |
+
return None
|
| 90 |
|
| 91 |
|
| 92 |
+
TOKEN = "5182224145:AAEjkSlPqV-Q3rH8A9X8HfCDYYEQ44v_qy0"
|
| 93 |
+
chat_id = "5075390513"
|
| 94 |
+
from requests_futures.sessions import FuturesSession
|
| 95 |
+
session = FuturesSession()
|
| 96 |
+
|
| 97 |
+
def run(cmd, timeout_sec,forever_cmd):
|
| 98 |
+
global Parent
|
| 99 |
+
if forever_cmd == 'true':
|
| 100 |
+
Parent.close()
|
| 101 |
+
Parent = pexpect.spawn("bash")
|
| 102 |
+
command="cd /app/code_interpreter/ && "+cmd
|
| 103 |
+
|
| 104 |
+
Parent.sendline(command)
|
| 105 |
+
Parent.readline().decode()
|
| 106 |
+
return str(Parent.readline().decode())
|
| 107 |
+
t=time.time()
|
| 108 |
+
child = pexpect.spawn("bash")
|
| 109 |
+
output=""
|
| 110 |
+
command="cd /app/code_interpreter/ && "+cmd
|
| 111 |
+
|
| 112 |
+
child.sendline('PROMPT_COMMAND="echo END"')
|
| 113 |
+
child.readline().decode()
|
| 114 |
+
child.readline().decode()
|
| 115 |
+
|
| 116 |
+
child.sendline(command)
|
| 117 |
+
|
| 118 |
+
while (not child.eof() ) and (time.time()-t<timeout_sec):
|
| 119 |
+
x=child.readline().decode()
|
| 120 |
+
output=output+x
|
| 121 |
+
print(x)
|
| 122 |
+
if "END" in x :
|
| 123 |
+
output=output.replace("END","")
|
| 124 |
+
child.close()
|
| 125 |
+
break
|
| 126 |
+
if "true" in forever_cmd:
|
| 127 |
+
break
|
| 128 |
+
return output
|
| 129 |
+
|
| 130 |
+
@mcp.prompt()
|
| 131 |
+
def ask_about_topic(topic: str) -> str:
|
| 132 |
+
"""Generates a user message asking for an explanation of a topic."""
|
| 133 |
+
return f"Can you please explain the concept of '{topic}'?"
|
| 134 |
+
|
| 135 |
+
@mcp.resource("config://app")
|
| 136 |
+
def get_config() -> str:
|
| 137 |
+
"""Static configuration data"""
|
| 138 |
+
return "App configuration here"
|
| 139 |
|
| 140 |
@mcp.tool()
|
| 141 |
+
def analyse_audio(audiopath,query) -> dict:
|
| 142 |
+
"""Ask another AI model about audios.The AI model can listen to the audio and give answers.Eg-query:Generate detailed minutes of meeting from the audio clip,audiopath='/app/code_interpreter/<audioname>'.Note:The audios are automatically present in the /app/code_interpreter directory."""
|
| 143 |
+
transfer_files2()
|
| 144 |
+
myfile = client.files.upload(file=audiopath)
|
| 145 |
+
|
| 146 |
+
response = client.models.generate_content(
|
| 147 |
+
model='gemini-2.0-flash',
|
| 148 |
+
contents=[query, myfile]
|
| 149 |
+
)
|
| 150 |
+
return {"Output":str(response.text)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
@mcp.tool()
|
| 153 |
+
def analyse_video(videopath,query) -> dict:
|
| 154 |
+
"""Ask another AI model about videos.The AI model can see the videos and give answers.Eg-query:Create a very detailed transcript and summary of the video,videopath='/app/code_interpreter/<videoname>'Note:The videos are automatically present in the /app/code_interpreter directory."""
|
| 155 |
+
transfer_files2()
|
| 156 |
+
video_file = client.files.upload(file=videopath)
|
| 157 |
+
|
| 158 |
+
while video_file.state.name == "PROCESSING":
|
| 159 |
+
print('.', end='')
|
| 160 |
+
time.sleep(1)
|
| 161 |
+
video_file = client.files.get(name=video_file.name)
|
| 162 |
+
|
| 163 |
+
if video_file.state.name == "FAILED":
|
| 164 |
+
raise ValueError(video_file.state.name)
|
| 165 |
+
|
| 166 |
+
response = client.models.generate_content(
|
| 167 |
+
model='gemini-2.0-flash',
|
| 168 |
+
contents=[query, video_file]
|
| 169 |
+
)
|
| 170 |
+
return {"Output":str(response.text)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
|
| 173 |
@mcp.tool()
|
| 174 |
+
def analyse_images(imagepath,query) -> dict:
|
| 175 |
+
"""Ask another AI model about images.The AI model can see the images and give answers.Eg-query:Who is the person in this image?,imagepath='/app/code_interpreter/<imagename>'.Note:The images are automatically present in the /app/code_interpreter directory."""
|
| 176 |
+
transfer_files2()
|
| 177 |
+
video_file = client.files.upload(file=imagepath)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
+
response = client.models.generate_content(
|
| 181 |
+
model='gemini-2.0-flash',
|
| 182 |
+
contents=[query, video_file]
|
| 183 |
+
)
|
| 184 |
+
return {"Output":str(response.text)}
|
| 185 |
|
| 186 |
|
| 187 |
+
# @mcp.tool()
|
| 188 |
+
# def generate_images(imagepath,query) -> dict:
|
| 189 |
+
# """Ask another AI model to generate images based on the query and the image path.Set image path as an empty string , if you dont want to edit images , but rather generate images.Eg-query:Generate a cartoon version of this image,imagepath='/app/code_interpreter/<imagename>'.Note:The images are automatically present in the /app/code_interpreter directory."""
|
| 190 |
+
# transfer_files2()
|
| 191 |
+
# video_file = client.files.upload(file=imagepath)
|
|
|
|
| 192 |
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
+
# response = client.models.generate_content(
|
| 195 |
+
# model='gemini-2.0-flash',
|
| 196 |
+
# contents=[query, video_file]
|
| 197 |
+
# )
|
| 198 |
+
# return {"Output":str(response.text)}
|
| 199 |
|
| 200 |
@mcp.tool()
|
| 201 |
+
def create_code_files(filename: str, code) -> dict:
|
| 202 |
+
"""Create code files by passing the the filename as well the entire code to write.The file is created by default in the /app/code_interpreter directory.Note:All user uploaded files that you might need to work upon are stored in the /app/code_interpreter directory."""
|
| 203 |
+
global destination_dir
|
| 204 |
+
transfer_files()
|
| 205 |
+
transfer_files2()
|
| 206 |
+
if not os.path.exists(os.path.join(destination_dir, filename)):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
+
if isinstance(code, dict):
|
| 209 |
+
with open(os.path.join(destination_dir, filename), 'w', encoding='utf-8') as f:
|
| 210 |
+
json.dump(code, f, ensure_ascii=False, indent=4)
|
| 211 |
+
else:
|
| 212 |
+
f = open(os.path.join(destination_dir, filename), "w")
|
| 213 |
+
f.write(str(code))
|
| 214 |
+
f.close()
|
| 215 |
+
return {"info":"The referenced code files were created successfully."}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
+
else:
|
| 218 |
+
if isinstance(code, dict):
|
| 219 |
+
with open(os.path.join(destination_dir, filename), 'w', encoding='utf-8') as f:
|
| 220 |
+
json.dump(code, f, ensure_ascii=False, indent=4)
|
| 221 |
+
else:
|
| 222 |
+
f = open(os.path.join(destination_dir, filename), "w")
|
| 223 |
+
f.write(str(code))
|
| 224 |
+
f.close()
|
| 225 |
+
return {"info":"The referenced code files were created successfully."}
|
| 226 |
+
# return {"info":"The referenced code files already exist. Please rename the file or delete the existing one."}
|
| 227 |
|
| 228 |
+
|
| 229 |
@mcp.tool()
|
| 230 |
+
def run_code(language:str,packages:str,filename: str, code: str,start_cmd:str,forever_cmd:str) -> dict:
|
| 231 |
+
"""
|
| 232 |
+
Execute code in a controlled environment with package installation and file handling.
|
|
|
|
| 233 |
Args:
|
| 234 |
+
language:Programming language of the code (eg:"python", "nodejs", "bash","html",etc).
|
| 235 |
+
packages: Space-separated list of packages to install.(python packages are installed if language set to python and npm packages are installed if language set to nodejs).
|
| 236 |
+
Preinstalled python packages: gradio, XlsxWriter, openpyxl , mpxj , jpype1.
|
| 237 |
+
Preinstalled npm packages: express, ejs, chart.js.
|
| 238 |
+
filename:Name of the file to create (stored in /app/code_interpreter/).
|
| 239 |
+
code:Full code to write to the file.
|
| 240 |
+
start_cmd:Command to execute the file (e.g., "python /app/code_interpreter/app.py"
|
| 241 |
+
or "bash /app/code_interpreter/app.py").
|
| 242 |
+
Leave blank ('') if only file creation is needed / start_cmd not required.
|
| 243 |
+
forever_cmd:If 'true', the command will run indefinitely.Set to 'true', when runnig a website/server.Run all servers/website on port 1337. If 'false', the command will time out after 300 second and the result will be returned.
|
| 244 |
+
Notes:
|
| 245 |
+
- All user-uploaded files are in /app/code_interpreter/.
|
| 246 |
+
- After execution, embed a download link (or display images/gifs/videos directly in markdown format) in your response.
|
| 247 |
+
- bash/apk packages cannot be installed.
|
| 248 |
+
- When editing and subsequently re-executing the server with the forever_cmd='true' setting, the previous server instance will be automatically terminated, and the updated server will commence operation. This functionality negates the requirement for manual process termination commands such as pkill node.
|
| 249 |
+
- The opened ports can be externally accessed at https://suitable-liked-ibex.ngrok-free.app/ (ONLY if the website is running successfully)
|
| 250 |
+
- Do not use `plt.show()` in this headless environment. Save visualizations directly (e.g., `plt.savefig("happiness_img.png")` or export GIFs/videos).User-Interactive libraries and programs like `pygame` are also not supported.Try to create a website to accomplish the same task instead.
|
| 251 |
"""
|
| 252 |
+
global destination_dir
|
| 253 |
+
package_names = packages.strip()
|
| 254 |
+
if "python" in language:
|
| 255 |
+
command="pip install --break-system-packages "
|
| 256 |
+
elif "node" in language:
|
| 257 |
+
command="npm install "
|
| 258 |
+
else:
|
| 259 |
+
command="ls"
|
| 260 |
+
if packages != "" and packages != " ":
|
| 261 |
+
package_logs=run(
|
| 262 |
+
f"{command} {package_names}", timeout_sec=300,forever_cmd= 'false'
|
| 263 |
+
)
|
| 264 |
+
if "ERROR" in package_logs:
|
| 265 |
+
return {"package_installation_log":package_logs,"info":"Package installation failed. Please check the package names. Tip:Try using another package/method to accomplish the task."}
|
| 266 |
+
transfer_files2()
|
| 267 |
+
transfer_files()
|
| 268 |
+
f = open(os.path.join(destination_dir, filename), "w")
|
| 269 |
+
f.write(code)
|
| 270 |
+
f.close()
|
| 271 |
+
global files_list
|
| 272 |
+
if start_cmd != "" and start_cmd != " ":
|
| 273 |
+
stdot=run(start_cmd, 120,forever_cmd)
|
| 274 |
else:
|
| 275 |
+
stdot="File created successfully."
|
| 276 |
+
onlyfiles = glob.glob("/app/code_interpreter/*")
|
| 277 |
+
onlyfiles=list(set(onlyfiles)-set(files_list))
|
| 278 |
+
uploaded_filenames=[]
|
| 279 |
+
for files in onlyfiles:
|
| 280 |
+
try:
|
| 281 |
+
uploaded_filename = upload_file(files, "https://opengpt-4ik5.onrender.com/upload")
|
| 282 |
+
uploaded_filenames.append(f"https://opengpt-4ik5.onrender.com/static/{uploaded_filename}")
|
| 283 |
+
except:
|
| 284 |
+
pass
|
| 285 |
+
files_list=onlyfiles
|
| 286 |
+
return {"output":stdot,"Files_download_link":uploaded_filenames}
|
| 287 |
|
| 288 |
|
| 289 |
@mcp.tool()
|
| 290 |
+
def run_code_files(start_cmd:str,forever_cmd:str) -> dict:
|
| 291 |
+
"""Executes a shell command to run code files from /app/code_interpreter.
|
| 292 |
+
Runs the given `start_cmd`. The execution behavior depends on `forever_cmd`.
|
| 293 |
+
Any server/website started should use port 1337.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
Args:
|
| 295 |
+
start_cmd (str): The shell command to execute the code.
|
| 296 |
+
(e.g., ``python /app/code_interpreter/app.py`` or ``node /app/code_interpreter/server.js``).
|
| 297 |
+
Files must be in ``/app/code_interpreter``.
|
| 298 |
+
forever_cmd (str): Execution mode.
|
| 299 |
+
- ``'true'``: Runs indefinitely (for servers/websites).
|
| 300 |
+
- ``'false'``: Runs up to 300s, captures output.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
Returns:
|
| 302 |
+
dict: A dictionary containing:
|
| 303 |
+
- ``'output'`` (str): Captured stdout (mainly when forever_cmd='false').
|
| 304 |
+
- ``'Files_download_link'`` (Any): Links/identifiers for downloadable files.
|
| 305 |
+
Notes:
|
| 306 |
+
- After execution, embed a download link (or display images/gifs/videos directly in markdown format) in your response.
|
| 307 |
+
- When editing and subsequently re-executing the server with the forever_cmd='true' setting, the previous server instance will be automatically terminated, and the updated server will commence operation. This functionality negates the requirement for manual process termination commands such as pkill node.
|
| 308 |
+
- The opened ports can be externally accessed at https://suitable-liked-ibex.ngrok-free.app/ (ONLY if the website is running successfully)
|
| 309 |
+
"""
|
| 310 |
+
global files_list
|
| 311 |
+
|
| 312 |
+
stdot=run(start_cmd, 300,forever_cmd)
|
| 313 |
+
onlyfiles = glob.glob("/app/code_interpreter/*")
|
| 314 |
+
onlyfiles=list(set(onlyfiles)-set(files_list))
|
| 315 |
+
uploaded_filenames=[]
|
| 316 |
+
for files in onlyfiles:
|
| 317 |
+
try:
|
| 318 |
+
uploaded_filename = upload_file(files, "https://opengpt-4ik5.onrender.com/upload")
|
| 319 |
+
uploaded_filenames.append(f"https://opengpt-4ik5.onrender.com/static/{uploaded_filename}")
|
| 320 |
+
except:
|
| 321 |
+
pass
|
| 322 |
+
files_list=onlyfiles
|
| 323 |
+
return {"output":stdot,"Files_download_link":uploaded_filenames}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
|
| 326 |
@mcp.tool()
|
| 327 |
+
def run_shell_command(cmd:str,forever_cmd:str) -> dict:
|
| 328 |
+
"""Executes a shell command in a sandboxed Alpine Linux environment.
|
| 329 |
+
Runs the provided `cmd` string within a bash shell. Commands are executed
|
| 330 |
+
relative to the `/app/code_interpreter/` working directory by default.
|
| 331 |
+
The execution behavior (indefinite run vs. timeout) is controlled by
|
| 332 |
+
the `forever_cmd` parameter.
|
| 333 |
+
Important Environment Notes:
|
| 334 |
+
- The execution environment is **Alpine Linux**. Commands should be
|
| 335 |
+
compatible .
|
| 336 |
+
- `sudo` commands are restricted for security reasons.Hence commands which require elevated privelages like `apk add` CANNOT be executed.Instead try to use `pip install` or `npm install` commands.
|
| 337 |
+
- Standard bash features like `&&`, `||`, pipes (`|`), etc., are supported.
|
| 338 |
Args:
|
| 339 |
+
cmd (str): The shell command to execute.
|
| 340 |
+
Example: ``mkdir test_dir && ls -l``
|
| 341 |
+
forever_cmd (str): Determines the execution mode.
|
| 342 |
+
- ``'true'``: Runs the command indefinitely. Suitable
|
| 343 |
+
for starting servers or long-running processes.
|
| 344 |
+
Output capture might be limited.
|
| 345 |
+
- ``'false'``: Runs the command until completion or
|
| 346 |
+
a 300-second timeout, whichever comes first.
|
| 347 |
+
Captures standard output.
|
| 348 |
Returns:
|
| 349 |
+
dict: A dictionary containing the execution results:
|
| 350 |
+
- ``'output'`` (str): The captured standard output (stdout) and potentially
|
| 351 |
+
standard error (stderr) from the command.
|
| 352 |
+
"""
|
| 353 |
+
transfer_files()
|
| 354 |
+
transfer_files2()
|
| 355 |
+
output=run(cmd, 300,forever_cmd)
|
| 356 |
+
return {"output":output}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
|
| 359 |
|
| 360 |
@mcp.tool()
|
| 361 |
+
def install_python_packages(python_packages:str) -> dict:
|
| 362 |
+
"""python_packages to install seperated by space.eg-(python packages:numpy matplotlib).The following python packages are preinstalled:gradio XlsxWriter openpyxl"""
|
| 363 |
+
global sbx
|
| 364 |
+
package_names = python_packages.strip()
|
| 365 |
+
command="pip install"
|
| 366 |
+
if not package_names:
|
| 367 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
|
| 369 |
+
stdot=run(
|
| 370 |
+
f"{command} --break-system-packages {package_names}", timeout_sec=300, forever_cmd= 'false'
|
| 371 |
+
)
|
|
|
|
| 372 |
|
| 373 |
+
return {"stdout":stdot,"info":"Ran package installation command"}
|
| 374 |
|
| 375 |
@mcp.tool()
|
| 376 |
+
def get_youtube_transcript(videoid:str) -> dict:
|
| 377 |
+
"""Get the transcript of a youtube video by passing the video id.Eg videoid=ZacjOVVgoLY"""
|
| 378 |
+
conn = http.client.HTTPSConnection("youtube-transcript3.p.rapidapi.com")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
headers = {
|
| 380 |
+
'x-rapidapi-key': "2a155d4498mshd52b7d6b7a2ff86p10cdd0jsn6252e0f2f529",
|
| 381 |
+
'x-rapidapi-host': "youtube-transcript3.p.rapidapi.com"
|
| 382 |
}
|
| 383 |
+
conn.request("GET",f"/api/transcript?videoId={videoid}", headers=headers)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
|
| 385 |
+
res = conn.getresponse()
|
| 386 |
+
data = res.read()
|
| 387 |
+
return json.loads(data)
|
| 388 |
|
| 389 |
@mcp.tool()
|
| 390 |
+
def read_excel_file(filename) -> dict:
|
| 391 |
+
"""Reads the contents of an excel file.Returns a dict with key :value pair = cell location:cell content.Always run this command first , when working with excels.The excel file is automatically present in the /app/code_interpreter directory. """
|
| 392 |
+
global destination_dir
|
| 393 |
+
transfer_files2()
|
| 394 |
+
transfer_files()
|
|
|
|
| 395 |
|
| 396 |
+
workbook = openpyxl.load_workbook(os.path.join(destination_dir, filename))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
+
# Create an empty dictionary to store the data
|
| 399 |
excel_data_dict = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
+
# Iterate over all sheets
|
| 402 |
+
for sheet_name in workbook.sheetnames:
|
| 403 |
+
sheet = workbook[sheet_name]
|
| 404 |
+
# Iterate over all rows and columns
|
| 405 |
+
for row in sheet.iter_rows():
|
| 406 |
+
for cell in row:
|
| 407 |
+
# Get cell coordinate (e.g., 'A1') and value
|
| 408 |
+
cell_coordinate = cell.coordinate
|
| 409 |
+
cell_value = cell.value
|
| 410 |
+
if cell_value is not None:
|
| 411 |
+
excel_data_dict[cell_coordinate] = str(cell_value)
|
| 412 |
+
return excel_data_dict
|
| 413 |
@mcp.tool()
|
| 414 |
+
def scrape_websites(url_list:list,query:str) -> list:
|
| 415 |
+
"""Scrapes specific website content.query is the question you want to ask about the content of the website.e.g-query:Give .pptx links in the website,Summarise the content in very great detail,etc.Maximum 4 urls can be passed at a time."""
|
|
|
|
|
|
|
| 416 |
|
| 417 |
+
conn = http.client.HTTPSConnection("scrapeninja.p.rapidapi.com")
|
|
|
|
|
|
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
|
|
|
|
| 420 |
headers = {
|
| 421 |
+
'x-rapidapi-key': "2a155d4498mshd52b7d6b7a2ff86p10cdd0jsn6252e0f2f529",
|
| 422 |
+
'x-rapidapi-host': "scrapeninja.p.rapidapi.com",
|
| 423 |
+
'Content-Type': "application/json"
|
| 424 |
}
|
| 425 |
+
Output=""
|
| 426 |
+
links=""
|
| 427 |
+
content=""
|
| 428 |
+
for urls in url_list:
|
| 429 |
+
payload = {"url" :urls}
|
| 430 |
+
payload=json.dumps(payload)
|
| 431 |
+
conn.request("POST", "/scrape", payload, headers)
|
| 432 |
+
res = conn.getresponse()
|
| 433 |
+
data = res.read()
|
| 434 |
+
content=content+str(data.decode("utf-8"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
|
| 436 |
+
#Only thing llama 4 is good for.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
|
| 438 |
+
response = clienty.chat.completions.create(
|
| 439 |
+
model="Llama-4-Maverick-17B-128E-Instruct-FP8",
|
| 440 |
+
messages=[
|
| 441 |
+
{"role": "user", "content": f"{query} [CONTENT]:{content}"}
|
| 442 |
+
],stream=True
|
| 443 |
+
)
|
| 444 |
+
for chunk in response:
|
| 445 |
+
Output = Output +str(chunk.choices[0].delta.content)
|
| 446 |
+
#--------------
|
| 447 |
+
response2 = clienty.chat.completions.create(
|
| 448 |
+
model="meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
|
| 449 |
+
messages=[
|
| 450 |
+
{"role": "user", "content": f"Give all relevant and different types of links in this content.The links may be relevant image links , file links , video links , website links , etc .You must give Minimum 30 links and maximum 50 links.[CONTENT]:{content}"}
|
| 451 |
+
],stream=True
|
| 452 |
+
)
|
| 453 |
+
for chunk in response2:
|
| 454 |
+
links = links +str(chunk.choices[0].delta.content)
|
| 455 |
+
return {"website_content":Output,"relevant_links":links}
|
| 456 |
|
| 457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
|
|
|
|
|
|
| 460 |
if __name__ == "__main__":
|
| 461 |
+
# Initialize and run the server
|
| 462 |
+
Ngrok=pexpect.spawn('bash')
|
| 463 |
+
Ngrok.sendline("ngrok http --url=suitable-liked-ibex.ngrok-free.app 1337 --config /home/node/.config/ngrok/ngrok.yml")
|
| 464 |
+
Ngrok.readline().decode()
|
| 465 |
+
mcp.run(transport='stdio')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|