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)