Skip to content

Commit 47cd665

Browse files
committed
updated to include streamable http
1 parent 83ef106 commit 47cd665

File tree

7 files changed

+872
-531
lines changed

7 files changed

+872
-531
lines changed
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
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

Comments
 (0)