agent-mcp 0.1.3__py3-none-any.whl → 0.1.5__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.
- agent_mcp/__init__.py +66 -12
- agent_mcp/a2a_protocol.py +316 -0
- agent_mcp/agent_lightning_library.py +214 -0
- agent_mcp/camel_mcp_adapter.py +521 -0
- agent_mcp/claude_mcp_adapter.py +195 -0
- agent_mcp/cli.py +47 -0
- agent_mcp/google_ai_mcp_adapter.py +183 -0
- agent_mcp/heterogeneous_group_chat.py +412 -38
- agent_mcp/langchain_mcp_adapter.py +176 -43
- agent_mcp/llamaindex_mcp_adapter.py +410 -0
- agent_mcp/mcp_agent.py +26 -0
- agent_mcp/mcp_transport.py +11 -5
- agent_mcp/microsoft_agent_framework.py +591 -0
- agent_mcp/missing_frameworks.py +435 -0
- agent_mcp/openapi_protocol.py +616 -0
- agent_mcp/payments.py +804 -0
- agent_mcp/pydantic_ai_mcp_adapter.py +628 -0
- agent_mcp/registry.py +768 -0
- agent_mcp/security.py +864 -0
- {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.5.dist-info}/METADATA +173 -49
- agent_mcp-0.1.5.dist-info/RECORD +62 -0
- {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.5.dist-info}/WHEEL +1 -1
- agent_mcp-0.1.5.dist-info/entry_points.txt +4 -0
- agent_mcp-0.1.5.dist-info/top_level.txt +3 -0
- demos/__init__.py +1 -0
- demos/basic/__init__.py +1 -0
- demos/basic/framework_examples.py +108 -0
- demos/basic/langchain_camel_demo.py +272 -0
- demos/basic/simple_chat.py +355 -0
- demos/basic/simple_integration_example.py +51 -0
- demos/collaboration/collaborative_task_example.py +437 -0
- demos/collaboration/group_chat_example.py +130 -0
- demos/collaboration/simplified_crewai_example.py +39 -0
- demos/comprehensive_framework_demo.py +202 -0
- demos/langgraph/autonomous_langgraph_network.py +808 -0
- demos/langgraph/langgraph_agent_network.py +415 -0
- demos/langgraph/langgraph_collaborative_task.py +619 -0
- demos/langgraph/langgraph_example.py +227 -0
- demos/langgraph/run_langgraph_examples.py +213 -0
- demos/network/agent_network_example.py +381 -0
- demos/network/email_agent.py +130 -0
- demos/network/email_agent_demo.py +46 -0
- demos/network/heterogeneous_network_example.py +216 -0
- demos/network/multi_framework_example.py +199 -0
- demos/utils/check_imports.py +49 -0
- demos/workflows/autonomous_agent_workflow.py +248 -0
- demos/workflows/mcp_features_demo.py +353 -0
- demos/workflows/run_agent_collaboration_demo.py +63 -0
- demos/workflows/run_agent_collaboration_with_logs.py +396 -0
- demos/workflows/show_agent_interactions.py +107 -0
- demos/workflows/simplified_autonomous_demo.py +74 -0
- functions/main.py +144 -0
- functions/mcp_network_server.py +513 -0
- functions/utils.py +47 -0
- agent_mcp-0.1.3.dist-info/RECORD +0 -18
- agent_mcp-0.1.3.dist-info/entry_points.txt +0 -2
- agent_mcp-0.1.3.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAPI Protocol Support for AgentMCP
|
|
3
|
+
REST API discovery and standardization for agent tools
|
|
4
|
+
|
|
5
|
+
This module provides OpenAPI 3.0 specification generation and handling,
|
|
6
|
+
enabling agents to expose their capabilities through standard REST APIs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import uuid
|
|
11
|
+
import inspect
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Dict, Any, List, Optional, Callable, Union
|
|
14
|
+
from dataclasses import dataclass, asdict
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class OpenAPIInfo:
|
|
21
|
+
"""OpenAPI info object"""
|
|
22
|
+
title: str
|
|
23
|
+
version: str
|
|
24
|
+
description: str
|
|
25
|
+
contact: Dict[str, Any] = None
|
|
26
|
+
license: Dict[str, Any] = None
|
|
27
|
+
|
|
28
|
+
def __post_init__(self):
|
|
29
|
+
if self.contact is None:
|
|
30
|
+
self.contact = {}
|
|
31
|
+
if self.license is None:
|
|
32
|
+
self.license = {}
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class OpenAPIPath:
|
|
36
|
+
"""OpenAPI path object"""
|
|
37
|
+
path: str
|
|
38
|
+
method: str
|
|
39
|
+
operation_id: str
|
|
40
|
+
summary: str
|
|
41
|
+
description: str
|
|
42
|
+
parameters: List[Dict[str, Any]] = None
|
|
43
|
+
responses: Dict[str, Any] = None
|
|
44
|
+
tags: List[str] = None
|
|
45
|
+
|
|
46
|
+
def __post_init__(self):
|
|
47
|
+
if self.parameters is None:
|
|
48
|
+
self.parameters = []
|
|
49
|
+
if self.responses is None:
|
|
50
|
+
self.responses = {}
|
|
51
|
+
if self.tags is None:
|
|
52
|
+
self.tags = []
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class OpenAPISchema:
|
|
56
|
+
"""OpenAPI schema definition"""
|
|
57
|
+
type: str
|
|
58
|
+
properties: Dict[str, Any] = None
|
|
59
|
+
required: List[str] = None
|
|
60
|
+
items: Dict[str, Any] = None
|
|
61
|
+
description: str = ""
|
|
62
|
+
|
|
63
|
+
def __post_init__(self):
|
|
64
|
+
if self.properties is None:
|
|
65
|
+
self.properties = {}
|
|
66
|
+
if self.required is None:
|
|
67
|
+
self.required = []
|
|
68
|
+
if self.items is None:
|
|
69
|
+
self.items = {}
|
|
70
|
+
|
|
71
|
+
class OpenAPIGenerator:
|
|
72
|
+
"""Generate OpenAPI 3.0 specifications from agent capabilities"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, agent_name: str, agent_description: str = ""):
|
|
75
|
+
self.agent_name = agent_name
|
|
76
|
+
self.agent_description = agent_description
|
|
77
|
+
self.paths = []
|
|
78
|
+
self.schemas = {}
|
|
79
|
+
self.tags = []
|
|
80
|
+
|
|
81
|
+
def add_tool_from_function(
|
|
82
|
+
self,
|
|
83
|
+
func: Callable,
|
|
84
|
+
path: str = None,
|
|
85
|
+
method: str = "POST",
|
|
86
|
+
tags: List[str] = None
|
|
87
|
+
):
|
|
88
|
+
"""Add a tool as an OpenAPI path"""
|
|
89
|
+
try:
|
|
90
|
+
# Get function signature
|
|
91
|
+
sig = inspect.signature(func)
|
|
92
|
+
func_name = func.__name__
|
|
93
|
+
|
|
94
|
+
# Generate path if not provided
|
|
95
|
+
if not path:
|
|
96
|
+
path = f"/{func_name}"
|
|
97
|
+
|
|
98
|
+
# Extract parameters from function signature
|
|
99
|
+
parameters = []
|
|
100
|
+
required_params = []
|
|
101
|
+
|
|
102
|
+
for param_name, param in sig.parameters.items():
|
|
103
|
+
if param_name in ('self', 'ctx'):
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
param_info = self._analyze_parameter(param_name, param)
|
|
107
|
+
parameters.append(param_info)
|
|
108
|
+
|
|
109
|
+
if param.default == inspect.Parameter.empty:
|
|
110
|
+
required_params.append(param_name)
|
|
111
|
+
|
|
112
|
+
# Create schema for request body
|
|
113
|
+
request_schema = None
|
|
114
|
+
if method.upper() in ['POST', 'PUT', 'PATCH'] and parameters:
|
|
115
|
+
request_schema = OpenAPISchema(
|
|
116
|
+
type="object",
|
|
117
|
+
properties={p["name"]: p["schema"] for p in parameters},
|
|
118
|
+
required=required_params,
|
|
119
|
+
description=f"Request body for {func_name}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Create response schemas
|
|
123
|
+
responses = {
|
|
124
|
+
"200": {
|
|
125
|
+
"description": f"Successful response from {func_name}",
|
|
126
|
+
"content": {
|
|
127
|
+
"application/json": {
|
|
128
|
+
"schema": self._create_response_schema(func)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Add error responses
|
|
135
|
+
responses.update({
|
|
136
|
+
"400": {"description": "Bad request - invalid parameters"},
|
|
137
|
+
"500": {"description": "Internal server error"}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
# Convert parameters to OpenAPI format
|
|
141
|
+
openapi_params = []
|
|
142
|
+
for param in parameters:
|
|
143
|
+
openapi_param = {
|
|
144
|
+
"name": param["name"],
|
|
145
|
+
"in": "body" if method.upper() in ['POST', 'PUT', 'PATCH'] else "query",
|
|
146
|
+
"description": param["description"],
|
|
147
|
+
"required": param["required"],
|
|
148
|
+
"schema": param["schema"]
|
|
149
|
+
}
|
|
150
|
+
openapi_params.append(openapi_param)
|
|
151
|
+
|
|
152
|
+
# Create path object
|
|
153
|
+
path_obj = OpenAPIPath(
|
|
154
|
+
path=path,
|
|
155
|
+
method=method.upper(),
|
|
156
|
+
operation_id=func_name,
|
|
157
|
+
summary=self._get_function_summary(func),
|
|
158
|
+
description=func.__doc__ or f"Execute {func_name}",
|
|
159
|
+
parameters=openapi_params,
|
|
160
|
+
responses=responses,
|
|
161
|
+
tags=tags or ["tools"]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
self.paths.append(path_obj)
|
|
165
|
+
|
|
166
|
+
# Store schema for request body
|
|
167
|
+
if request_schema:
|
|
168
|
+
self.schemas[f"{func_name}Request"] = request_schema
|
|
169
|
+
|
|
170
|
+
logger.info(f"Added OpenAPI path: {method} {path}")
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"Error adding tool {func.__name__} to OpenAPI: {e}")
|
|
174
|
+
|
|
175
|
+
def add_mcp_tools_as_paths(self, mcp_tools: Dict[str, Any]):
|
|
176
|
+
"""Add MCP tools as OpenAPI paths"""
|
|
177
|
+
for tool_name, tool_info in mcp_tools.items():
|
|
178
|
+
# Create a wrapper function for the tool
|
|
179
|
+
def tool_wrapper(**kwargs):
|
|
180
|
+
return {"tool": tool_name, "args": kwargs}
|
|
181
|
+
|
|
182
|
+
# Add as OpenAPI path
|
|
183
|
+
self.add_tool_from_function(
|
|
184
|
+
func=tool_wrapper,
|
|
185
|
+
path=f"/tools/{tool_name}",
|
|
186
|
+
method="POST",
|
|
187
|
+
tags=["mcp", "tools"]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def add_agent_info_paths(self, agent_id: str, agent_info: Dict[str, Any]):
|
|
191
|
+
"""Add standard agent information paths"""
|
|
192
|
+
|
|
193
|
+
# GET /agent/info
|
|
194
|
+
def get_agent_info():
|
|
195
|
+
return {
|
|
196
|
+
"agent_id": agent_id,
|
|
197
|
+
"name": agent_info.get("name", agent_id),
|
|
198
|
+
"description": agent_info.get("description", ""),
|
|
199
|
+
"framework": agent_info.get("framework", "unknown"),
|
|
200
|
+
"capabilities": agent_info.get("capabilities", []),
|
|
201
|
+
"status": "active"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
self.add_tool_from_function(
|
|
205
|
+
func=get_agent_info,
|
|
206
|
+
path="/agent/info",
|
|
207
|
+
method="GET",
|
|
208
|
+
tags=["agent"]
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# GET /agent/health
|
|
212
|
+
def get_agent_health():
|
|
213
|
+
return {
|
|
214
|
+
"status": "healthy",
|
|
215
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
216
|
+
"agent_id": agent_id
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
self.add_tool_from_function(
|
|
220
|
+
func=get_agent_health,
|
|
221
|
+
path="/agent/health",
|
|
222
|
+
method="GET",
|
|
223
|
+
tags=["agent", "health"]
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# GET /agent/capabilities
|
|
227
|
+
def get_agent_capabilities():
|
|
228
|
+
return {
|
|
229
|
+
"agent_id": agent_id,
|
|
230
|
+
"capabilities": agent_info.get("capabilities", []),
|
|
231
|
+
"tools": list(self.schemas.keys())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
self.add_tool_from_function(
|
|
235
|
+
func=get_agent_capabilities,
|
|
236
|
+
path="/agent/capabilities",
|
|
237
|
+
method="GET",
|
|
238
|
+
tags=["agent", "capabilities"]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def _analyze_parameter(self, param_name: str, param) -> Dict[str, Any]:
|
|
242
|
+
"""Analyze a function parameter and create OpenAPI schema"""
|
|
243
|
+
param_type = "string"
|
|
244
|
+
param_format = None
|
|
245
|
+
param_enum = None
|
|
246
|
+
param_description = f"Parameter {param_name}"
|
|
247
|
+
|
|
248
|
+
# Try to get type from annotation
|
|
249
|
+
if param.annotation != inspect.Parameter.empty:
|
|
250
|
+
annotation_str = str(param.annotation)
|
|
251
|
+
|
|
252
|
+
if "int" in annotation_str.lower():
|
|
253
|
+
param_type = "integer"
|
|
254
|
+
elif "float" in annotation_str.lower() or "double" in annotation_str.lower():
|
|
255
|
+
param_type = "number"
|
|
256
|
+
elif "bool" in annotation_str.lower():
|
|
257
|
+
param_type = "boolean"
|
|
258
|
+
elif "list" in annotation_str.lower():
|
|
259
|
+
param_type = "array"
|
|
260
|
+
# Try to get item type
|
|
261
|
+
if hasattr(param.annotation, '__args__') and param.annotation.__args__:
|
|
262
|
+
item_type = str(param.annotation.__args__[0])
|
|
263
|
+
if "int" in item_type.lower():
|
|
264
|
+
param_items = {"type": "integer"}
|
|
265
|
+
elif "str" in item_type.lower():
|
|
266
|
+
param_items = {"type": "string"}
|
|
267
|
+
else:
|
|
268
|
+
param_items = {"type": "string"}
|
|
269
|
+
else:
|
|
270
|
+
param_items = {"type": "string"}
|
|
271
|
+
elif "dict" in annotation_str.lower():
|
|
272
|
+
param_type = "object"
|
|
273
|
+
|
|
274
|
+
# Check if parameter has default value
|
|
275
|
+
param_required = param.default == inspect.Parameter.empty
|
|
276
|
+
|
|
277
|
+
# Create schema
|
|
278
|
+
schema = {
|
|
279
|
+
"type": param_type,
|
|
280
|
+
"description": param_description
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if param_format:
|
|
284
|
+
schema["format"] = param_format
|
|
285
|
+
|
|
286
|
+
if param_enum:
|
|
287
|
+
schema["enum"] = param_enum
|
|
288
|
+
|
|
289
|
+
if param_type == "array" and 'param_items' in locals():
|
|
290
|
+
schema["items"] = locals()['param_items']
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
"name": param_name,
|
|
294
|
+
"description": param_description,
|
|
295
|
+
"required": param_required,
|
|
296
|
+
"type": param_type,
|
|
297
|
+
"schema": schema
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
def _create_response_schema(self, func: Callable) -> Dict[str, Any]:
|
|
301
|
+
"""Create response schema from function return annotation"""
|
|
302
|
+
try:
|
|
303
|
+
sig = inspect.signature(func)
|
|
304
|
+
return_annotation = sig.return_annotation
|
|
305
|
+
|
|
306
|
+
if return_annotation == inspect.Parameter.empty:
|
|
307
|
+
return {"type": "object"} # Default to object
|
|
308
|
+
|
|
309
|
+
annotation_str = str(return_annotation)
|
|
310
|
+
|
|
311
|
+
if "dict" in annotation_str.lower():
|
|
312
|
+
return {
|
|
313
|
+
"type": "object",
|
|
314
|
+
"additionalProperties": True
|
|
315
|
+
}
|
|
316
|
+
elif "list" in annotation_str.lower():
|
|
317
|
+
if hasattr(return_annotation, '__args__') and return_annotation.__args__:
|
|
318
|
+
item_type = str(return_annotation.__args__[0])
|
|
319
|
+
if "str" in item_type.lower():
|
|
320
|
+
return {"type": "array", "items": {"type": "string"}}
|
|
321
|
+
elif "int" in item_type.lower():
|
|
322
|
+
return {"type": "array", "items": {"type": "integer"}}
|
|
323
|
+
else:
|
|
324
|
+
return {"type": "array", "items": {"type": "object"}}
|
|
325
|
+
else:
|
|
326
|
+
return {"type": "array", "items": {"type": "object"}}
|
|
327
|
+
elif "str" in annotation_str.lower():
|
|
328
|
+
return {"type": "string"}
|
|
329
|
+
elif "int" in annotation_str.lower():
|
|
330
|
+
return {"type": "integer"}
|
|
331
|
+
elif "bool" in annotation_str.lower():
|
|
332
|
+
return {"type": "boolean"}
|
|
333
|
+
else:
|
|
334
|
+
return {"type": "object"}
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Error creating response schema: {e}")
|
|
338
|
+
return {"type": "object"}
|
|
339
|
+
|
|
340
|
+
def _get_function_summary(self, func: Callable) -> str:
|
|
341
|
+
"""Get a summary for a function"""
|
|
342
|
+
func_name = func.__name__
|
|
343
|
+
|
|
344
|
+
# Convert snake_case to Title Case
|
|
345
|
+
return ' '.join(word.capitalize() for word in func_name.split('_'))
|
|
346
|
+
|
|
347
|
+
def generate_openapi_spec(self, servers: List[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
348
|
+
"""Generate complete OpenAPI 3.0 specification"""
|
|
349
|
+
|
|
350
|
+
# Create info object
|
|
351
|
+
info = OpenAPIInfo(
|
|
352
|
+
title=self.agent_name,
|
|
353
|
+
version="1.0.0",
|
|
354
|
+
description=self.agent_description or f"OpenAPI specification for {self.agent_name}",
|
|
355
|
+
contact={
|
|
356
|
+
"name": "AgentMCP",
|
|
357
|
+
"url": "https://github.com/agentmcp"
|
|
358
|
+
},
|
|
359
|
+
license={
|
|
360
|
+
"name": "MIT",
|
|
361
|
+
"url": "https://opensource.org/licenses/MIT"
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Group paths by path
|
|
366
|
+
paths = {}
|
|
367
|
+
for path_obj in self.paths:
|
|
368
|
+
if path_obj.path not in paths:
|
|
369
|
+
paths[path_obj.path] = {}
|
|
370
|
+
|
|
371
|
+
paths[path_obj.path][path_obj.method.lower()] = {
|
|
372
|
+
"operationId": path_obj.operation_id,
|
|
373
|
+
"summary": path_obj.summary,
|
|
374
|
+
"description": path_obj.description,
|
|
375
|
+
"tags": path_obj.tags,
|
|
376
|
+
"parameters": path_obj.parameters,
|
|
377
|
+
"responses": path_obj.responses
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# Default servers
|
|
381
|
+
if not servers:
|
|
382
|
+
servers = [
|
|
383
|
+
{
|
|
384
|
+
"url": "http://localhost:8000",
|
|
385
|
+
"description": "Development server"
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
|
|
389
|
+
# Create tags
|
|
390
|
+
tags_set = set()
|
|
391
|
+
for path_obj in self.paths:
|
|
392
|
+
tags_set.update(path_obj.tags)
|
|
393
|
+
|
|
394
|
+
tags = [
|
|
395
|
+
{"name": tag, "description": f"Operations related to {tag}"}
|
|
396
|
+
for tag in sorted(list(tags_set))
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
# Complete OpenAPI spec
|
|
400
|
+
spec = {
|
|
401
|
+
"openapi": "3.0.0",
|
|
402
|
+
"info": asdict(info),
|
|
403
|
+
"servers": servers,
|
|
404
|
+
"paths": paths,
|
|
405
|
+
"tags": tags,
|
|
406
|
+
"components": {
|
|
407
|
+
"schemas": self.schemas
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return spec
|
|
412
|
+
|
|
413
|
+
class OpenAPIServer:
|
|
414
|
+
"""Serve OpenAPI specification and handle requests"""
|
|
415
|
+
|
|
416
|
+
def __init__(
|
|
417
|
+
self,
|
|
418
|
+
agent_id: str,
|
|
419
|
+
agent_info: Dict[str, Any],
|
|
420
|
+
mcp_tools: Dict[str, Any] = None,
|
|
421
|
+
host: str = "0.0.0.0",
|
|
422
|
+
port: int = 8080
|
|
423
|
+
):
|
|
424
|
+
self.agent_id = agent_id
|
|
425
|
+
self.agent_info = agent_info
|
|
426
|
+
self.mcp_tools = mcp_tools or {}
|
|
427
|
+
self.host = host
|
|
428
|
+
self.port = port
|
|
429
|
+
|
|
430
|
+
# Generate OpenAPI spec
|
|
431
|
+
self.generator = OpenAPIGenerator(
|
|
432
|
+
agent_name=agent_info.get("name", agent_id),
|
|
433
|
+
agent_description=agent_info.get("description", "")
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Add agent info paths
|
|
437
|
+
self.generator.add_agent_info_paths(agent_id, agent_info)
|
|
438
|
+
|
|
439
|
+
# Add MCP tools as paths
|
|
440
|
+
if self.mcp_tools:
|
|
441
|
+
self.generator.add_mcp_tools_as_paths(self.mcp_tools)
|
|
442
|
+
|
|
443
|
+
# Generate the spec
|
|
444
|
+
self.openapi_spec = self.generator.generate_openapi_spec([
|
|
445
|
+
{"url": f"http://{host}:{port}", "description": "Development server"}
|
|
446
|
+
])
|
|
447
|
+
|
|
448
|
+
def get_spec_json(self) -> str:
|
|
449
|
+
"""Get OpenAPI specification as JSON"""
|
|
450
|
+
return json.dumps(self.openapi_spec, indent=2)
|
|
451
|
+
|
|
452
|
+
def get_spec_yaml(self) -> str:
|
|
453
|
+
"""Get OpenAPI specification as YAML"""
|
|
454
|
+
try:
|
|
455
|
+
import yaml
|
|
456
|
+
return yaml.dump(self.openapi_spec, default_flow_style=False)
|
|
457
|
+
except ImportError:
|
|
458
|
+
logger.warning("PyYAML not available, returning JSON")
|
|
459
|
+
return self.get_spec_json()
|
|
460
|
+
|
|
461
|
+
async def handle_openapi_request(
|
|
462
|
+
self,
|
|
463
|
+
method: str,
|
|
464
|
+
path: str,
|
|
465
|
+
query_params: Dict[str, str] = None,
|
|
466
|
+
body: Dict[str, Any] = None
|
|
467
|
+
) -> Dict[str, Any]:
|
|
468
|
+
"""Handle a request to the OpenAPI endpoints"""
|
|
469
|
+
try:
|
|
470
|
+
# Normalize path
|
|
471
|
+
if not path.startswith('/'):
|
|
472
|
+
path = '/' + path
|
|
473
|
+
|
|
474
|
+
# Find matching path in our spec
|
|
475
|
+
path_obj = None
|
|
476
|
+
for p in self.generator.paths:
|
|
477
|
+
if p.path == path:
|
|
478
|
+
path_obj = p
|
|
479
|
+
break
|
|
480
|
+
|
|
481
|
+
if not path_obj:
|
|
482
|
+
return {
|
|
483
|
+
"error": "Path not found",
|
|
484
|
+
"status_code": 404
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
# Check method
|
|
488
|
+
if path_obj.method.lower() != method.lower():
|
|
489
|
+
return {
|
|
490
|
+
"error": f"Method {method} not allowed for {path}",
|
|
491
|
+
"status_code": 405
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# Execute the operation
|
|
495
|
+
if path == "/agent/info":
|
|
496
|
+
return await self._handle_agent_info()
|
|
497
|
+
elif path == "/agent/health":
|
|
498
|
+
return await self._handle_agent_health()
|
|
499
|
+
elif path == "/agent/capabilities":
|
|
500
|
+
return await self._handle_agent_capabilities()
|
|
501
|
+
elif path.startswith("/tools/") and self.mcp_tools:
|
|
502
|
+
tool_name = path.replace("/tools/", "")
|
|
503
|
+
if tool_name in self.mcp_tools:
|
|
504
|
+
return await self._handle_tool_call(tool_name, body or query_params or {})
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
"error": "Operation not implemented",
|
|
508
|
+
"status_code": 501
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
except Exception as e:
|
|
512
|
+
logger.error(f"Error handling OpenAPI request: {e}")
|
|
513
|
+
return {
|
|
514
|
+
"error": str(e),
|
|
515
|
+
"status_code": 500
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async def _handle_agent_info(self) -> Dict[str, Any]:
|
|
519
|
+
"""Handle agent info request"""
|
|
520
|
+
return {
|
|
521
|
+
"agent_id": self.agent_id,
|
|
522
|
+
"name": self.agent_info.get("name", self.agent_id),
|
|
523
|
+
"description": self.agent_info.get("description", ""),
|
|
524
|
+
"framework": self.agent_info.get("framework", "unknown"),
|
|
525
|
+
"capabilities": self.agent_info.get("capabilities", []),
|
|
526
|
+
"tools": list(self.mcp_tools.keys()),
|
|
527
|
+
"openapi_spec": self.openapi_spec,
|
|
528
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async def _handle_agent_health(self) -> Dict[str, Any]:
|
|
532
|
+
"""Handle health check request"""
|
|
533
|
+
return {
|
|
534
|
+
"status": "healthy",
|
|
535
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
536
|
+
"agent_id": self.agent_id,
|
|
537
|
+
"uptime_seconds": 3600, # Would be calculated in real implementation
|
|
538
|
+
"memory_usage_mb": 128, # Would be monitored in real implementation
|
|
539
|
+
"cpu_usage_percent": 15.0 # Would be monitored in real implementation
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async def _handle_agent_capabilities(self) -> Dict[str, Any]:
|
|
543
|
+
"""Handle capabilities request"""
|
|
544
|
+
return {
|
|
545
|
+
"agent_id": self.agent_id,
|
|
546
|
+
"framework": self.agent_info.get("framework", "unknown"),
|
|
547
|
+
"capabilities": self.agent_info.get("capabilities", []),
|
|
548
|
+
"tools": [
|
|
549
|
+
{
|
|
550
|
+
"name": tool_name,
|
|
551
|
+
"description": tool_info.get("description", ""),
|
|
552
|
+
"parameters": tool_info.get("parameters", [])
|
|
553
|
+
}
|
|
554
|
+
for tool_name, tool_info in self.mcp_tools.items()
|
|
555
|
+
],
|
|
556
|
+
"openapi_endpoints": [
|
|
557
|
+
{
|
|
558
|
+
"path": path_obj.path,
|
|
559
|
+
"method": path_obj.method,
|
|
560
|
+
"operation_id": path_obj.operation_id,
|
|
561
|
+
"summary": path_obj.summary
|
|
562
|
+
}
|
|
563
|
+
for path_obj in self.generator.paths
|
|
564
|
+
],
|
|
565
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async def _handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
569
|
+
"""Handle tool execution request"""
|
|
570
|
+
if tool_name not in self.mcp_tools:
|
|
571
|
+
return {
|
|
572
|
+
"error": f"Tool {tool_name} not found",
|
|
573
|
+
"available_tools": list(self.mcp_tools.keys())
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
tool_info = self.mcp_tools[tool_name]
|
|
577
|
+
tool_func = tool_info.get("function")
|
|
578
|
+
|
|
579
|
+
if not tool_func:
|
|
580
|
+
return {
|
|
581
|
+
"error": f"Tool {tool_name} has no executable function"
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
# Execute the tool
|
|
586
|
+
if asyncio.iscoroutinefunction(tool_func):
|
|
587
|
+
result = await tool_func(**arguments)
|
|
588
|
+
else:
|
|
589
|
+
result = tool_func(**arguments)
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
"tool_name": tool_name,
|
|
593
|
+
"arguments": arguments,
|
|
594
|
+
"result": result,
|
|
595
|
+
"status": "success",
|
|
596
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
except Exception as e:
|
|
600
|
+
logger.error(f"Error executing tool {tool_name}: {e}")
|
|
601
|
+
return {
|
|
602
|
+
"tool_name": tool_name,
|
|
603
|
+
"arguments": arguments,
|
|
604
|
+
"error": str(e),
|
|
605
|
+
"status": "error",
|
|
606
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
# Export classes for easy importing
|
|
610
|
+
__all__ = [
|
|
611
|
+
'OpenAPIInfo',
|
|
612
|
+
'OpenAPIPath',
|
|
613
|
+
'OpenAPISchema',
|
|
614
|
+
'OpenAPIGenerator',
|
|
615
|
+
'OpenAPIServer'
|
|
616
|
+
]
|