feat: Add support for 'streamable http transport' mcp servers
Rafael Uzarowski committed
Jun 21, 2025 at 16:42 UTC
48c0026206ab8e4e81b645bd986b4ecf22db50d8
5 files changed
+550
-78
docs/mcp_setup.md
+25
-3
@@ -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:
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
@@ -65,6 +66,7 @@ Here are templates for configuring individual servers within the `mcp_servers` J
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
@@ -83,6 +85,7 @@ Here are templates for configuring individual servers within the `mcp_servers` J
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",
@@ -94,6 +97,24 @@ Here are templates for configuring individual servers within the `mcp_servers` J
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
@@ -107,8 +128,9 @@ Here are templates for configuring individual servers within the `mcp_servers` J
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.
111
-* `"url"`: **Required for Remote SSE Servers.** The endpoint URL.
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.
@@ -121,4 +143,4 @@ Once configured, successfully installed (if applicable, e.g., for `npx` based se
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
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.
\ No newline at end of file
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
+85
-75
@@ -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
-
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
35
-from mcp.types import CallToolResult, ListToolsResult, JSONRPCMessage
32
+from mcp.types import CallToolResult, ListToolsResult
33
from anyio.streams.memory import (
34
MemoryObjectReceiveStream,
35
MemoryObjectSendStream,
@@ -40,7 +37,6 @@ from anyio.streams.memory import (
37
38
from pydantic import BaseModel, Field, Discriminator, Tag, PrivateAttr
39
from python.helpers import dirty_json
43
-from python.helpers.dirty_json import DirtyJson
40
from python.helpers.print_style import PrintStyle
41
from python.helpers.tool import Tool, Response
42
@@ -55,6 +51,33 @@ def normalize_name(name: str) -> str:
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:
@@ -67,11 +90,10 @@ def initialize_mcp(mcp_servers_config: str):
90
content=f"Failed to update MCP settings: {e}",
91
temp=False,
92
)
70
-
93
+
94
PrintStyle(
95
background_color="black", font_color="red", padding=True
96
).print(f"Failed to update MCP settings: {e}")
74
-
97
98
99
class MCPTool(Tool):
@@ -145,9 +167,9 @@ 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
148
- user_message_text = content.get(
170
+ user_message_text = str(content.get(
171
"message", json.dumps(content, indent=2)
150
- )
172
+ ))
173
elif isinstance(content, str):
174
user_message_text = content
175
else:
@@ -164,27 +186,6 @@ class MCPTool(Tool):
186
user_message_text[:max_user_context_len] + "... (truncated)"
187
)
188
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
-
189
final_text_for_agent = raw_tool_response
190
191
self.agent.hist_add_tool_result(self.name, final_text_for_agent)
@@ -210,6 +211,7 @@ class MCPTool(Tool):
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)
@@ -256,6 +258,7 @@ class MCPServerRemote(BaseModel):
258
if key in [
259
"name",
260
"description",
261
+ "type",
262
"url",
263
"serverUrl",
264
"headers",
@@ -280,6 +283,7 @@ class MCPServerRemote(BaseModel):
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])
@@ -331,6 +335,7 @@ class MCPServerLocal(BaseModel):
335
if key in [
336
"name",
337
"description",
338
+ "type",
339
"command",
340
"args",
341
"env",
@@ -356,7 +361,7 @@ MCPServer = Annotated[
361
Annotated[MCPServerRemote, Tag("MCPServerRemote")],
362
Annotated[MCPServerLocal, Tag("MCPServerLocal")],
363
],
359
- Discriminator(lambda v: "MCPServerRemote" if "url" in v else "MCPServerLocal"),
364
+ Discriminator(_determine_server_type),
365
]
366
367
@@ -370,9 +375,9 @@ class MCPConfig(BaseModel):
375
@classmethod
376
def get_instance(cls) -> "MCPConfig":
377
# with cls.__lock:
373
- if cls.__instance is None:
374
- cls.__instance = cls(servers_list=[])
375
- return cls.__instance
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):
@@ -660,7 +665,7 @@ class MCPConfig(BaseModel):
665
if server.name == server_name:
666
try:
667
tools = server.get_tools()
663
- except Exception as e:
668
+ except Exception:
669
tools = []
670
return {
671
"name": server.name,
@@ -718,40 +723,9 @@ class MCPConfig(BaseModel):
723
# f"#### Arguments:\n"
724
)
725
721
- tool_args = ""
726
input_schema = (
727
json.dumps(tool["input_schema"]) if tool["input_schema"] else ""
728
)
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
- # )
729
730
prompt += f"#### Input schema for tool_args:\n{input_schema}\n"
731
@@ -1047,6 +1021,11 @@ class MCPClientLocal(MCPClientBase):
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[
@@ -1056,12 +1035,43 @@ class MCPClientRemote(MCPClientBase):
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()
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"],
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
)
1066
- )
1067
- return stdio_transport
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
new
+223
@@ -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
new
+208
@@ -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
new
+9
@@ -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)