Skip to content

Commit d4b3b0f

Browse files
Merge pull request #76 from mainframecomputer/feat/add-close-method-mcp-adapter
Feat/add close method mcp adapter
2 parents 2e26ac4 + 6d357af commit d4b3b0f

File tree

4 files changed

+63
-5
lines changed

4 files changed

+63
-5
lines changed

packages/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "mainframe-orchestra"
3-
version = "0.0.33"
3+
version = "0.0.34"
44
description = "Mainframe-Orchestra is a lightweight, open-source agentic framework for building LLM based pipelines and self-orchestrating multi-agent teams"
55
authors = [
66
"Mainframe Computer Inc. <[email protected]>",

packages/python/src/mainframe_orchestra/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
# Copyright 2024 Mainframe-Orchestra Contributors. Licensed under Apache License 2.0.
55

6-
__version__ = "0.0.33"
6+
__version__ = "0.0.34"
77

88
import importlib
99

packages/python/src/mainframe_orchestra/adapters/mcp_adapter.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,64 @@ async def close(self) -> None:
384384
process.wait()
385385
logger.debug(f"Server process for {server_name} killed")
386386

387+
388+
async def close_session(self, server_name: str) -> None:
389+
"""
390+
Close a specific MCP server session by name.
391+
392+
This method is designed to be called from the same task context where the connection
393+
was established, to avoid the "Attempted to exit cancel scope in a different task" error.
394+
395+
Args:
396+
server_name: The name of the server session to close
397+
"""
398+
logger.debug(f"Closing MCP server session: {server_name}")
399+
400+
# Check if the session exists
401+
if server_name not in self.sessions:
402+
logger.warning(f"Attempted to close non-existent session: {server_name}")
403+
return
404+
405+
# Remove the session from our tracking
406+
session = self.sessions.pop(server_name)
407+
logger.debug(f"Retrieved session for {server_name}, type: {type(session)}")
408+
409+
# Remove tools associated with this server
410+
if server_name in self.server_tools:
411+
tools_to_remove = self.server_tools.pop(server_name)
412+
self.tools.difference_update(tools_to_remove)
413+
logger.debug(f"Removed {len(tools_to_remove)} tools associated with {server_name}")
414+
415+
# Check if the session has a close method
416+
if not hasattr(session, 'close'):
417+
logger.warning(f"Session for {server_name} does not have a close method")
418+
return
419+
420+
# Close the session directly
421+
try:
422+
logger.debug(f"Calling close() method on session for {server_name}")
423+
await session.close()
424+
logger.info(f"Successfully closed session for {server_name}")
425+
except Exception as e:
426+
logger.error(f"Error closing session for {server_name}: {e}", exc_info=True)
427+
428+
# Also terminate the server process if we started it
429+
if server_name in self.server_processes:
430+
process = self.server_processes.pop(server_name)
431+
logger.debug(f"Terminating server process for {server_name} (PID: {process.pid})")
432+
try:
433+
process.terminate()
434+
process.wait(timeout=5)
435+
logger.info(f"Server process for {server_name} terminated gracefully")
436+
except subprocess.TimeoutExpired:
437+
logger.debug(f"Server process for {server_name} did not terminate gracefully, forcing kill")
438+
process.kill()
439+
process.wait()
440+
logger.info(f"Server process for {server_name} killed")
441+
else:
442+
logger.debug(f"No server process found for {server_name}")
443+
444+
387445
async def __aenter__(self) -> "MCPOrchestra":
388446
"""Support for async context manager."""
389447
return self

packages/python/src/mainframe_orchestra/task.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,10 +706,10 @@ def hash_tool_call(tool_call: dict) -> str:
706706
if "tool" not in tool_call:
707707
raise ValueError("Each tool call must specify a 'tool' name")
708708

709+
# Make params default to empty dict if not provided
709710
if "params" not in tool_call:
710-
raise ValueError("Each tool call must include 'params'")
711-
712-
if not isinstance(tool_call["params"], dict):
711+
tool_call["params"] = {}
712+
elif not isinstance(tool_call["params"], dict):
713713
raise ValueError("Tool 'params' must be an object")
714714

715715
tool_name = tool_call.get("tool")

0 commit comments

Comments
 (0)