letta-nightly 0.7.29.dev20250602104315__py3-none-any.whl → 0.8.0.dev20250604104349__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. letta/__init__.py +7 -1
  2. letta/agent.py +16 -9
  3. letta/agents/base_agent.py +1 -0
  4. letta/agents/ephemeral_summary_agent.py +104 -0
  5. letta/agents/helpers.py +35 -3
  6. letta/agents/letta_agent.py +492 -176
  7. letta/agents/letta_agent_batch.py +22 -16
  8. letta/agents/prompts/summary_system_prompt.txt +62 -0
  9. letta/agents/voice_agent.py +22 -7
  10. letta/agents/voice_sleeptime_agent.py +13 -8
  11. letta/constants.py +33 -1
  12. letta/data_sources/connectors.py +52 -36
  13. letta/errors.py +4 -0
  14. letta/functions/ast_parsers.py +13 -30
  15. letta/functions/function_sets/base.py +3 -1
  16. letta/functions/functions.py +2 -0
  17. letta/functions/mcp_client/base_client.py +151 -97
  18. letta/functions/mcp_client/sse_client.py +49 -31
  19. letta/functions/mcp_client/stdio_client.py +107 -106
  20. letta/functions/schema_generator.py +22 -22
  21. letta/groups/helpers.py +3 -4
  22. letta/groups/sleeptime_multi_agent.py +4 -4
  23. letta/groups/sleeptime_multi_agent_v2.py +22 -0
  24. letta/helpers/composio_helpers.py +16 -0
  25. letta/helpers/converters.py +20 -0
  26. letta/helpers/datetime_helpers.py +1 -6
  27. letta/helpers/tool_rule_solver.py +2 -1
  28. letta/interfaces/anthropic_streaming_interface.py +17 -2
  29. letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
  30. letta/interfaces/openai_streaming_interface.py +18 -2
  31. letta/jobs/llm_batch_job_polling.py +1 -1
  32. letta/jobs/scheduler.py +1 -1
  33. letta/llm_api/anthropic_client.py +24 -3
  34. letta/llm_api/google_ai_client.py +0 -15
  35. letta/llm_api/google_vertex_client.py +6 -5
  36. letta/llm_api/llm_client_base.py +15 -0
  37. letta/llm_api/openai.py +2 -2
  38. letta/llm_api/openai_client.py +60 -8
  39. letta/orm/__init__.py +2 -0
  40. letta/orm/agent.py +45 -43
  41. letta/orm/base.py +0 -2
  42. letta/orm/block.py +1 -0
  43. letta/orm/custom_columns.py +13 -0
  44. letta/orm/enums.py +5 -0
  45. letta/orm/file.py +3 -1
  46. letta/orm/files_agents.py +68 -0
  47. letta/orm/mcp_server.py +48 -0
  48. letta/orm/message.py +1 -0
  49. letta/orm/organization.py +11 -2
  50. letta/orm/passage.py +25 -10
  51. letta/orm/sandbox_config.py +5 -2
  52. letta/orm/sqlalchemy_base.py +171 -110
  53. letta/prompts/system/memgpt_base.txt +6 -1
  54. letta/prompts/system/memgpt_v2_chat.txt +57 -0
  55. letta/prompts/system/sleeptime.txt +2 -0
  56. letta/prompts/system/sleeptime_v2.txt +28 -0
  57. letta/schemas/agent.py +87 -20
  58. letta/schemas/block.py +7 -1
  59. letta/schemas/file.py +57 -0
  60. letta/schemas/mcp.py +74 -0
  61. letta/schemas/memory.py +5 -2
  62. letta/schemas/message.py +9 -0
  63. letta/schemas/openai/openai.py +0 -6
  64. letta/schemas/providers.py +33 -4
  65. letta/schemas/tool.py +26 -21
  66. letta/schemas/tool_execution_result.py +5 -0
  67. letta/server/db.py +23 -8
  68. letta/server/rest_api/app.py +73 -56
  69. letta/server/rest_api/interface.py +4 -4
  70. letta/server/rest_api/routers/v1/agents.py +132 -47
  71. letta/server/rest_api/routers/v1/blocks.py +3 -2
  72. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  73. letta/server/rest_api/routers/v1/groups.py +3 -3
  74. letta/server/rest_api/routers/v1/jobs.py +14 -17
  75. letta/server/rest_api/routers/v1/organizations.py +10 -10
  76. letta/server/rest_api/routers/v1/providers.py +12 -10
  77. letta/server/rest_api/routers/v1/runs.py +3 -3
  78. letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
  79. letta/server/rest_api/routers/v1/sources.py +108 -43
  80. letta/server/rest_api/routers/v1/steps.py +8 -6
  81. letta/server/rest_api/routers/v1/tools.py +134 -95
  82. letta/server/rest_api/utils.py +12 -1
  83. letta/server/server.py +272 -73
  84. letta/services/agent_manager.py +246 -313
  85. letta/services/block_manager.py +30 -9
  86. letta/services/context_window_calculator/__init__.py +0 -0
  87. letta/services/context_window_calculator/context_window_calculator.py +150 -0
  88. letta/services/context_window_calculator/token_counter.py +82 -0
  89. letta/services/file_processor/__init__.py +0 -0
  90. letta/services/file_processor/chunker/__init__.py +0 -0
  91. letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
  92. letta/services/file_processor/embedder/__init__.py +0 -0
  93. letta/services/file_processor/embedder/openai_embedder.py +84 -0
  94. letta/services/file_processor/file_processor.py +123 -0
  95. letta/services/file_processor/parser/__init__.py +0 -0
  96. letta/services/file_processor/parser/base_parser.py +9 -0
  97. letta/services/file_processor/parser/mistral_parser.py +54 -0
  98. letta/services/file_processor/types.py +0 -0
  99. letta/services/files_agents_manager.py +184 -0
  100. letta/services/group_manager.py +118 -0
  101. letta/services/helpers/agent_manager_helper.py +76 -21
  102. letta/services/helpers/tool_execution_helper.py +3 -0
  103. letta/services/helpers/tool_parser_helper.py +100 -0
  104. letta/services/identity_manager.py +44 -42
  105. letta/services/job_manager.py +21 -10
  106. letta/services/mcp/base_client.py +5 -2
  107. letta/services/mcp/sse_client.py +3 -5
  108. letta/services/mcp/stdio_client.py +3 -5
  109. letta/services/mcp_manager.py +281 -0
  110. letta/services/message_manager.py +40 -26
  111. letta/services/organization_manager.py +55 -19
  112. letta/services/passage_manager.py +211 -13
  113. letta/services/provider_manager.py +48 -2
  114. letta/services/sandbox_config_manager.py +105 -0
  115. letta/services/source_manager.py +4 -5
  116. letta/services/step_manager.py +9 -6
  117. letta/services/summarizer/summarizer.py +50 -23
  118. letta/services/telemetry_manager.py +7 -0
  119. letta/services/tool_executor/tool_execution_manager.py +11 -52
  120. letta/services/tool_executor/tool_execution_sandbox.py +4 -34
  121. letta/services/tool_executor/tool_executor.py +107 -105
  122. letta/services/tool_manager.py +56 -17
  123. letta/services/tool_sandbox/base.py +39 -92
  124. letta/services/tool_sandbox/e2b_sandbox.py +16 -11
  125. letta/services/tool_sandbox/local_sandbox.py +51 -23
  126. letta/services/user_manager.py +36 -3
  127. letta/settings.py +10 -3
  128. letta/templates/__init__.py +0 -0
  129. letta/templates/sandbox_code_file.py.j2 +47 -0
  130. letta/templates/template_helper.py +16 -0
  131. letta/tracing.py +30 -1
  132. letta/types/__init__.py +7 -0
  133. letta/utils.py +25 -1
  134. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/METADATA +7 -2
  135. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/RECORD +138 -112
  136. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/LICENSE +0 -0
  137. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/WHEEL +0 -0
  138. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/entry_points.txt +0 -0
