agenticwerx-mcp-client 1.0.0__py3-none-any.whl → 1.0.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.
- agenticwerx_mcp_client/__init__.py +3 -3
- agenticwerx_mcp_client/__main__.py +285 -46
- agenticwerx_mcp_client/api.py +144 -69
- agenticwerx_mcp_client/client.py +292 -81
- {agenticwerx_mcp_client-1.0.0.dist-info → agenticwerx_mcp_client-1.0.5.dist-info}/METADATA +44 -11
- agenticwerx_mcp_client-1.0.5.dist-info/RECORD +9 -0
- agenticwerx_mcp_client-1.0.0.dist-info/RECORD +0 -9
- {agenticwerx_mcp_client-1.0.0.dist-info → agenticwerx_mcp_client-1.0.5.dist-info}/WHEEL +0 -0
- {agenticwerx_mcp_client-1.0.0.dist-info → agenticwerx_mcp_client-1.0.5.dist-info}/entry_points.txt +0 -0
- {agenticwerx_mcp_client-1.0.0.dist-info → agenticwerx_mcp_client-1.0.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,11 +5,11 @@ A Model Context Protocol (MCP) client for AgenticWerx rule packages.
|
|
|
5
5
|
Provides universal code analysis across all IDEs and programming languages.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.0.
|
|
8
|
+
__version__ = "1.0.5"
|
|
9
9
|
__author__ = "AgenticWerx"
|
|
10
10
|
__email__ = "support@agenticwerx.com"
|
|
11
11
|
|
|
12
|
-
from .client import AgenticWerxMCPClient
|
|
13
12
|
from .api import AgenticWerxAPI
|
|
13
|
+
from .client import AgenticWerxMCPClient
|
|
14
14
|
|
|
15
|
-
__all__ = ["AgenticWerxMCPClient", "AgenticWerxAPI"]
|
|
15
|
+
__all__ = ["AgenticWerxMCPClient", "AgenticWerxAPI"]
|
|
@@ -6,13 +6,14 @@ This module serves as the main entry point when the package is executed
|
|
|
6
6
|
via uvx or python -m agenticwerx_mcp_client.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import asyncio
|
|
10
9
|
import argparse
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
11
12
|
import logging
|
|
12
13
|
import os
|
|
13
14
|
import sys
|
|
14
|
-
from typing import Optional
|
|
15
15
|
|
|
16
|
+
from .api import AgenticWerxAPI
|
|
16
17
|
from .client import AgenticWerxMCPClient
|
|
17
18
|
|
|
18
19
|
|
|
@@ -22,31 +23,26 @@ def setup_logging(debug: bool = False) -> None:
|
|
|
22
23
|
logging.basicConfig(
|
|
23
24
|
level=level,
|
|
24
25
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
25
|
-
handlers=[
|
|
26
|
-
logging.StreamHandler(sys.stderr)
|
|
27
|
-
]
|
|
26
|
+
handlers=[logging.StreamHandler(sys.stderr)],
|
|
28
27
|
)
|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
def get_api_key(args_api_key:
|
|
30
|
+
def get_api_key(args_api_key: str | None) -> str:
|
|
32
31
|
"""Get API key from arguments or environment variables."""
|
|
33
32
|
api_key = args_api_key or os.getenv("AGENTICWERX_API_KEY")
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
if not api_key:
|
|
36
35
|
print(
|
|
37
36
|
"Error: API key required via --api-key argument or AGENTICWERX_API_KEY environment variable",
|
|
38
|
-
file=sys.stderr
|
|
37
|
+
file=sys.stderr,
|
|
39
38
|
)
|
|
40
39
|
print(
|
|
41
40
|
"Get your API key at: https://agenticwerx.com/dashboard/api-keys",
|
|
42
|
-
file=sys.stderr
|
|
41
|
+
file=sys.stderr,
|
|
43
42
|
)
|
|
44
43
|
sys.exit(1)
|
|
45
|
-
|
|
46
|
-
return api_key
|
|
47
|
-
|
|
48
|
-
|
|
49
44
|
|
|
45
|
+
return api_key
|
|
50
46
|
|
|
51
47
|
|
|
52
48
|
def create_parser() -> argparse.ArgumentParser:
|
|
@@ -56,69 +52,312 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
56
52
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
57
53
|
epilog="""
|
|
58
54
|
Examples:
|
|
59
|
-
#
|
|
55
|
+
# MCP Server Mode (for IDEs)
|
|
60
56
|
export AGENTICWERX_API_KEY=your_key_here
|
|
61
57
|
agenticwerx-mcp-client
|
|
62
58
|
|
|
63
|
-
#
|
|
64
|
-
agenticwerx-mcp-client --api-key
|
|
65
|
-
|
|
59
|
+
# CLI Mode - Get rules
|
|
60
|
+
agenticwerx-mcp-client --api-key your_key get-rules
|
|
61
|
+
agenticwerx-mcp-client --api-key your_key get-rules --package-id pkg_123
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
agenticwerx-mcp-client --api-key
|
|
63
|
+
# CLI Mode - Analyze code
|
|
64
|
+
agenticwerx-mcp-client --api-key your_key analyze-code --code "print('hello')"
|
|
65
|
+
agenticwerx-mcp-client --api-key your_key analyze-code --file script.py --language python
|
|
70
66
|
|
|
71
67
|
For more information, visit: https://docs.agenticwerx.com/mcp-client
|
|
72
|
-
"""
|
|
68
|
+
""",
|
|
73
69
|
)
|
|
74
|
-
|
|
70
|
+
|
|
75
71
|
parser.add_argument(
|
|
76
72
|
"--api-key",
|
|
77
73
|
type=str,
|
|
78
|
-
help="AgenticWerx API key (can also use AGENTICWERX_API_KEY env var)"
|
|
74
|
+
help="AgenticWerx API key (can also use AGENTICWERX_API_KEY env var)",
|
|
79
75
|
)
|
|
80
|
-
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"--debug",
|
|
85
|
-
action="store_true",
|
|
86
|
-
help="Enable debug logging"
|
|
87
|
-
)
|
|
88
|
-
|
|
77
|
+
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
|
78
|
+
|
|
89
79
|
parser.add_argument(
|
|
90
80
|
"--version",
|
|
91
81
|
action="version",
|
|
92
|
-
version=f"agenticwerx-mcp-client {__import__('agenticwerx_mcp_client').__version__}"
|
|
82
|
+
version=f"agenticwerx-mcp-client {__import__('agenticwerx_mcp_client').__version__}",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Subcommands for CLI mode
|
|
86
|
+
subparsers = parser.add_subparsers(dest="command", help="CLI commands")
|
|
87
|
+
|
|
88
|
+
# get-rules command
|
|
89
|
+
get_rules_parser = subparsers.add_parser(
|
|
90
|
+
"get-rules", help="Get AgenticWerx rules"
|
|
93
91
|
)
|
|
94
|
-
|
|
92
|
+
get_rules_parser.add_argument(
|
|
93
|
+
"--package-id", type=str, help="Optional package ID to filter rules"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# analyze-code command
|
|
97
|
+
analyze_parser = subparsers.add_parser(
|
|
98
|
+
"analyze-code", help="Analyze code using AgenticWerx rules"
|
|
99
|
+
)
|
|
100
|
+
code_group = analyze_parser.add_mutually_exclusive_group(required=True)
|
|
101
|
+
code_group.add_argument("--code", type=str, help="Code snippet to analyze")
|
|
102
|
+
code_group.add_argument("--file", type=str, help="File path to analyze")
|
|
103
|
+
analyze_parser.add_argument(
|
|
104
|
+
"--language", type=str, help="Programming language (auto-detected if omitted)"
|
|
105
|
+
)
|
|
106
|
+
analyze_parser.add_argument(
|
|
107
|
+
"--package-ids",
|
|
108
|
+
type=str,
|
|
109
|
+
nargs="+",
|
|
110
|
+
help="Package IDs to use for analysis",
|
|
111
|
+
)
|
|
112
|
+
|
|
95
113
|
return parser
|
|
96
114
|
|
|
97
115
|
|
|
116
|
+
# Maximum code size before chunking (in characters)
|
|
117
|
+
# Lambda server has a limit around 10KB, so we chunk at 8KB to be safe
|
|
118
|
+
MAX_CODE_SIZE = 8000
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def detect_language_from_file(filepath: str) -> str | None:
|
|
122
|
+
"""Detect programming language from file extension."""
|
|
123
|
+
ext_map = {
|
|
124
|
+
".py": "python",
|
|
125
|
+
".js": "javascript",
|
|
126
|
+
".jsx": "javascript",
|
|
127
|
+
".ts": "typescript",
|
|
128
|
+
".tsx": "typescript",
|
|
129
|
+
".java": "java",
|
|
130
|
+
".c": "c",
|
|
131
|
+
".cpp": "cpp",
|
|
132
|
+
".cc": "cpp",
|
|
133
|
+
".cxx": "cpp",
|
|
134
|
+
".h": "c",
|
|
135
|
+
".hpp": "cpp",
|
|
136
|
+
".cs": "csharp",
|
|
137
|
+
".go": "go",
|
|
138
|
+
".rs": "rust",
|
|
139
|
+
".rb": "ruby",
|
|
140
|
+
".php": "php",
|
|
141
|
+
".swift": "swift",
|
|
142
|
+
".kt": "kotlin",
|
|
143
|
+
".scala": "scala",
|
|
144
|
+
".sh": "bash",
|
|
145
|
+
".bash": "bash",
|
|
146
|
+
".zsh": "zsh",
|
|
147
|
+
".sql": "sql",
|
|
148
|
+
".html": "html",
|
|
149
|
+
".css": "css",
|
|
150
|
+
".scss": "scss",
|
|
151
|
+
".sass": "sass",
|
|
152
|
+
".json": "json",
|
|
153
|
+
".xml": "xml",
|
|
154
|
+
".yaml": "yaml",
|
|
155
|
+
".yml": "yaml",
|
|
156
|
+
".md": "markdown",
|
|
157
|
+
".r": "r",
|
|
158
|
+
".R": "r",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
import os
|
|
162
|
+
|
|
163
|
+
_, ext = os.path.splitext(filepath)
|
|
164
|
+
return ext_map.get(ext.lower())
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def smart_chunk_code(code: str, max_size: int = MAX_CODE_SIZE) -> list[str]:
|
|
168
|
+
"""
|
|
169
|
+
Split code into chunks intelligently, trying to break at logical boundaries.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
code: The code to chunk
|
|
173
|
+
max_size: Maximum size per chunk in characters
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of code chunks
|
|
177
|
+
"""
|
|
178
|
+
if len(code) <= max_size:
|
|
179
|
+
return [code]
|
|
180
|
+
|
|
181
|
+
chunks = []
|
|
182
|
+
lines = code.split("\n")
|
|
183
|
+
current_chunk = []
|
|
184
|
+
current_size = 0
|
|
185
|
+
|
|
186
|
+
for line in lines:
|
|
187
|
+
line_size = len(line) + 1 # +1 for newline
|
|
188
|
+
|
|
189
|
+
# If adding this line would exceed max_size, save current chunk
|
|
190
|
+
if current_size + line_size > max_size and current_chunk:
|
|
191
|
+
chunks.append("\n".join(current_chunk))
|
|
192
|
+
current_chunk = []
|
|
193
|
+
current_size = 0
|
|
194
|
+
|
|
195
|
+
current_chunk.append(line)
|
|
196
|
+
current_size += line_size
|
|
197
|
+
|
|
198
|
+
# Add remaining lines
|
|
199
|
+
if current_chunk:
|
|
200
|
+
chunks.append("\n".join(current_chunk))
|
|
201
|
+
|
|
202
|
+
return chunks
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def aggregate_analysis_results(results: list[dict]) -> dict:
|
|
206
|
+
"""
|
|
207
|
+
Aggregate multiple analysis results into a single result.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
results: List of analysis results from chunks
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Aggregated result
|
|
214
|
+
"""
|
|
215
|
+
if not results:
|
|
216
|
+
return {"summary": {}, "suggestions": [], "warnings": []}
|
|
217
|
+
|
|
218
|
+
# Start with first result as base
|
|
219
|
+
aggregated = {
|
|
220
|
+
"summary": {
|
|
221
|
+
"language": results[0].get("summary", {}).get("language"),
|
|
222
|
+
"totalChunks": len(results),
|
|
223
|
+
"totalCodeSize": sum(
|
|
224
|
+
r.get("summary", {}).get("codeSize", 0) for r in results
|
|
225
|
+
),
|
|
226
|
+
"totalRulesApplied": sum(
|
|
227
|
+
r.get("summary", {}).get("rulesApplied", 0) for r in results
|
|
228
|
+
),
|
|
229
|
+
"totalIssues": sum(
|
|
230
|
+
r.get("summary", {}).get("totalIssues", 0) for r in results
|
|
231
|
+
),
|
|
232
|
+
"totalProcessingTime": sum(
|
|
233
|
+
r.get("summary", {}).get("processingTime", 0) for r in results
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
"suggestions": [],
|
|
237
|
+
"warnings": [],
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Collect all suggestions and warnings
|
|
241
|
+
for result in results:
|
|
242
|
+
aggregated["suggestions"].extend(result.get("suggestions", []))
|
|
243
|
+
aggregated["warnings"].extend(result.get("warnings", []))
|
|
244
|
+
|
|
245
|
+
# Update counts
|
|
246
|
+
aggregated["summary"]["returned"] = len(aggregated["suggestions"])
|
|
247
|
+
|
|
248
|
+
return aggregated
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def run_cli_command(args: argparse.Namespace, api_key: str) -> None:
|
|
252
|
+
"""Run CLI command mode."""
|
|
253
|
+
async with AgenticWerxAPI(api_key) as api:
|
|
254
|
+
if args.command == "get-rules":
|
|
255
|
+
# Get rules
|
|
256
|
+
result = await api.get_rules(args.package_id)
|
|
257
|
+
print(json.dumps(result, indent=2))
|
|
258
|
+
|
|
259
|
+
elif args.command == "analyze-code":
|
|
260
|
+
# Read code from file if specified
|
|
261
|
+
language = args.language
|
|
262
|
+
logger = logging.getLogger(__name__)
|
|
263
|
+
|
|
264
|
+
if args.file:
|
|
265
|
+
try:
|
|
266
|
+
with open(args.file, "r") as f:
|
|
267
|
+
code = f.read()
|
|
268
|
+
# Auto-detect language from file extension if not provided
|
|
269
|
+
if not language:
|
|
270
|
+
language = detect_language_from_file(args.file)
|
|
271
|
+
if language:
|
|
272
|
+
logger.info(f"Auto-detected language: {language}")
|
|
273
|
+
except Exception as e:
|
|
274
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
275
|
+
sys.exit(1)
|
|
276
|
+
else:
|
|
277
|
+
code = args.code
|
|
278
|
+
|
|
279
|
+
# Check if code needs to be chunked
|
|
280
|
+
if len(code) > MAX_CODE_SIZE:
|
|
281
|
+
logger.info(
|
|
282
|
+
f"Code size ({len(code)} chars) exceeds limit ({MAX_CODE_SIZE} chars), chunking..."
|
|
283
|
+
)
|
|
284
|
+
chunks = smart_chunk_code(code, MAX_CODE_SIZE)
|
|
285
|
+
logger.info(f"Split code into {len(chunks)} chunks")
|
|
286
|
+
|
|
287
|
+
# Analyze each chunk
|
|
288
|
+
results = []
|
|
289
|
+
for i, chunk in enumerate(chunks, 1):
|
|
290
|
+
logger.info(
|
|
291
|
+
f"Analyzing chunk {i}/{len(chunks)} ({len(chunk)} chars)..."
|
|
292
|
+
)
|
|
293
|
+
try:
|
|
294
|
+
result = await api.analyze_code(
|
|
295
|
+
code=chunk,
|
|
296
|
+
language=language,
|
|
297
|
+
package_ids=args.package_ids,
|
|
298
|
+
)
|
|
299
|
+
results.append(result)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.warning(f"Chunk {i} analysis failed: {e}")
|
|
302
|
+
# Continue with other chunks
|
|
303
|
+
|
|
304
|
+
# Aggregate results
|
|
305
|
+
if results:
|
|
306
|
+
aggregated = aggregate_analysis_results(results)
|
|
307
|
+
logger.info(
|
|
308
|
+
f"Analysis complete: {aggregated['summary']['totalIssues']} total issues found"
|
|
309
|
+
)
|
|
310
|
+
print(json.dumps(aggregated, indent=2))
|
|
311
|
+
else:
|
|
312
|
+
print(
|
|
313
|
+
json.dumps(
|
|
314
|
+
{"error": "All chunks failed to analyze"}, indent=2
|
|
315
|
+
),
|
|
316
|
+
file=sys.stderr,
|
|
317
|
+
)
|
|
318
|
+
sys.exit(1)
|
|
319
|
+
else:
|
|
320
|
+
# Single request for small code
|
|
321
|
+
result = await api.analyze_code(
|
|
322
|
+
code=code, language=language, package_ids=args.package_ids
|
|
323
|
+
)
|
|
324
|
+
print(json.dumps(result, indent=2))
|
|
325
|
+
|
|
326
|
+
|
|
98
327
|
async def async_main() -> None:
|
|
99
328
|
"""Async main function."""
|
|
100
329
|
parser = create_parser()
|
|
101
330
|
args = parser.parse_args()
|
|
102
|
-
|
|
331
|
+
|
|
103
332
|
# Setup logging
|
|
104
333
|
setup_logging(args.debug)
|
|
105
334
|
logger = logging.getLogger(__name__)
|
|
106
|
-
|
|
335
|
+
|
|
107
336
|
# Get API key
|
|
108
337
|
api_key = get_api_key(args.api_key)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
338
|
+
|
|
339
|
+
# Check if running in CLI mode (subcommand provided)
|
|
340
|
+
if args.command:
|
|
341
|
+
logger.debug(f"Running CLI command: {args.command}")
|
|
342
|
+
try:
|
|
343
|
+
await run_cli_command(args, api_key)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logger.error(f"Command failed: {e}")
|
|
346
|
+
if args.debug:
|
|
347
|
+
logger.exception("Full traceback:")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
# MCP Server mode (default)
|
|
352
|
+
logger.info("Starting AgenticWerx MCP Client in server mode...")
|
|
353
|
+
logger.debug("Debug logging enabled")
|
|
354
|
+
|
|
113
355
|
try:
|
|
114
356
|
# Create and run the MCP client
|
|
115
|
-
client = AgenticWerxMCPClient(
|
|
116
|
-
|
|
117
|
-
debug=args.debug
|
|
118
|
-
)
|
|
119
|
-
|
|
357
|
+
client = AgenticWerxMCPClient(api_key=api_key, debug=args.debug)
|
|
358
|
+
|
|
120
359
|
await client.run()
|
|
121
|
-
|
|
360
|
+
|
|
122
361
|
except KeyboardInterrupt:
|
|
123
362
|
logger.info("Received interrupt signal, shutting down...")
|
|
124
363
|
sys.exit(0)
|
|
@@ -141,4 +380,4 @@ def main() -> None:
|
|
|
141
380
|
|
|
142
381
|
|
|
143
382
|
if __name__ == "__main__":
|
|
144
|
-
main()
|
|
383
|
+
main()
|