genxai-framework 0.1.0__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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Web scraper tool for extracting content from web pages."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import logging
|
|
5
|
+
import asyncio
|
|
6
|
+
from urllib.parse import urljoin, urlparse
|
|
7
|
+
|
|
8
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WebScraperTool(Tool):
|
|
14
|
+
"""Extract content from web pages using BeautifulSoup."""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
"""Initialize web scraper tool."""
|
|
18
|
+
metadata = ToolMetadata(
|
|
19
|
+
name="web_scraper",
|
|
20
|
+
description="Extract content, text, and links from web pages",
|
|
21
|
+
category=ToolCategory.WEB,
|
|
22
|
+
tags=["scraping", "web", "extraction", "html", "parsing"],
|
|
23
|
+
version="1.0.0",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parameters = [
|
|
27
|
+
ToolParameter(
|
|
28
|
+
name="url",
|
|
29
|
+
type="string",
|
|
30
|
+
description="URL of the web page to scrape",
|
|
31
|
+
required=True,
|
|
32
|
+
pattern=r"^https?://",
|
|
33
|
+
),
|
|
34
|
+
ToolParameter(
|
|
35
|
+
name="selector",
|
|
36
|
+
type="string",
|
|
37
|
+
description="CSS selector to extract specific content (optional)",
|
|
38
|
+
required=False,
|
|
39
|
+
),
|
|
40
|
+
ToolParameter(
|
|
41
|
+
name="extract_links",
|
|
42
|
+
type="boolean",
|
|
43
|
+
description="Whether to extract all links from the page",
|
|
44
|
+
required=False,
|
|
45
|
+
default=False,
|
|
46
|
+
),
|
|
47
|
+
ToolParameter(
|
|
48
|
+
name="extract_images",
|
|
49
|
+
type="boolean",
|
|
50
|
+
description="Whether to extract all image URLs",
|
|
51
|
+
required=False,
|
|
52
|
+
default=False,
|
|
53
|
+
),
|
|
54
|
+
ToolParameter(
|
|
55
|
+
name="timeout",
|
|
56
|
+
type="number",
|
|
57
|
+
description="Request timeout in seconds",
|
|
58
|
+
required=False,
|
|
59
|
+
default=30,
|
|
60
|
+
min_value=1,
|
|
61
|
+
max_value=120,
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
super().__init__(metadata, parameters)
|
|
66
|
+
|
|
67
|
+
async def _execute(
|
|
68
|
+
self,
|
|
69
|
+
url: str,
|
|
70
|
+
selector: Optional[str] = None,
|
|
71
|
+
extract_links: bool = False,
|
|
72
|
+
extract_images: bool = False,
|
|
73
|
+
timeout: int = 30,
|
|
74
|
+
) -> Dict[str, Any]:
|
|
75
|
+
"""Execute web scraping.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
url: URL to scrape
|
|
79
|
+
selector: CSS selector for specific content
|
|
80
|
+
extract_links: Whether to extract links
|
|
81
|
+
extract_images: Whether to extract images
|
|
82
|
+
timeout: Request timeout
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary containing scraped content
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
import httpx
|
|
89
|
+
from bs4 import BeautifulSoup
|
|
90
|
+
except ImportError as e:
|
|
91
|
+
raise ImportError(
|
|
92
|
+
f"Required package not installed: {e}. "
|
|
93
|
+
"Install with: pip install httpx beautifulsoup4"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Validate URL
|
|
97
|
+
parsed = urlparse(url)
|
|
98
|
+
if not parsed.scheme or not parsed.netloc:
|
|
99
|
+
raise ValueError(f"Invalid URL: {url}")
|
|
100
|
+
|
|
101
|
+
# Fetch page content
|
|
102
|
+
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
|
|
103
|
+
response = await client.get(url)
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
html_content = response.text
|
|
106
|
+
|
|
107
|
+
# Parse HTML
|
|
108
|
+
soup = BeautifulSoup(html_content, "html.parser")
|
|
109
|
+
|
|
110
|
+
result: Dict[str, Any] = {
|
|
111
|
+
"url": url,
|
|
112
|
+
"title": soup.title.string if soup.title else None,
|
|
113
|
+
"status_code": response.status_code,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Extract specific content with selector
|
|
117
|
+
if selector:
|
|
118
|
+
elements = soup.select(selector)
|
|
119
|
+
result["selected_content"] = [
|
|
120
|
+
{"text": elem.get_text(strip=True), "html": str(elem)} for elem in elements
|
|
121
|
+
]
|
|
122
|
+
result["selected_count"] = len(elements)
|
|
123
|
+
else:
|
|
124
|
+
# Extract all text content
|
|
125
|
+
result["text"] = soup.get_text(separator="\n", strip=True)
|
|
126
|
+
|
|
127
|
+
# Extract links
|
|
128
|
+
if extract_links:
|
|
129
|
+
links = []
|
|
130
|
+
for link in soup.find_all("a", href=True):
|
|
131
|
+
href = link["href"]
|
|
132
|
+
absolute_url = urljoin(url, href)
|
|
133
|
+
links.append(
|
|
134
|
+
{
|
|
135
|
+
"text": link.get_text(strip=True),
|
|
136
|
+
"href": absolute_url,
|
|
137
|
+
"title": link.get("title"),
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
result["links"] = links
|
|
141
|
+
result["links_count"] = len(links)
|
|
142
|
+
|
|
143
|
+
# Extract images
|
|
144
|
+
if extract_images:
|
|
145
|
+
images = []
|
|
146
|
+
for img in soup.find_all("img"):
|
|
147
|
+
src = img.get("src")
|
|
148
|
+
if src:
|
|
149
|
+
absolute_url = urljoin(url, src)
|
|
150
|
+
images.append(
|
|
151
|
+
{
|
|
152
|
+
"src": absolute_url,
|
|
153
|
+
"alt": img.get("alt"),
|
|
154
|
+
"title": img.get("title"),
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
result["images"] = images
|
|
158
|
+
result["images_count"] = len(images)
|
|
159
|
+
|
|
160
|
+
# Extract metadata
|
|
161
|
+
meta_tags = {}
|
|
162
|
+
for meta in soup.find_all("meta"):
|
|
163
|
+
name = meta.get("name") or meta.get("property")
|
|
164
|
+
content = meta.get("content")
|
|
165
|
+
if name and content:
|
|
166
|
+
meta_tags[name] = content
|
|
167
|
+
result["metadata"] = meta_tags
|
|
168
|
+
|
|
169
|
+
logger.info(f"Successfully scraped {url}")
|
|
170
|
+
return result
|
genxai/tools/dynamic.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Dynamic tool creation from Python code."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
import logging
|
|
5
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter
|
|
6
|
+
from genxai.tools.security import SafeExecutor, ExecutionTimeout
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DynamicTool(Tool):
|
|
12
|
+
"""Tool created dynamically from Python code with security sandboxing."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
metadata: ToolMetadata,
|
|
17
|
+
parameters: list[ToolParameter],
|
|
18
|
+
code: str,
|
|
19
|
+
timeout: int = 30
|
|
20
|
+
):
|
|
21
|
+
"""Initialize dynamic tool.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
metadata: Tool metadata
|
|
25
|
+
parameters: Tool parameters
|
|
26
|
+
code: Python code to execute
|
|
27
|
+
timeout: Maximum execution time in seconds (default: 30)
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(metadata, parameters)
|
|
30
|
+
self.code = code
|
|
31
|
+
self.timeout = timeout
|
|
32
|
+
self._compiled_code = None
|
|
33
|
+
self._safe_executor = SafeExecutor(timeout=timeout)
|
|
34
|
+
self._compile_code()
|
|
35
|
+
|
|
36
|
+
def _compile_code(self) -> None:
|
|
37
|
+
"""Compile the Python code for execution with security checks."""
|
|
38
|
+
try:
|
|
39
|
+
# Use SafeExecutor for secure compilation
|
|
40
|
+
self._compiled_code = self._safe_executor.compile_code(
|
|
41
|
+
self.code,
|
|
42
|
+
f'<dynamic:{self.metadata.name}>'
|
|
43
|
+
)
|
|
44
|
+
logger.info(f"Securely compiled code for tool: {self.metadata.name}")
|
|
45
|
+
except (SyntaxError, ValueError) as e:
|
|
46
|
+
logger.error(f"Failed to compile code for {self.metadata.name}: {e}")
|
|
47
|
+
raise ValueError(f"Invalid Python code: {e}")
|
|
48
|
+
|
|
49
|
+
async def _execute(self, **kwargs: Any) -> Any:
|
|
50
|
+
"""Execute the dynamic tool code in a sandboxed environment.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
**kwargs: Tool parameters
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Tool execution result
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
# Execute with SafeExecutor (includes timeout and sandboxing)
|
|
60
|
+
result = self._safe_executor.execute(
|
|
61
|
+
self._compiled_code,
|
|
62
|
+
kwargs,
|
|
63
|
+
enable_timeout=True
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
logger.info(f"Dynamic tool {self.metadata.name} executed successfully")
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
except ExecutionTimeout as e:
|
|
70
|
+
logger.error(f"Dynamic tool {self.metadata.name} timed out: {e}")
|
|
71
|
+
raise RuntimeError(f"Tool execution timed out after {self.timeout} seconds")
|
|
72
|
+
except ValueError as e:
|
|
73
|
+
logger.error(f"Dynamic tool {self.metadata.name} validation failed: {e}")
|
|
74
|
+
raise RuntimeError(f"Tool execution failed: {e}")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Dynamic tool {self.metadata.name} execution failed: {e}")
|
|
77
|
+
raise RuntimeError(f"Tool execution failed: {e}")
|
|
78
|
+
|
|
79
|
+
def get_code(self) -> str:
|
|
80
|
+
"""Get the tool's source code.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Python source code
|
|
84
|
+
"""
|
|
85
|
+
return self.code
|
|
86
|
+
|
|
87
|
+
def update_code(self, new_code: str) -> None:
|
|
88
|
+
"""Update the tool's code.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
new_code: New Python code
|
|
92
|
+
"""
|
|
93
|
+
self.code = new_code
|
|
94
|
+
self._compile_code()
|
|
95
|
+
logger.info(f"Updated code for tool: {self.metadata.name}")
|
|
96
|
+
|
|
97
|
+
def set_timeout(self, timeout: int) -> None:
|
|
98
|
+
"""Update the execution timeout.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
timeout: New timeout in seconds
|
|
102
|
+
"""
|
|
103
|
+
self.timeout = timeout
|
|
104
|
+
self._safe_executor = SafeExecutor(timeout=timeout)
|
|
105
|
+
logger.info(f"Updated timeout for tool {self.metadata.name}: {timeout}s")
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) server for GenXAI tools.
|
|
2
|
+
|
|
3
|
+
This module provides an MCP server that exposes GenXAI tools to external
|
|
4
|
+
applications like Claude Desktop, IDEs, and other AI systems.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
from mcp.server import Server
|
|
11
|
+
from mcp.server.stdio import stdio_server
|
|
12
|
+
from mcp.types import Tool as MCPTool, TextContent, CallToolResult
|
|
13
|
+
|
|
14
|
+
from genxai.tools.registry import ToolRegistry
|
|
15
|
+
from genxai.tools.base import Tool, ToolCategory
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GenXAIMCPServer:
|
|
21
|
+
"""MCP server for GenXAI tools."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, name: str = "genxai-tools"):
|
|
24
|
+
"""Initialize MCP server.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name: Server name
|
|
28
|
+
"""
|
|
29
|
+
self.server = Server(name)
|
|
30
|
+
self._setup_handlers()
|
|
31
|
+
|
|
32
|
+
def _setup_handlers(self) -> None:
|
|
33
|
+
"""Setup MCP server handlers."""
|
|
34
|
+
|
|
35
|
+
@self.server.list_tools()
|
|
36
|
+
async def list_tools() -> List[MCPTool]:
|
|
37
|
+
"""List all available GenXAI tools."""
|
|
38
|
+
tools = ToolRegistry.list_all()
|
|
39
|
+
mcp_tools = []
|
|
40
|
+
|
|
41
|
+
for tool in tools:
|
|
42
|
+
mcp_tool = self._convert_to_mcp_tool(tool)
|
|
43
|
+
mcp_tools.append(mcp_tool)
|
|
44
|
+
|
|
45
|
+
logger.info(f"Listed {len(mcp_tools)} tools via MCP")
|
|
46
|
+
return mcp_tools
|
|
47
|
+
|
|
48
|
+
@self.server.call_tool()
|
|
49
|
+
async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
50
|
+
"""Execute a GenXAI tool.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
name: Tool name
|
|
54
|
+
arguments: Tool arguments
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Tool execution result
|
|
58
|
+
"""
|
|
59
|
+
logger.info(f"MCP tool call: {name} with args: {arguments}")
|
|
60
|
+
|
|
61
|
+
# Get tool from registry
|
|
62
|
+
tool = ToolRegistry.get(name)
|
|
63
|
+
if not tool:
|
|
64
|
+
error_msg = f"Tool '{name}' not found"
|
|
65
|
+
logger.error(error_msg)
|
|
66
|
+
return CallToolResult(
|
|
67
|
+
content=[TextContent(type="text", text=error_msg)],
|
|
68
|
+
isError=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
# Execute tool
|
|
73
|
+
result = await tool.execute(**arguments)
|
|
74
|
+
|
|
75
|
+
if result.success:
|
|
76
|
+
# Format successful result
|
|
77
|
+
content = self._format_tool_result(result.data)
|
|
78
|
+
return CallToolResult(
|
|
79
|
+
content=[TextContent(type="text", text=content)],
|
|
80
|
+
isError=False,
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
# Format error result
|
|
84
|
+
error_msg = result.error or "Tool execution failed"
|
|
85
|
+
logger.error(f"Tool {name} failed: {error_msg}")
|
|
86
|
+
return CallToolResult(
|
|
87
|
+
content=[TextContent(type="text", text=error_msg)],
|
|
88
|
+
isError=True,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
error_msg = f"Tool execution error: {str(e)}"
|
|
93
|
+
logger.error(error_msg)
|
|
94
|
+
return CallToolResult(
|
|
95
|
+
content=[TextContent(type="text", text=error_msg)],
|
|
96
|
+
isError=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _convert_to_mcp_tool(self, tool: Tool) -> MCPTool:
|
|
100
|
+
"""Convert GenXAI tool to MCP tool format.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
tool: GenXAI tool
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
MCP tool definition
|
|
107
|
+
"""
|
|
108
|
+
schema = tool.get_schema()
|
|
109
|
+
|
|
110
|
+
return MCPTool(
|
|
111
|
+
name=tool.metadata.name,
|
|
112
|
+
description=tool.metadata.description,
|
|
113
|
+
inputSchema={
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": schema["parameters"]["properties"],
|
|
116
|
+
"required": schema["parameters"].get("required", []),
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _format_tool_result(self, data: Any) -> str:
|
|
121
|
+
"""Format tool result for MCP response.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
data: Tool result data
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Formatted string
|
|
128
|
+
"""
|
|
129
|
+
import json
|
|
130
|
+
|
|
131
|
+
if isinstance(data, (dict, list)):
|
|
132
|
+
return json.dumps(data, indent=2)
|
|
133
|
+
return str(data)
|
|
134
|
+
|
|
135
|
+
async def run(self) -> None:
|
|
136
|
+
"""Run the MCP server."""
|
|
137
|
+
logger.info("Starting GenXAI MCP server...")
|
|
138
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
139
|
+
await self.server.run(
|
|
140
|
+
read_stream,
|
|
141
|
+
write_stream,
|
|
142
|
+
self.server.create_initialization_options(),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def main():
|
|
147
|
+
"""Main entry point for MCP server."""
|
|
148
|
+
# Setup logging
|
|
149
|
+
logging.basicConfig(
|
|
150
|
+
level=logging.INFO,
|
|
151
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Register built-in tools
|
|
155
|
+
from genxai.tools.builtin.computation.calculator import CalculatorTool
|
|
156
|
+
from genxai.tools.builtin.file.file_reader import FileReaderTool
|
|
157
|
+
|
|
158
|
+
ToolRegistry.register(CalculatorTool())
|
|
159
|
+
ToolRegistry.register(FileReaderTool())
|
|
160
|
+
|
|
161
|
+
# Create and run server
|
|
162
|
+
server = GenXAIMCPServer()
|
|
163
|
+
await server.run()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Database models for tools."""
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column, Integer, String, Text, JSON, DateTime
|
|
4
|
+
from sqlalchemy.orm import declarative_base
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
Base = declarative_base()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolModel(Base):
|
|
11
|
+
"""Database model for tools."""
|
|
12
|
+
|
|
13
|
+
__tablename__ = "tools"
|
|
14
|
+
|
|
15
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
16
|
+
name = Column(String, unique=True, index=True, nullable=False)
|
|
17
|
+
description = Column(Text, nullable=False)
|
|
18
|
+
category = Column(String, nullable=False)
|
|
19
|
+
tags = Column(JSON, default=[])
|
|
20
|
+
version = Column(String, default="1.0.0")
|
|
21
|
+
author = Column(String, default="GenXAI User")
|
|
22
|
+
|
|
23
|
+
# Tool type: "code_based" or "template_based"
|
|
24
|
+
tool_type = Column(String, nullable=False)
|
|
25
|
+
|
|
26
|
+
# For code-based tools
|
|
27
|
+
code = Column(Text, nullable=True)
|
|
28
|
+
parameters = Column(JSON, default=[])
|
|
29
|
+
|
|
30
|
+
# For template-based tools
|
|
31
|
+
template_name = Column(String, nullable=True)
|
|
32
|
+
template_config = Column(JSON, nullable=True)
|
|
33
|
+
|
|
34
|
+
# Timestamps
|
|
35
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
36
|
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
37
|
+
|
|
38
|
+
def to_dict(self):
|
|
39
|
+
"""Convert model to dictionary."""
|
|
40
|
+
return {
|
|
41
|
+
"id": self.id,
|
|
42
|
+
"name": self.name,
|
|
43
|
+
"description": self.description,
|
|
44
|
+
"category": self.category,
|
|
45
|
+
"tags": self.tags,
|
|
46
|
+
"version": self.version,
|
|
47
|
+
"author": self.author,
|
|
48
|
+
"tool_type": self.tool_type,
|
|
49
|
+
"code": self.code,
|
|
50
|
+
"parameters": self.parameters,
|
|
51
|
+
"template_name": self.template_name,
|
|
52
|
+
"template_config": self.template_config,
|
|
53
|
+
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
54
|
+
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
55
|
+
}
|