massgen 0.1.0a2__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of massgen might be problematic. Click here for more details.
- massgen/__init__.py +1 -1
- massgen/agent_config.py +17 -0
- massgen/api_params_handler/_api_params_handler_base.py +1 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +8 -1
- massgen/api_params_handler/_claude_api_params_handler.py +8 -1
- massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
- massgen/api_params_handler/_response_api_params_handler.py +8 -1
- massgen/backend/base.py +31 -0
- massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +282 -11
- massgen/backend/chat_completions.py +182 -92
- massgen/backend/claude.py +115 -18
- massgen/backend/claude_code.py +378 -14
- massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
- massgen/backend/gemini.py +1275 -1607
- massgen/backend/gemini_mcp_manager.py +545 -0
- massgen/backend/gemini_trackers.py +344 -0
- massgen/backend/gemini_utils.py +43 -0
- massgen/backend/response.py +129 -70
- massgen/cli.py +643 -132
- massgen/config_builder.py +381 -32
- massgen/configs/README.md +111 -80
- massgen/configs/basic/multi/three_agents_default.yaml +1 -1
- massgen/configs/basic/single/single_agent.yaml +1 -1
- massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
- massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
- massgen/formatter/_chat_completions_formatter.py +104 -0
- massgen/formatter/_claude_formatter.py +120 -0
- massgen/formatter/_gemini_formatter.py +448 -0
- massgen/formatter/_response_formatter.py +88 -0
- massgen/frontend/coordination_ui.py +4 -2
- massgen/logger_config.py +35 -3
- massgen/message_templates.py +56 -6
- massgen/orchestrator.py +179 -10
- massgen/stream_chunk/base.py +3 -0
- massgen/tests/custom_tools_example.py +392 -0
- massgen/tests/mcp_test_server.py +17 -7
- massgen/tests/test_config_builder.py +423 -0
- massgen/tests/test_custom_tools.py +401 -0
- massgen/tests/test_tools.py +127 -0
- massgen/tool/README.md +935 -0
- massgen/tool/__init__.py +39 -0
- massgen/tool/_async_helpers.py +70 -0
- massgen/tool/_basic/__init__.py +8 -0
- massgen/tool/_basic/_two_num_tool.py +24 -0
- massgen/tool/_code_executors/__init__.py +10 -0
- massgen/tool/_code_executors/_python_executor.py +74 -0
- massgen/tool/_code_executors/_shell_executor.py +61 -0
- massgen/tool/_exceptions.py +39 -0
- massgen/tool/_file_handlers/__init__.py +10 -0
- massgen/tool/_file_handlers/_file_operations.py +218 -0
- massgen/tool/_manager.py +634 -0
- massgen/tool/_registered_tool.py +88 -0
- massgen/tool/_result.py +66 -0
- massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
- massgen/tool/docs/builtin_tools.md +681 -0
- massgen/tool/docs/exceptions.md +794 -0
- massgen/tool/docs/execution_results.md +691 -0
- massgen/tool/docs/manager.md +887 -0
- massgen/tool/docs/workflow_toolkits.md +529 -0
- massgen/tool/workflow_toolkits/__init__.py +57 -0
- massgen/tool/workflow_toolkits/base.py +55 -0
- massgen/tool/workflow_toolkits/new_answer.py +126 -0
- massgen/tool/workflow_toolkits/vote.py +167 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/METADATA +89 -131
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/RECORD +111 -36
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/WHEEL +0 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Example script demonstrating custom tools usage with ResponseBackend.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Add parent directory to path
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
15
|
+
|
|
16
|
+
from massgen.backend.response import ResponseBackend # noqa: E402
|
|
17
|
+
from massgen.tool import ExecutionResult # noqa: E402
|
|
18
|
+
from massgen.tool._result import TextContent # noqa: E402
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Define custom tool functions
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def calculator(operation: str, x: float, y: float) -> ExecutionResult:
|
|
26
|
+
"""
|
|
27
|
+
Perform basic math operations.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
operation: The operation to perform (add, subtract, multiply, divide)
|
|
31
|
+
x: First number
|
|
32
|
+
y: Second number
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
ExecutionResult with the calculation result
|
|
36
|
+
"""
|
|
37
|
+
operations = {
|
|
38
|
+
"add": lambda a, b: a + b,
|
|
39
|
+
"subtract": lambda a, b: a - b,
|
|
40
|
+
"multiply": lambda a, b: a * b,
|
|
41
|
+
"divide": lambda a, b: a / b if b != 0 else "Cannot divide by zero",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if operation in operations:
|
|
45
|
+
result = operations[operation](x, y)
|
|
46
|
+
return ExecutionResult(
|
|
47
|
+
output_blocks=[
|
|
48
|
+
TextContent(data=f"{operation}({x}, {y}) = {result}"),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
return ExecutionResult(
|
|
53
|
+
output_blocks=[
|
|
54
|
+
TextContent(data=f"Unknown operation: {operation}"),
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def text_analyzer(text: str, analysis_type: str = "basic") -> ExecutionResult:
|
|
60
|
+
"""
|
|
61
|
+
Analyze text and return statistics.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
text: The text to analyze
|
|
65
|
+
analysis_type: Type of analysis (basic, detailed)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
ExecutionResult with text analysis
|
|
69
|
+
"""
|
|
70
|
+
word_count = len(text.split())
|
|
71
|
+
char_count = len(text)
|
|
72
|
+
line_count = len(text.splitlines())
|
|
73
|
+
|
|
74
|
+
if analysis_type == "basic":
|
|
75
|
+
result = f"Words: {word_count}, Characters: {char_count}"
|
|
76
|
+
else:
|
|
77
|
+
unique_words = len(set(text.lower().split()))
|
|
78
|
+
result = f"Words: {word_count}, Unique words: {unique_words}, " f"Characters: {char_count}, Lines: {line_count}"
|
|
79
|
+
|
|
80
|
+
return ExecutionResult(
|
|
81
|
+
output_blocks=[TextContent(data=result)],
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def async_data_processor(data_type: str, count: int = 10) -> ExecutionResult:
|
|
86
|
+
"""
|
|
87
|
+
Async function to simulate data processing.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
data_type: Type of data to generate (numbers, strings)
|
|
91
|
+
count: Number of items to generate
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
ExecutionResult with generated data
|
|
95
|
+
"""
|
|
96
|
+
await asyncio.sleep(0.5) # Simulate processing time
|
|
97
|
+
|
|
98
|
+
if data_type == "numbers":
|
|
99
|
+
data = list(range(1, min(count + 1, 100)))
|
|
100
|
+
elif data_type == "strings":
|
|
101
|
+
data = [f"Item_{i}" for i in range(1, min(count + 1, 100))]
|
|
102
|
+
else:
|
|
103
|
+
data = ["Unknown data type"]
|
|
104
|
+
|
|
105
|
+
return ExecutionResult(
|
|
106
|
+
output_blocks=[
|
|
107
|
+
TextContent(data=f"Generated {len(data)} {data_type}: {data[:5]}..."),
|
|
108
|
+
],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ============================================================================
|
|
113
|
+
# Demo functions
|
|
114
|
+
# ============================================================================
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def demo_basic_usage():
|
|
118
|
+
"""Demonstrate basic custom tools usage."""
|
|
119
|
+
print("=" * 60)
|
|
120
|
+
print("BASIC CUSTOM TOOLS DEMO")
|
|
121
|
+
print("=" * 60)
|
|
122
|
+
|
|
123
|
+
# Create ResponseBackend with custom tools
|
|
124
|
+
backend = ResponseBackend(
|
|
125
|
+
api_key=os.getenv("OPENAI_API_KEY", "test-key"),
|
|
126
|
+
custom_tools=[
|
|
127
|
+
{
|
|
128
|
+
"func": calculator,
|
|
129
|
+
"description": "Perform mathematical calculations",
|
|
130
|
+
"category": "math",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"func": text_analyzer,
|
|
134
|
+
"description": "Analyze text content",
|
|
135
|
+
"category": "text",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"func": async_data_processor,
|
|
139
|
+
"description": "Process data asynchronously",
|
|
140
|
+
"category": "data",
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
print(f"\nRegistered {len(backend._custom_tool_names)} custom tools:")
|
|
146
|
+
for tool_name in backend._custom_tool_names:
|
|
147
|
+
print(f" - {tool_name}")
|
|
148
|
+
|
|
149
|
+
# Get tool schemas
|
|
150
|
+
schemas = backend._get_custom_tools_schemas()
|
|
151
|
+
print(f"\nGenerated {len(schemas)} tool schemas")
|
|
152
|
+
|
|
153
|
+
# Test tool execution
|
|
154
|
+
print("\n" + "-" * 40)
|
|
155
|
+
print("Testing tool execution:")
|
|
156
|
+
print("-" * 40)
|
|
157
|
+
|
|
158
|
+
# Test calculator
|
|
159
|
+
calc_input = {
|
|
160
|
+
"operation": "multiply",
|
|
161
|
+
"x": 7,
|
|
162
|
+
"y": 9,
|
|
163
|
+
}
|
|
164
|
+
call = {
|
|
165
|
+
"name": "calculator",
|
|
166
|
+
"call_id": "calc_1",
|
|
167
|
+
"arguments": json.dumps(calc_input),
|
|
168
|
+
}
|
|
169
|
+
print(f"\nCalculator input: {calc_input}")
|
|
170
|
+
result = await backend._execute_custom_tool(call)
|
|
171
|
+
print(f"Calculator result: {result}")
|
|
172
|
+
|
|
173
|
+
# Test text analyzer
|
|
174
|
+
text_input = {
|
|
175
|
+
"text": "This is a sample text for analysis. It has multiple words and sentences.",
|
|
176
|
+
"analysis_type": "detailed",
|
|
177
|
+
}
|
|
178
|
+
call = {
|
|
179
|
+
"name": "text_analyzer",
|
|
180
|
+
"call_id": "text_1",
|
|
181
|
+
"arguments": json.dumps(text_input),
|
|
182
|
+
}
|
|
183
|
+
print(f"\nText analyzer input: {text_input}")
|
|
184
|
+
result = await backend._execute_custom_tool(call)
|
|
185
|
+
print(f"Text analyzer result: {result}")
|
|
186
|
+
|
|
187
|
+
# Test async data processor
|
|
188
|
+
data_input = {
|
|
189
|
+
"data_type": "numbers",
|
|
190
|
+
"count": 20,
|
|
191
|
+
}
|
|
192
|
+
call = {
|
|
193
|
+
"name": "async_data_processor",
|
|
194
|
+
"call_id": "data_1",
|
|
195
|
+
"arguments": json.dumps(data_input),
|
|
196
|
+
}
|
|
197
|
+
print(f"\nData processor input: {data_input}")
|
|
198
|
+
result = await backend._execute_custom_tool(call)
|
|
199
|
+
print(f"Data processor result: {result}")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def demo_with_presets():
|
|
203
|
+
"""Demonstrate custom tools with preset arguments."""
|
|
204
|
+
print("\n" + "=" * 60)
|
|
205
|
+
print("CUSTOM TOOLS WITH PRESET ARGUMENTS")
|
|
206
|
+
print("=" * 60)
|
|
207
|
+
|
|
208
|
+
backend = ResponseBackend(
|
|
209
|
+
api_key="test-key",
|
|
210
|
+
custom_tools=[
|
|
211
|
+
{
|
|
212
|
+
"func": calculator,
|
|
213
|
+
"description": "Addition calculator",
|
|
214
|
+
"preset_args": {"operation": "add"}, # Preset the operation
|
|
215
|
+
"category": "math",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"func": text_analyzer,
|
|
219
|
+
"description": "Detailed text analyzer",
|
|
220
|
+
"preset_args": {"analysis_type": "detailed"}, # Always detailed
|
|
221
|
+
"category": "text",
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Now calculator only needs x and y
|
|
227
|
+
calc_preset_input = {"x": 15, "y": 25} # operation is preset
|
|
228
|
+
call = {
|
|
229
|
+
"name": "calculator",
|
|
230
|
+
"call_id": "calc_2",
|
|
231
|
+
"arguments": json.dumps(calc_preset_input),
|
|
232
|
+
}
|
|
233
|
+
print(f"\nAddition calculator input (operation preset to 'add'): {calc_preset_input}")
|
|
234
|
+
result = await backend._execute_custom_tool(call)
|
|
235
|
+
print(f"Addition calculator result: {result}")
|
|
236
|
+
|
|
237
|
+
# Text analyzer only needs text
|
|
238
|
+
text_preset_input = {"text": "Short text"} # analysis_type is preset
|
|
239
|
+
call = {
|
|
240
|
+
"name": "text_analyzer",
|
|
241
|
+
"call_id": "text_2",
|
|
242
|
+
"arguments": json.dumps(text_preset_input),
|
|
243
|
+
}
|
|
244
|
+
print(f"\nDetailed analyzer input (analysis_type preset to 'detailed'): {text_preset_input}")
|
|
245
|
+
result = await backend._execute_custom_tool(call)
|
|
246
|
+
print(f"Detailed analyzer result: {result}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def demo_from_file():
|
|
250
|
+
"""Demonstrate loading custom tools from Python files."""
|
|
251
|
+
print("\n" + "=" * 60)
|
|
252
|
+
print("LOADING CUSTOM TOOLS FROM FILES")
|
|
253
|
+
print("=" * 60)
|
|
254
|
+
|
|
255
|
+
# Create a temporary Python file with custom functions
|
|
256
|
+
custom_file = Path(__file__).parent / "my_custom_tools.py"
|
|
257
|
+
custom_file.write_text(
|
|
258
|
+
'''
|
|
259
|
+
def word_counter(text: str) -> str:
|
|
260
|
+
"""Count words in text."""
|
|
261
|
+
from massgen.tool import ExecutionResult
|
|
262
|
+
from massgen.tool._result import TextContent
|
|
263
|
+
|
|
264
|
+
word_count = len(text.split())
|
|
265
|
+
return ExecutionResult(
|
|
266
|
+
output_blocks=[TextContent(data=f"Word count: {word_count}")]
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def list_processor(items: list, action: str = "join") -> str:
|
|
270
|
+
"""Process a list of items."""
|
|
271
|
+
from massgen.tool import ExecutionResult
|
|
272
|
+
from massgen.tool._result import TextContent
|
|
273
|
+
|
|
274
|
+
if action == "join":
|
|
275
|
+
result = ", ".join(str(item) for item in items)
|
|
276
|
+
elif action == "reverse":
|
|
277
|
+
result = str(items[::-1])
|
|
278
|
+
elif action == "sort":
|
|
279
|
+
result = str(sorted(items))
|
|
280
|
+
else:
|
|
281
|
+
result = str(items)
|
|
282
|
+
|
|
283
|
+
return ExecutionResult(
|
|
284
|
+
output_blocks=[TextContent(data=f"Result: {result}")]
|
|
285
|
+
)
|
|
286
|
+
''',
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
backend = ResponseBackend(
|
|
291
|
+
api_key="test-key",
|
|
292
|
+
custom_tools=[
|
|
293
|
+
{
|
|
294
|
+
"path": str(custom_file),
|
|
295
|
+
"func": "word_counter", # Specific function
|
|
296
|
+
"description": "Count words in text",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"path": str(custom_file),
|
|
300
|
+
"func": "list_processor", # Another function from same file
|
|
301
|
+
"description": "Process lists",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
print(f"\nLoaded tools from {custom_file.name}:")
|
|
307
|
+
for tool_name in backend._custom_tool_names:
|
|
308
|
+
print(f" - {tool_name}")
|
|
309
|
+
|
|
310
|
+
# Test word counter
|
|
311
|
+
word_input = {"text": "This is a test sentence with seven words"}
|
|
312
|
+
call = {
|
|
313
|
+
"name": "word_counter",
|
|
314
|
+
"call_id": "wc_1",
|
|
315
|
+
"arguments": json.dumps(word_input),
|
|
316
|
+
}
|
|
317
|
+
print(f"\nWord counter input: {word_input}")
|
|
318
|
+
result = await backend._execute_custom_tool(call)
|
|
319
|
+
print(f"Word counter result: {result}")
|
|
320
|
+
|
|
321
|
+
# Test list processor
|
|
322
|
+
list_input = {
|
|
323
|
+
"items": [3, 1, 4, 1, 5, 9, 2, 6],
|
|
324
|
+
"action": "sort",
|
|
325
|
+
}
|
|
326
|
+
call = {
|
|
327
|
+
"name": "list_processor",
|
|
328
|
+
"call_id": "lp_1",
|
|
329
|
+
"arguments": json.dumps(list_input),
|
|
330
|
+
}
|
|
331
|
+
print(f"\nList processor input: {list_input}")
|
|
332
|
+
result = await backend._execute_custom_tool(call)
|
|
333
|
+
print(f"List processor result: {result}")
|
|
334
|
+
|
|
335
|
+
finally:
|
|
336
|
+
# Cleanup
|
|
337
|
+
if custom_file.exists():
|
|
338
|
+
custom_file.unlink()
|
|
339
|
+
print(f"\nCleaned up temporary file: {custom_file}")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
async def demo_builtin_tools():
|
|
343
|
+
"""Demonstrate using built-in tools from the tool module."""
|
|
344
|
+
print("\n" + "=" * 60)
|
|
345
|
+
print("USING BUILT-IN TOOLS")
|
|
346
|
+
print("=" * 60)
|
|
347
|
+
|
|
348
|
+
backend = ResponseBackend(
|
|
349
|
+
api_key="test-key",
|
|
350
|
+
custom_tools=[
|
|
351
|
+
{
|
|
352
|
+
"func": "read_file_content", # Built-in tool name
|
|
353
|
+
"description": "Read file content",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# This might fail if the built-in function isn't found
|
|
359
|
+
# but demonstrates the concept
|
|
360
|
+
if backend._custom_tool_names:
|
|
361
|
+
print(f"\nFound built-in tools: {backend._custom_tool_names}")
|
|
362
|
+
else:
|
|
363
|
+
print("\nNote: Built-in tools require the tool module to be properly set up")
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# ============================================================================
|
|
367
|
+
# Main execution
|
|
368
|
+
# ============================================================================
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
async def main():
|
|
372
|
+
"""Run all demos."""
|
|
373
|
+
try:
|
|
374
|
+
await demo_basic_usage()
|
|
375
|
+
await demo_with_presets()
|
|
376
|
+
await demo_from_file()
|
|
377
|
+
await demo_builtin_tools()
|
|
378
|
+
|
|
379
|
+
print("\n" + "=" * 60)
|
|
380
|
+
print("ALL DEMOS COMPLETED SUCCESSFULLY!")
|
|
381
|
+
print("=" * 60)
|
|
382
|
+
|
|
383
|
+
except Exception as e:
|
|
384
|
+
print(f"\nError during demo: {e}")
|
|
385
|
+
import traceback
|
|
386
|
+
|
|
387
|
+
traceback.print_exc()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
# Run the async main function
|
|
392
|
+
asyncio.run(main())
|
massgen/tests/mcp_test_server.py
CHANGED
|
@@ -10,7 +10,7 @@ It implements the MCP protocol over stdio transport.
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import json
|
|
12
12
|
import sys
|
|
13
|
-
from typing import Any, Dict
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class SimpleMCPServer:
|
|
@@ -86,12 +86,21 @@ class SimpleMCPServer:
|
|
|
86
86
|
else:
|
|
87
87
|
raise ValueError(f"Unknown tool: {tool_name}")
|
|
88
88
|
|
|
89
|
-
async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
|
90
|
-
"""Handle incoming JSON-RPC request."""
|
|
89
|
+
async def handle_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
90
|
+
"""Handle incoming JSON-RPC request or notification."""
|
|
91
91
|
method = request.get("method")
|
|
92
92
|
params = request.get("params", {})
|
|
93
93
|
request_id = request.get("id")
|
|
94
94
|
|
|
95
|
+
# If no id, this is a notification - don't send response
|
|
96
|
+
if request_id is None:
|
|
97
|
+
# Handle notifications silently (no response needed)
|
|
98
|
+
if method == "notifications/initialized":
|
|
99
|
+
# Client has finished initialization - no action needed
|
|
100
|
+
pass
|
|
101
|
+
# Add other notification handlers here if needed
|
|
102
|
+
return None
|
|
103
|
+
|
|
95
104
|
try:
|
|
96
105
|
if method == "initialize":
|
|
97
106
|
result = await self.handle_initialize(params)
|
|
@@ -126,10 +135,11 @@ class SimpleMCPServer:
|
|
|
126
135
|
# Handle request
|
|
127
136
|
response = await self.handle_request(request)
|
|
128
137
|
|
|
129
|
-
# Send response to stdout
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
# Send response to stdout (only if not a notification)
|
|
139
|
+
if response is not None:
|
|
140
|
+
json.dump(response, sys.stdout)
|
|
141
|
+
sys.stdout.write("\n")
|
|
142
|
+
sys.stdout.flush()
|
|
133
143
|
|
|
134
144
|
except KeyboardInterrupt:
|
|
135
145
|
break
|