@@ -5,23 +5,13 @@ import typing
5
5
  from typing import Dict, Optional, Tuple
6
6
 
7
7
  from letta.errors import LettaToolCreateError
8
-
9
- # Registry of known types for annotation resolution
10
- BUILTIN_TYPES = {
11
- "int": int,
12
- "float": float,
13
- "str": str,
14
- "dict": dict,
15
- "list": list,
16
- "set": set,
17
- "tuple": tuple,
18
- "bool": bool,
19
- }
8
+ from letta.types import JsonDict
20
9
 
21
10
 
22
11
  def resolve_type(annotation: str):
23
12
  """
24
13
  Resolve a type annotation string into a Python type.
14
+ Previously, primitive support for int, float, str, dict, list, set, tuple, bool.
25
15
 
26
16
  Args:
27
17
  annotation (str): The annotation string (e.g., 'int', 'list[int]', 'dict[str, int]').
@@ -32,24 +22,19 @@ def resolve_type(annotation: str):
32
22
  Raises:
33
23
  ValueError: If the annotation is unsupported or invalid.
34
24
  """
35
- if annotation in BUILTIN_TYPES:
36
- return BUILTIN_TYPES[annotation]
25
+ python_types = {**vars(typing), **vars(builtins)}
26
+
27
+ if annotation in python_types:
28
+ return python_types[annotation]
37
29
 
