-
Notifications
You must be signed in to change notification settings - Fork 4
It doesn't work with MCP tools #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Is the sandbox linked to the external environment in any way, or is it completely isolated (meaning the entire tool's code needs to be copied)? |
I've put the whole MCP logic inside, but now
source code: from typing import Dict, List, Any, Callable, Optional
import inspect
import re
import json
from langchain_core.messages import AIMessage
from langgraph_codeact import EvalCoroutine
from langchain_sandbox import PyodideSandbox
from langchain_core.tools import BaseTool
sandbox = PyodideSandbox("./sessions", allow_net=True, allow_run=True, allow_ffi=True)
def make_safe_function_name(name: str) -> str:
"""Convert a tool name to a valid Python function name."""
# Replace non-alphanumeric characters with underscores
safe_name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
# Ensure the name doesn't start with a digit
if safe_name and safe_name[0].isdigit():
safe_name = f"tool_{safe_name}"
# Handle empty name edge case
if not safe_name:
safe_name = "unnamed_tool"
return safe_name
def create_pyodide_eval_fn(
session_id: str | None = None,
mcp_tools: List[BaseTool] = None, # from client.get_tools()
mcp_config: Dict[str, Any] = None
) -> EvalCoroutine:
"""Create an eval_fn that uses PyodideSandbox.
Args:
session_id: ID of the session to use
mcp_tools: List of MCP tools to make available in the sandbox
mcp_config: MCP server configuration
Returns:
A function that evaluates code using PyodideSandbox
"""
mcp_tools = mcp_tools or []
mcp_config = mcp_config or {}
# Get the list of MCP tool names
mcp_tool_names = [tool.name for tool in mcp_tools if hasattr(tool, "name")]
# Get make_safe_function_name source code for inclusion in the sandbox
make_safe_function_name_src = inspect.getsource(make_safe_function_name)
async def async_eval_fn(code: str, _locals: dict[str, Any]) -> tuple[str, dict[str, Any]]:
# Create a wrapper function that will execute the code and return locals
# Initialize MCP server and tools directly in the wrapper code
wrapper_code = f'''
# MCP tool initialization
from langchain_mcp_adapters.client import MultiServerMCPClient
import json
import os
import re
{make_safe_function_name_src}
async def execute():
try:
# MCP configurations from parent environment
__mcp_config = {json.dumps(mcp_config)}
# Define MCP tools as variables in the sandbox
async with MultiServerMCPClient(__mcp_config) as __mcp_client:
__mcp_tools = __mcp_client.get_tools()
# Create variables for each MCP tool
for __tool in __mcp_tools:
if hasattr(__tool, "name"):
__safe_name = make_safe_function_name(__tool.name)
globals()[__safe_name] = __tool
# Execute the provided code
{chr(10).join(" " + line for line in code.strip().split(chr(10)))}
result = locals()
print("execute() function result keys: ", list(result.keys()))
return result
except Exception as e:
import traceback
error_details = traceback.format_exc()
print("Error in execute(): ", str(e))
print("Traceback: ", error_details)
return {{"error": str(e) + " Traceback: " + str(error_details)}}
return await execute()
'''
# Convert functions in _locals to their string representation
context_setup = ""
safe_mcp_tool_names = [make_safe_function_name(tool_name) for tool_name in mcp_tool_names]
for key, value in _locals.items():
if callable(value):
if key not in safe_mcp_tool_names:
# Get the function's source code for non-MCP tools
src = inspect.getsource(value)
context_setup += f"\n{src}"
else:
context_setup += f"\n{key} = {repr(value)}"
try:
# Execute the code and get the result
response = await sandbox.execute(
code=context_setup + "\n\n" + wrapper_code,
session_id=session_id,
)
# Check if execution was successful
if response.stderr:
return f"Error during execution: {response.stderr}", {}
# Get the output from stdout
output = (
response.stdout if response.stdout else "<Code ran, no output printed to stdout>"
)
# Added debugging logs
print(f"PyodideSandbox response structure: {dir(response)}")
print(f"PyodideSandbox stdout: {response.stdout}")
if hasattr(response, 'stderr'):
print(f"PyodideSandbox stderr: {response.stderr}")
result = response.result
print(f"PyodideSandbox result type: {type(result)}")
# If there was an error in the result, return it
if isinstance(result, dict) and "error" in result:
return f"Error during execution: {result['error']}", {}
# Check if result is None before trying to access items()
if result is None:
return f"Error during execution: Python code execution returned None result", {}
# Get the new variables by comparing with original locals
new_vars = {
k: v for k, v in result.items() if k not in _locals and not k.startswith("_")
}
return output, new_vars
except Exception as e:
return f"Error during PyodideSandbox execution: {repr(e)}", {}
return async_eval_fn |
Removing this line makes it run: So the issue is somewhere on micropip level. |
I tested it out in the console in isolation and there are no issues with that library: https://pyodide.org/en/latest/console.html
|
But in the version inside langchain-sandbox (0.27.4), it fails:
|
Most likely the latest version already contains it: pyodide/pyodide#4730 (comment) |
The current problem is that the 0.28 doesn't output stdout same way as 0.27.X (based on 0.28:
0.27.X:
|
@n-sviridenko thanks for the report we'll take a look. |
@n-sviridenko the newest release doesn't install langchain still i haven't updated to the pre-release of pyodide, but it won't swallow the error anymore. I'll review the prerelease next week to determine if I'm OK changing to it |
The MCP adapters code
https://github.com/langchain-ai/langchain-mcp-adapters/blob/94f0588ca86a40737d16a8e8d6adf84520dcd807/langchain_mcp_adapters/tools.py#L41C5-L41C39
outputs to the sandbox:
and due to the indentation, code execution fails.
The text was updated successfully, but these errors were encountered: