Spaces:
Running
Running
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')
|
|
|
|
|
|
|
|
|
|