-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathmcp_community_test_client.py
More file actions
295 lines (245 loc) · 10.7 KB
/
mcp_community_test_client.py
File metadata and controls
295 lines (245 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
"""
mcp_community_test_client.py
Async Python client for discovering and calling all tools on an MCP (Model Context Protocol) server using streamable-http, SSE, or stdio transport.
Features:
- Connects to a running MCP server via streamable-http, SSE, or stdio transport.
- Lists all available tools on the server.
- Demonstrates how to call each tool registered on the server, using appropriate or sample arguments for each tool.
- Supports passing environment variables to stdio subprocesses.
- Uses native MCP client (no external dependencies beyond mcp package).
Usage examples:
# Connect via streamable-http (default)
$ python mcp_community_test_client.py --transport streamable-http --url http://localhost:8000/mcp
# Connect via SSE
$ python mcp_community_test_client.py --transport sse --url http://localhost:8000/sse
# Connect via stdio
$ python mcp_community_test_client.py --transport stdio --stdio-cmd "uv run dh-mcp-systems --transport stdio" --env DH_MCP_CONFIG_FILE=/path/to/file.json
Arguments:
--transport Transport type: 'streamable-http' (default), 'sse', or 'stdio'.
--url HTTP server URL (auto-detected: http://localhost:8000/mcp for streamable-http, http://localhost:8000/sse for SSE).
--stdio-cmd Command to launch stdio server (default: uv run dh-mcp-systems --transport stdio).
--env Environment variable for stdio, format KEY=VALUE. Can be specified multiple times.
--token Optional authorization token for HTTP transports (Bearer token).
See the project README for further details.
"""
import argparse
import asyncio
import logging
import shlex
import sys
import httpx
from mcp import StdioServerParameters
from mcp.client.sse import sse_client
from mcp.client.stdio import stdio_client
from mcp.client.streamable_http import streamable_http_client
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
_LOGGER = logging.getLogger(__name__)
def parse_args():
"""
Parse command-line arguments for the MCP test client.
Returns:
argparse.Namespace: Parsed arguments with fields:
- transport: Transport type ('streamable-http', 'sse', or 'stdio')
- url: HTTP server URL (auto-detected if not specified)
- stdio_cmd: Command to launch stdio server
- env: List of environment variable strings (KEY=VALUE)
- token: Optional authorization token for HTTP transports
"""
parser = argparse.ArgumentParser(
description="MCP test client for streamable-http, SSE, or stdio server"
)
parser.add_argument(
"--transport",
choices=["streamable-http", "sse", "stdio"],
default="streamable-http",
help="Transport type (streamable-http, sse, or stdio)",
)
parser.add_argument(
"--url",
default=None,
help="HTTP server URL (auto-detected based on transport if not specified)",
)
parser.add_argument(
"--stdio-cmd",
default="uv run dh-mcp-systems --transport stdio",
help="Stdio server command (pass as a shell string, e.g. 'uv run dh-mcp-systems --transport stdio')",
)
parser.add_argument(
"--env",
action="append",
default=[],
help="Environment variable for stdio transport, format KEY=VALUE. Can be specified multiple times.",
)
parser.add_argument(
"--token",
default=None,
help="Optional authorization token for HTTP transports (Bearer token)",
)
return parser.parse_args()
async def call_tool(session, tool_name, arguments):
"""
Call an MCP tool and handle errors gracefully.
Args:
session: Active MCP session object
tool_name: Name of the tool to call
arguments: Dictionary of arguments to pass to the tool
Returns:
str: Tool result as text, or error message if the call fails
"""
try:
result = await session.call_tool(tool_name, arguments=arguments)
return result.content[0].text if result.content else str(result)
except Exception as e:
_LOGGER.error(f"Error calling tool {tool_name}: {e}")
return f"Error: {e}"
async def main():
"""
Connect to MCP server and demonstrate tool invocation.
- Establishes a connection to the MCP server using the selected transport (streamable-http, SSE, or stdio).
- Lists all registered tools on the server.
- Calls test_tools() to demonstrate tool invocation with sample arguments.
- Prints the results or errors for each tool invocation.
Raises:
ValueError: If --env entries are malformed or --stdio-cmd is empty
"""
args = parse_args()
# Auto-detect URL based on transport if not specified
if args.url is None:
if args.transport == "sse":
args.url = "http://localhost:8000/sse"
elif args.transport == "streamable-http":
args.url = "http://localhost:8000/mcp"
_LOGGER.info(f"Connecting to MCP Systems server via {args.transport} transport")
if args.transport in ["sse", "streamable-http"]:
# Prepare HTTP client with optional auth token
headers = {}
if args.token:
headers["Authorization"] = f"Bearer {args.token}"
http_client = httpx.AsyncClient(headers=headers) if headers else None
if args.transport == "streamable-http":
client_func = lambda url: streamable_http_client(
url, http_client=http_client
)
else:
client_func = lambda url: sse_client(url, http_client=http_client)
_LOGGER.info(f"Server URL: {args.url}")
# Use async context manager to ensure proper cleanup of HTTP client
try:
async with client_func(args.url) as (read, write):
async with read, write:
await write.send_initialize()
result = await read.recv_initialize()
_LOGGER.info(f"Connected to MCP server: {result}")
session = await write.get_result(read)
# List tools
tools_result = await session.list_tools()
tools = tools_result.tools
tool_names = [t.name for t in tools]
_LOGGER.info(f"Available tools: {tool_names}")
print("Available tools:", tool_names)
# Test tools
await test_tools(session)
finally:
if http_client:
await http_client.aclose()
else: # stdio
# Parse env vars from --env KEY=VALUE
env_dict = {}
for item in args.env:
if "=" in item:
k, v = item.split("=", 1)
env_dict[k] = v
else:
raise ValueError(f"Invalid --env entry: {item}. Must be KEY=VALUE.")
stdio_tokens = shlex.split(args.stdio_cmd)
if not stdio_tokens:
raise ValueError("--stdio-cmd must not be empty")
server_params = StdioServerParameters(
command=stdio_tokens[0],
args=stdio_tokens[1:],
env=env_dict if env_dict else None,
)
async with stdio_client(server_params) as (read, write):
async with read, write:
await write.send_initialize()
result = await read.recv_initialize()
_LOGGER.info(f"Connected to MCP server: {result}")
session = await write.get_result(read)
# List tools
tools_result = await session.list_tools()
tools = tools_result.tools
tool_names = [t.name for t in tools]
_LOGGER.info(f"Available tools: {tool_names}")
print("Available tools:", tool_names)
# Test tools
await test_tools(session)
async def test_tools(session):
"""
Demonstrate calling various MCP tools with example arguments.
This function shows how to call each tool type. Modify the session_id
and other arguments as needed for your actual server setup.
Args:
session: Active MCP session object
"""
# 1. mcp_reload
_LOGGER.info("Testing tool: mcp_reload")
print("\nCalling tool: mcp_reload")
result = await call_tool(session, "mcp_reload", {})
print(f"Result for mcp_reload: {result}")
# 2. sessions_list
_LOGGER.info("Testing tool: sessions_list")
print("\nCalling tool: sessions_list")
result = await call_tool(session, "sessions_list", {})
print(f"Result for sessions_list: {result}")
# 3. session_details (example - requires session_id)
_LOGGER.info("Testing tool: session_details")
print("\nCalling tool: session_details (example)")
result = await call_tool(
session, "session_details", {"session_id": "community:local:example"}
)
print(f"Result for session_details: {result}")
# 4. session_tables_schema (example - requires session_id)
_LOGGER.info("Testing tool: session_tables_schema")
print("\nCalling tool: session_tables_schema (example)")
result = await call_tool(
session, "session_tables_schema", {"session_id": "community:local:example"}
)
print(f"Result for session_tables_schema: {result}")
# 5. session_script_run (example - requires session_id and script)
_LOGGER.info("Testing tool: session_script_run")
print("\nCalling tool: session_script_run (example)")
result = await call_tool(
session,
"session_script_run",
{"session_id": "community:local:example", "script": "print('hello world')"},
)
print(f"Result for session_script_run: {result}")
# 6. enterprise_systems_status
_LOGGER.info("Testing tool: enterprise_systems_status")
print("\nCalling tool: enterprise_systems_status")
result = await call_tool(session, "enterprise_systems_status", {})
print(f"Result for enterprise_systems_status: {result}")
# 7. session_pip_list (example - requires session_id)
_LOGGER.info("Testing tool: session_pip_list")
print("\nCalling tool: session_pip_list (example)")
result = await call_tool(
session, "session_pip_list", {"session_id": "community:local:example"}
)
print(f"Result for session_pip_list: {result}")
if __name__ == "__main__":
try:
_LOGGER.info("Starting MCP Community test client")
asyncio.run(main())
_LOGGER.info("Test client completed successfully")
except KeyboardInterrupt:
_LOGGER.info("Interrupted by user")
print("\nInterrupted by user.", file=sys.stderr)
except Exception as e:
_LOGGER.error(f"Fatal error in main: {e}")
print(f"Fatal error in main: {e}", file=sys.stderr)
raise