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.
@@ -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.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: Optional[str]) -> str:
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
- # Run with API key from environment
55
+ # MCP Server Mode (for IDEs)
60
56
  export AGENTICWERX_API_KEY=your_key_here
61
57
  agenticwerx-mcp-client
62
58
 
63
- # Run with API key as argument
64
- agenticwerx-mcp-client --api-key your_key_here
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
- # Run with debug logging
69
- agenticwerx-mcp-client --api-key your_key_here --debug
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
- parser.add_argument(
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
- logger.info("Starting AgenticWerx MCP Client...")
111
- logger.debug(f"Debug logging enabled")
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
- api_key=api_key,
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()