38
30
  try:
39
31
  # Allow use of typing and builtins in a safe eval context
40
- namespace = {
41
- **vars(typing),
42
- **vars(builtins),
43
- "list": list,
44
- "dict": dict,
45
- "tuple": tuple,
46
- "set": set,
47
- }
48
- return eval(annotation, namespace)
32
+ return eval(annotation, python_types)
49
33
  except Exception:
50
34
  raise ValueError(f"Unsupported annotation: {annotation}")
51
35
 
52
36
 
37
+ # TODO :: THIS MUST BE EDITED TO HANDLE THINGS
53
38
  def get_function_annotations_from_source(source_code: str, function_name: str) -> Dict[str, str]:
54
39
  """
55
40
  Parse the source code to extract annotations for a given function name.
@@ -76,7 +61,8 @@ def get_function_annotations_from_source(source_code: str, function_name: str) -
76
61
  raise ValueError(f"Function '{function_name}' not found in the provided source code.")
77
62
 
78
63
 
79
- def coerce_dict_args_by_annotations(function_args: dict, annotations: Dict[str, str]) -> dict:
64
+ # NOW json_loads -> ast.literal_eval -> typing.get_origin
65
+ def coerce_dict_args_by_annotations(function_args: JsonDict, annotations: Dict[str, str]) -> dict:
80
66
  coerced_args = dict(function_args) # Shallow copy
81
67
 
82
68
  for arg_name, value in coerced_args.items():
@@ -110,8 +96,8 @@ def coerce_dict_args_by_annotations(function_args: dict, annotations: Dict[str,
110
96
  return coerced_args
111
97
 
112
98
 
113
- def get_function_name_and_description(source_code: str, name: Optional[str] = None) -> Tuple[str, str]:
114
- """Gets the name and description for a given function source code by parsing the AST.
99
+ def get_function_name_and_docstring(source_code: str, name: Optional[str] = None) -> Tuple[str, str]:
100
+ """Gets the name and docstring for a given function source code by parsing the AST.
115
101
 
116
102
  Args:
117
103
  source_code: The source code to parse
@@ -147,11 +133,8 @@ def get_function_name_and_description(source_code: str, name: Optional[str] = No
147
133
 
148
134
  return function_name, docstring
149
135
 
150
- except Exception as e:
151
- raise LettaToolCreateError(f"Failed to parse function name and docstring: {str(e)}")
152
-
153
136
  except Exception as e:
154
137
  import traceback
155
138
 
156
139
  traceback.print_exc()
157
- raise LettaToolCreateError(f"Name and docstring generation failed: {str(e)}")
140
+ raise LettaToolCreateError(f"Failed to parse function name and docstring: {str(e)}")
@@ -288,7 +288,9 @@ def memory_insert(agent_state: "AgentState", label: str, new_str: str, insert_li
288
288
  n_lines = len(current_value_lines)
289
289
 
290
290
  # Check if we're in range, from 0 (pre-line), to 1 (first line), to n_lines (last line)
291
- if insert_line < 0 or insert_line > n_lines:
291
+ if insert_line == -1:
292
+ insert_line = n_lines
293
+ elif insert_line < 0 or insert_line > n_lines:
292
294
  raise ValueError(
293
295
  f"Invalid `insert_line` parameter: {insert_line}. It should be within the range of lines of the memory block: {[0, n_lines]}, or -1 to append to the end of the memory block."
294
296
  )
@@ -12,6 +12,8 @@ from letta.functions.schema_generator import generate_schema
12
12
  def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> dict:
13
13
  """Derives the OpenAI JSON schema for a given function source code.
