2323
2424
2525class StatelessTemporalMCPServer (MCPServer ):
26+ """A stateless MCP server implementation for Temporal workflows.
27+
28+ This class wraps an MCP server to make it stateless by executing each MCP operation
29+ as a separate Temporal activity. Each operation (list_tools, call_tool, etc.) will
30+ connect to the underlying server, execute the operation, and then clean up the connection.
31+
32+ This approach is suitable for simple use cases where connection overhead is acceptable
33+ and you don't need to maintain state between operations.
34+ """
35+
2636 def __init__ (
2737 self , server : Union [MCPServer , str ], config : Optional [ActivityConfig ] = None
2838 ):
39+ """Initialize the stateless temporal MCP server.
40+
41+ Args:
42+ server: Either an MCPServer instance or a string name for the server.
43+ config: Optional activity configuration for Temporal activities. Defaults to
44+ 1-minute start-to-close timeout if not provided.
45+ """
2946 self .server = server if isinstance (server , MCPServer ) else None
3047 self ._name = (server if isinstance (server , str ) else server .name ) + "-stateless"
3148 self .config = config or ActivityConfig (
@@ -35,19 +52,49 @@ def __init__(
3552
3653 @property
3754 def name (self ) -> str :
55+ """Get the server name with '-stateless' suffix.
56+
57+ Returns:
58+ The server name with '-stateless' appended.
59+ """
3860 return self ._name
3961
4062 async def connect (self ) -> None :
63+ """Connect to the MCP server.
64+
65+ For stateless servers, this is a no-op since connections are made
66+ on a per-operation basis.
67+ """
4168 pass
4269
4370 async def cleanup (self ) -> None :
71+ """Clean up the MCP server connection.
72+
73+ For stateless servers, this is a no-op since connections are cleaned
74+ up after each operation.
75+ """
4476 pass
4577
4678 async def list_tools (
4779 self ,
4880 run_context : Optional [RunContextWrapper [Any ]] = None ,
4981 agent : Optional [AgentBase ] = None ,
5082 ) -> list [MCPTool ]:
83+ """List available tools from the MCP server.
84+
85+ This method executes a Temporal activity to connect to the MCP server,
86+ retrieve the list of available tools, and clean up the connection.
87+
88+ Args:
89+ run_context: Optional run context wrapper (unused in stateless mode).
90+ agent: Optional agent base (unused in stateless mode).
91+
92+ Returns:
93+ A list of available MCP tools.
94+
95+ Raises:
96+ ActivityError: If the underlying Temporal activity fails.
97+ """
5198 return await workflow .execute_activity (
5299 self .name + "-list-tools" ,
53100 args = [],
@@ -58,6 +105,21 @@ async def list_tools(
58105 async def call_tool (
59106 self , tool_name : str , arguments : Optional [dict [str , Any ]]
60107 ) -> CallToolResult :
108+ """Call a specific tool on the MCP server.
109+
110+ This method executes a Temporal activity to connect to the MCP server,
111+ call the specified tool with the given arguments, and clean up the connection.
112+
113+ Args:
114+ tool_name: The name of the tool to call.
115+ arguments: Optional dictionary of arguments to pass to the tool.
116+
117+ Returns:
118+ The result of the tool call.
119+
120+ Raises:
121+ ActivityError: If the underlying Temporal activity fails.
122+ """
61123 return await workflow .execute_activity (
62124 self .name + "-call-tool" ,
63125 args = [tool_name , arguments ],
@@ -66,6 +128,17 @@ async def call_tool(
66128 )
67129
68130 async def list_prompts (self ) -> ListPromptsResult :
131+ """List available prompts from the MCP server.
132+
133+ This method executes a Temporal activity to connect to the MCP server,
134+ retrieve the list of available prompts, and clean up the connection.
135+
136+ Returns:
137+ A list of available prompts.
138+
139+ Raises:
140+ ActivityError: If the underlying Temporal activity fails.
141+ """
69142 return await workflow .execute_activity (
70143 self .name + "-list-prompts" ,
71144 args = [],
@@ -76,6 +149,21 @@ async def list_prompts(self) -> ListPromptsResult:
76149 async def get_prompt (
77150 self , name : str , arguments : Optional [dict [str , Any ]] = None
78151 ) -> GetPromptResult :
152+ """Get a specific prompt from the MCP server.
153+
154+ This method executes a Temporal activity to connect to the MCP server,
155+ retrieve the specified prompt with optional arguments, and clean up the connection.
156+
157+ Args:
158+ name: The name of the prompt to retrieve.
159+ arguments: Optional dictionary of arguments for the prompt.
160+
161+ Returns:
162+ The prompt result.
163+
164+ Raises:
165+ ActivityError: If the underlying Temporal activity fails.
166+ """
79167 return await workflow .execute_activity (
80168 self .name + "-get-prompt" ,
81169 args = [name , arguments ],
@@ -84,6 +172,17 @@ async def get_prompt(
84172 )
85173
86174 def get_activities (self ) -> Sequence [Callable ]:
175+ """Get the Temporal activities for this MCP server.
176+
177+ Creates and returns the Temporal activity functions that handle MCP operations.
178+ Each activity manages its own connection lifecycle (connect -> operate -> cleanup).
179+
180+ Returns:
181+ A sequence of Temporal activity functions.
182+
183+ Raises:
184+ ValueError: If no MCP server instance was provided during initialization.
185+ """
87186 server = self .server
88187 if server is None :
89188 raise ValueError (
@@ -130,12 +229,35 @@ async def get_prompt(
130229
131230
132231class StatefulTemporalMCPServer (MCPServer ):
232+ """A stateful MCP server implementation for Temporal workflows.
233+
234+ This class wraps an MCP server to maintain a persistent connection throughout
235+ the workflow execution. It creates a dedicated worker that stays connected to
236+ the MCP server and processes operations on a dedicated task queue.
237+
238+ This approach is more efficient for workflows that make multiple MCP calls,
239+ as it avoids connection overhead, but requires more resources to maintain
240+ the persistent connection and worker.
241+
242+ The caller will have to handle cases where the dedicated worker fails, as Temporal is
243+ unable to seamlessly recreate any lost state in that case.
244+ """
245+
133246 def __init__ (
134247 self ,
135248 server : Union [MCPServer , str ],
136249 config : Optional [ActivityConfig ] = None ,
137250 connect_config : Optional [ActivityConfig ] = None ,
138251 ):
252+ """Initialize the stateful temporal MCP server.
253+
254+ Args:
255+ server: Either an MCPServer instance or a string name for the server.
256+ config: Optional activity configuration for MCP operation activities.
257+ Defaults to 1-minute start-to-close and 30-second schedule-to-start timeouts.
258+ connect_config: Optional activity configuration for the connection activity.
259+ Defaults to 1-hour start-to-close timeout.
260+ """
139261 self .server = server if isinstance (server , MCPServer ) else None
140262 self ._name = (server if isinstance (server , str ) else server .name ) + "-stateful"
141263 self .config = config or ActivityConfig (
@@ -150,9 +272,20 @@ def __init__(
150272
151273 @property
152274 def name (self ) -> str :
275+ """Get the server name with '-stateful' suffix.
276+
277+ Returns:
278+ The server name with '-stateful' appended.
279+ """
153280 return self ._name
154281
155282 async def connect (self ) -> None :
283+ """Connect to the MCP server and start the dedicated worker.
284+
285+ This method creates a dedicated task queue for this workflow and starts
286+ a long-running activity that maintains the connection and runs a worker
287+ to handle MCP operations.
288+ """
156289 self .config ["task_queue" ] = workflow .info ().workflow_id + "-" + self .name
157290 self ._connect_handle = workflow .start_activity (
158291 self .name + "-connect" ,
@@ -161,21 +294,55 @@ async def connect(self) -> None:
161294 )
162295
163296 async def cleanup (self ) -> None :
297+ """Clean up the MCP server connection.
298+
299+ This method cancels the long-running connection activity, which will
300+ cause the dedicated worker to shut down and the MCP server connection
301+ to be closed.
302+ """
164303 if self ._connect_handle :
165304 self ._connect_handle .cancel ()
166305
167306 async def __aenter__ (self ):
307+ """Async context manager entry point.
308+
309+ Returns:
310+ This server instance after connecting.
311+ """
168312 await self .connect ()
169313 return self
170314
171315 async def __aexit__ (self , exc_type , exc_value , traceback ):
316+ """Async context manager exit point.
317+
318+ Args:
319+ exc_type: Exception type if an exception occurred.
320+ exc_value: Exception value if an exception occurred.
321+ traceback: Exception traceback if an exception occurred.
322+ """
172323 await self .cleanup ()
173324
174325 async def list_tools (
175326 self ,
176327 run_context : Optional [RunContextWrapper [Any ]] = None ,
177328 agent : Optional [AgentBase ] = None ,
178329 ) -> list [MCPTool ]:
330+ """List available tools from the MCP server.
331+
332+ This method executes a Temporal activity on the dedicated task queue
333+ to retrieve the list of available tools from the persistent MCP connection.
334+
335+ Args:
336+ run_context: Optional run context wrapper (unused in stateful mode).
337+ agent: Optional agent base (unused in stateful mode).
338+
339+ Returns:
340+ A list of available MCP tools.
341+
342+ Raises:
343+ ApplicationError: If the MCP worker fails to schedule or heartbeat.
344+ ActivityError: If the underlying Temporal activity fails.
345+ """
179346 try :
180347 logger .info ("Executing list-tools: %s" , self .config )
181348 return await workflow .execute_activity (
@@ -208,6 +375,21 @@ async def list_tools(
208375 async def call_tool (
209376 self , tool_name : str , arguments : Optional [dict [str , Any ]]
210377 ) -> CallToolResult :
378+ """Call a specific tool on the MCP server.
379+
380+ This method executes a Temporal activity on the dedicated task queue
381+ to call the specified tool using the persistent MCP connection.
382+
383+ Args:
384+ tool_name: The name of the tool to call.
385+ arguments: Optional dictionary of arguments to pass to the tool.
386+
387+ Returns:
388+ The result of the tool call.
389+
390+ Raises:
391+ ActivityError: If the underlying Temporal activity fails.
392+ """
211393 return await workflow .execute_activity (
212394 self .name + "-call-tool" ,
213395 args = [tool_name , arguments ],
@@ -216,6 +398,17 @@ async def call_tool(
216398 )
217399
218400 async def list_prompts (self ) -> ListPromptsResult :
401+ """List available prompts from the MCP server.
402+
403+ This method executes a Temporal activity on the dedicated task queue
404+ to retrieve the list of available prompts from the persistent MCP connection.
405+
406+ Returns:
407+ A list of available prompts.
408+
409+ Raises:
410+ ActivityError: If the underlying Temporal activity fails.
411+ """
219412 return await workflow .execute_activity (
220413 self .name + "-list-prompts" ,
221414 args = [],
@@ -226,6 +419,21 @@ async def list_prompts(self) -> ListPromptsResult:
226419 async def get_prompt (
227420 self , name : str , arguments : Optional [dict [str , Any ]] = None
228421 ) -> GetPromptResult :
422+ """Get a specific prompt from the MCP server.
423+
424+ This method executes a Temporal activity on the dedicated task queue
425+ to retrieve the specified prompt using the persistent MCP connection.
426+
427+ Args:
428+ name: The name of the prompt to retrieve.
429+ arguments: Optional dictionary of arguments for the prompt.
430+
431+ Returns:
432+ The prompt result.
433+
434+ Raises:
435+ ActivityError: If the underlying Temporal activity fails.
436+ """
229437 return await workflow .execute_activity (
230438 self .name + "-get-prompt" ,
231439 args = [name , arguments ],
@@ -234,6 +442,18 @@ async def get_prompt(
234442 )
235443
236444 def get_activities (self ) -> Sequence [Callable ]:
445+ """Get the Temporal activities for this stateful MCP server.
446+
447+ Creates and returns the Temporal activity functions that handle MCP operations
448+ and connection management. This includes a long-running connect activity that
449+ maintains the MCP connection and runs a dedicated worker.
450+
451+ Returns:
452+ A sequence containing the connect activity function.
453+
454+ Raises:
455+ ValueError: If no MCP server instance was provided during initialization.
456+ """
237457 server = self .server
238458 if server is None :
239459 raise ValueError (
0 commit comments