Rafael Uzarowski commited on
Commit
3c480b2
Β·
unverified Β·
1 Parent(s): fe5adf9

feat: Add support for 'streamable http transport' mcp servers

Browse files
docs/mcp_setup.md CHANGED
@@ -4,10 +4,11 @@ This guide explains how to configure and utilize external tool providers through
4
 
5
  ## What are MCP Servers?
6
 
7
- MCP servers are external processes or services that expose a set of tools that Agent Zero can use. Agent Zero acts as an MCP *client*, consuming tools made available by these servers. The integration supports two main types of MCP servers:
8
 
9
  1. **Local Stdio Servers**: These are typically local executables that Agent Zero communicates with via standard input/output (stdio).
10
  2. **Remote SSE Servers**: These are servers, often accessible over a network, that Agent Zero communicates with using Server-Sent Events (SSE), usually over HTTP/S.
 
11
 
12
  ## How Agent Zero Consumes MCP Tools
13
 
@@ -65,6 +66,7 @@ Here are templates for configuring individual servers within the `mcp_servers` J
65
  {
66
  "name": "My Local Tool Server",
67
  "description": "Optional: A brief description of this server.",
 
68
  "command": "python", // The executable to run (e.g., python, /path/to/my_tool_server)
69
  "args": ["path/to/your/mcp_stdio_script.py", "--some-arg"], // List of arguments for the command
70
  "env": { // Optional: Environment variables for the command's process
@@ -83,6 +85,7 @@ Here are templates for configuring individual servers within the `mcp_servers` J
83
  {
84
  "name": "My Remote API Tools",
85
  "description": "Optional: Description of the remote SSE server.",
 
86
  "url": "https://api.example.com/mcp-sse-endpoint", // The full URL for the SSE endpoint of the MCP server.
87
  "headers": { // Optional: Any HTTP headers required for the connection.
88
  "Authorization": "Bearer YOUR_API_KEY_OR_TOKEN",
@@ -94,6 +97,24 @@ Here are templates for configuring individual servers within the `mcp_servers` J
94
  }
95
  ```
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  **Example `mcp_servers` value in `tmp/settings.json`:**
98
 
99
  ```json
@@ -107,8 +128,9 @@ Here are templates for configuring individual servers within the `mcp_servers` J
107
  **Key Configuration Fields:**
108
 
109
  * `"name"`: A unique name for the server. This name will be used to prefix the tools provided by this server (e.g., `my_server_name.tool_name`). The name is normalized internally (converted to lowercase, spaces and hyphens replaced with underscores).
 
110
  * `"disabled"`: A boolean (`true` or `false`). If `true`, Agent Zero will ignore this server configuration.
111
- * `"url"`: **Required for Remote SSE Servers.** The endpoint URL.
112
  * `"command"`: **Required for Local Stdio Servers.** The executable command.
113
  * `"args"`: Optional list of arguments for local Stdio servers.
114
  * Other fields are specific to the server type and mostly optional with defaults.
@@ -121,4 +143,4 @@ Once configured, successfully installed (if applicable, e.g., for `npx` based se
121
  * **Agent Interaction**: You can instruct the agent to use these tools. For example: "Agent, use the `sequential_thinking.run_chain` tool with the following input..." The agent's LLM will then formulate the appropriate JSON request.
122
  * **Execution Flow**: Agent Zero's `process_tools` method (with logic in `python/helpers/mcp_handler.py`) prioritizes looking up the tool name in the `MCPConfig`. If found, the execution is delegated to the corresponding MCP server. If not found as an MCP tool, it then attempts to find a local/built-in tool with that name.
123
 
124
- This setup provides a flexible way to extend Agent Zero's capabilities by integrating with various external tool providers without modifying its core codebase.
 
4
 
5
  ## What are MCP Servers?
6
 
7
+ MCP servers are external processes or services that expose a set of tools that Agent Zero can use. Agent Zero acts as an MCP *client*, consuming tools made available by these servers. The integration supports three main types of MCP servers:
8
 
9
  1. **Local Stdio Servers**: These are typically local executables that Agent Zero communicates with via standard input/output (stdio).
10
  2. **Remote SSE Servers**: These are servers, often accessible over a network, that Agent Zero communicates with using Server-Sent Events (SSE), usually over HTTP/S.
11
+ 3. **Remote Streaming HTTP Servers**: These are servers that use the streamable HTTP transport protocol for MCP communication, providing an alternative to SSE for network-based MCP servers.
12
 
13
  ## How Agent Zero Consumes MCP Tools
14
 
 
66
  {
67
  "name": "My Local Tool Server",
68
  "description": "Optional: A brief description of this server.",
69
+ "type": "stdio", // Optional: Explicitly specify server type. Can be "stdio", "sse", or streaming HTTP variants ("http-stream", "streaming-http", "streamable-http", "http-streaming"). Auto-detected if omitted.
70
  "command": "python", // The executable to run (e.g., python, /path/to/my_tool_server)
71
  "args": ["path/to/your/mcp_stdio_script.py", "--some-arg"], // List of arguments for the command
72
  "env": { // Optional: Environment variables for the command's process
 
85
  {
86
  "name": "My Remote API Tools",
87
  "description": "Optional: Description of the remote SSE server.",
88
+ "type": "sse", // Optional: Explicitly specify server type. Can be "stdio", "sse", or streaming HTTP variants ("http-stream", "streaming-http", "streamable-http", "http-streaming"). Auto-detected if omitted.
89
  "url": "https://api.example.com/mcp-sse-endpoint", // The full URL for the SSE endpoint of the MCP server.
90
  "headers": { // Optional: Any HTTP headers required for the connection.
91
  "Authorization": "Bearer YOUR_API_KEY_OR_TOKEN",
 
97
  }
98
  ```
99
 
100
+ **3. Remote Streaming HTTP Server**
101
+
102
+ ```json
103
+ {
104
+ "name": "My Streaming HTTP Tools",
105
+ "description": "Optional: Description of the remote streaming HTTP server.",
106
+ "type": "streaming-http", // Optional: Explicitly specify server type. Can be "stdio", "sse", or streaming HTTP variants ("http-stream", "streaming-http", "streamable-http", "http-streaming"). Auto-detected if omitted.
107
+ "url": "https://api.example.com/mcp-http-endpoint", // The full URL for the streaming HTTP endpoint of the MCP server.
108
+ "headers": { // Optional: Any HTTP headers required for the connection.
109
+ "Authorization": "Bearer YOUR_API_KEY_OR_TOKEN",
110
+ "X-Custom-Header": "some_value"
111
+ },
112
+ "timeout": 5.0, // Optional: Connection timeout in seconds (default: 5.0).
113
+ "sse_read_timeout": 300.0, // Optional: Read timeout for the SSE and streaming HTTP streams in seconds (default: 300.0, i.e., 5 minutes).
114
+ "disabled": false
115
+ }
116
+ ```
117
+
118
  **Example `mcp_servers` value in `tmp/settings.json`:**
119
 
120
  ```json
 
128
  **Key Configuration Fields:**
129
 
130
  * `"name"`: A unique name for the server. This name will be used to prefix the tools provided by this server (e.g., `my_server_name.tool_name`). The name is normalized internally (converted to lowercase, spaces and hyphens replaced with underscores).
131
+ * `"type"`: Optional explicit server type specification. Can be `"stdio"`, `"sse"`, or streaming HTTP variants (`"http-stream"`, `"streaming-http"`, `"streamable-http"`, `"http-streaming"`). If omitted, the type is auto-detected based on the presence of `"command"` (stdio) or `"url"` (defaults to sse for backward compatibility).
132
  * `"disabled"`: A boolean (`true` or `false`). If `true`, Agent Zero will ignore this server configuration.
133
+ * `"url"`: **Required for Remote SSE and Streaming HTTP Servers.** The endpoint URL.
134
  * `"command"`: **Required for Local Stdio Servers.** The executable command.
135
  * `"args"`: Optional list of arguments for local Stdio servers.
136
  * Other fields are specific to the server type and mostly optional with defaults.
 
143
  * **Agent Interaction**: You can instruct the agent to use these tools. For example: "Agent, use the `sequential_thinking.run_chain` tool with the following input..." The agent's LLM will then formulate the appropriate JSON request.
144
  * **Execution Flow**: Agent Zero's `process_tools` method (with logic in `python/helpers/mcp_handler.py`) prioritizes looking up the tool name in the `MCPConfig`. If found, the execution is delegated to the corresponding MCP server. If not found as an MCP tool, it then attempts to find a local/built-in tool with that name.
145
 
146
+ This setup provides a flexible way to extend Agent Zero's capabilities by integrating with various external tool providers without modifying its core codebase.
python/helpers/mcp_handler.py CHANGED
@@ -24,15 +24,12 @@ import json
24
  from python.helpers import errors
25
  from python.helpers import settings
26
 
27
- import os
28
-
29
- # print(f"DEBUG: Listing /opt/venv/lib/python3.11/site-packages/ before mcp import: {os.listdir('/opt/venv/lib/python3.11/site-packages/')}") # This line caused FileNotFoundError, **FOR CUDA CHANGE TO '3.12'**
30
-
31
  from mcp import ClientSession, StdioServerParameters
32
  from mcp.client.stdio import stdio_client
33
  from mcp.client.sse import sse_client
 
34
  from mcp.shared.message import SessionMessage
35
- from mcp.types import CallToolResult, ListToolsResult, JSONRPCMessage
36
  from anyio.streams.memory import (
37
  MemoryObjectReceiveStream,
38
  MemoryObjectSendStream,
@@ -40,7 +37,6 @@ from anyio.streams.memory import (
40
 
41
  from pydantic import BaseModel, Field, Discriminator, Tag, PrivateAttr
42
  from python.helpers import dirty_json
43
- from python.helpers.dirty_json import DirtyJson
44
  from python.helpers.print_style import PrintStyle
45
  from python.helpers.tool import Tool, Response
46
 
@@ -55,6 +51,33 @@ def normalize_name(name: str) -> str:
55
  return name
56
 
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def initialize_mcp(mcp_servers_config: str):
59
  if not MCPConfig.get_instance().is_initialized():
60
  try:
@@ -67,11 +90,10 @@ def initialize_mcp(mcp_servers_config: str):
67
  content=f"Failed to update MCP settings: {e}",
68
  temp=False,
69
  )
70
-
71
  PrintStyle(
72
  background_color="black", font_color="red", padding=True
73
  ).print(f"Failed to update MCP settings: {e}")
74
-
75
 
76
 
77
  class MCPTool(Tool):
@@ -145,9 +167,9 @@ class MCPTool(Tool):
145
  content = self.agent.last_user_message.content
146
  if isinstance(content, dict):
147
  # Attempt to get a 'message' field, otherwise stringify the dict
148
- user_message_text = content.get(
149
  "message", json.dumps(content, indent=2)
150
- )
151
  elif isinstance(content, str):
152
  user_message_text = content
153
  else:
@@ -164,27 +186,6 @@ class MCPTool(Tool):
164
  user_message_text[:max_user_context_len] + "... (truncated)"
165
  )
166
 
167
- # commented out for now, output should be unified between tools and MCPs
168
-
169
- # contextual_block = f"""
170
- # \n--- End of Results for MCP Tool: {self.name} ---
171
-
172
- # **Original Tool Call Details:**
173
- # * **Tool:** `{self.name}`
174
- # * **Arguments Given:**
175
- # ```json
176
- # {json.dumps(self.args, indent=2)}
177
- # ```
178
-
179
- # **Related User Request Context:**
180
- # {user_message_text}
181
-
182
- # **Next Steps Reminder for {self.name}:**
183
- # If this action is part of an ongoing sequence, consider the next step with this tool or another appropriate tool. If the sequence is complete or this was a one-off action, analyze the final output and report to the user or proceed with the overall plan.
184
- # """
185
-
186
- # final_text_for_agent = raw_tool_response + contextual_block
187
-
188
  final_text_for_agent = raw_tool_response
189
 
190
  self.agent.hist_add_tool_result(self.name, final_text_for_agent)
@@ -210,6 +211,7 @@ class MCPTool(Tool):
210
  class MCPServerRemote(BaseModel):
211
  name: str = Field(default_factory=str)
212
  description: Optional[str] = Field(default="Remote SSE Server")
 
213
  url: str = Field(default_factory=str)
214
  headers: dict[str, Any] | None = Field(default_factory=dict[str, Any])
215
  init_timeout: int = Field(default=0)
@@ -256,6 +258,7 @@ class MCPServerRemote(BaseModel):
256
  if key in [
257
  "name",
258
  "description",
 
259
  "url",
260
  "serverUrl",
261
  "headers",
@@ -280,6 +283,7 @@ class MCPServerRemote(BaseModel):
280
  class MCPServerLocal(BaseModel):
281
  name: str = Field(default_factory=str)
282
  description: Optional[str] = Field(default="Local StdIO Server")
 
283
  command: str = Field(default_factory=str)
284
  args: list[str] = Field(default_factory=list)
285
  env: dict[str, str] | None = Field(default_factory=dict[str, str])
@@ -331,6 +335,7 @@ class MCPServerLocal(BaseModel):
331
  if key in [
332
  "name",
333
  "description",
 
334
  "command",
335
  "args",
336
  "env",
@@ -356,7 +361,7 @@ MCPServer = Annotated[
356
  Annotated[MCPServerRemote, Tag("MCPServerRemote")],
357
  Annotated[MCPServerLocal, Tag("MCPServerLocal")],
358
  ],
359
- Discriminator(lambda v: "MCPServerRemote" if "url" in v else "MCPServerLocal"),
360
  ]
361
 
362
 
@@ -370,9 +375,9 @@ class MCPConfig(BaseModel):
370
  @classmethod
371
  def get_instance(cls) -> "MCPConfig":
372
  # with cls.__lock:
373
- if cls.__instance is None:
374
- cls.__instance = cls(servers_list=[])
375
- return cls.__instance
376
 
377
  @classmethod
378
  def wait_for_lock(cls):
@@ -660,7 +665,7 @@ class MCPConfig(BaseModel):
660
  if server.name == server_name:
661
  try:
662
  tools = server.get_tools()
663
- except Exception as e:
664
  tools = []
665
  return {
666
  "name": server.name,
@@ -718,40 +723,9 @@ class MCPConfig(BaseModel):
718
  # f"#### Arguments:\n"
719
  )
720
 
721
- tool_args = ""
722
  input_schema = (
723
  json.dumps(tool["input_schema"]) if tool["input_schema"] else ""
724
  )
725
- # properties: dict[str, Any] = tool["input_schema"]["properties"]
726
- # for key, value in properties.items():
727
- # optional = False
728
- # examples = ""
729
- # description = ""
730
- # type = ""
731
- # if "anyOf" in value:
732
- # for nested_value in value["anyOf"]:
733
- # if "type" in nested_value and nested_value["type"] != "null":
734
- # optional = True
735
- # value = nested_value
736
- # break
737
- # tool_args += f" \"{key}\": \"...\",\n"
738
- # if "examples" in value:
739
- # examples = f"(examples: {value['examples']})"
740
- # if "description" in value:
741
- # description = f": {value['description']}"
742
- # if "type" in value:
743
- # if optional:
744
- # type = f"{value['type']}, optional"
745
- # else:
746
- # type = f"{value['type']}"
747
- # else:
748
- # if optional:
749
- # type = "string, optional"
750
- # else:
751
- # type = "string"
752
- # prompt += (
753
- # f" * {key} ({type}){description} {examples}\n"
754
- # )
755
 
756
  prompt += f"#### Input schema for tool_args:\n{input_schema}\n"
757
 
@@ -1047,6 +1021,11 @@ class MCPClientLocal(MCPClientBase):
1047
 
1048
  class MCPClientRemote(MCPClientBase):
1049
 
 
 
 
 
 
1050
  async def _create_stdio_transport(
1051
  self, current_exit_stack: AsyncExitStack
1052
  ) -> tuple[
@@ -1056,12 +1035,43 @@ class MCPClientRemote(MCPClientBase):
1056
  """Connect to an MCP server, init client and save stdio/write streams"""
1057
  server: MCPServerRemote = cast(MCPServerRemote, self.server)
1058
  set = settings.get_settings()
1059
- stdio_transport = await current_exit_stack.enter_async_context(
1060
- sse_client(
1061
- url=server.url,
1062
- headers=server.headers,
1063
- timeout=server.init_timeout or set["mcp_client_init_timeout"],
1064
- sse_read_timeout=server.tool_timeout or set["mcp_client_tool_timeout"],
 
 
 
 
 
 
 
 
 
1065
  )
1066
- )
1067
- return stdio_transport
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  from python.helpers import errors
25
  from python.helpers import settings
26
 
 
 
 
 
27
  from mcp import ClientSession, StdioServerParameters
28
  from mcp.client.stdio import stdio_client
29
  from mcp.client.sse import sse_client
30
+ from mcp.client.streamable_http import streamablehttp_client
31
  from mcp.shared.message import SessionMessage
32
+ from mcp.types import CallToolResult, ListToolsResult
33
  from anyio.streams.memory import (
34
  MemoryObjectReceiveStream,
35
  MemoryObjectSendStream,
 
37
 
38
  from pydantic import BaseModel, Field, Discriminator, Tag, PrivateAttr
39
  from python.helpers import dirty_json
 
40
  from python.helpers.print_style import PrintStyle
41
  from python.helpers.tool import Tool, Response
42
 
 
51
  return name
52
 
53
 
54
+ def _determine_server_type(config_dict: dict) -> str:
55
+ """Determine the server type based on configuration, with backward compatibility."""
56
+ # First check if type is explicitly specified
57
+ if "type" in config_dict:
58
+ server_type = config_dict["type"].lower()
59
+ if server_type in ["sse", "http-stream", "streaming-http", "streamable-http", "http-streaming"]:
60
+ return "MCPServerRemote"
61
+ elif server_type == "stdio":
62
+ return "MCPServerLocal"
63
+ # For future types, we could add more cases here
64
+ else:
65
+ # For unknown types, fall back to URL-based detection
66
+ # This allows for graceful handling of new types
67
+ pass
68
+
69
+ # Backward compatibility: if no type specified, use URL-based detection
70
+ if "url" in config_dict or "serverUrl" in config_dict:
71
+ return "MCPServerRemote"
72
+ else:
73
+ return "MCPServerLocal"
74
+
75
+
76
+ def _is_streaming_http_type(server_type: str) -> bool:
77
+ """Check if the server type is a streaming HTTP variant."""
78
+ return server_type.lower() in ["http-stream", "streaming-http", "streamable-http", "http-streaming"]
79
+
80
+
81
  def initialize_mcp(mcp_servers_config: str):
82
  if not MCPConfig.get_instance().is_initialized():
83
  try:
 
90
  content=f"Failed to update MCP settings: {e}",
91
  temp=False,
92
  )
93
+
94
  PrintStyle(
95
  background_color="black", font_color="red", padding=True
96
  ).print(f"Failed to update MCP settings: {e}")
 
97
 
98
 
99
  class MCPTool(Tool):
 
167
  content = self.agent.last_user_message.content
168
  if isinstance(content, dict):
169
  # Attempt to get a 'message' field, otherwise stringify the dict
170
+ user_message_text = str(content.get(
171
  "message", json.dumps(content, indent=2)
172
+ ))
173
  elif isinstance(content, str):
174
  user_message_text = content
175
  else:
 
186
  user_message_text[:max_user_context_len] + "... (truncated)"
187
  )
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  final_text_for_agent = raw_tool_response
190
 
191
  self.agent.hist_add_tool_result(self.name, final_text_for_agent)
 
211
  class MCPServerRemote(BaseModel):
212
  name: str = Field(default_factory=str)
213
  description: Optional[str] = Field(default="Remote SSE Server")
214
+ type: str = Field(default="sse", description="Server connection type")
215
  url: str = Field(default_factory=str)
216
  headers: dict[str, Any] | None = Field(default_factory=dict[str, Any])
217
  init_timeout: int = Field(default=0)
 
258
  if key in [
259
  "name",
260
  "description",
261
+ "type",
262
  "url",
263
  "serverUrl",
264
  "headers",
 
283
  class MCPServerLocal(BaseModel):
284
  name: str = Field(default_factory=str)
285
  description: Optional[str] = Field(default="Local StdIO Server")
286
+ type: str = Field(default="stdio", description="Server connection type")
287
  command: str = Field(default_factory=str)
288
  args: list[str] = Field(default_factory=list)
289
  env: dict[str, str] | None = Field(default_factory=dict[str, str])
 
335
  if key in [
336
  "name",
337
  "description",
338
+ "type",
339
  "command",
340
  "args",
341
  "env",
 
361
  Annotated[MCPServerRemote, Tag("MCPServerRemote")],
362
  Annotated[MCPServerLocal, Tag("MCPServerLocal")],
363
  ],
364
+ Discriminator(_determine_server_type),
365
  ]
366
 
367
 
 
375
  @classmethod
376
  def get_instance(cls) -> "MCPConfig":
377
  # with cls.__lock:
378
+ if cls.__instance is None:
379
+ cls.__instance = cls(servers_list=[])
380
+ return cls.__instance
381
 
382
  @classmethod
383
  def wait_for_lock(cls):
 
665
  if server.name == server_name:
666
  try:
667
  tools = server.get_tools()
668
+ except Exception:
669
  tools = []
670
  return {
671
  "name": server.name,
 
723
  # f"#### Arguments:\n"
724
  )
725
 
 
726
  input_schema = (
727
  json.dumps(tool["input_schema"]) if tool["input_schema"] else ""
728
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
 
730
  prompt += f"#### Input schema for tool_args:\n{input_schema}\n"
731
 
 
1021
 
1022
  class MCPClientRemote(MCPClientBase):
1023
 
1024
+ def __init__(self, server: Union[MCPServerLocal, MCPServerRemote]):
1025
+ super().__init__(server)
1026
+ self.session_id: Optional[str] = None # Track session ID for streaming HTTP clients
1027
+ self.session_id_callback: Optional[Callable[[], Optional[str]]] = None
1028
+
1029
  async def _create_stdio_transport(
1030
  self, current_exit_stack: AsyncExitStack
1031
  ) -> tuple[
 
1035
  """Connect to an MCP server, init client and save stdio/write streams"""
1036
  server: MCPServerRemote = cast(MCPServerRemote, self.server)
1037
  set = settings.get_settings()
1038
+
1039
+ # Use lower timeouts for faster failure detection
1040
+ init_timeout = min(server.init_timeout or set["mcp_client_init_timeout"], 5)
1041
+ tool_timeout = min(server.tool_timeout or set["mcp_client_tool_timeout"], 10)
1042
+
1043
+ # Check if this is a streaming HTTP type
1044
+ if _is_streaming_http_type(server.type):
1045
+ # Use streamable HTTP client
1046
+ transport_result = await current_exit_stack.enter_async_context(
1047
+ streamablehttp_client(
1048
+ url=server.url,
1049
+ headers=server.headers,
1050
+ timeout=timedelta(seconds=init_timeout),
1051
+ sse_read_timeout=timedelta(seconds=tool_timeout),
1052
+ )
1053
  )
1054
+ # streamablehttp_client returns (read_stream, write_stream, get_session_id_callback)
1055
+ read_stream, write_stream, get_session_id_callback = transport_result
1056
+
1057
+ # Store session ID callback for potential future use
1058
+ self.session_id_callback = get_session_id_callback
1059
+
1060
+ return read_stream, write_stream
1061
+ else:
1062
+ # Use traditional SSE client (default behavior)
1063
+ stdio_transport = await current_exit_stack.enter_async_context(
1064
+ sse_client(
1065
+ url=server.url,
1066
+ headers=server.headers,
1067
+ timeout=init_timeout,
1068
+ sse_read_timeout=tool_timeout,
1069
+ )
1070
+ )
1071
+ return stdio_transport
1072
+
1073
+ def get_session_id(self) -> Optional[str]:
1074
+ """Get the current session ID if available (for streaming HTTP clients)."""
1075
+ if self.session_id_callback is not None:
1076
+ return self.session_id_callback()
1077
+ return None
tests/mcp/stream_http_mcp_server.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hello World MCP Server using FastMCP with Streamable HTTP Protocol
4
+
5
+ This is a simple example demonstrating how to create an MCP server using
6
+ the FastMCP framework with the streamable-http transport protocol.
7
+
8
+ Features:
9
+ - Hello world tool that greets users
10
+ - Simple resource that provides server information
11
+ - Basic prompt template for greeting
12
+ - Runs using streamable-http transport for better scalability
13
+ """
14
+
15
+ from fastmcp import FastMCP, Context
16
+ import os
17
+ from datetime import datetime
18
+
19
+
20
+ # Create a FastMCP server instance
21
+ mcp: FastMCP = FastMCP(
22
+ "Hello World Server πŸš€",
23
+ dependencies=[] # No special dependencies for this simple example
24
+ )
25
+
26
+
27
+ # ========== TOOLS ==========
28
+
29
+ @mcp.tool()
30
+ def hello_world(name: str = "World") -> str:
31
+ """Say hello to someone with a personalized greeting.
32
+
33
+ Args:
34
+ name: The name of the person to greet (defaults to "World")
35
+
36
+ Returns:
37
+ A friendly greeting message
38
+ """
39
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
40
+ return f"Hello, {name}! πŸ‘‹ Welcome to the FastMCP Hello World Server. Current time: {current_time}"
41
+
42
+
43
+ @mcp.tool()
44
+ def add_numbers(a: float, b: float) -> float:
45
+ """Add two numbers together.
46
+
47
+ Args:
48
+ a: First number
49
+ b: Second number
50
+
51
+ Returns:
52
+ The sum of the two numbers
53
+ """
54
+ result = a + b
55
+ return result
56
+
57
+
58
+ @mcp.tool()
59
+ async def get_server_status(ctx: Context) -> str:
60
+ """Get the current server status and information.
61
+
62
+ Returns:
63
+ Server status information including uptime and capabilities
64
+ """
65
+ # Log that someone is checking server status
66
+ await ctx.info("Server status requested")
67
+
68
+ # Get basic server info
69
+ server_info = {
70
+ "status": "running",
71
+ "protocol": "MCP (Model Context Protocol)",
72
+ "transport": "streamable-http",
73
+ "framework": "FastMCP 2.0",
74
+ "capabilities": ["tools", "resources", "prompts"],
75
+ "timestamp": datetime.now().isoformat()
76
+ }
77
+
78
+ return f"""
79
+ 🟒 Server Status: {server_info['status'].upper()}
80
+
81
+ πŸ“Š Server Information:
82
+ β€’ Protocol: {server_info['protocol']}
83
+ β€’ Transport: {server_info['transport']}
84
+ β€’ Framework: {server_info['framework']}
85
+ β€’ Capabilities: {', '.join(server_info['capabilities'])}
86
+ β€’ Last checked: {server_info['timestamp']}
87
+
88
+ βœ… All systems operational!
89
+ """
90
+
91
+
92
+ # ========== RESOURCES ==========
93
+
94
+ @mcp.resource("info://server")
95
+ def get_server_info() -> str:
96
+ """Static resource providing information about this MCP server."""
97
+ return """
98
+ πŸš€ Hello World MCP Server
99
+
100
+ This is a demonstration MCP server built with FastMCP, showcasing the
101
+ streamable-http transport protocol.
102
+
103
+ Available capabilities:
104
+ β€’ Tools: Interactive functions the LLM can call
105
+ β€’ Resources: Data sources for context
106
+ β€’ Prompts: Reusable message templates
107
+
108
+ Built with FastMCP 2.0 for production-ready MCP applications.
109
+ """
110
+
111
+
112
+ @mcp.resource("greeting://{user_name}")
113
+ def get_personal_greeting(user_name: str) -> str:
114
+ """Dynamic resource template that provides personalized greetings.
115
+
116
+ Args:
117
+ user_name: The name of the user to create a greeting for
118
+
119
+ Returns:
120
+ A personalized greeting message
121
+ """
122
+ greetings = [
123
+ f"Welcome, {user_name}! πŸŽ‰",
124
+ f"Hello there, {user_name}! Great to see you! πŸ‘‹",
125
+ f"Greetings, {user_name}! Hope you're having a wonderful day! β˜€οΈ"
126
+ ]
127
+
128
+ # Select greeting based on name length (simple example)
129
+ greeting_index = len(user_name) % len(greetings)
130
+ return greetings[greeting_index]
131
+
132
+
133
+ # ========== PROMPTS ==========
134
+
135
+ @mcp.prompt()
136
+ def introduction_prompt(user_name: str = "friend") -> str:
137
+ """Generate a friendly introduction prompt.
138
+
139
+ Args:
140
+ user_name: Name of the person to introduce to
141
+
142
+ Returns:
143
+ A prompt for introducing the MCP server capabilities
144
+ """
145
+ return f"""
146
+ Hello {user_name}! πŸ‘‹
147
+
148
+ I'm your Hello World MCP Server, here to demonstrate the power of the Model Context Protocol with FastMCP!
149
+
150
+ Here's what I can help you with:
151
+
152
+ πŸ”§ **Tools I can execute:**
153
+ β€’ hello_world - Give you personalized greetings
154
+ β€’ add_numbers - Perform simple math operations
155
+ β€’ get_server_status - Check my current status
156
+
157
+ πŸ“š **Resources I can provide:**
158
+ β€’ Server information and documentation
159
+ β€’ Personalized greeting messages
160
+
161
+ πŸ’‘ **How to use me:**
162
+ Try asking me to say hello, add some numbers, or check my status!
163
+
164
+ What would you like to do first?
165
+ """
166
+
167
+
168
+ @mcp.prompt()
169
+ def math_prompt(operation: str = "addition") -> str:
170
+ """Create a prompt for helping with math operations.
171
+
172
+ Args:
173
+ operation: The type of math operation to help with
174
+
175
+ Returns:
176
+ A simple prompt for math assistance
177
+ """
178
+ return (f"I need help with {operation}. I'd be happy to help you with {operation}! "
179
+ f"I can add numbers together using my add_numbers tool. "
180
+ f"Just tell me which numbers you'd like me to work with.")
181
+
182
+
183
+ # ========== SERVER LIFECYCLE ==========
184
+
185
+ def main():
186
+ """Main function to run the MCP server."""
187
+ print("πŸš€ Starting Hello World MCP Server with Streamable HTTP...")
188
+ print("πŸ“‘ Transport: streamable-http")
189
+ print("🌐 Framework: FastMCP 2.0")
190
+ print("πŸ”— Protocol: Model Context Protocol (MCP)")
191
+ print()
192
+
193
+ # Get configuration from environment or use defaults
194
+ host = os.getenv("MCP_HOST", "0.0.0.0")
195
+ port = int(os.getenv("MCP_PORT", "8000"))
196
+ path = os.getenv("MCP_PATH", "/mcp")
197
+
198
+ print(f"🏠 Host: {host}")
199
+ print(f"πŸšͺ Port: {port}")
200
+ print(f"πŸ›€οΈ Path: {path}")
201
+ print(f"πŸ“ Full URL: http://{host}:{port}{path}")
202
+ print()
203
+ print("βœ… Server is ready to accept MCP connections!")
204
+ print("πŸ’‘ Use this server with MCP clients that support streamable-http transport")
205
+ print()
206
+
207
+ # Run the server with streamable-http transport
208
+ try:
209
+ mcp.run(
210
+ transport="streamable-http",
211
+ host=host,
212
+ port=port,
213
+ path=path
214
+ )
215
+ except KeyboardInterrupt:
216
+ print("\nπŸ‘‹ Server shutting down gracefully...")
217
+ except Exception as e:
218
+ print(f"❌ Server error: {e}")
219
+ raise
220
+
221
+
222
+ if __name__ == "__main__":
223
+ main()
tests/mcp/stream_http_mcp_server_README.md ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastMCP Hello World Server with Streamable HTTP
2
+
3
+ A comprehensive hello world example demonstrating how to build an MCP (Model Context Protocol) server using the FastMCP framework with streamable-http transport.
4
+
5
+ ## πŸš€ Features
6
+
7
+ This server demonstrates all three core MCP primitives:
8
+
9
+ ### πŸ”§ Tools (LLM-callable functions)
10
+ - **hello_world** - Personalized greetings with timestamps
11
+ - **add_numbers** - Simple math operations
12
+ - **get_server_status** - Server status and information with context logging
13
+
14
+ ### πŸ“š Resources (Data sources)
15
+ - **info://server** - Static server information
16
+ - **greeting://{user_name}** - Dynamic personalized greetings template
17
+
18
+ ### πŸ’‘ Prompts (Reusable templates)
19
+ - **introduction_prompt** - Server capability introduction
20
+ - **math_prompt** - Math assistance template
21
+
22
+ ## πŸ“‹ Prerequisites
23
+
24
+ - Python 3.10+
25
+ - pip or uv package manager
26
+
27
+ ## πŸ› οΈ Installation
28
+
29
+ ### Option 1: Using pip
30
+ ```bash
31
+ # Install dependencies
32
+ pip install -r stream_http_mcp_server_requirements.txt
33
+
34
+ # Or install FastMCP directly
35
+ pip install fastmcp
36
+ ```
37
+
38
+ ### Option 2: Using uv (recommended)
39
+ ```bash
40
+ # Install FastMCP with uv
41
+ uv pip install fastmcp
42
+ ```
43
+
44
+ ## ▢️ Running the Server
45
+
46
+ ### Basic Usage
47
+ ```bash
48
+ # Run with default settings (localhost:8000/mcp)
49
+ python stream_http_mcp_server.py
50
+ ```
51
+
52
+ ### Custom Configuration via Environment Variables
53
+ ```bash
54
+ # Set custom host, port, and path
55
+ export MCP_HOST=0.0.0.0
56
+ export MCP_PORT=3000
57
+ export MCP_PATH=/hello-mcp
58
+
59
+ python stream_http_mcp_server.py
60
+ ```
61
+
62
+ ### Expected Output
63
+ ```
64
+ πŸš€ Starting Hello World MCP Server with Streamable HTTP...
65
+ πŸ“‘ Transport: streamable-http
66
+ 🌐 Framework: FastMCP 2.0
67
+ πŸ”— Protocol: Model Context Protocol (MCP)
68
+
69
+ 🏠 Host: 127.0.0.1
70
+ πŸšͺ Port: 8000
71
+ πŸ›€οΈ Path: /mcp
72
+ πŸ“ Full URL: http://127.0.0.1:8000/mcp
73
+
74
+ βœ… Server is ready to accept MCP connections!
75
+ πŸ’‘ Use this server with MCP clients that support streamable-http transport
76
+ ```
77
+
78
+ ## πŸ§ͺ Testing the Server
79
+
80
+ ### Method 1: Using MCP Inspector (Recommended)
81
+
82
+ 1. **Install MCP Inspector**:
83
+ ```bash
84
+ npm install -g @modelcontextprotocol/inspector
85
+ ```
86
+
87
+ 2. **Run the Inspector**:
88
+ ```bash
89
+ npx @modelcontextprotocol/inspector
90
+ ```
91
+
92
+ 3. **Connect to the Server**:
93
+ - Choose "Streamable HTTP" transport
94
+ - Enter URL: `http://localhost:8000/mcp`
95
+ - Click "Connect"
96
+
97
+ 4. **Test Tools**:
98
+ - Go to the "Tools" tab
99
+ - Try `hello_world` with `{"name": "Alice"}`
100
+ - Try `add_numbers` with `{"a": 5, "b": 3}`
101
+ - Try `get_server_status` (no parameters needed)
102
+
103
+ 5. **Test Resources**:
104
+ - Go to "Resources" tab
105
+ - View `info://server`
106
+ - Try `greeting://YourName`
107
+
108
+ 6. **Test Prompts**:
109
+ - Go to "Prompts" tab
110
+ - Try `introduction_prompt` with `{"user_name": "Developer"}`
111
+ - Try `math_prompt` with `{"operation": "multiplication"}`
112
+
113
+ ### Method 2: Agent Zero Integration
114
+
115
+ Configure Agent Zero to use this server by adding to your MCP servers configuration:
116
+
117
+ ```json
118
+ [
119
+ {
120
+ "name": "hello_world_server",
121
+ "type": "streamable-http",
122
+ "url": "http://localhost:8000/mcp",
123
+ "description": "Hello World FastMCP Server with streamable HTTP"
124
+ }
125
+ ]
126
+ ```
127
+
128
+ ### Method 3: Custom MCP Client
129
+
130
+ Example using the MCP Python SDK:
131
+
132
+ ```python
133
+ from mcp.client.streamable_http import streamablehttp_client
134
+ from mcp import ClientSession
135
+
136
+ async def test_server():
137
+ async with streamablehttp_client("http://localhost:8000/mcp") as (read, write, get_session_id):
138
+ async with ClientSession(read, write) as session:
139
+ await session.initialize()
140
+
141
+ # Test tool
142
+ result = await session.call_tool("hello_world", {"name": "Test"})
143
+ print(f"Tool result: {result}")
144
+
145
+ # Test resource
146
+ resource = await session.read_resource("info://server")
147
+ print(f"Resource: {resource}")
148
+
149
+ # Run with: asyncio.run(test_server())
150
+ ```
151
+
152
+ ## πŸ”§ Configuration Options
153
+
154
+ ### Environment Variables
155
+ - `MCP_HOST` - Server host (default: 127.0.0.1)
156
+ - `MCP_PORT` - Server port (default: 8000)
157
+ - `MCP_PATH` - Server path (default: /mcp)
158
+
159
+ ### Server Capabilities
160
+ This server supports all MCP capabilities:
161
+ - βœ… Tools (with async support and context logging)
162
+ - βœ… Resources (static and dynamic templates)
163
+ - βœ… Prompts (string and message-based)
164
+ - βœ… Streamable HTTP transport
165
+ - βœ… Session management
166
+
167
+ ## 🎯 Key Concepts Demonstrated
168
+
169
+ 1. **FastMCP Framework**: Modern, production-ready MCP server development
170
+ 2. **Streamable HTTP Transport**: Scalable transport for web deployments
171
+ 3. **Type Safety**: Full Python type hints and docstrings
172
+ 4. **Async Support**: Proper async/await patterns with context
173
+ 5. **Dynamic Resources**: Template-based resources with parameters
174
+ 6. **Context Logging**: Using MCP context for client communication
175
+ 7. **Error Handling**: Graceful startup and shutdown
176
+
177
+ ## πŸ“š Next Steps
178
+
179
+ - **Scale Up**: Use FastMCP's server composition to mount multiple apps
180
+ - **Add Auth**: Implement OAuth authentication for production
181
+ - **Deploy**: Use Docker or cloud platforms for production deployment
182
+ - **Integrate**: Connect with Claude Desktop, Agent Zero, or custom clients
183
+ - **Extend**: Add more sophisticated tools, resources, and prompts
184
+
185
+ ## πŸ› Troubleshooting
186
+
187
+ ### Server Won't Start
188
+ - Check if port 8000 is available: `lsof -i :8000`
189
+ - Try a different port: `MCP_PORT=8001 python stream_http_mcp_server.py`
190
+
191
+ ### Connection Issues
192
+ - Verify the URL in your client matches the server output
193
+ - Check firewall settings for the port
194
+ - Ensure you're using "streamable-http" transport type
195
+
196
+ ### Import Errors
197
+ - Install FastMCP: `pip install fastmcp`
198
+ - Check Python version: `python --version` (requires 3.10+)
199
+
200
+ ## πŸ“– Documentation Links
201
+
202
+ - [FastMCP Documentation](https://gofastmcp.com/)
203
+ - [MCP Specification](https://spec.modelcontextprotocol.io/)
204
+ - [Agent Zero MCP Integration](../../docs/mcp_setup.md)
205
+
206
+ ---
207
+
208
+ Built with ❀️ using FastMCP 2.0 and the Model Context Protocol
tests/mcp/stream_http_mcp_server_requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # FastMCP Hello World Server Requirements
2
+ # Install with: pip install -r stream_http_mcp_server_requirements.txt
3
+
4
+ # FastMCP framework for building MCP servers
5
+ fastmcp>=2.8.0
6
+
7
+ # Optional: Additional dependencies that might be useful
8
+ # uvicorn>=0.18.0 # ASGI server (may be included with FastMCP)
9
+ # httpx>=0.24.0 # HTTP client (may be included with FastMCP)