@@ -637,7 +637,7 @@ async def get_items(count: int):
637637
638638
639639# ==============================
640- # Comparison tests: MCP PythonTool vs DirectPythonTool
640+ # DirectPythonTool functional tests
641641# ==============================
642642
643643
@@ -815,74 +815,50 @@ async def test_direct_python_tool_cleanup_request_deletes_session():
815815
816816
817817@pytest .mark .asyncio
818- async def test_mcp_vs_direct_python_tool_parity ():
819- """MCP-based PythonTool and DirectPythonTool produce identical results for the same tool calls ."""
820- from nemo_skills .mcp .servers .python_tool import DirectPythonTool , PythonTool
818+ async def test_direct_python_tool_session_sequence ():
819+ """DirectPythonTool runs a multi-step stateful session and computes correct results ."""
820+ from nemo_skills .mcp .servers .python_tool import DirectPythonTool
821821
822822 sandbox_context = {"sandbox" : {"sandbox_type" : "local" }}
823823
824- # Set up DirectPythonTool
825824 direct = DirectPythonTool ()
826825 direct .configure (context = sandbox_context )
827826
828- # Set up MCP PythonTool
829- mcp_tool = PythonTool ()
830- mcp_tool .configure (context = sandbox_context )
831-
832- # Verify both expose the same tool name
833- direct_tools = await direct .list_tools ()
834- mcp_tools = await mcp_tool .list_tools ()
835- assert direct_tools [0 ]["name" ] == mcp_tools [0 ]["name" ] == "stateful_python_code_exec"
827+ assert (await direct .list_tools ())[0 ]["name" ] == "stateful_python_code_exec"
836828
837- # Define a sequence of tool calls that exercises session persistence
829+ # A sequence of tool calls that exercises session persistence
838830 tool_calls = [
839- {"code" : "import math" , "request_id" : "parity " },
840- {"code" : "result = math.factorial(10)" , "request_id" : "parity " },
841- {"code" : "print(result)" , "request_id" : "parity " },
842- {"code" : "x = [i**2 for i in range(5)]" , "request_id" : "parity " },
843- {"code" : "print(sum(x))" , "request_id" : "parity " },
831+ {"code" : "import math" , "request_id" : "seq " },
832+ {"code" : "result = math.factorial(10)" , "request_id" : "seq " },
833+ {"code" : "print(result)" , "request_id" : "seq " },
834+ {"code" : "x = [i**2 for i in range(5)]" , "request_id" : "seq " },
835+ {"code" : "print(sum(x))" , "request_id" : "seq " },
844836 ]
845837
846- direct_results = await _run_tool_sequence (direct , tool_calls )
847- mcp_results = await _run_tool_sequence (mcp_tool , tool_calls )
848-
849- for i , (d , m ) in enumerate (zip (direct_results , mcp_results )):
850- assert d == m , f"Mismatch at step { i } : direct={ d !r} , mcp={ m !r} "
838+ results = await _run_tool_sequence (direct , tool_calls )
851839
852840 # Verify the actual computed values are correct
853- assert direct_results [2 ] == "3628800" # 10!
854- assert direct_results [4 ] == "30" # 0 + 1 + 4 + 9 + 16
841+ assert results [2 ] == "3628800" # 10!
842+ assert results [4 ] == "30" # 0 + 1 + 4 + 9 + 16
855843
856844 await direct .shutdown ()
857- await mcp_tool .shutdown ()
858845
859846
860847@pytest .mark .asyncio
861- async def test_mcp_vs_direct_error_parity ():
862- """Both implementations handle errors the same way ."""
863- from nemo_skills .mcp .servers .python_tool import DirectPythonTool , PythonTool
848+ async def test_direct_python_tool_surfaces_exceptions ():
849+ """DirectPythonTool surfaces Python exceptions in the returned output ."""
850+ from nemo_skills .mcp .servers .python_tool import DirectPythonTool
864851
865852 sandbox_context = {"sandbox" : {"sandbox_type" : "local" }}
866853
867854 direct = DirectPythonTool ()
868855 direct .configure (context = sandbox_context )
869856
870- mcp_tool = PythonTool ()
871- mcp_tool .configure (context = sandbox_context )
872-
873- tool_calls = [
874- {"code" : "1 / 0" , "request_id" : "err" },
875- ]
876-
877- direct_results = await _run_tool_sequence (direct , tool_calls )
878- mcp_results = await _run_tool_sequence (mcp_tool , tool_calls )
857+ results = await _run_tool_sequence (direct , [{"code" : "1 / 0" , "request_id" : "err" }])
879858
880- # Both should contain ZeroDivisionError
881- assert "ZeroDivisionError" in direct_results [0 ]
882- assert "ZeroDivisionError" in mcp_results [0 ]
859+ assert "ZeroDivisionError" in results [0 ]
883860
884861 await direct .shutdown ()
885- await mcp_tool .shutdown ()
886862
887863
888864# ==============================
@@ -938,24 +914,21 @@ def test_python_tools_accept_exec_timeout_argument():
938914 assert DirectPythonTool (exec_timeout_s = 42 ).default_config ()["exec_timeout_s" ] == 42
939915
940916
917+ def test_python_tool_defaults_to_direct_provider ():
918+ from nemo_skills .mcp .servers .python_tool import DirectPythonTool , PythonTool
919+
920+ # PythonTool is now the in-process direct provider (no stdio MCP transport).
921+ assert isinstance (PythonTool (), DirectPythonTool )
922+
923+
941924@pytest .mark .asyncio
942925async def test_python_tool_description_uses_exec_timeout_override ():
943926 from nemo_skills .mcp .servers .python_tool import PythonTool
944927
945- class FakeClient :
946- async def list_tools (self ):
947- return [
948- {
949- "name" : "stateful_python_code_exec" ,
950- "description" : "stale server description" ,
951- "input_schema" : {"type" : "object" , "properties" : {"code" : {"type" : "string" }}},
952- }
953- ]
954-
955928 tool = PythonTool ()
956929 tool .configure (
957- overrides = {"client" : FakeClient , "client_params" : {}, " exec_timeout_s" : 37 , "init_hook" : None },
958- context = {},
930+ overrides = {"exec_timeout_s" : 37 },
931+ context = {"sandbox" : { "sandbox_type" : "local" } },
959932 )
960933
961934 tools = await tool .list_tools ()
@@ -964,45 +937,6 @@ async def list_tools(self):
964937 assert "10.0 seconds" not in tools [0 ]["description" ]
965938
966939
967- @pytest .mark .asyncio
968- async def test_python_tool_uses_exec_timeout_argument ():
969- from nemo_skills .mcp .servers .python_tool import PythonTool
970-
971- class FakeClient :
972- def __init__ (self ):
973- self .extra_args = None
974-
975- async def list_tools (self ):
976- return [
977- {
978- "name" : "stateful_python_code_exec" ,
979- "description" : "stale server description" ,
980- "input_schema" : {"type" : "object" , "properties" : {"code" : {"type" : "string" }}},
981- }
982- ]
983-
984- async def call_tool (self , tool , args , extra_args = None ):
985- self .extra_args = extra_args
986- return {"session_id" : "session" , "output_dict" : {"stdout" : "done\n " , "stderr" : "" }}
987-
988- fake_client = FakeClient ()
989- tool = PythonTool (exec_timeout_s = 42 )
990- tool ._client = fake_client
991-
992- result = await tool .execute (
993- "stateful_python_code_exec" ,
994- {"code" : "print('done')" },
995- extra_args = {"request_id" : "timeout-arg" },
996- )
997-
998- assert result == "done"
999- assert fake_client .extra_args ["timeout" ] == 42
1000-
1001- tools = await tool .list_tools ()
1002- assert "42.0 seconds" in tools [0 ]["description" ]
1003- assert "10.0 seconds" not in tools [0 ]["description" ]
1004-
1005-
1006940@pytest .mark .asyncio
1007941async def test_direct_python_tool_uses_exec_timeout_argument ():
1008942 from nemo_skills .mcp .servers .python_tool import DirectPythonTool
0 commit comments