1+ #!/usr/bin/env python
2+ """
3+ mcp_streamable_http_example_calling_usage.py
4+ ─────────────────────────────────────────────
5+ Updated demo showcasing HTTP Streamable transport using chuk-mcp patterns.
6+
7+ This version properly checks for chuk-mcp dependencies and provides
8+ clear error messages if they're not available.
9+ """
10+
11+ from __future__ import annotations
12+
13+ import asyncio
14+ import json
15+ import os
16+ import sys
17+ from pathlib import Path
18+ from typing import Any , List , Tuple
19+
20+ from colorama import Fore , Style , init as colorama_init
21+
22+ colorama_init (autoreset = True )
23+
24+ # ─── local-package bootstrap ───────────────────────────────────────────────
25+ PROJECT_ROOT = Path (__file__ ).resolve ().parents [1 ]
26+ sys .path .insert (0 , str (PROJECT_ROOT ))
27+
28+ from chuk_tool_processor .logging import get_logger
29+ from chuk_tool_processor .registry .provider import ToolRegistryProvider
30+
31+ # Check for chuk-mcp availability
32+ try :
33+ from chuk_tool_processor .mcp .setup_mcp_http_streamable import setup_mcp_http_streamable
34+ HAS_HTTP_STREAMABLE_SETUP = True
35+ except ImportError as e :
36+ HAS_HTTP_STREAMABLE_SETUP = False
37+ HTTP_STREAMABLE_IMPORT_ERROR = str (e )
38+
39+ # Check for transport availability
40+ try :
41+ from chuk_tool_processor .mcp .transport import (
42+ HTTPStreamableTransport ,
43+ HAS_HTTP_STREAMABLE_TRANSPORT
44+ )
45+ except ImportError as e :
46+ HAS_HTTP_STREAMABLE_TRANSPORT = False
47+ HTTPStreamableTransport = None
48+
49+ # parsers
50+ from chuk_tool_processor .plugins .parsers .json_tool import JsonToolPlugin
51+ from chuk_tool_processor .plugins .parsers .xml_tool import XmlToolPlugin
52+ from chuk_tool_processor .plugins .parsers .function_call_tool import FunctionCallPlugin
53+
54+ # executor
55+ from chuk_tool_processor .execution .tool_executor import ToolExecutor
56+ from chuk_tool_processor .execution .strategies .inprocess_strategy import InProcessStrategy
57+
58+ from chuk_tool_processor .models .tool_call import ToolCall
59+ from chuk_tool_processor .models .tool_result import ToolResult
60+
61+ logger = get_logger ("mcp-http-streamable-demo" )
62+
63+ # ─── config / bootstrap ─────────────────────────────────────────────────────
64+ HTTP_SERVER_URL = "http://localhost:8000"
65+ SERVER_NAME = "mock_http_server"
66+ NAMESPACE = "http"
67+
68+
69+ def check_dependencies () -> Tuple [bool , List [str ]]:
70+ """Check if all required dependencies are available."""
71+ issues = []
72+
73+ if not HAS_HTTP_STREAMABLE_SETUP :
74+ issues .append (f"❌ HTTP Streamable setup not available: { HTTP_STREAMABLE_IMPORT_ERROR } " )
75+
76+ if not HAS_HTTP_STREAMABLE_TRANSPORT :
77+ issues .append ("❌ HTTPStreamableTransport not available - likely missing chuk-mcp" )
78+
79+ # Check for chuk-mcp specifically
80+ try :
81+ import chuk_mcp
82+ logger .info (f"✅ chuk-mcp available at { chuk_mcp .__file__ } " )
83+ except ImportError :
84+ issues .append ("❌ chuk-mcp package not installed" )
85+
86+ # Check for chuk-mcp HTTP transport
87+ try :
88+ from chuk_mcp .transports .http import http_client
89+ from chuk_mcp .transports .http .parameters import StreamableHTTPParameters
90+ logger .info ("✅ chuk-mcp HTTP transport components available" )
91+ except ImportError as e :
92+ issues .append (f"❌ chuk-mcp HTTP transport not available: { e } " )
93+
94+ # Check for chuk-mcp protocol messages
95+ try :
96+ from chuk_mcp .protocol .messages import (
97+ send_initialize , send_ping , send_tools_list , send_tools_call
98+ )
99+ logger .info ("✅ chuk-mcp protocol messages available" )
100+ except ImportError as e :
101+ issues .append (f"❌ chuk-mcp protocol messages not available: { e } " )
102+
103+ return len (issues ) == 0 , issues
104+
105+
106+ async def bootstrap_mcp () -> None :
107+ """Start the HTTP Streamable transport and connect to the mock test server."""
108+ try :
109+ print ("🔄 Connecting to mock MCP HTTP Streamable server..." )
110+
111+ # Check dependencies first
112+ deps_ok , issues = check_dependencies ()
113+ if not deps_ok :
114+ print ("❌ Dependency issues found:" )
115+ for issue in issues :
116+ print (f" { issue } " )
117+ print ("\n 💡 To fix these issues:" )
118+ print (" 1. Install chuk-mcp: pip install chuk-mcp" )
119+ print (" 2. Ensure all required components are available" )
120+ print (" 3. Check that chuk-mcp has HTTP transport support" )
121+ raise RuntimeError ("Missing required dependencies" )
122+
123+ _ , sm = await setup_mcp_http_streamable (
124+ servers = [
125+ {
126+ "name" : SERVER_NAME ,
127+ "url" : HTTP_SERVER_URL ,
128+ }
129+ ],
130+ server_names = {0 : SERVER_NAME },
131+ namespace = NAMESPACE ,
132+ )
133+
134+ # keep for shutdown
135+ bootstrap_mcp .stream_manager = sm
136+ print ("✅ Connected to mock server successfully!" )
137+
138+ except Exception as e :
139+ logger .error (f"Failed to bootstrap MCP HTTP Streamable: { e } " )
140+ print (f"❌ Could not connect to mock HTTP server at { HTTP_SERVER_URL } " )
141+ print ("Please start the test server first:" )
142+ print (" python examples/mcp_streamable_http_server.py" )
143+ print ("\n 🔍 Troubleshooting:" )
144+ print (" • Check if the server is running" )
145+ print (" • Verify the server URL is correct" )
146+ print (" • Ensure chuk-mcp is properly installed" )
147+ raise
148+
149+
150+ def create_test_plugins ():
151+ """Create test plugins with proper string handling."""
152+ return [
153+ (
154+ "JSON Plugin" ,
155+ JsonToolPlugin (),
156+ json .dumps ({
157+ "tool_calls" : [{
158+ "tool" : f"{ NAMESPACE } .http_greet" ,
159+ "arguments" : {"name" : "Alice" , "style" : "formal" },
160+ }]
161+ }),
162+ ),
163+ (
164+ "XML Plugin" ,
165+ XmlToolPlugin (),
166+ f'<tool name="{ NAMESPACE } .session_info" args="{{}}"/>' ,
167+ ),
168+ (
169+ "FunctionCall Plugin" ,
170+ FunctionCallPlugin (),
171+ json .dumps ({
172+ "function_call" : {
173+ "name" : f"{ NAMESPACE } .http_counter" ,
174+ "arguments" : {"increment" : 5 },
175+ }
176+ }),
177+ ),
178+ ]
179+
180+
181+ def banner (text : str , colour : str = Fore .CYAN ) -> None :
182+ print (colour + f"\n === { text } ===" + Style .RESET_ALL )
183+
184+
185+ def show_results (title : str , calls : List [ToolCall ], results : List [ToolResult ]) -> None :
186+ banner (title )
187+ for call , res in zip (calls , results ):
188+ ok = res .error is None
189+ head_colour = Fore .GREEN if ok else Fore .RED
190+ duration = (res .end_time - res .start_time ).total_seconds ()
191+ print (f"{ head_colour } { res .tool } ({ duration :.3f} s){ Style .RESET_ALL } " )
192+ print (Fore .YELLOW + " args :" + Style .RESET_ALL , call .arguments )
193+ if ok :
194+ print (Fore .MAGENTA + " result :" + Style .RESET_ALL )
195+ result_str = str (res .result )
196+ if len (result_str ) > 250 :
197+ print (f"{ result_str [:250 ]} ..." )
198+ else :
199+ print (res .result )
200+ else :
201+ print (Fore .RED + " error :" + Style .RESET_ALL , res .error )
202+ print (Style .DIM + "-" * 60 )
203+
204+
205+ async def run_demo () -> None :
206+ print (Fore .GREEN + "=== MCP HTTP Streamable Tool-Calling Demo ===" + Style .RESET_ALL )
207+ print ("This demo uses chuk-mcp HTTP Streamable transport (spec 2025-03-26)" )
208+ print ("Modern replacement for deprecated SSE transport" )
209+
210+ # Check dependencies before starting
211+ deps_ok , issues = check_dependencies ()
212+ if not deps_ok :
213+ banner ("❌ Missing Dependencies" , Fore .RED )
214+ for issue in issues :
215+ print (f" { issue } " )
216+ print ("\n 💡 Installation instructions:" )
217+ print (" pip install chuk-mcp" )
218+ print (" # Ensure chuk-mcp has HTTP transport support" )
219+ return
220+
221+ try :
222+ await bootstrap_mcp ()
223+ except Exception :
224+ return # Error already logged
225+
226+ registry = await ToolRegistryProvider .get_registry ()
227+
228+ executor = ToolExecutor (
229+ registry ,
230+ strategy = InProcessStrategy (
231+ registry ,
232+ default_timeout = 10.0 ,
233+ max_concurrency = 2 ,
234+ ),
235+ )
236+
237+ # Check available tools
238+ tools = await registry .list_tools (NAMESPACE )
239+ if not tools :
240+ banner ("❌ No Tools Found" , Fore .RED )
241+ print ("No tools were registered from the HTTP Streamable server." )
242+ await bootstrap_mcp .stream_manager .close ()
243+ return
244+
245+ banner ("Available HTTP Streamable Tools" , Fore .BLUE )
246+ for ns , name in tools :
247+ tool_meta = await registry .get_metadata (name , ns )
248+ desc = tool_meta .description if tool_meta else "No description"
249+ print (f" 🔧 { name } : { desc } " )
250+
251+ # sequential examples with different parsers
252+ plugins = create_test_plugins ()
253+ for title , plugin , raw in plugins :
254+ try :
255+ calls = await plugin .try_parse (raw )
256+ results = await executor .execute (calls )
257+ show_results (f"{ title } → sequential" , calls , results )
258+ except Exception as e :
259+ print (f"❌ { title } failed: { e } " )
260+
261+ # parallel demo
262+ banner ("Parallel HTTP Streamable Calls" )
263+
264+ parallel_calls = [
265+ ToolCall (tool = f"{ NAMESPACE } .http_greet" , arguments = {"name" : "Bob" , "style" : "casual" }),
266+ ToolCall (tool = f"{ NAMESPACE } .session_info" , arguments = {}),
267+ ToolCall (tool = f"{ NAMESPACE } .http_counter" , arguments = {"increment" : 3 }),
268+ ToolCall (tool = f"{ NAMESPACE } .slow_operation" , arguments = {"duration" : 2 }),
269+ ]
270+
271+ try :
272+ parallel_results = await executor .execute (parallel_calls )
273+ show_results ("Parallel HTTP Streamable Execution" , parallel_calls , parallel_results )
274+ except Exception as e :
275+ print (f"❌ Parallel execution failed: { e } " )
276+
277+ # error handling test
278+ banner ("Error Handling Test" )
279+
280+ error_calls = [ToolCall (tool = f"{ NAMESPACE } .nonexistent_tool" , arguments = {"query" : "This should fail" })]
281+
282+ try :
283+ error_results = await executor .execute (error_calls )
284+ show_results ("Error Handling" , error_calls , error_results )
285+ except Exception as e :
286+ print (f"Expected error test result: { e } " )
287+
288+ # Test HTTP Streamable specific features
289+ banner ("HTTP Streamable Features Test" )
290+
291+ # Get transport metrics if available
292+ try :
293+ transport = None
294+ for transport_name , transport_instance in bootstrap_mcp .stream_manager .transports .items ():
295+ if hasattr (transport_instance , 'get_metrics' ):
296+ transport = transport_instance
297+ break
298+
299+ if transport :
300+ metrics = transport .get_metrics ()
301+ print ("📊 HTTP Streamable Transport Metrics:" )
302+ for key , value in metrics .items ():
303+ if value is not None :
304+ if isinstance (value , float ):
305+ print (f" { key } : { value :.3f} " )
306+ else :
307+ print (f" { key } : { value } " )
308+ else :
309+ print (" 📊 Transport metrics not available" )
310+ except Exception as e :
311+ print (f" ❌ Error getting metrics: { e } " )
312+
313+ # summary
314+ banner ("Demo Summary" , Fore .GREEN )
315+ print ("✅ Successfully demonstrated:" )
316+ print (" • MCP HTTP Streamable transport using chuk-mcp" )
317+ print (" • Modern replacement for SSE transport (spec 2025-03-26)" )
318+ print (" • Multiple parser plugins (JSON, XML, FunctionCall)" )
319+ print (" • Parallel tool execution with HTTP requests" )
320+ print (" • Different HTTP Streamable tool types" )
321+ print (" • Error handling and timeout management" )
322+ print (" • Performance metrics and monitoring" )
323+ print (" • Session management via HTTP headers" )
324+
325+ # cleanup
326+ await bootstrap_mcp .stream_manager .close ()
327+ print ("\n 🎉 HTTP Streamable demo completed successfully!" )
328+
329+
330+ if __name__ == "__main__" :
331+ import logging
332+
333+ logging .getLogger ("chuk_tool_processor" ).setLevel (
334+ getattr (logging , os .environ .get ("LOGLEVEL" , "INFO" ).upper ())
335+ )
336+
337+ asyncio .run (run_demo ())
0 commit comments