amd-gaia 0.15.0__py3-none-any.whl → 0.15.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.
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +835 -835
- gaia/agents/chat/app.py +1058 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2036 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1974 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +173 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5632
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/llm/__init__.py +9 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3236 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +120 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +163 -163
- gaia/talk/app.py +289 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.15.0.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -723
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
gaia/mcp/agent_mcp_server.py
CHANGED
|
@@ -1,245 +1,245 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
Generic MCP Server for MCPAgent Subclasses
|
|
6
|
-
Wraps any MCPAgent and exposes it via the MCP Python SDK
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import io
|
|
10
|
-
import json
|
|
11
|
-
import sys
|
|
12
|
-
from typing import Any, Dict, Type
|
|
13
|
-
|
|
14
|
-
from mcp.server.fastmcp import FastMCP
|
|
15
|
-
|
|
16
|
-
from gaia.agents.base.mcp_agent import MCPAgent
|
|
17
|
-
from gaia.logger import get_logger
|
|
18
|
-
|
|
19
|
-
logger = get_logger(__name__)
|
|
20
|
-
|
|
21
|
-
# Default MCP server configuration
|
|
22
|
-
MCP_DEFAULT_PORT = 8080
|
|
23
|
-
MCP_DEFAULT_HOST = "localhost"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class AgentMCPServer:
|
|
27
|
-
"""Generic MCP server that wraps any MCPAgent subclass using the MCP SDK"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
agent_class: Type[MCPAgent],
|
|
32
|
-
name: str = None,
|
|
33
|
-
port: int = None,
|
|
34
|
-
host: str = None,
|
|
35
|
-
verbose: bool = False,
|
|
36
|
-
agent_params: Dict[str, Any] = None,
|
|
37
|
-
):
|
|
38
|
-
"""
|
|
39
|
-
Initialize MCP server for an agent.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
agent_class: MCPAgent subclass to wrap
|
|
43
|
-
name: Display name for the server
|
|
44
|
-
port: Port to listen on (default: 8080)
|
|
45
|
-
host: Host to bind to (default: localhost)
|
|
46
|
-
verbose: Enable verbose logging
|
|
47
|
-
agent_params: Parameters to pass to agent __init__
|
|
48
|
-
"""
|
|
49
|
-
# Verify agent_class is MCPAgent subclass
|
|
50
|
-
if not issubclass(agent_class, MCPAgent):
|
|
51
|
-
raise TypeError(f"{agent_class.__name__} must inherit from MCPAgent")
|
|
52
|
-
|
|
53
|
-
# Initialize agent
|
|
54
|
-
self.agent = agent_class(**(agent_params or {}))
|
|
55
|
-
self.agent_class = agent_class
|
|
56
|
-
|
|
57
|
-
# Server configuration
|
|
58
|
-
self.name = name or f"GAIA {agent_class.__name__} MCP"
|
|
59
|
-
self.port = port or MCP_DEFAULT_PORT
|
|
60
|
-
self.host = host or MCP_DEFAULT_HOST
|
|
61
|
-
self.verbose = verbose
|
|
62
|
-
|
|
63
|
-
# Create FastMCP server
|
|
64
|
-
server_info = self.agent.get_mcp_server_info()
|
|
65
|
-
self.mcp = FastMCP(name=server_info.get("name", self.name))
|
|
66
|
-
|
|
67
|
-
# Configure server settings (host, port)
|
|
68
|
-
self.mcp.settings.host = self.host
|
|
69
|
-
self.mcp.settings.port = self.port
|
|
70
|
-
|
|
71
|
-
# Register tools dynamically from agent
|
|
72
|
-
self._register_agent_tools()
|
|
73
|
-
|
|
74
|
-
def _register_agent_tools(self):
|
|
75
|
-
"""Dynamically register agent tools with FastMCP"""
|
|
76
|
-
tools = self.agent.get_mcp_tool_definitions()
|
|
77
|
-
|
|
78
|
-
for tool_def in tools:
|
|
79
|
-
tool_name = tool_def["name"]
|
|
80
|
-
tool_description = tool_def.get("description", "")
|
|
81
|
-
input_schema = tool_def.get("inputSchema", {})
|
|
82
|
-
|
|
83
|
-
# Create a wrapper function for this tool
|
|
84
|
-
# We need to capture tool_name in the closure properly
|
|
85
|
-
# NOTE: Using **kwargs means FastMCP won't validate parameters,
|
|
86
|
-
# allowing us to handle both standard and VSCode's kwargs format
|
|
87
|
-
def create_tool_wrapper(name: str, description: str, verbose: bool):
|
|
88
|
-
async def tool_wrapper(**kwargs) -> Dict[str, Any]:
|
|
89
|
-
"""Dynamically generated tool wrapper"""
|
|
90
|
-
if verbose:
|
|
91
|
-
logger.info("=" * 80)
|
|
92
|
-
logger.info(f"[MCP TOOL] Tool call: {name}")
|
|
93
|
-
logger.info(f"[MCP TOOL] Raw kwargs type: {type(kwargs)}")
|
|
94
|
-
logger.info(f"[MCP TOOL] Raw kwargs: {kwargs}")
|
|
95
|
-
try:
|
|
96
|
-
pretty_kwargs = json.dumps(kwargs, indent=2)
|
|
97
|
-
logger.info(
|
|
98
|
-
f"[MCP TOOL] Raw kwargs (pretty):\n{pretty_kwargs}"
|
|
99
|
-
)
|
|
100
|
-
except Exception as e:
|
|
101
|
-
logger.warning(
|
|
102
|
-
f"[MCP TOOL] Could not JSON format kwargs: {e}"
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
import time
|
|
107
|
-
|
|
108
|
-
start_time = time.time()
|
|
109
|
-
|
|
110
|
-
# Handle VSCode/Copilot kwargs wrapper format
|
|
111
|
-
# VSCode sends parameters wrapped in a "kwargs" field
|
|
112
|
-
# Can be either a dict object or stringified JSON:
|
|
113
|
-
# - {"kwargs": {"param": "value"}} <- dict format
|
|
114
|
-
# - {"kwargs": "{\"param\": \"value\"}"} <- string format
|
|
115
|
-
if "kwargs" in kwargs:
|
|
116
|
-
kwargs_value = kwargs["kwargs"]
|
|
117
|
-
|
|
118
|
-
if isinstance(kwargs_value, dict):
|
|
119
|
-
# Already a dict, just unwrap it
|
|
120
|
-
if verbose:
|
|
121
|
-
logger.info(
|
|
122
|
-
f"[MCP] Unwrapped kwargs dict: {kwargs_value}"
|
|
123
|
-
)
|
|
124
|
-
kwargs = kwargs_value
|
|
125
|
-
elif isinstance(kwargs_value, str):
|
|
126
|
-
# Stringified JSON, try to parse it
|
|
127
|
-
try:
|
|
128
|
-
parsed = json.loads(kwargs_value)
|
|
129
|
-
if verbose:
|
|
130
|
-
logger.info(
|
|
131
|
-
f"[MCP] Parsed stringified kwargs: {parsed}"
|
|
132
|
-
)
|
|
133
|
-
kwargs = parsed
|
|
134
|
-
except json.JSONDecodeError as parse_error:
|
|
135
|
-
logger.warning(
|
|
136
|
-
f"[MCP] Failed to parse kwargs string: {kwargs_value}, error: {parse_error}"
|
|
137
|
-
)
|
|
138
|
-
# Keep original kwargs if parsing fails
|
|
139
|
-
|
|
140
|
-
# Map common parameter variations to what agent expects
|
|
141
|
-
# VSCode may send different param names than agent expects
|
|
142
|
-
|
|
143
|
-
# Map VSCode's app_dir to Docker agent's appPath
|
|
144
|
-
if "app_dir" in kwargs and "appPath" not in kwargs:
|
|
145
|
-
kwargs["appPath"] = kwargs.pop("app_dir")
|
|
146
|
-
if verbose:
|
|
147
|
-
logger.info(f"[MCP] Mapped app_dir to appPath")
|
|
148
|
-
|
|
149
|
-
# Map other common variations
|
|
150
|
-
if "directory" in kwargs and "appPath" not in kwargs:
|
|
151
|
-
kwargs["appPath"] = kwargs.pop("directory")
|
|
152
|
-
if verbose:
|
|
153
|
-
logger.info(f"[MCP] Mapped directory to appPath")
|
|
154
|
-
|
|
155
|
-
if "project_path" in kwargs and "appPath" not in kwargs:
|
|
156
|
-
kwargs["appPath"] = kwargs.pop("project_path")
|
|
157
|
-
if verbose:
|
|
158
|
-
logger.info(f"[MCP] Mapped project_path to appPath")
|
|
159
|
-
|
|
160
|
-
if verbose:
|
|
161
|
-
logger.info(f"[MCP TOOL] Final args to agent:")
|
|
162
|
-
try:
|
|
163
|
-
pretty_final = json.dumps(kwargs, indent=2)
|
|
164
|
-
logger.info(f"{pretty_final}")
|
|
165
|
-
except Exception:
|
|
166
|
-
logger.info(f"{kwargs}")
|
|
167
|
-
logger.info("=" * 80)
|
|
168
|
-
|
|
169
|
-
result = self.agent.execute_mcp_tool(name, kwargs)
|
|
170
|
-
|
|
171
|
-
elapsed = time.time() - start_time
|
|
172
|
-
if verbose:
|
|
173
|
-
logger.info(
|
|
174
|
-
f"[MCP TOOL] Tool {name} completed in {elapsed:.2f}s"
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
return result
|
|
178
|
-
except Exception as e:
|
|
179
|
-
logger.error(f"[MCP] Error executing tool {name}: {e}")
|
|
180
|
-
if verbose:
|
|
181
|
-
import traceback
|
|
182
|
-
|
|
183
|
-
logger.error(f"[MCP] Traceback: {traceback.format_exc()}")
|
|
184
|
-
return {"error": str(e), "success": False}
|
|
185
|
-
|
|
186
|
-
# Set proper metadata
|
|
187
|
-
tool_wrapper.__name__ = name
|
|
188
|
-
tool_wrapper.__doc__ = description
|
|
189
|
-
|
|
190
|
-
return tool_wrapper
|
|
191
|
-
|
|
192
|
-
# Create the tool function
|
|
193
|
-
tool_func = create_tool_wrapper(tool_name, tool_description, self.verbose)
|
|
194
|
-
|
|
195
|
-
# Register using FastMCP's decorator API
|
|
196
|
-
# This ensures proper registration using the public API
|
|
197
|
-
self.mcp.tool()(tool_func)
|
|
198
|
-
|
|
199
|
-
if self.verbose:
|
|
200
|
-
logger.info(f"Registered tool: {tool_name}")
|
|
201
|
-
|
|
202
|
-
def start(self):
|
|
203
|
-
"""Start the MCP server with Streamable HTTP transport"""
|
|
204
|
-
self._print_startup_info()
|
|
205
|
-
|
|
206
|
-
try:
|
|
207
|
-
# Run with streamable-http transport (industry standard)
|
|
208
|
-
# This automatically serves at /mcp endpoint
|
|
209
|
-
# Supports both HTTP POST and SSE streaming
|
|
210
|
-
# Host and port are configured via mcp.settings
|
|
211
|
-
self.mcp.run(transport="streamable-http")
|
|
212
|
-
except KeyboardInterrupt:
|
|
213
|
-
print("\n✅ Server stopped")
|
|
214
|
-
|
|
215
|
-
def stop(self):
|
|
216
|
-
"""Stop the MCP server"""
|
|
217
|
-
# Note: With uvicorn, stopping is handled by KeyboardInterrupt
|
|
218
|
-
# This method is kept for API compatibility
|
|
219
|
-
pass
|
|
220
|
-
|
|
221
|
-
def _print_startup_info(self):
|
|
222
|
-
"""Print startup banner"""
|
|
223
|
-
# Fix Windows Unicode
|
|
224
|
-
if sys.platform == "win32":
|
|
225
|
-
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
226
|
-
|
|
227
|
-
tools = self.agent.get_mcp_tool_definitions()
|
|
228
|
-
|
|
229
|
-
print("=" * 60)
|
|
230
|
-
print(f"🚀 {self.name}")
|
|
231
|
-
print("=" * 60)
|
|
232
|
-
print(f"Server: http://{self.host}:{self.port}")
|
|
233
|
-
print(f"Agent: {self.agent_class.__name__}")
|
|
234
|
-
print(f"Tools: {len(tools)}")
|
|
235
|
-
for tool in tools:
|
|
236
|
-
print(f" - {tool['name']}: {tool.get('description', 'No description')}")
|
|
237
|
-
if self.verbose:
|
|
238
|
-
print(f"\n🔍 Verbose Mode: ENABLED")
|
|
239
|
-
print("\n📍 MCP Endpoint:")
|
|
240
|
-
print(f" http://{self.host}:{self.port}/mcp")
|
|
241
|
-
print("\n Supports:")
|
|
242
|
-
print(" - HTTP POST for requests")
|
|
243
|
-
print(" - SSE streaming for real-time responses")
|
|
244
|
-
print("=" * 60)
|
|
245
|
-
print("\nPress Ctrl+C to stop\n")
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Generic MCP Server for MCPAgent Subclasses
|
|
6
|
+
Wraps any MCPAgent and exposes it via the MCP Python SDK
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any, Dict, Type
|
|
13
|
+
|
|
14
|
+
from mcp.server.fastmcp import FastMCP
|
|
15
|
+
|
|
16
|
+
from gaia.agents.base.mcp_agent import MCPAgent
|
|
17
|
+
from gaia.logger import get_logger
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
# Default MCP server configuration
|
|
22
|
+
MCP_DEFAULT_PORT = 8080
|
|
23
|
+
MCP_DEFAULT_HOST = "localhost"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AgentMCPServer:
|
|
27
|
+
"""Generic MCP server that wraps any MCPAgent subclass using the MCP SDK"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
agent_class: Type[MCPAgent],
|
|
32
|
+
name: str = None,
|
|
33
|
+
port: int = None,
|
|
34
|
+
host: str = None,
|
|
35
|
+
verbose: bool = False,
|
|
36
|
+
agent_params: Dict[str, Any] = None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Initialize MCP server for an agent.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
agent_class: MCPAgent subclass to wrap
|
|
43
|
+
name: Display name for the server
|
|
44
|
+
port: Port to listen on (default: 8080)
|
|
45
|
+
host: Host to bind to (default: localhost)
|
|
46
|
+
verbose: Enable verbose logging
|
|
47
|
+
agent_params: Parameters to pass to agent __init__
|
|
48
|
+
"""
|
|
49
|
+
# Verify agent_class is MCPAgent subclass
|
|
50
|
+
if not issubclass(agent_class, MCPAgent):
|
|
51
|
+
raise TypeError(f"{agent_class.__name__} must inherit from MCPAgent")
|
|
52
|
+
|
|
53
|
+
# Initialize agent
|
|
54
|
+
self.agent = agent_class(**(agent_params or {}))
|
|
55
|
+
self.agent_class = agent_class
|
|
56
|
+
|
|
57
|
+
# Server configuration
|
|
58
|
+
self.name = name or f"GAIA {agent_class.__name__} MCP"
|
|
59
|
+
self.port = port or MCP_DEFAULT_PORT
|
|
60
|
+
self.host = host or MCP_DEFAULT_HOST
|
|
61
|
+
self.verbose = verbose
|
|
62
|
+
|
|
63
|
+
# Create FastMCP server
|
|
64
|
+
server_info = self.agent.get_mcp_server_info()
|
|
65
|
+
self.mcp = FastMCP(name=server_info.get("name", self.name))
|
|
66
|
+
|
|
67
|
+
# Configure server settings (host, port)
|
|
68
|
+
self.mcp.settings.host = self.host
|
|
69
|
+
self.mcp.settings.port = self.port
|
|
70
|
+
|
|
71
|
+
# Register tools dynamically from agent
|
|
72
|
+
self._register_agent_tools()
|
|
73
|
+
|
|
74
|
+
def _register_agent_tools(self):
|
|
75
|
+
"""Dynamically register agent tools with FastMCP"""
|
|
76
|
+
tools = self.agent.get_mcp_tool_definitions()
|
|
77
|
+
|
|
78
|
+
for tool_def in tools:
|
|
79
|
+
tool_name = tool_def["name"]
|
|
80
|
+
tool_description = tool_def.get("description", "")
|
|
81
|
+
input_schema = tool_def.get("inputSchema", {})
|
|
82
|
+
|
|
83
|
+
# Create a wrapper function for this tool
|
|
84
|
+
# We need to capture tool_name in the closure properly
|
|
85
|
+
# NOTE: Using **kwargs means FastMCP won't validate parameters,
|
|
86
|
+
# allowing us to handle both standard and VSCode's kwargs format
|
|
87
|
+
def create_tool_wrapper(name: str, description: str, verbose: bool):
|
|
88
|
+
async def tool_wrapper(**kwargs) -> Dict[str, Any]:
|
|
89
|
+
"""Dynamically generated tool wrapper"""
|
|
90
|
+
if verbose:
|
|
91
|
+
logger.info("=" * 80)
|
|
92
|
+
logger.info(f"[MCP TOOL] Tool call: {name}")
|
|
93
|
+
logger.info(f"[MCP TOOL] Raw kwargs type: {type(kwargs)}")
|
|
94
|
+
logger.info(f"[MCP TOOL] Raw kwargs: {kwargs}")
|
|
95
|
+
try:
|
|
96
|
+
pretty_kwargs = json.dumps(kwargs, indent=2)
|
|
97
|
+
logger.info(
|
|
98
|
+
f"[MCP TOOL] Raw kwargs (pretty):\n{pretty_kwargs}"
|
|
99
|
+
)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.warning(
|
|
102
|
+
f"[MCP TOOL] Could not JSON format kwargs: {e}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
import time
|
|
107
|
+
|
|
108
|
+
start_time = time.time()
|
|
109
|
+
|
|
110
|
+
# Handle VSCode/Copilot kwargs wrapper format
|
|
111
|
+
# VSCode sends parameters wrapped in a "kwargs" field
|
|
112
|
+
# Can be either a dict object or stringified JSON:
|
|
113
|
+
# - {"kwargs": {"param": "value"}} <- dict format
|
|
114
|
+
# - {"kwargs": "{\"param\": \"value\"}"} <- string format
|
|
115
|
+
if "kwargs" in kwargs:
|
|
116
|
+
kwargs_value = kwargs["kwargs"]
|
|
117
|
+
|
|
118
|
+
if isinstance(kwargs_value, dict):
|
|
119
|
+
# Already a dict, just unwrap it
|
|
120
|
+
if verbose:
|
|
121
|
+
logger.info(
|
|
122
|
+
f"[MCP] Unwrapped kwargs dict: {kwargs_value}"
|
|
123
|
+
)
|
|
124
|
+
kwargs = kwargs_value
|
|
125
|
+
elif isinstance(kwargs_value, str):
|
|
126
|
+
# Stringified JSON, try to parse it
|
|
127
|
+
try:
|
|
128
|
+
parsed = json.loads(kwargs_value)
|
|
129
|
+
if verbose:
|
|
130
|
+
logger.info(
|
|
131
|
+
f"[MCP] Parsed stringified kwargs: {parsed}"
|
|
132
|
+
)
|
|
133
|
+
kwargs = parsed
|
|
134
|
+
except json.JSONDecodeError as parse_error:
|
|
135
|
+
logger.warning(
|
|
136
|
+
f"[MCP] Failed to parse kwargs string: {kwargs_value}, error: {parse_error}"
|
|
137
|
+
)
|
|
138
|
+
# Keep original kwargs if parsing fails
|
|
139
|
+
|
|
140
|
+
# Map common parameter variations to what agent expects
|
|
141
|
+
# VSCode may send different param names than agent expects
|
|
142
|
+
|
|
143
|
+
# Map VSCode's app_dir to Docker agent's appPath
|
|
144
|
+
if "app_dir" in kwargs and "appPath" not in kwargs:
|
|
145
|
+
kwargs["appPath"] = kwargs.pop("app_dir")
|
|
146
|
+
if verbose:
|
|
147
|
+
logger.info(f"[MCP] Mapped app_dir to appPath")
|
|
148
|
+
|
|
149
|
+
# Map other common variations
|
|
150
|
+
if "directory" in kwargs and "appPath" not in kwargs:
|
|
151
|
+
kwargs["appPath"] = kwargs.pop("directory")
|
|
152
|
+
if verbose:
|
|
153
|
+
logger.info(f"[MCP] Mapped directory to appPath")
|
|
154
|
+
|
|
155
|
+
if "project_path" in kwargs and "appPath" not in kwargs:
|
|
156
|
+
kwargs["appPath"] = kwargs.pop("project_path")
|
|
157
|
+
if verbose:
|
|
158
|
+
logger.info(f"[MCP] Mapped project_path to appPath")
|
|
159
|
+
|
|
160
|
+
if verbose:
|
|
161
|
+
logger.info(f"[MCP TOOL] Final args to agent:")
|
|
162
|
+
try:
|
|
163
|
+
pretty_final = json.dumps(kwargs, indent=2)
|
|
164
|
+
logger.info(f"{pretty_final}")
|
|
165
|
+
except Exception:
|
|
166
|
+
logger.info(f"{kwargs}")
|
|
167
|
+
logger.info("=" * 80)
|
|
168
|
+
|
|
169
|
+
result = self.agent.execute_mcp_tool(name, kwargs)
|
|
170
|
+
|
|
171
|
+
elapsed = time.time() - start_time
|
|
172
|
+
if verbose:
|
|
173
|
+
logger.info(
|
|
174
|
+
f"[MCP TOOL] Tool {name} completed in {elapsed:.2f}s"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return result
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(f"[MCP] Error executing tool {name}: {e}")
|
|
180
|
+
if verbose:
|
|
181
|
+
import traceback
|
|
182
|
+
|
|
183
|
+
logger.error(f"[MCP] Traceback: {traceback.format_exc()}")
|
|
184
|
+
return {"error": str(e), "success": False}
|
|
185
|
+
|
|
186
|
+
# Set proper metadata
|
|
187
|
+
tool_wrapper.__name__ = name
|
|
188
|
+
tool_wrapper.__doc__ = description
|
|
189
|
+
|
|
190
|
+
return tool_wrapper
|
|
191
|
+
|
|
192
|
+
# Create the tool function
|
|
193
|
+
tool_func = create_tool_wrapper(tool_name, tool_description, self.verbose)
|
|
194
|
+
|
|
195
|
+
# Register using FastMCP's decorator API
|
|
196
|
+
# This ensures proper registration using the public API
|
|
197
|
+
self.mcp.tool()(tool_func)
|
|
198
|
+
|
|
199
|
+
if self.verbose:
|
|
200
|
+
logger.info(f"Registered tool: {tool_name}")
|
|
201
|
+
|
|
202
|
+
def start(self):
|
|
203
|
+
"""Start the MCP server with Streamable HTTP transport"""
|
|
204
|
+
self._print_startup_info()
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
# Run with streamable-http transport (industry standard)
|
|
208
|
+
# This automatically serves at /mcp endpoint
|
|
209
|
+
# Supports both HTTP POST and SSE streaming
|
|
210
|
+
# Host and port are configured via mcp.settings
|
|
211
|
+
self.mcp.run(transport="streamable-http")
|
|
212
|
+
except KeyboardInterrupt:
|
|
213
|
+
print("\n✅ Server stopped")
|
|
214
|
+
|
|
215
|
+
def stop(self):
|
|
216
|
+
"""Stop the MCP server"""
|
|
217
|
+
# Note: With uvicorn, stopping is handled by KeyboardInterrupt
|
|
218
|
+
# This method is kept for API compatibility
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
def _print_startup_info(self):
|
|
222
|
+
"""Print startup banner"""
|
|
223
|
+
# Fix Windows Unicode
|
|
224
|
+
if sys.platform == "win32":
|
|
225
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
226
|
+
|
|
227
|
+
tools = self.agent.get_mcp_tool_definitions()
|
|
228
|
+
|
|
229
|
+
print("=" * 60)
|
|
230
|
+
print(f"🚀 {self.name}")
|
|
231
|
+
print("=" * 60)
|
|
232
|
+
print(f"Server: http://{self.host}:{self.port}")
|
|
233
|
+
print(f"Agent: {self.agent_class.__name__}")
|
|
234
|
+
print(f"Tools: {len(tools)}")
|
|
235
|
+
for tool in tools:
|
|
236
|
+
print(f" - {tool['name']}: {tool.get('description', 'No description')}")
|
|
237
|
+
if self.verbose:
|
|
238
|
+
print(f"\n🔍 Verbose Mode: ENABLED")
|
|
239
|
+
print("\n📍 MCP Endpoint:")
|
|
240
|
+
print(f" http://{self.host}:{self.port}/mcp")
|
|
241
|
+
print("\n Supports:")
|
|
242
|
+
print(" - HTTP POST for requests")
|
|
243
|
+
print(" - SSE streaming for real-time responses")
|
|
244
|
+
print("=" * 60)
|
|
245
|
+
print("\nPress Ctrl+C to stop\n")
|