14
14
 
15
+ # TODO (cliandy): I don't think we need to or should execute here
16
+ # TODO (cliandy): CONFIRM THIS BEFORE MERGING.
15
17
  First, attempts to execute the source code in a custom environment with only the necessary imports.
16
18
  Then, it generates the schema from the function's docstring and signature.
17
19
  """
@@ -1,102 +1,156 @@
1
- import asyncio
2
- from typing import List, Optional, Tuple
3
-
4
- from mcp import ClientSession
5
- from mcp.types import TextContent
6
-
7
- from letta.functions.mcp_client.exceptions import MCPTimeoutError
8
- from letta.functions.mcp_client.types import BaseServerConfig, MCPTool
9
1
  from letta.log import get_logger
10
- from letta.settings import tool_settings
11
2
 
12
3
  logger = get_logger(__name__)
13
4
 
14
5
 
15
- class BaseMCPClient:
16
- def __init__(self, server_config: BaseServerConfig):
17
- self.server_config = server_config
18
- self.session: Optional[ClientSession] = None
19
- self.stdio = None
20
- self.write = None
21
- self.initialized = False
22
- self.loop = asyncio.new_event_loop()
23
- self.cleanup_funcs = []
24
-
25
- def connect_to_server(self):
26
- asyncio.set_event_loop(self.loop)
27
- success = self._initialize_connection(self.server_config, timeout=tool_settings.mcp_connect_to_server_timeout)
28
-
29
- if success:
30
- try:
31
- self.loop.run_until_complete(
32
- asyncio.wait_for(self.session.initialize(), timeout=tool_settings.mcp_connect_to_server_timeout)
33
- )
34
- self.initialized = True
35
- except asyncio.TimeoutError:
36
- raise MCPTimeoutError("initializing session", self.server_config.server_name, tool_settings.mcp_connect_to_server_timeout)
37
- else:
38
- raise RuntimeError(
39
- f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}"
40
- )
41
-
42
- def _initialize_connection(self, server_config: BaseServerConfig, timeout: float) -> bool:
43
- raise NotImplementedError("Subclasses must implement _initialize_connection")
44
-
45
- def list_tools(self) -> List[MCPTool]:
46
- self._check_initialized()
47
- try:
48
- response = self.loop.run_until_complete(
49
- asyncio.wait_for(self.session.list_tools(), timeout=tool_settings.mcp_list_tools_timeout)
50
- )
51
- return response.tools
52
- except asyncio.TimeoutError:
53
- logger.error(
54
- f"Timed out while listing tools for MCP server {self.server_config.server_name} (timeout={tool_settings.mcp_list_tools_timeout}s)."
55
- )
56
- raise MCPTimeoutError("listing tools", self.server_config.server_name, tool_settings.mcp_list_tools_timeout)
57
-
58
- def execute_tool(self, tool_name: str, tool_args: dict) -> Tuple[str, bool]:
59
- self._check_initialized()
60
- try:
61
- result = self.loop.run_until_complete(
62
- asyncio.wait_for(self.session.call_tool(tool_name, tool_args), timeout=tool_settings.mcp_execute_tool_timeout)
63
- )
64
-
65
- parsed_content = []
66
- for content_piece in result.content:
67
- if isinstance(content_piece, TextContent):
68
- parsed_content.append(content_piece.text)
69
- print("parsed_content (text)", parsed_content)
70
- else:
71
- parsed_content.append(str(content_piece))
72
- print("parsed_content (other)", parsed_content)
73
-
74
- if len(parsed_content) > 0:
75
- final_content = " ".join(parsed_content)
76
- else:
77
- # TODO move hardcoding to constants
78
- final_content = "Empty response from tool"
79
-
80
- return final_content, result.isError
81
- except asyncio.TimeoutError:
82
- logger.error(
83
- f"Timed out while executing tool '{tool_name}' for MCP server {self.server_config.server_name} (timeout={tool_settings.mcp_execute_tool_timeout}s)."
84
- )
85
- raise MCPTimeoutError(f"executing tool '{tool_name}'", self.server_config.server_name, tool_settings.mcp_execute_tool_timeout)
86
-
87
- def _check_initialized(self):
88
- if not self.initialized:
89
- logger.error("MCPClient has not been initialized")
90
- raise RuntimeError("MCPClient has not been initialized")
91
-
92
- def cleanup(self):
93
- try:
94
- for cleanup_func in self.cleanup_funcs:
95
- cleanup_func()
96
- self.initialized = False
97
- if not self.loop.is_closed():
98
- self.loop.close()
99
- except Exception as e:
100
- logger.warning(e)
101
- finally:
102
- logger.info("Cleaned up MCP clients on shutdown.")
6
+ # class BaseMCPClient:
7
+ # def __init__(self, server_config: BaseServerConfig):
8
+ # self.server_config = server_config
9
+ # self.session: Optional[ClientSession] = None
10
+ # self.stdio = None
11
+ # self.write = None
12
+ # self.initialized = False
13
+ # self.loop = asyncio.new_event_loop()
14
+ # self.cleanup_funcs = []
15
+ #
16
+ # def connect_to_server(self):
17
+ # asyncio.set_event_loop(self.loop)
18
+ # success = self._initialize_connection(self.server_config, timeout=tool_settings.mcp_connect_to_server_timeout)
19
+ #
20
+ # if success:
21
+ # try:
22
+ # self.loop.run_until_complete(
23
+ # asyncio.wait_for(self.session.initialize(), timeout=tool_settings.mcp_connect_to_server_timeout)
24
+ # )
25
+ # self.initialized = True
26
+ # except asyncio.TimeoutError:
27
+ # raise MCPTimeoutError("initializing session", self.server_config.server_name, tool_settings.mcp_connect_to_server_timeout)
28
+ # else:
29
+ # raise RuntimeError(
30
+ # f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}"
31
+ # )
32
+ #
33
+ # def _initialize_connection(self, server_config: BaseServerConfig, timeout: float) -> bool:
34
+ # raise NotImplementedError("Subclasses must implement _initialize_connection")
35
+ #
36
+ # def list_tools(self) -> List[MCPTool]:
37
+ # self._check_initialized()
38
+ # try:
39
+ # response = self.loop.run_until_complete(
40
+ # asyncio.wait_for(self.session.list_tools(), timeout=tool_settings.mcp_list_tools_timeout)
41
+ # )
42
+ # return response.tools
43
+ # except asyncio.TimeoutError:
44
+ # logger.error(
45
+ # f"Timed out while listing tools for MCP server {self.server_config.server_name} (timeout={tool_settings.mcp_list_tools_timeout}s)."
46
+ # )
47
+ # raise MCPTimeoutError("listing tools", self.server_config.server_name, tool_settings.mcp_list_tools_timeout)
48
+ #
49
+ # def execute_tool(self, tool_name: str, tool_args: dict) -> Tuple[str, bool]:
50
+ # self._check_initialized()
51
+ # try:
52
+ # result = self.loop.run_until_complete(
53
+ # asyncio.wait_for(self.session.call_tool(tool_name, tool_args), timeout=tool_settings.mcp_execute_tool_timeout)
54
+ # )
55
+ #
56
+ # parsed_content = []
57
+ # for content_piece in result.content:
58
+ # if isinstance(content_piece, TextContent):
59
+ # parsed_content.append(content_piece.text)
60
+ # print("parsed_content (text)", parsed_content)
61
+ # else:
62
+ # parsed_content.append(str(content_piece))
63
+ # print("parsed_content (other)", parsed_content)
64
+ #
65
+ # if len(parsed_content) > 0:
66
+ # final_content = " ".join(parsed_content)
67
+ # else:
68
+ # # TODO move hardcoding to constants
69
+ # final_content = "Empty response from tool"
70
+ #
71
+ # return final_content, result.isError
72
+ # except asyncio.TimeoutError:
73
+ # logger.error(
74
+ # f"Timed out while executing tool '{tool_name}' for MCP server {self.server_config.server_name} (timeout={tool_settings.mcp_execute_tool_timeout}s)."
75
+ # )
76
+ # raise MCPTimeoutError(f"executing tool '{tool_name}'", self.server_config.server_name, tool_settings.mcp_execute_tool_timeout)
77
+ #
78
+ # def _check_initialized(self):
79
+ # if not self.initialized:
80
+ # logger.error("MCPClient has not been initialized")
81
+ # raise RuntimeError("MCPClient has not been initialized")
82
+ #
83
+ # def cleanup(self):
84
+ # try:
85
+ # for cleanup_func in self.cleanup_funcs:
86
+ # cleanup_func()
87
+ # self.initialized = False
88
+ # if not self.loop.is_closed():
89
+ # self.loop.close()
90
+ # except Exception as e:
91
+ # logger.warning(e)
92
+ # finally:
93
+ # logger.info("Cleaned up MCP clients on shutdown.")
94
+ #
95
+ #
96
+ # class BaseAsyncMCPClient:
97
+ # def __init__(self, server_config: BaseServerConfig):
98
+ # self.server_config = server_config
99
+ # self.session: Optional[ClientSession] = None
100
+ # self.stdio = None
101
+ # self.write = None
102
+ # self.initialized = False
103
+ # self.cleanup_funcs = []
104
+ #
105
+ # async def connect_to_server(self):
106
+ #
107
+ # success = await self._initialize_connection(self.server_config, timeout=tool_settings.mcp_connect_to_server_timeout)
108
+ #
109
+ # if success:
110
+ # self.initialized = True
111
+ # else:
112
+ # raise RuntimeError(
113
+ # f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}"
114
+ # )
115
+ #
116
+ # async def list_tools(self) -> List[MCPTool]:
117
+ # self._check_initialized()
118
+ # response = await self.session.list_tools()
119
+ # return response.tools
120
+ #
121
+ # async def execute_tool(self, tool_name: str, tool_args: dict) -> Tuple[str, bool]:
122
+ # self._check_initialized()
123
+ # result = await self.session.call_tool(tool_name, tool_args)
124
+ #
125
+ # parsed_content = []
126
+ # for content_piece in result.content:
127
+ # if isinstance(content_piece, TextContent):
128
+ # parsed_content.append(content_piece.text)
129
+ # else:
130
+ # parsed_content.append(str(content_piece))
131
+ #
132
+ # if len(parsed_content) > 0:
133
+ # final_content = " ".join(parsed_content)
134
+ # else:
135
+ # # TODO move hardcoding to constants
136
+ # final_content = "Empty response from tool"
137
+ #
138
+ # return final_content, result.isError
139
+ #
140
+ # def _check_initialized(self):
141
+ # if not self.initialized:
142
+ # logger.error("MCPClient has not been initialized")
143
+ # raise RuntimeError("MCPClient has not been initialized")
144
+ #
145
+ # async def cleanup(self):
146
+ # try:
147
+ # for cleanup_func in self.cleanup_funcs:
148
+ # cleanup_func()
149
+ # self.initialized = False
150
+ # if not self.loop.is_closed():
151
+ # self.loop.close()
152
+ # except Exception as e:
153
+ # logger.warning(e)
154
+ # finally:
155
+ # logger.info("Cleaned up MCP clients on shutdown.")
156
+ #
@@ -1,33 +1,51 @@
1
- import asyncio
1
+ # import asyncio
2
+ #
3
+ # from mcp import ClientSession
4
+ # from mcp.client.sse import sse_client
5
+ #
6
+ # from letta.functions.mcp_client.base_client import BaseAsyncMCPClient, BaseMCPClient
7
+ # from letta.functions.mcp_client.types import SSEServerConfig
8
+ # from letta.log import get_logger
9
+ #
10
+ ## see: https://modelcontextprotocol.io/quickstart/user
11
+ #
12
+ # logger = get_logger(__name__)
2
13
 
3
- from mcp import ClientSession
4
- from mcp.client.sse import sse_client
5
14
 
6
- from letta.functions.mcp_client.base_client import BaseMCPClient
7
- from letta.functions.mcp_client.types import SSEServerConfig
8
- from letta.log import get_logger
9
-
10
- # see: https://modelcontextprotocol.io/quickstart/user
11
- MCP_CONFIG_TOPLEVEL_KEY = "mcpServers"
12
-
13
- logger = get_logger(__name__)
14
-
15
-
16
- class SSEMCPClient(BaseMCPClient):
17
- def _initialize_connection(self, server_config: SSEServerConfig, timeout: float) -> bool:
18
- try:
19
- sse_cm = sse_client(url=server_config.server_url)
20
- sse_transport = self.loop.run_until_complete(asyncio.wait_for(sse_cm.__aenter__(), timeout=timeout))
21
- self.stdio, self.write = sse_transport
22
- self.cleanup_funcs.append(lambda: self.loop.run_until_complete(sse_cm.__aexit__(None, None, None)))
23
-
24
- session_cm = ClientSession(self.stdio, self.write)
25
- self.session = self.loop.run_until_complete(asyncio.wait_for(session_cm.__aenter__(), timeout=timeout))
26
- self.cleanup_funcs.append(lambda: self.loop.run_until_complete(session_cm.__aexit__(None, None, None)))
27
- return True
28
- except asyncio.TimeoutError:
29
- logger.error(f"Timed out while establishing SSE connection (timeout={timeout}s).")
30
- return False
31
- except Exception:
32
- logger.exception("Exception occurred while initializing SSE client session.")
33
- return False
15
+ # class SSEMCPClient(BaseMCPClient):
16
+ # def _initialize_connection(self, server_config: SSEServerConfig, timeout: float) -> bool:
17
+ # try:
18
+ # sse_cm = sse_client(url=server_config.server_url)
19
+ # sse_transport = self.loop.run_until_complete(asyncio.wait_for(sse_cm.__aenter__(), timeout=timeout))
20
+ # self.stdio, self.write = sse_transport
21
+ # self.cleanup_funcs.append(lambda: self.loop.run_until_complete(sse_cm.__aexit__(None, None, None)))
22
+ #
23
+ # session_cm = ClientSession(self.stdio, self.write)
24
+ # self.session = self.loop.run_until_complete(asyncio.wait_for(session_cm.__aenter__(), timeout=timeout))
25
+ # self.cleanup_funcs.append(lambda: self.loop.run_until_complete(session_cm.__aexit__(None, None, None)))
26
+ # return True
27
+ # except asyncio.TimeoutError:
28
+ # logger.error(f"Timed out while establishing SSE connection (timeout={timeout}s).")
29
+ # return False
30
+ # except Exception:
31
+ # logger.exception("Exception occurred while initializing SSE client session.")
32
+ # return False
33
+ #
34
+ #
35
+ # class AsyncSSEMCPClient(BaseAsyncMCPClient):
36
+ #
37
+ # async def _initialize_connection(self, server_config: SSEServerConfig, timeout: float) -> bool:
38
+ # try:
39
+ # sse_cm = sse_client(url=server_config.server_url)
40
+ # sse_transport = await sse_cm.__aenter__()
41
+ # self.stdio, self.write = sse_transport
42
+ # self.cleanup_funcs.append(lambda: sse_cm.__aexit__(None, None, None))
43
+ #
44
+ # session_cm = ClientSession(self.stdio, self.write)
45
+ # self.session = await session_cm.__aenter__()
46
+ # self.cleanup_funcs.append(lambda: session_cm.__aexit__(None, None, None))
47
+ # return True
48
+ # except Exception:
49
+ # logger.exception("Exception occurred while initializing SSE client session.")
50
+ # return False
51
+ #