Skip to content

Commit 8f4557b

Browse files
committed
current
1 parent f2de326 commit 8f4557b

4 files changed

Lines changed: 128 additions & 61 deletions

File tree

eval_protocol/mcp/mcpgym.py

Lines changed: 122 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
import logging
2020
import os
2121
import threading
22+
import time
2223
from abc import ABC, abstractmethod
2324
from typing import Any, Callable, Dict, Optional, Tuple
2425

2526
import uvicorn
26-
from mcp.server.fastmcp import Context, FastMCP
27+
28+
# from mcp.server.fastmcp import Context, FastMCP
29+
from fastmcp import Context, FastMCP
2730
from starlette.requests import Request
2831
from starlette.responses import JSONResponse
2932
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
@@ -87,11 +90,11 @@ def __init__(self, server_name: str, adapter: EnvironmentAdapter, seed: Optional
8790
self.adapter = adapter
8891

8992
# Create FastMCP server
90-
self.mcp = FastMCP(
91-
server_name,
92-
host="0.0.0.0",
93-
port=int(os.environ.get("PORT", 8000)),
94-
)
93+
self.mcp = FastMCP(name=server_name)
94+
95+
# Store host and port for later use in run() method
96+
self.host = "0.0.0.0"
97+
self.port = int(os.environ.get("PORT", 8000))
9598

9699
# Multi-session support
97100
self.sessions = {} # session_id -> {"env": env, "obs": obs, "session_data": data}
@@ -117,6 +120,7 @@ def __init__(self, server_name: str, adapter: EnvironmentAdapter, seed: Optional
117120
self._register_tools()
118121
self._discover_and_register_control_plane_endpoints()
119122
self._register_session_reset_endpoint()
123+
# self._register_health_check_endpoint()
120124

121125
def _get_session_id(self, ctx: Context) -> str:
122126
"""
@@ -184,6 +188,7 @@ def _get_or_create_session(self, ctx: Context) -> Dict[str, Any]:
184188
"""
185189
session_id = self._get_session_id(ctx)
186190
print(f"🔍 _get_or_create_session: session_id: {session_id}")
191+
return self.sessions[session_id]
187192

188193
with self.session_lock:
189194
if session_id not in self.sessions:
@@ -238,18 +243,56 @@ async def reset_session_endpoint(request: Request) -> JSONResponse:
238243
print(f"🔍 _register_session_reset_endpoint: Resetting session, session_id: {session_id}, seed: {seed}")
239244
if not session_id:
240245
return JSONResponse({"error": "Missing mcp-session-id header"}, status_code=400)
241-
with self.session_lock:
242-
if session_id in self.sessions:
243-
env, obs, _ = self._new_env(seed=seed)
244-
self.sessions[session_id] = {
245-
"env": env,
246-
"obs": obs,
247-
"session_data": {},
248-
"session_id": session_id,
249-
}
250-
print(f"🔍 _register_session_reset_endpoint: Finished reset session, session_id: {session_id}")
246+
# with self.session_lock:
247+
# if session_id in self.sessions:
248+
# env, obs, _ = self._new_env(seed=seed)
249+
# self.sessions[session_id] = {
250+
# "env": env,
251+
# "obs": obs,
252+
# "session_data": {},
253+
# "session_id": session_id,
254+
# }
255+
# print(f"🔍 _register_session_reset_endpoint: Finished reset session, session_id: {session_id}")
251256
return JSONResponse({"message": "Session reset successfully"})
252257

258+
# def _register_health_check_endpoint(self):
259+
# """Register a simple health check endpoint for diagnostics."""
260+
261+
# @self.mcp.custom_route("/health", methods=["GET"])
262+
# async def health_check_endpoint(request: Request) -> JSONResponse:
263+
# """Simple health check that returns immediately."""
264+
# return JSONResponse({"ok": True, "timestamp": time.time()})
265+
266+
# def _add_timing_middleware(self, starlette_app):
267+
# """Add ASGI middleware to log request arrival times."""
268+
269+
# class TimingMiddleware:
270+
# def __init__(self, app):
271+
# self.app = app
272+
273+
# async def __call__(self, scope, receive, send):
274+
# if scope["type"] != "http":
275+
# await self.app(scope, receive, send)
276+
# return
277+
278+
# # Log immediately when request arrives at server
279+
# start_time = time.time()
280+
# path = scope.get("path", "")
281+
# method = scope.get("method", "")
282+
283+
# print(f"🚀 REQUEST ARRIVED: {method} {path} at {start_time}")
284+
285+
# async def send_wrapper(message):
286+
# if message["type"] == "http.response.start":
287+
# # Log completion time for comparison
288+
# end_time = time.time()
289+
# if path in ["/health", "/control/initial_state"]:
290+
# print(f"✅ REQUEST took: {end_time - start_time:.3f}s")
291+
# await send(message)
292+
293+
# await self.app(scope, receive, send_wrapper)
294+
# starlette_app.add_middleware(TimingMiddleware)
295+
253296
def _discover_and_register_control_plane_endpoints(self):
254297
"""
255298
Discover and register control plane endpoints on the subclass instance.
@@ -271,6 +314,9 @@ def _discover_and_register_control_plane_endpoints(self):
271314
# Create session-aware handler for this endpoint
272315
def create_endpoint_handler(func: Callable):
273316
async def endpoint_handler(request: Request) -> JSONResponse:
317+
318+
if func.__name__ == "get_initial_state_endpoint":
319+
logger.info(f"===== starting to handle endpoint: {func.__name__}, time: {time.time()}")
274320
try:
275321
# Extract session ID from request headers (similar to StreamableHTTP pattern)
276322
session_id = request.headers.get("mcp-session-id")
@@ -284,31 +330,43 @@ async def endpoint_handler(request: Request) -> JSONResponse:
284330
with self.session_lock:
285331
session_data = self.sessions.get(session_id)
286332
if not session_data:
287-
# For initial state endpoint, we need to create the session
288-
# based on the session ID and available information
289-
if func.__name__ == "get_initial_state_endpoint":
290-
env, obs, info = self._new_env(seed=None)
291-
# Initialize session state with extracted seed from session ID
292-
session_data = {
293-
"env": env,
294-
"obs": obs,
295-
"session_data": {}, # Subclasses can store additional data here
296-
"session_id": session_id,
297-
}
298-
# Store the session
299-
self.sessions[session_id] = session_data
300-
else:
301-
return JSONResponse(
302-
{"error": f"Session {session_id} not found"},
303-
status_code=404,
304-
)
333+
# create a placeholder session data
334+
self.sessions[session_id] = {"placeholder": True}
335+
# For initial state endpoint, we need to create the session
336+
# based on the session ID and available information
337+
if func.__name__ == "get_initial_state_endpoint":
338+
env, obs, info = self._new_env(seed=None)
339+
# Initialize session state with extracted seed from session ID
340+
session_data = {
341+
"env": env,
342+
"obs": obs,
343+
"session_data": {}, # Subclasses can store additional data here
344+
"session_id": session_id,
345+
}
346+
# Store the session
347+
with self.session_lock:
348+
self.sessions[session_id] = session_data
349+
350+
else:
351+
return JSONResponse(
352+
{"error": f"Session {session_id} not found"},
353+
status_code=404,
354+
)
305355

306356
# Call the endpoint function with session data
357+
method_start = time.time()
358+
if func.__name__ == "get_initial_state_endpoint":
359+
print(f"🎯 METHOD START: {func.__name__} at {method_start}")
360+
307361
if inspect.iscoroutinefunction(func):
308362
result = await func(session_data=session_data)
309363
else:
310364
result = func(session_data=session_data)
311365

366+
# method_end = time.time()
367+
# if func.__name__ == "get_initial_state_endpoint":
368+
# print(f"🎯 METHOD END: {func.__name__} at {method_end} (took {method_end - method_start:.3f}s)")
369+
312370
return JSONResponse(result)
313371

314372
except Exception as e:
@@ -351,22 +409,25 @@ def _update_control_plane(self, reward: float, terminated: bool, truncated: bool
351409

352410
def _get_or_create_session_control_plane(self, session_id: str) -> Dict[str, Any]:
353411
"""Get or create control plane state for a specific session."""
354-
with self.session_lock:
355-
if session_id not in self.sessions:
356-
return {}
357-
358-
session_data = self.sessions[session_id]
359-
if "control_plane" not in session_data["session_data"]:
360-
session_data["session_data"]["control_plane"] = {
361-
"reward": 0.0,
362-
"terminated": False,
363-
"truncated": False,
364-
"info": {},
365-
"step_count": 0,
366-
"total_reward": 0.0,
367-
}
412+
if session_id not in self.sessions:
413+
raise Exception(f"Session {session_id} not found")
414+
415+
# with self.session_lock:
416+
# if session_id not in self.sessions:
417+
# return {}
418+
419+
session_data = self.sessions[session_id]
420+
if "control_plane" not in session_data["session_data"]:
421+
session_data["session_data"]["control_plane"] = {
422+
"reward": 0.0,
423+
"terminated": False,
424+
"truncated": False,
425+
"info": {},
426+
"step_count": 0,
427+
"total_reward": 0.0,
428+
}
368429

369-
return session_data["session_data"]["control_plane"]
430+
return session_data["session_data"]["control_plane"]
370431

371432
def _update_session_control_plane(
372433
self,
@@ -391,12 +452,12 @@ def _update_session_control_plane(
391452
f"🎛️ Session {session_id[:16]}... control plane: reward={reward}, terminated={terminated}, step={control_plane['step_count']}, total_reward={control_plane['total_reward']}"
392453
)
393454

394-
def get_control_plane_state(self, session_id: str) -> Optional[Dict[str, Any]]:
395-
"""Get control plane state for a specific session (for rollout system)."""
396-
with self.session_lock:
397-
if session_id in self.sessions:
398-
return self._get_or_create_session_control_plane(session_id).copy()
399-
return None
455+
# def get_control_plane_state(self, session_id: str) -> Optional[Dict[str, Any]]:
456+
# """Get control plane state for a specific session (for rollout system)."""
457+
# with self.session_lock:
458+
# if session_id in self.sessions:
459+
# return self._get_or_create_session_control_plane(session_id).copy()
460+
# return None
400461

401462
def _execute_environment_step(self, action_int: int) -> Dict[str, Any]:
402463
"""
@@ -507,6 +568,7 @@ def get_info_endpoint(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
507568
@control_plane_endpoint("/control/initial_state")
508569
def get_initial_state_endpoint(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
509570
"""Get initial state for this session."""
571+
print(f"🔍 STARTING get_initial_state_endpoint: {time.time()}")
510572
env = session_data.get("env")
511573
obs = session_data.get("obs")
512574

@@ -593,14 +655,14 @@ async def run_with_high_concurrency():
593655

594656
config = uvicorn.Config(
595657
starlette_app,
596-
host=self.mcp.settings.host,
597-
port=self.mcp.settings.port,
598-
log_level=self.mcp.settings.log_level.lower(),
658+
host=self.host,
659+
port=self.port,
660+
log_level="info", # Use default log level instead of accessing settings
599661
proxy_headers=True,
600662
forwarded_allow_ips="*",
601663
# HIGH CONCURRENCY SETTINGS
602-
limit_concurrency=200, # Increase for HTTP endpoints + MCP
603-
limit_max_requests=100000, # Higher request limit
664+
limit_concurrency=None, # Increase for HTTP endpoints + MCP
665+
limit_max_requests=None, # Higher request limit
604666
timeout_keep_alive=120, # Longer keep-alive for control plane
605667
timeout_notify=180,
606668
h11_max_incomplete_event_size=4 * 1024 * 1024, # Handle larger events

examples/tau2_mcp/tau2_mcp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from typing import Annotated, Any, Dict, List, Optional
1313

1414
from airplane_environment.airline_environment import AirlineEnvironment
15-
from mcp.server.fastmcp import Context
15+
16+
# from mcp.server.fastmcp import Context
17+
from fastmcp import Context
1618
from mock_environment.mock_environment import MockEnvironment
1719
from pydantic import Field
1820
from retail_environment.retail_environment import RetailEnvironment

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies = [
4949
"watchdog>=2.1.0",
5050
"websockets>=15.0.1",
5151
"fastapi>=0.116.1",
52+
"fastmcp>=2.10.6",
5253
]
5354

5455
[project.urls]

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)