agenticwerx-mcp-client 1.0.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.
- agenticwerx_mcp_client/__init__.py +15 -0
- agenticwerx_mcp_client/__main__.py +144 -0
- agenticwerx_mcp_client/api.py +239 -0
- agenticwerx_mcp_client/client.py +221 -0
- agenticwerx_mcp_client-1.0.0.dist-info/METADATA +266 -0
- agenticwerx_mcp_client-1.0.0.dist-info/RECORD +9 -0
- agenticwerx_mcp_client-1.0.0.dist-info/WHEEL +4 -0
- agenticwerx_mcp_client-1.0.0.dist-info/entry_points.txt +2 -0
- agenticwerx_mcp_client-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgenticWerx MCP Client
|
|
3
|
+
|
|
4
|
+
A Model Context Protocol (MCP) client for AgenticWerx rule packages.
|
|
5
|
+
Provides universal code analysis across all IDEs and programming languages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__author__ = "AgenticWerx"
|
|
10
|
+
__email__ = "support@agenticwerx.com"
|
|
11
|
+
|
|
12
|
+
from .client import AgenticWerxMCPClient
|
|
13
|
+
from .api import AgenticWerxAPI
|
|
14
|
+
|
|
15
|
+
__all__ = ["AgenticWerxMCPClient", "AgenticWerxAPI"]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AgenticWerx MCP Client - Entry point for uvx execution
|
|
4
|
+
|
|
5
|
+
This module serves as the main entry point when the package is executed
|
|
6
|
+
via uvx or python -m agenticwerx_mcp_client.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import argparse
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from .client import AgenticWerxMCPClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup_logging(debug: bool = False) -> None:
|
|
20
|
+
"""Configure logging for the application."""
|
|
21
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
22
|
+
logging.basicConfig(
|
|
23
|
+
level=level,
|
|
24
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
25
|
+
handlers=[
|
|
26
|
+
logging.StreamHandler(sys.stderr)
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_api_key(args_api_key: Optional[str]) -> str:
|
|
32
|
+
"""Get API key from arguments or environment variables."""
|
|
33
|
+
api_key = args_api_key or os.getenv("AGENTICWERX_API_KEY")
|
|
34
|
+
|
|
35
|
+
if not api_key:
|
|
36
|
+
print(
|
|
37
|
+
"Error: API key required via --api-key argument or AGENTICWERX_API_KEY environment variable",
|
|
38
|
+
file=sys.stderr
|
|
39
|
+
)
|
|
40
|
+
print(
|
|
41
|
+
"Get your API key at: https://agenticwerx.com/dashboard/api-keys",
|
|
42
|
+
file=sys.stderr
|
|
43
|
+
)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
return api_key
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
53
|
+
"""Create and configure the argument parser."""
|
|
54
|
+
parser = argparse.ArgumentParser(
|
|
55
|
+
description="AgenticWerx MCP Client - Universal code analysis for all IDEs",
|
|
56
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
57
|
+
epilog="""
|
|
58
|
+
Examples:
|
|
59
|
+
# Run with API key from environment
|
|
60
|
+
export AGENTICWERX_API_KEY=your_key_here
|
|
61
|
+
agenticwerx-mcp-client
|
|
62
|
+
|
|
63
|
+
# Run with API key as argument
|
|
64
|
+
agenticwerx-mcp-client --api-key your_key_here
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Run with debug logging
|
|
69
|
+
agenticwerx-mcp-client --api-key your_key_here --debug
|
|
70
|
+
|
|
71
|
+
For more information, visit: https://docs.agenticwerx.com/mcp-client
|
|
72
|
+
"""
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"--api-key",
|
|
77
|
+
type=str,
|
|
78
|
+
help="AgenticWerx API key (can also use AGENTICWERX_API_KEY env var)"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--debug",
|
|
85
|
+
action="store_true",
|
|
86
|
+
help="Enable debug logging"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--version",
|
|
91
|
+
action="version",
|
|
92
|
+
version=f"agenticwerx-mcp-client {__import__('agenticwerx_mcp_client').__version__}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return parser
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def async_main() -> None:
|
|
99
|
+
"""Async main function."""
|
|
100
|
+
parser = create_parser()
|
|
101
|
+
args = parser.parse_args()
|
|
102
|
+
|
|
103
|
+
# Setup logging
|
|
104
|
+
setup_logging(args.debug)
|
|
105
|
+
logger = logging.getLogger(__name__)
|
|
106
|
+
|
|
107
|
+
# Get API key
|
|
108
|
+
api_key = get_api_key(args.api_key)
|
|
109
|
+
|
|
110
|
+
logger.info("Starting AgenticWerx MCP Client...")
|
|
111
|
+
logger.debug(f"Debug logging enabled")
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
# Create and run the MCP client
|
|
115
|
+
client = AgenticWerxMCPClient(
|
|
116
|
+
api_key=api_key,
|
|
117
|
+
debug=args.debug
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
await client.run()
|
|
121
|
+
|
|
122
|
+
except KeyboardInterrupt:
|
|
123
|
+
logger.info("Received interrupt signal, shutting down...")
|
|
124
|
+
sys.exit(0)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Fatal error: {e}")
|
|
127
|
+
if args.debug:
|
|
128
|
+
logger.exception("Full traceback:")
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> None:
|
|
133
|
+
"""Main entry point for the application."""
|
|
134
|
+
try:
|
|
135
|
+
asyncio.run(async_main())
|
|
136
|
+
except KeyboardInterrupt:
|
|
137
|
+
sys.exit(0)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print(f"Fatal error: {e}", file=sys.stderr)
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgenticWerx API client - Lambda MCP communication
|
|
3
|
+
|
|
4
|
+
This module handles communication with the AgenticWerx Lambda MCP server
|
|
5
|
+
using proper JSON-RPC 2.0 protocol.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import random
|
|
10
|
+
import string
|
|
11
|
+
from typing import Dict, List, Any, Optional
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgenticWerxAPIError(Exception):
|
|
19
|
+
"""Custom exception for API errors."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, status_code: Optional[int] = None):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.status_code = status_code
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AgenticWerxAPI:
|
|
27
|
+
"""
|
|
28
|
+
AgenticWerx Lambda MCP API client.
|
|
29
|
+
|
|
30
|
+
This client connects to the AgenticWerx Lambda MCP server using
|
|
31
|
+
proper JSON-RPC 2.0 protocol for MCP communication.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, api_key: str):
|
|
35
|
+
"""
|
|
36
|
+
Initialize the API client.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
api_key: AgenticWerx API key
|
|
40
|
+
"""
|
|
41
|
+
self.api_key = api_key
|
|
42
|
+
self.lambda_url = "https://rph7c2jq5zpisbenj73y2hpjfm0gtwdw.lambda-url.us-west-2.on.aws/"
|
|
43
|
+
|
|
44
|
+
# Create HTTP client with proper headers and timeout
|
|
45
|
+
self.client = httpx.AsyncClient(
|
|
46
|
+
headers={
|
|
47
|
+
"Authorization": f"Bearer {api_key}",
|
|
48
|
+
"User-Agent": "AgenticWerx-Lambda-MCP-Client/1.0.0",
|
|
49
|
+
"Content-Type": "application/json"
|
|
50
|
+
},
|
|
51
|
+
timeout=httpx.Timeout(30.0, connect=10.0)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
logger.debug(f"Initialized Lambda MCP client for: {self.lambda_url}")
|
|
55
|
+
|
|
56
|
+
async def __aenter__(self):
|
|
57
|
+
"""Async context manager entry."""
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
61
|
+
"""Async context manager exit."""
|
|
62
|
+
await self.close()
|
|
63
|
+
|
|
64
|
+
async def close(self) -> None:
|
|
65
|
+
"""Close the HTTP client."""
|
|
66
|
+
await self.client.aclose()
|
|
67
|
+
|
|
68
|
+
def _generate_request_id(self) -> str:
|
|
69
|
+
"""Generate a random request ID."""
|
|
70
|
+
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=7))
|
|
71
|
+
|
|
72
|
+
async def _make_mcp_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
73
|
+
"""
|
|
74
|
+
Make an MCP request to the Lambda function.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
method: MCP method name
|
|
78
|
+
params: Optional parameters for the method
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The result from the MCP server
|
|
82
|
+
"""
|
|
83
|
+
request_id = self._generate_request_id()
|
|
84
|
+
|
|
85
|
+
mcp_request = {
|
|
86
|
+
"jsonrpc": "2.0",
|
|
87
|
+
"id": request_id,
|
|
88
|
+
"method": method,
|
|
89
|
+
"params": params or {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logger.debug(f"Making MCP request to Lambda: {method}")
|
|
93
|
+
logger.debug(f"Request payload: {mcp_request}")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
response = await self.client.post(
|
|
97
|
+
self.lambda_url,
|
|
98
|
+
json=mcp_request
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
logger.debug(f"Response status: {response.status_code}")
|
|
102
|
+
logger.debug(f"Response headers: {dict(response.headers)}")
|
|
103
|
+
|
|
104
|
+
if response.status_code >= 400:
|
|
105
|
+
error_text = response.text
|
|
106
|
+
logger.error(f"Lambda function returned error: {response.status_code} {error_text}")
|
|
107
|
+
raise AgenticWerxAPIError(
|
|
108
|
+
f"Lambda request failed: {response.status_code}",
|
|
109
|
+
status_code=response.status_code
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
result = response.json()
|
|
114
|
+
logger.debug(f"Lambda response received for request {request_id}: {result}")
|
|
115
|
+
except Exception as json_error:
|
|
116
|
+
logger.error(f"Failed to parse JSON response: {json_error}")
|
|
117
|
+
logger.error(f"Raw response: {response.text}")
|
|
118
|
+
raise AgenticWerxAPIError(f"Invalid JSON response from Lambda: {json_error}")
|
|
119
|
+
|
|
120
|
+
if "error" in result:
|
|
121
|
+
error_msg = result["error"].get("message", "Unknown MCP error")
|
|
122
|
+
logger.error(f"MCP error from Lambda: {error_msg}")
|
|
123
|
+
raise AgenticWerxAPIError(f"MCP Error: {error_msg}")
|
|
124
|
+
|
|
125
|
+
return result.get("result")
|
|
126
|
+
|
|
127
|
+
except httpx.TimeoutException:
|
|
128
|
+
raise AgenticWerxAPIError("Request timeout")
|
|
129
|
+
except httpx.ConnectError:
|
|
130
|
+
raise AgenticWerxAPIError("Connection error")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"Error making MCP request: {e}")
|
|
133
|
+
raise AgenticWerxAPIError(f"Failed to make MCP request: {str(e)}")
|
|
134
|
+
|
|
135
|
+
async def test_connection(self) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
Test connection to the Lambda MCP server.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if connection is successful, False otherwise
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
logger.info("Testing Lambda connection...")
|
|
144
|
+
result = await self._make_mcp_request("health")
|
|
145
|
+
logger.info(f"Lambda connection successful: {result}")
|
|
146
|
+
return True
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Lambda connection test failed: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
async def list_tools(self) -> List[Dict[str, Any]]:
|
|
152
|
+
"""
|
|
153
|
+
List available tools from the MCP server.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List of available tools
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
result = await self._make_mcp_request("tools/list")
|
|
160
|
+
return result.get("tools", [])
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(f"Failed to list tools: {e}")
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
async def call_tool(self, name: str, arguments: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Call a tool on the MCP server.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
name: Tool name
|
|
171
|
+
arguments: Tool arguments
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Tool execution result
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
result = await self._make_mcp_request("tools/call", {
|
|
178
|
+
"name": name,
|
|
179
|
+
"arguments": arguments or {}
|
|
180
|
+
})
|
|
181
|
+
return result
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Tool call failed for {name}: {e}")
|
|
184
|
+
raise
|
|
185
|
+
|
|
186
|
+
async def get_rules(self, package_id: Optional[str] = None) -> Dict[str, Any]:
|
|
187
|
+
"""
|
|
188
|
+
Get rules from the AgenticWerx MCP server.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
package_id: Optional specific package ID to retrieve
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Rules data from the server
|
|
195
|
+
"""
|
|
196
|
+
logger.debug("Fetching rules via MCP tool call")
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# Use the get_rules tool via MCP
|
|
200
|
+
arguments = {}
|
|
201
|
+
if package_id:
|
|
202
|
+
arguments["packageId"] = package_id
|
|
203
|
+
|
|
204
|
+
result = await self.call_tool("get_rules", arguments)
|
|
205
|
+
|
|
206
|
+
# Extract the actual rules data from the MCP response
|
|
207
|
+
if "content" in result and result["content"]:
|
|
208
|
+
# The content should be a list with text content
|
|
209
|
+
# Look for the JSON content (usually the second item)
|
|
210
|
+
json_content = None
|
|
211
|
+
for content_item in result["content"]:
|
|
212
|
+
if content_item.get("type") == "text":
|
|
213
|
+
text = content_item["text"]
|
|
214
|
+
# Try to parse as JSON - if it starts with { or [, it's likely JSON
|
|
215
|
+
if text.strip().startswith(("{", "[")):
|
|
216
|
+
try:
|
|
217
|
+
import json
|
|
218
|
+
json_content = json.loads(text)
|
|
219
|
+
break
|
|
220
|
+
except json.JSONDecodeError:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
if json_content:
|
|
224
|
+
logger.info("Successfully retrieved rules via MCP")
|
|
225
|
+
return json_content
|
|
226
|
+
else:
|
|
227
|
+
# If no JSON found, return the raw content
|
|
228
|
+
logger.info("No JSON content found, returning raw content")
|
|
229
|
+
return {
|
|
230
|
+
"content": result["content"],
|
|
231
|
+
"message": "Rules retrieved but not in JSON format"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
logger.warning("No rules content found in MCP response")
|
|
235
|
+
return {"rules": [], "message": "No rules found"}
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"Error fetching rules via MCP: {e}")
|
|
239
|
+
raise AgenticWerxAPIError(f"Failed to fetch rules: {str(e)}")
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgenticWerx MCP Client - Simple rule retrieval client
|
|
3
|
+
|
|
4
|
+
This module implements a simple MCP server that connects to your
|
|
5
|
+
AgenticWerx MCP server to retrieve rules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
from mcp.server import Server
|
|
13
|
+
from mcp.types import Resource, Tool, TextContent
|
|
14
|
+
from mcp.server.models import InitializationOptions
|
|
15
|
+
|
|
16
|
+
from .api import AgenticWerxAPI, AgenticWerxAPIError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AgenticWerxMCPClient:
|
|
23
|
+
"""
|
|
24
|
+
Simple AgenticWerx MCP Client for rule retrieval.
|
|
25
|
+
|
|
26
|
+
This client connects to your AgenticWerx MCP server and provides
|
|
27
|
+
a simple interface to get rules through the MCP protocol.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, api_key: str, debug: bool = False):
|
|
31
|
+
"""
|
|
32
|
+
Initialize the MCP client.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
api_key: AgenticWerx API key
|
|
36
|
+
debug: Enable debug logging
|
|
37
|
+
"""
|
|
38
|
+
self.api_key = api_key
|
|
39
|
+
self.debug = debug
|
|
40
|
+
|
|
41
|
+
# Initialize the API client
|
|
42
|
+
self.api = AgenticWerxAPI(api_key)
|
|
43
|
+
|
|
44
|
+
# Initialize the MCP server
|
|
45
|
+
self.server = Server("agenticwerx")
|
|
46
|
+
|
|
47
|
+
# Configure logging
|
|
48
|
+
if debug:
|
|
49
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
50
|
+
|
|
51
|
+
logger.info("Initializing AgenticWerx MCP Client")
|
|
52
|
+
|
|
53
|
+
# Set up MCP handlers
|
|
54
|
+
self._setup_handlers()
|
|
55
|
+
|
|
56
|
+
def _setup_handlers(self) -> None:
|
|
57
|
+
"""Set up MCP server handlers."""
|
|
58
|
+
|
|
59
|
+
@self.server.list_resources()
|
|
60
|
+
async def list_resources() -> List[Resource]:
|
|
61
|
+
"""List available rule resources."""
|
|
62
|
+
logger.debug("Listing available rule resources")
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Test if we can get rules from the Lambda MCP server
|
|
66
|
+
rules_data = await self.api.get_rules()
|
|
67
|
+
|
|
68
|
+
# Create a single resource for all rules
|
|
69
|
+
resource = Resource(
|
|
70
|
+
uri="agenticwerx://rules",
|
|
71
|
+
name="AgenticWerx Rules",
|
|
72
|
+
description="All available AgenticWerx rules from Lambda MCP server",
|
|
73
|
+
mimeType="application/json"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
logger.info("Listed rule resources from Lambda MCP server")
|
|
77
|
+
return [resource]
|
|
78
|
+
|
|
79
|
+
except AgenticWerxAPIError as e:
|
|
80
|
+
logger.error(f"API error listing resources: {e}")
|
|
81
|
+
return []
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.error(f"Unexpected error listing resources: {e}")
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
@self.server.read_resource()
|
|
87
|
+
async def read_resource(uri: str) -> str:
|
|
88
|
+
"""Read rule resource."""
|
|
89
|
+
logger.debug(f"Reading resource: {uri}")
|
|
90
|
+
|
|
91
|
+
if uri != "agenticwerx://rules":
|
|
92
|
+
raise ValueError(f"Unknown resource URI: {uri}")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
rules_data = await self.api.get_rules()
|
|
96
|
+
logger.debug("Successfully read rules resource from Lambda MCP server")
|
|
97
|
+
return json.dumps(rules_data, indent=2)
|
|
98
|
+
|
|
99
|
+
except AgenticWerxAPIError as e:
|
|
100
|
+
logger.error(f"API error reading resource: {e}")
|
|
101
|
+
raise
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Unexpected error reading resource: {e}")
|
|
104
|
+
raise ValueError(f"Failed to read resource: {str(e)}")
|
|
105
|
+
|
|
106
|
+
@self.server.list_tools()
|
|
107
|
+
async def list_tools() -> List[Tool]:
|
|
108
|
+
"""List available tools."""
|
|
109
|
+
logger.debug("Listing available tools")
|
|
110
|
+
|
|
111
|
+
# Only provide the analyze tool (which calls get_rules internally)
|
|
112
|
+
tools = [
|
|
113
|
+
Tool(
|
|
114
|
+
name="analyze",
|
|
115
|
+
description="Analyze code using AgenticWerx rules. Optionally specify a packageId to use rules from a specific package.",
|
|
116
|
+
inputSchema={
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"packageId": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Optional package ID to use rules from a specific package"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"additionalProperties": False
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
logger.info(f"Listed {len(tools)} available tools (analyze only)")
|
|
130
|
+
return tools
|
|
131
|
+
|
|
132
|
+
@self.server.call_tool()
|
|
133
|
+
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
|
134
|
+
"""Execute a tool."""
|
|
135
|
+
logger.debug(f"Executing tool: {name}")
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
if name == "analyze":
|
|
139
|
+
# Handle analyze tool - internally calls get_rules
|
|
140
|
+
package_id = arguments.get("packageId") or arguments.get("package_id")
|
|
141
|
+
result = await self.api.get_rules(package_id)
|
|
142
|
+
|
|
143
|
+
response = {
|
|
144
|
+
"tool": "analyze",
|
|
145
|
+
"packageId": package_id,
|
|
146
|
+
"rules": result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
logger.debug("Successfully retrieved rules for analysis")
|
|
150
|
+
return [TextContent(type="text", text=json.dumps(response, indent=2))]
|
|
151
|
+
|
|
152
|
+
else:
|
|
153
|
+
# Only analyze tool is supported
|
|
154
|
+
error_msg = f"Tool '{name}' is not supported. Only 'analyze' tool is available."
|
|
155
|
+
logger.warning(f"Unsupported tool requested: {name}")
|
|
156
|
+
return [TextContent(type="text", text=json.dumps({"error": error_msg}, indent=2))]
|
|
157
|
+
|
|
158
|
+
except AgenticWerxAPIError as e:
|
|
159
|
+
error_msg = f"AgenticWerx API Error: {str(e)}"
|
|
160
|
+
logger.error(f"API error in tool {name}: {e}")
|
|
161
|
+
return [TextContent(type="text", text=json.dumps({"error": error_msg}, indent=2))]
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
error_msg = f"Tool execution error: {str(e)}"
|
|
165
|
+
logger.error(f"Unexpected error in tool {name}: {e}")
|
|
166
|
+
return [TextContent(type="text", text=json.dumps({"error": error_msg}, indent=2))]
|
|
167
|
+
|
|
168
|
+
async def test_connection(self) -> bool:
|
|
169
|
+
"""Test connection to the Lambda MCP server."""
|
|
170
|
+
return await self.api.test_connection()
|
|
171
|
+
|
|
172
|
+
async def run(self) -> None:
|
|
173
|
+
"""Run the MCP server."""
|
|
174
|
+
logger.info("Starting AgenticWerx MCP Client")
|
|
175
|
+
|
|
176
|
+
# Test connection to Lambda MCP server on startup
|
|
177
|
+
logger.info("Testing connection to Lambda MCP server...")
|
|
178
|
+
connection_ok = await self.test_connection()
|
|
179
|
+
if not connection_ok:
|
|
180
|
+
logger.error("Failed to connect to Lambda MCP server")
|
|
181
|
+
# Continue anyway - the client might still work for some operations
|
|
182
|
+
else:
|
|
183
|
+
logger.info("Successfully connected to Lambda MCP server")
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
from mcp.server.stdio import stdio_server
|
|
187
|
+
|
|
188
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
189
|
+
logger.info("MCP Client started, waiting for connections...")
|
|
190
|
+
|
|
191
|
+
from mcp.types import ServerCapabilities, ResourcesCapability, ToolsCapability
|
|
192
|
+
|
|
193
|
+
init_options = InitializationOptions(
|
|
194
|
+
server_name="agenticwerx",
|
|
195
|
+
server_version="1.0.0",
|
|
196
|
+
capabilities=ServerCapabilities(
|
|
197
|
+
resources=ResourcesCapability(subscribe=False, listChanged=False),
|
|
198
|
+
tools=ToolsCapability(listChanged=False)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
await self.server.run(
|
|
203
|
+
read_stream,
|
|
204
|
+
write_stream,
|
|
205
|
+
init_options
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.error(f"Error running MCP server: {e}")
|
|
210
|
+
raise
|
|
211
|
+
finally:
|
|
212
|
+
await self.api.close()
|
|
213
|
+
logger.info("AgenticWerx MCP Client stopped")
|
|
214
|
+
|
|
215
|
+
async def __aenter__(self):
|
|
216
|
+
"""Async context manager entry."""
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
220
|
+
"""Async context manager exit."""
|
|
221
|
+
await self.api.close()
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agenticwerx-mcp-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Simple MCP client that connects to your AgenticWerx MCP server to retrieve rules
|
|
5
|
+
Project-URL: Homepage, https://agenticwerx.com
|
|
6
|
+
Project-URL: Documentation, https://docs.agenticwerx.com/mcp-client
|
|
7
|
+
Project-URL: Bug Reports, https://github.com/agenticwerx/mcp-client/issues
|
|
8
|
+
Project-URL: Source Code, https://github.com/agenticwerx/mcp-client
|
|
9
|
+
Author-email: AgenticWerx <support@agenticwerx.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agenticwerx,client,mcp,rules
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Requires-Dist: httpx>=0.25.0
|
|
26
|
+
Requires-Dist: mcp>=1.0.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Requires-Dist: typing-extensions>=4.0.0; python_version < '3.10'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: build>=0.10.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: safety>=2.3.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: twine>=4.0.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: types-requests>=2.31.0; extra == 'dev'
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
|
|
43
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
|
|
44
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'test'
|
|
45
|
+
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# AgenticWerx MCP Client
|
|
49
|
+
|
|
50
|
+
[](https://badge.fury.io/py/agenticwerx-mcp-client)
|
|
51
|
+
[](https://pypi.org/project/agenticwerx-mcp-client/)
|
|
52
|
+
[](https://opensource.org/licenses/MIT)
|
|
53
|
+
|
|
54
|
+
A Model Context Protocol (MCP) client that connects to the AgenticWerx Lambda MCP server using JSON-RPC 2.0 protocol to provide code analysis and rule management capabilities.
|
|
55
|
+
|
|
56
|
+
## 🚀 Quick Start
|
|
57
|
+
|
|
58
|
+
### For IDE Users
|
|
59
|
+
|
|
60
|
+
Add this configuration to your MCP-compatible IDE (Kiro, Amazon Q Developer, etc.):
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"agenticwerx": {
|
|
66
|
+
"command": "uvx",
|
|
67
|
+
"args": ["agenticwerx-mcp-client@latest", "--api-key", "${AGENTICWERX_API_KEY}"],
|
|
68
|
+
"env": {
|
|
69
|
+
"AGENTICWERX_API_KEY": "your-api-key-here"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Get Your API Key
|
|
77
|
+
|
|
78
|
+
1. Visit [AgenticWerx Dashboard](https://agenticwerx.com/dashboard)
|
|
79
|
+
2. Navigate to API Keys section
|
|
80
|
+
3. Create a new API key
|
|
81
|
+
4. Copy the key to your MCP configuration
|
|
82
|
+
|
|
83
|
+
## 🛠️ Available Tools
|
|
84
|
+
|
|
85
|
+
The AgenticWerx MCP client provides a simple tool for code analysis:
|
|
86
|
+
|
|
87
|
+
### **analyze**
|
|
88
|
+
Analyze code using AgenticWerx rules from the server.
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
- `packageId` (optional): Specific package ID to use rules from a specific package
|
|
92
|
+
|
|
93
|
+
**Example:**
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"tool": "analyze",
|
|
97
|
+
"packageId": "stripe-integration-excellence-pack"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The tool connects to AgenticWerx services and retrieves the rules, which are then processed and returned to your IDE.
|
|
102
|
+
|
|
103
|
+
## 🔗 Simple Connection
|
|
104
|
+
|
|
105
|
+
This client acts as a simple bridge between your IDE and AgenticWerx services. It retrieves rules and passes them back to your IDE for code analysis.
|
|
106
|
+
|
|
107
|
+
## 🔧 Installation Methods
|
|
108
|
+
|
|
109
|
+
### Method 1: UVX (Recommended)
|
|
110
|
+
No installation needed! Your IDE will automatically download and run the client:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uvx agenticwerx-mcp-client@latest --api-key your_key_here
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Method 2: pip install
|
|
117
|
+
```bash
|
|
118
|
+
pip install agenticwerx-mcp-client
|
|
119
|
+
agenticwerx-mcp-client --api-key your_key_here
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Method 3: From Source
|
|
123
|
+
```bash
|
|
124
|
+
git clone https://github.com/agenticwerx/mcp-client.git
|
|
125
|
+
cd mcp-client
|
|
126
|
+
pip install -e .
|
|
127
|
+
agenticwerx-mcp-client --api-key your_key_here
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 📋 IDE Configuration Examples
|
|
131
|
+
|
|
132
|
+
### Kiro IDE
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"mcpServers": {
|
|
136
|
+
"agenticwerx": {
|
|
137
|
+
"command": "uvx",
|
|
138
|
+
"args": ["agenticwerx-mcp-client@latest", "--api-key", "${AGENTICWERX_API_KEY}"],
|
|
139
|
+
"env": {
|
|
140
|
+
"AGENTICWERX_API_KEY": "your-api-key-here"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Amazon Q Developer
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"mcpServers": {
|
|
151
|
+
"agenticwerx": {
|
|
152
|
+
"command": "uvx",
|
|
153
|
+
"args": ["agenticwerx-mcp-client@latest", "--api-key", "${AGENTICWERX_API_KEY}"],
|
|
154
|
+
"env": {
|
|
155
|
+
"AGENTICWERX_API_KEY": "your-api-key-here"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### VS Code (with MCP extension)
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"mcp.servers": {
|
|
166
|
+
"agenticwerx": {
|
|
167
|
+
"command": "uvx",
|
|
168
|
+
"args": ["agenticwerx-mcp-client@latest", "--api-key", "${AGENTICWERX_API_KEY}"],
|
|
169
|
+
"env": {
|
|
170
|
+
"AGENTICWERX_API_KEY": "your-api-key-here"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 🔒 Security & Privacy
|
|
178
|
+
|
|
179
|
+
- **API Key Security:** Your API key is only used to authenticate with AgenticWerx services
|
|
180
|
+
- **Code Privacy:** Code analysis happens securely through encrypted connections
|
|
181
|
+
- **No Data Storage:** Your code is analyzed in real-time and not stored on our servers
|
|
182
|
+
- **Local Processing:** The MCP client runs locally on your machine
|
|
183
|
+
|
|
184
|
+
## 🚀 Features
|
|
185
|
+
|
|
186
|
+
- ✅ **Simple Connection:** Connects your IDE to AgenticWerx services
|
|
187
|
+
- ✅ **Rule Retrieval:** Fetches rules from the server
|
|
188
|
+
- ✅ **MCP Compatible:** Works with any MCP-compatible IDE
|
|
189
|
+
- ✅ **Zero Configuration:** Just add your API key
|
|
190
|
+
- ✅ **Lightweight:** Minimal overhead, just passes data through
|
|
191
|
+
|
|
192
|
+
## 📊 Example Output
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"tool": "analyze",
|
|
197
|
+
"packageId": "stripe-integration-excellence-pack",
|
|
198
|
+
"rules": {
|
|
199
|
+
"rules": [
|
|
200
|
+
{
|
|
201
|
+
"id": "rule-1",
|
|
202
|
+
"name": "Security Rule",
|
|
203
|
+
"description": "Prevents security vulnerabilities",
|
|
204
|
+
"pattern": "eval\\(",
|
|
205
|
+
"message": "Avoid using eval() as it can lead to code injection"
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
"metadata": {
|
|
209
|
+
"package_name": "Security Rules",
|
|
210
|
+
"version": "1.0.0",
|
|
211
|
+
"total_rules": 1
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 🛠️ Technical Requirements
|
|
218
|
+
|
|
219
|
+
### Runtime Requirements
|
|
220
|
+
- Python 3.8+
|
|
221
|
+
- httpx >= 0.25.0
|
|
222
|
+
- mcp >= 1.0.0
|
|
223
|
+
- pydantic >= 2.0.0
|
|
224
|
+
|
|
225
|
+
### Installation
|
|
226
|
+
```bash
|
|
227
|
+
# Via uvx (recommended)
|
|
228
|
+
uvx agenticwerx-mcp-client@latest --api-key your_key_here
|
|
229
|
+
|
|
230
|
+
# Via pip
|
|
231
|
+
pip install agenticwerx-mcp-client
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## 📚 Documentation
|
|
235
|
+
|
|
236
|
+
- [Full Documentation](https://docs.agenticwerx.com/mcp-client)
|
|
237
|
+
- [API Reference](https://docs.agenticwerx.com/api)
|
|
238
|
+
- [Rule Package Catalog](https://agenticwerx.com/packages)
|
|
239
|
+
- [IDE Integration Guides](https://docs.agenticwerx.com/integrations)
|
|
240
|
+
|
|
241
|
+
## 🆘 Support
|
|
242
|
+
|
|
243
|
+
- **Documentation:** [docs.agenticwerx.com](https://docs.agenticwerx.com)
|
|
244
|
+
- **Email Support:** [support@agenticwerx.com](mailto:support@agenticwerx.com)
|
|
245
|
+
- **Community:** [Discord Server](https://discord.gg/agenticwerx)
|
|
246
|
+
|
|
247
|
+
## 📄 License
|
|
248
|
+
|
|
249
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
250
|
+
|
|
251
|
+
**Note:** This is a proprietary package developed and maintained exclusively by AgenticWerx. We do not accept external contributions at this time.
|
|
252
|
+
|
|
253
|
+
## 🔄 Changelog
|
|
254
|
+
|
|
255
|
+
### v1.0.0 (2025-01-XX)
|
|
256
|
+
- Initial release
|
|
257
|
+
- Full MCP protocol support
|
|
258
|
+
- Rule retrieval tools
|
|
259
|
+
- Multi-language support
|
|
260
|
+
- Real-time code feedback
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
**Built with ❤️ by the AgenticWerx Team**
|
|
265
|
+
|
|
266
|
+
*Making code quality accessible to every developer, in every IDE, for every language.*
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
agenticwerx_mcp_client/__init__.py,sha256=gNU_Q5Vxp_VnsKaq7YuHI4hYQ2k-o-3O2aZV6tQqNVU,392
|
|
2
|
+
agenticwerx_mcp_client/__main__.py,sha256=zo38csH7kS-iEP1v3DrOIoY2y_h9-3RHG_g-JqQNcTc,3564
|
|
3
|
+
agenticwerx_mcp_client/api.py,sha256=y14uWFFzu_YxPAu7UcuqQKCnNdzRJR5nN8KHmUKykTc,8547
|
|
4
|
+
agenticwerx_mcp_client/client.py,sha256=k8_1pblBqHi9B83A3xF7okfxnX0DTPy8WIpXlCkeQHs,8572
|
|
5
|
+
agenticwerx_mcp_client-1.0.0.dist-info/METADATA,sha256=iVy-vf-EkDY7wgmAk19lOB4fWf5A02LORyEr0Bpy0BI,7819
|
|
6
|
+
agenticwerx_mcp_client-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
agenticwerx_mcp_client-1.0.0.dist-info/entry_points.txt,sha256=JZ_xQZG_DpeObI-W0Kkl_xJyuuWyDvp4nvvNEoTBnSo,80
|
|
8
|
+
agenticwerx_mcp_client-1.0.0.dist-info/licenses/LICENSE,sha256=q2BReOYGJ-oS4jzVbLIsPVDk1Gxw89w3EvFxTKEBfNk,1067
|
|
9
|
+
agenticwerx_mcp_client-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AgenticWerx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|