pwndoc-mcp-server 1.0.8__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.
@@ -0,0 +1,1987 @@
1
+ """
2
+ PwnDoc MCP Server - Model Context Protocol implementation.
3
+
4
+ This module implements the MCP server that exposes PwnDoc functionality
5
+ to AI assistants like Claude through a standardized protocol.
6
+
7
+ Supports multiple transports:
8
+ - stdio: Standard input/output (default, for Claude Desktop)
9
+ - sse: Server-Sent Events (for web clients)
10
+ - websocket: WebSocket (for real-time applications)
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ import logging
16
+ import sys
17
+ from dataclasses import dataclass, field
18
+ from typing import Any, Callable, Dict, List, Optional, Union
19
+
20
+ from .client import PwnDocClient, PwnDocError
21
+ from .config import Config, load_config
22
+ from .version import get_version
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @dataclass
28
+ class Tool:
29
+ """MCP Tool definition."""
30
+
31
+ name: str
32
+ description: str
33
+ parameters: Dict[str, Any]
34
+ handler: Callable
35
+ required: List[str] = field(default_factory=list)
36
+
37
+
38
+ @dataclass
39
+ class MCPMessage:
40
+ """MCP protocol message."""
41
+
42
+ jsonrpc: str = "2.0"
43
+ id: Optional[Union[str, int]] = None
44
+ method: Optional[str] = None
45
+ params: Optional[Dict[str, Any]] = None
46
+ result: Optional[Any] = None
47
+ error: Optional[Dict[str, Any]] = None
48
+
49
+
50
+ class PwnDocMCPServer:
51
+ """
52
+ Model Context Protocol server for PwnDoc.
53
+
54
+ Exposes PwnDoc API functionality as MCP tools that can be called
55
+ by AI assistants like Claude.
56
+
57
+ Example:
58
+ >>> server = PwnDocMCPServer(config)
59
+ >>> server.run() # Starts stdio transport
60
+ """
61
+
62
+ SERVER_NAME = "pwndoc-mcp-server"
63
+ SERVER_VERSION = get_version()
64
+ PROTOCOL_VERSION = "2024-11-05"
65
+
66
+ def __init__(
67
+ self,
68
+ config: Optional[Config] = None,
69
+ transport: Optional[str] = None,
70
+ _silent: bool = False,
71
+ ):
72
+ """
73
+ Initialize MCP server.
74
+
75
+ Args:
76
+ config: Configuration object (loads from environment if not provided)
77
+ transport: Transport type (stdio, sse, websocket)
78
+ _silent: Internal flag to suppress logging during tool definition extraction
79
+ """
80
+ self.config = config or load_config()
81
+ self.transport = transport or self.config.mcp_transport
82
+
83
+ # Validate transport
84
+ valid_transports = ["stdio", "sse", "websocket"]
85
+ if self.transport not in valid_transports:
86
+ raise ValueError(f"Invalid transport: {self.transport}")
87
+
88
+ self._client: Optional[PwnDocClient] = None
89
+ self._tools: Dict[str, Tool] = {}
90
+ self._initialized = False
91
+
92
+ # Register all tools
93
+ self._register_tools()
94
+
95
+ # Only log if not silent (prevents stdout pollution during module import)
96
+ if not _silent:
97
+ logger.info(f"PwnDocMCPServer initialized with {len(self._tools)} tools")
98
+
99
+ @property
100
+ def name(self) -> str:
101
+ """Get server name."""
102
+ return self.SERVER_NAME
103
+
104
+ @property
105
+ def version(self) -> str:
106
+ """Get server version."""
107
+ return self.SERVER_VERSION
108
+
109
+ @property
110
+ def client(self) -> PwnDocClient:
111
+ """Get or create PwnDoc client."""
112
+ if self._client is None:
113
+ self._client = PwnDocClient(self.config)
114
+ self._client.authenticate()
115
+ return self._client
116
+
117
+ def _register_tools(self):
118
+ """Register all available tools."""
119
+
120
+ # =====================================================================
121
+ # AUDIT TOOLS
122
+ # =====================================================================
123
+
124
+ self._register_tool(
125
+ name="list_audits",
126
+ description="List all audits/pentests. Can filter by finding title.",
127
+ parameters={
128
+ "type": "object",
129
+ "properties": {
130
+ "finding_title": {
131
+ "type": "string",
132
+ "description": "Filter audits containing findings with this title (optional)",
133
+ }
134
+ },
135
+ },
136
+ handler=self._handle_list_audits,
137
+ )
138
+
139
+ self._register_tool(
140
+ name="get_audit",
141
+ description="Get detailed information about a specific audit including all findings, scope, sections, and metadata.",
142
+ parameters={
143
+ "type": "object",
144
+ "properties": {
145
+ "audit_id": {"type": "string", "description": "The audit ID (MongoDB ObjectId)"}
146
+ },
147
+ "required": ["audit_id"],
148
+ },
149
+ handler=self._handle_get_audit,
150
+ )
151
+
152
+ self._register_tool(
153
+ name="create_audit",
154
+ description="Create a new audit/pentest.",
155
+ parameters={
156
+ "type": "object",
157
+ "properties": {
158
+ "name": {"type": "string", "description": "Audit name"},
159
+ "language": {"type": "string", "description": "Language code (e.g., 'en')"},
160
+ "audit_type": {"type": "string", "description": "Type of audit"},
161
+ },
162
+ "required": ["name", "language", "audit_type"],
163
+ },
164
+ handler=self._handle_create_audit,
165
+ )
166
+
167
+ self._register_tool(
168
+ name="update_audit_general",
169
+ description="Update general information of an audit.",
170
+ parameters={
171
+ "type": "object",
172
+ "properties": {
173
+ "audit_id": {"type": "string", "description": "The audit ID"},
174
+ "name": {"type": "string", "description": "Audit name"},
175
+ "client": {"type": "string", "description": "Client ID"},
176
+ "company": {"type": "string", "description": "Company ID"},
177
+ "date_start": {"type": "string", "description": "Start date (ISO format)"},
178
+ "date_end": {"type": "string", "description": "End date (ISO format)"},
179
+ "scope": {
180
+ "type": "array",
181
+ "items": {"type": "string"},
182
+ "description": "Scope items",
183
+ },
184
+ },
185
+ "required": ["audit_id"],
186
+ },
187
+ handler=self._handle_update_audit_general,
188
+ )
189
+
190
+ self._register_tool(
191
+ name="delete_audit",
192
+ description="Delete an audit permanently.",
193
+ parameters={
194
+ "type": "object",
195
+ "properties": {
196
+ "audit_id": {"type": "string", "description": "The audit ID to delete"}
197
+ },
198
+ "required": ["audit_id"],
199
+ },
200
+ handler=self._handle_delete_audit,
201
+ )
202
+
203
+ self._register_tool(
204
+ name="generate_audit_report",
205
+ description="Generate and download the audit report (DOCX).",
206
+ parameters={
207
+ "type": "object",
208
+ "properties": {"audit_id": {"type": "string", "description": "The audit ID"}},
209
+ "required": ["audit_id"],
210
+ },
211
+ handler=self._handle_generate_report,
212
+ )
213
+
214
+ self._register_tool(
215
+ name="get_audit_general",
216
+ description="Get audit general information (dates, client, company, scope).",
217
+ parameters={
218
+ "type": "object",
219
+ "properties": {"audit_id": {"type": "string", "description": "The audit ID"}},
220
+ "required": ["audit_id"],
221
+ },
222
+ handler=self._handle_get_audit_general,
223
+ )
224
+
225
+ self._register_tool(
226
+ name="get_audit_network",
227
+ description="Get audit network information.",
228
+ parameters={
229
+ "type": "object",
230
+ "properties": {"audit_id": {"type": "string", "description": "The audit ID"}},
231
+ "required": ["audit_id"],
232
+ },
233
+ handler=self._handle_get_audit_network,
234
+ )
235
+
236
+ self._register_tool(
237
+ name="update_audit_network",
238
+ description="Update audit network information.",
239
+ parameters={
240
+ "type": "object",
241
+ "properties": {
242
+ "audit_id": {"type": "string", "description": "The audit ID"},
243
+ "network_data": {"type": "object", "description": "Network configuration data"},
244
+ },
245
+ "required": ["audit_id", "network_data"],
246
+ },
247
+ handler=self._handle_update_audit_network,
248
+ )
249
+
250
+ self._register_tool(
251
+ name="toggle_audit_approval",
252
+ description="Toggle audit approval status.",
253
+ parameters={
254
+ "type": "object",
255
+ "properties": {"audit_id": {"type": "string", "description": "The audit ID"}},
256
+ "required": ["audit_id"],
257
+ },
258
+ handler=self._handle_toggle_audit_approval,
259
+ )
260
+
261
+ self._register_tool(
262
+ name="update_review_status",
263
+ description="Update audit ready-for-review status.",
264
+ parameters={
265
+ "type": "object",
266
+ "properties": {
267
+ "audit_id": {"type": "string", "description": "The audit ID"},
268
+ "state": {"type": "boolean", "description": "Ready for review state"},
269
+ },
270
+ "required": ["audit_id", "state"],
271
+ },
272
+ handler=self._handle_update_review_status,
273
+ )
274
+
275
+ self._register_tool(
276
+ name="get_audit_sections",
277
+ description="Get audit sections content.",
278
+ parameters={
279
+ "type": "object",
280
+ "properties": {"audit_id": {"type": "string", "description": "The audit ID"}},
281
+ "required": ["audit_id"],
282
+ },
283
+ handler=self._handle_get_audit_sections,
284
+ )
285
+
286
+ self._register_tool(
287
+ name="update_audit_sections",
288
+ description="Update audit sections content.",
289
+ parameters={
290
+ "type": "object",
291
+ "properties": {
292
+ "audit_id": {"type": "string", "description": "The audit ID"},
293
+ "sections": {"type": "object", "description": "Sections data to update"},
294
+ },
295
+ "required": ["audit_id", "sections"],
296
+ },
297
+ handler=self._handle_update_audit_sections,
298
+ )
299
+
300
+ # =====================================================================
301
+ # FINDING TOOLS
302
+ # =====================================================================
303
+
304
+ self._register_tool(
305
+ name="get_audit_findings",
306
+ description="Get all findings/vulnerabilities from a specific audit.",
307
+ parameters={
308
+ "type": "object",
309
+ "properties": {"audit_id": {"type": "string", "description": "The audit ID"}},
310
+ "required": ["audit_id"],
311
+ },
312
+ handler=self._handle_get_findings,
313
+ )
314
+
315
+ self._register_tool(
316
+ name="get_finding",
317
+ description="Get details of a specific finding in an audit.",
318
+ parameters={
319
+ "type": "object",
320
+ "properties": {
321
+ "audit_id": {"type": "string", "description": "The audit ID"},
322
+ "finding_id": {"type": "string", "description": "The finding ID"},
323
+ },
324
+ "required": ["audit_id", "finding_id"],
325
+ },
326
+ handler=self._handle_get_finding,
327
+ )
328
+
329
+ self._register_tool(
330
+ name="create_finding",
331
+ description="Create a new finding in an audit.",
332
+ parameters={
333
+ "type": "object",
334
+ "properties": {
335
+ "audit_id": {"type": "string", "description": "The audit ID"},
336
+ "title": {"type": "string", "description": "Finding title"},
337
+ "description": {"type": "string", "description": "Detailed description"},
338
+ "observation": {"type": "string", "description": "Observation/evidence"},
339
+ "remediation": {"type": "string", "description": "Remediation steps"},
340
+ "cvssv3": {"type": "string", "description": "CVSS v3 score/vector"},
341
+ "priority": {"type": "integer", "description": "Priority (1-4)"},
342
+ "category": {"type": "string", "description": "Category"},
343
+ "vuln_type": {"type": "string", "description": "Vulnerability type"},
344
+ "poc": {"type": "string", "description": "Proof of concept"},
345
+ "scope": {"type": "string", "description": "Affected scope"},
346
+ "references": {
347
+ "type": "array",
348
+ "items": {"type": "string"},
349
+ "description": "References",
350
+ },
351
+ },
352
+ "required": ["audit_id", "title"],
353
+ },
354
+ handler=self._handle_create_finding,
355
+ )
356
+
357
+ self._register_tool(
358
+ name="update_finding",
359
+ description="Update an existing finding.",
360
+ parameters={
361
+ "type": "object",
362
+ "properties": {
363
+ "audit_id": {"type": "string", "description": "The audit ID"},
364
+ "finding_id": {"type": "string", "description": "The finding ID"},
365
+ "title": {"type": "string"},
366
+ "description": {"type": "string"},
367
+ "observation": {"type": "string"},
368
+ "remediation": {"type": "string"},
369
+ "cvssv3": {"type": "string"},
370
+ "priority": {"type": "integer"},
371
+ "category": {"type": "string"},
372
+ "vuln_type": {"type": "string"},
373
+ "poc": {"type": "string"},
374
+ "scope": {"type": "string"},
375
+ "references": {"type": "array", "items": {"type": "string"}},
376
+ },
377
+ "required": ["audit_id", "finding_id"],
378
+ },
379
+ handler=self._handle_update_finding,
380
+ )
381
+
382
+ self._register_tool(
383
+ name="delete_finding",
384
+ description="Delete a finding from an audit.",
385
+ parameters={
386
+ "type": "object",
387
+ "properties": {
388
+ "audit_id": {"type": "string", "description": "The audit ID"},
389
+ "finding_id": {"type": "string", "description": "The finding ID to delete"},
390
+ },
391
+ "required": ["audit_id", "finding_id"],
392
+ },
393
+ handler=self._handle_delete_finding,
394
+ )
395
+
396
+ self._register_tool(
397
+ name="search_findings",
398
+ description="Search for findings across all audits by various criteria.",
399
+ parameters={
400
+ "type": "object",
401
+ "properties": {
402
+ "title": {"type": "string", "description": "Search by finding title"},
403
+ "category": {"type": "string", "description": "Filter by category"},
404
+ "severity": {
405
+ "type": "string",
406
+ "description": "Filter by severity (Critical, High, Medium, Low)",
407
+ },
408
+ "status": {"type": "string", "description": "Filter by status"},
409
+ },
410
+ },
411
+ handler=self._handle_search_findings,
412
+ )
413
+
414
+ self._register_tool(
415
+ name="get_all_findings_with_context",
416
+ description="Get ALL findings from ALL audits with full context (company, dates, team, scope, description, CWE, references) in a single request.",
417
+ parameters={
418
+ "type": "object",
419
+ "properties": {
420
+ "include_failed": {
421
+ "type": "boolean",
422
+ "description": "Include 'Failed' category findings (default: false)",
423
+ },
424
+ "exclude_categories": {
425
+ "type": "array",
426
+ "items": {"type": "string"},
427
+ "description": "Categories to exclude",
428
+ },
429
+ },
430
+ },
431
+ handler=self._handle_get_all_findings_with_context,
432
+ )
433
+
434
+ self._register_tool(
435
+ name="sort_findings",
436
+ description="Reorder findings within an audit.",
437
+ parameters={
438
+ "type": "object",
439
+ "properties": {
440
+ "audit_id": {"type": "string", "description": "The audit ID"},
441
+ "finding_order": {
442
+ "type": "array",
443
+ "items": {"type": "string"},
444
+ "description": "Ordered array of finding IDs",
445
+ },
446
+ },
447
+ "required": ["audit_id", "finding_order"],
448
+ },
449
+ handler=self._handle_sort_findings,
450
+ )
451
+
452
+ self._register_tool(
453
+ name="move_finding",
454
+ description="Move a finding from one audit to another.",
455
+ parameters={
456
+ "type": "object",
457
+ "properties": {
458
+ "audit_id": {"type": "string", "description": "Source audit ID"},
459
+ "finding_id": {"type": "string", "description": "Finding ID to move"},
460
+ "destination_audit_id": {
461
+ "type": "string",
462
+ "description": "Destination audit ID",
463
+ },
464
+ },
465
+ "required": ["audit_id", "finding_id", "destination_audit_id"],
466
+ },
467
+ handler=self._handle_move_finding,
468
+ )
469
+
470
+ # =====================================================================
471
+ # CLIENT & COMPANY TOOLS
472
+ # =====================================================================
473
+
474
+ self._register_tool(
475
+ name="list_clients",
476
+ description="List all clients.",
477
+ parameters={"type": "object", "properties": {}},
478
+ handler=self._handle_list_clients,
479
+ )
480
+
481
+ self._register_tool(
482
+ name="create_client",
483
+ description="Create a new client.",
484
+ parameters={
485
+ "type": "object",
486
+ "properties": {
487
+ "firstname": {"type": "string", "description": "First name"},
488
+ "lastname": {"type": "string", "description": "Last name"},
489
+ "email": {"type": "string", "description": "Client email"},
490
+ "phone": {"type": "string", "description": "Phone number"},
491
+ "cell": {"type": "string", "description": "Cell phone"},
492
+ "title": {"type": "string", "description": "Job title"},
493
+ "company": {"type": "string", "description": "Company ID"},
494
+ },
495
+ "required": ["email", "firstname", "lastname"],
496
+ },
497
+ handler=self._handle_create_client,
498
+ )
499
+
500
+ self._register_tool(
501
+ name="update_client",
502
+ description="Update an existing client.",
503
+ parameters={
504
+ "type": "object",
505
+ "properties": {
506
+ "client_id": {"type": "string", "description": "Client ID"},
507
+ "firstname": {"type": "string", "description": "First name"},
508
+ "lastname": {"type": "string", "description": "Last name"},
509
+ "email": {"type": "string", "description": "Client email"},
510
+ "phone": {"type": "string", "description": "Phone number"},
511
+ "cell": {"type": "string", "description": "Cell phone"},
512
+ "title": {"type": "string", "description": "Job title"},
513
+ "company": {"type": "string", "description": "Company ID"},
514
+ },
515
+ "required": ["client_id"],
516
+ },
517
+ handler=self._handle_update_client,
518
+ )
519
+
520
+ self._register_tool(
521
+ name="delete_client",
522
+ description="Delete a client.",
523
+ parameters={
524
+ "type": "object",
525
+ "properties": {
526
+ "client_id": {"type": "string", "description": "Client ID to delete"}
527
+ },
528
+ "required": ["client_id"],
529
+ },
530
+ handler=self._handle_delete_client,
531
+ )
532
+
533
+ self._register_tool(
534
+ name="list_companies",
535
+ description="List all companies.",
536
+ parameters={"type": "object", "properties": {}},
537
+ handler=self._handle_list_companies,
538
+ )
539
+
540
+ self._register_tool(
541
+ name="create_company",
542
+ description="Create a new company.",
543
+ parameters={
544
+ "type": "object",
545
+ "properties": {
546
+ "name": {"type": "string", "description": "Company name"},
547
+ "short_name": {"type": "string", "description": "Short name/abbreviation"},
548
+ "logo": {"type": "string", "description": "Logo (base64)"},
549
+ },
550
+ "required": ["name"],
551
+ },
552
+ handler=self._handle_create_company,
553
+ )
554
+
555
+ self._register_tool(
556
+ name="update_company",
557
+ description="Update an existing company.",
558
+ parameters={
559
+ "type": "object",
560
+ "properties": {
561
+ "company_id": {"type": "string", "description": "Company ID"},
562
+ "name": {"type": "string", "description": "Company name"},
563
+ "short_name": {"type": "string", "description": "Short name/abbreviation"},
564
+ "logo": {"type": "string", "description": "Logo (base64)"},
565
+ },
566
+ "required": ["company_id"],
567
+ },
568
+ handler=self._handle_update_company,
569
+ )
570
+
571
+ self._register_tool(
572
+ name="delete_company",
573
+ description="Delete a company.",
574
+ parameters={
575
+ "type": "object",
576
+ "properties": {
577
+ "company_id": {"type": "string", "description": "Company ID to delete"}
578
+ },
579
+ "required": ["company_id"],
580
+ },
581
+ handler=self._handle_delete_company,
582
+ )
583
+
584
+ # =====================================================================
585
+ # VULNERABILITY TEMPLATE TOOLS
586
+ # =====================================================================
587
+
588
+ self._register_tool(
589
+ name="list_vulnerabilities",
590
+ description="List all vulnerability templates in the library.",
591
+ parameters={"type": "object", "properties": {}},
592
+ handler=self._handle_list_vulnerabilities,
593
+ )
594
+
595
+ self._register_tool(
596
+ name="get_vulnerabilities_by_locale",
597
+ description="Get vulnerability templates for a specific language.",
598
+ parameters={
599
+ "type": "object",
600
+ "properties": {
601
+ "locale": {
602
+ "type": "string",
603
+ "description": "Language code (e.g., 'en', 'fr')",
604
+ "default": "en",
605
+ }
606
+ },
607
+ },
608
+ handler=self._handle_get_vulnerabilities_by_locale,
609
+ )
610
+
611
+ self._register_tool(
612
+ name="create_vulnerability",
613
+ description="Create a new vulnerability template.",
614
+ parameters={
615
+ "type": "object",
616
+ "properties": {
617
+ "details": {"type": "object", "description": "Vulnerability details by locale"},
618
+ "cvssv3": {"type": "string", "description": "CVSS v3 score"},
619
+ "priority": {"type": "integer", "description": "Priority (1-4)"},
620
+ "remediation_complexity": {
621
+ "type": "integer",
622
+ "description": "Complexity (1-3)",
623
+ },
624
+ "category": {"type": "string", "description": "Category"},
625
+ },
626
+ "required": ["details"],
627
+ },
628
+ handler=self._handle_create_vulnerability,
629
+ )
630
+
631
+ self._register_tool(
632
+ name="update_vulnerability",
633
+ description="Update an existing vulnerability template.",
634
+ parameters={
635
+ "type": "object",
636
+ "properties": {
637
+ "vuln_id": {"type": "string", "description": "Vulnerability template ID"},
638
+ "details": {"type": "object", "description": "Vulnerability details by locale"},
639
+ "cvssv3": {"type": "string", "description": "CVSS v3 score"},
640
+ "priority": {"type": "integer", "description": "Priority (1-4)"},
641
+ "remediation_complexity": {
642
+ "type": "integer",
643
+ "description": "Complexity (1-3)",
644
+ },
645
+ "category": {"type": "string", "description": "Category"},
646
+ },
647
+ "required": ["vuln_id"],
648
+ },
649
+ handler=self._handle_update_vulnerability,
650
+ )
651
+
652
+ self._register_tool(
653
+ name="delete_vulnerability",
654
+ description="Delete a vulnerability template.",
655
+ parameters={
656
+ "type": "object",
657
+ "properties": {
658
+ "vuln_id": {
659
+ "type": "string",
660
+ "description": "Vulnerability template ID to delete",
661
+ }
662
+ },
663
+ "required": ["vuln_id"],
664
+ },
665
+ handler=self._handle_delete_vulnerability,
666
+ )
667
+
668
+ self._register_tool(
669
+ name="bulk_delete_vulnerabilities",
670
+ description="Delete multiple vulnerability templates at once.",
671
+ parameters={
672
+ "type": "object",
673
+ "properties": {
674
+ "vuln_ids": {
675
+ "type": "array",
676
+ "items": {"type": "string"},
677
+ "description": "Array of vulnerability template IDs to delete",
678
+ }
679
+ },
680
+ "required": ["vuln_ids"],
681
+ },
682
+ handler=self._handle_bulk_delete_vulnerabilities,
683
+ )
684
+
685
+ self._register_tool(
686
+ name="export_vulnerabilities",
687
+ description="Export all vulnerability templates.",
688
+ parameters={"type": "object", "properties": {}},
689
+ handler=self._handle_export_vulnerabilities,
690
+ )
691
+
692
+ self._register_tool(
693
+ name="create_vulnerability_from_finding",
694
+ description="Create a vulnerability template from an existing finding.",
695
+ parameters={
696
+ "type": "object",
697
+ "properties": {
698
+ "audit_id": {"type": "string", "description": "Audit ID"},
699
+ "finding_id": {"type": "string", "description": "Finding ID"},
700
+ "locale": {"type": "string", "description": "Language code (e.g., 'en')"},
701
+ },
702
+ "required": ["audit_id", "finding_id"],
703
+ },
704
+ handler=self._handle_create_vulnerability_from_finding,
705
+ )
706
+
707
+ self._register_tool(
708
+ name="get_vulnerability_updates",
709
+ description="Get available vulnerability template updates.",
710
+ parameters={"type": "object", "properties": {}},
711
+ handler=self._handle_get_vulnerability_updates,
712
+ )
713
+
714
+ self._register_tool(
715
+ name="merge_vulnerability",
716
+ description="Merge vulnerability template with an update.",
717
+ parameters={
718
+ "type": "object",
719
+ "properties": {
720
+ "vuln_id": {"type": "string", "description": "Vulnerability template ID"},
721
+ "update_id": {"type": "string", "description": "Update ID to merge"},
722
+ },
723
+ "required": ["vuln_id", "update_id"],
724
+ },
725
+ handler=self._handle_merge_vulnerability,
726
+ )
727
+
728
+ # =====================================================================
729
+ # USER TOOLS
730
+ # =====================================================================
731
+
732
+ self._register_tool(
733
+ name="list_users",
734
+ description="List all users (admin only).",
735
+ parameters={"type": "object", "properties": {}},
736
+ handler=self._handle_list_users,
737
+ )
738
+
739
+ self._register_tool(
740
+ name="get_current_user",
741
+ description="Get current authenticated user's info.",
742
+ parameters={"type": "object", "properties": {}},
743
+ handler=self._handle_get_current_user,
744
+ )
745
+
746
+ self._register_tool(
747
+ name="get_user",
748
+ description="Get user information by username.",
749
+ parameters={
750
+ "type": "object",
751
+ "properties": {"username": {"type": "string", "description": "Username"}},
752
+ "required": ["username"],
753
+ },
754
+ handler=self._handle_get_user,
755
+ )
756
+
757
+ self._register_tool(
758
+ name="create_user",
759
+ description="Create a new user (admin only).",
760
+ parameters={
761
+ "type": "object",
762
+ "properties": {
763
+ "username": {"type": "string", "description": "Username"},
764
+ "password": {"type": "string", "description": "Password"},
765
+ "firstname": {"type": "string", "description": "First name"},
766
+ "lastname": {"type": "string", "description": "Last name"},
767
+ "email": {"type": "string", "description": "Email address"},
768
+ "role": {"type": "string", "description": "User role"},
769
+ },
770
+ "required": ["username", "password", "firstname", "lastname", "email", "role"],
771
+ },
772
+ handler=self._handle_create_user,
773
+ )
774
+
775
+ self._register_tool(
776
+ name="update_user",
777
+ description="Update a user (admin only).",
778
+ parameters={
779
+ "type": "object",
780
+ "properties": {
781
+ "user_id": {"type": "string", "description": "User ID"},
782
+ "username": {"type": "string", "description": "Username"},
783
+ "firstname": {"type": "string", "description": "First name"},
784
+ "lastname": {"type": "string", "description": "Last name"},
785
+ "email": {"type": "string", "description": "Email address"},
786
+ "role": {"type": "string", "description": "User role"},
787
+ },
788
+ "required": ["user_id"],
789
+ },
790
+ handler=self._handle_update_user,
791
+ )
792
+
793
+ self._register_tool(
794
+ name="update_current_user",
795
+ description="Update current user's profile.",
796
+ parameters={
797
+ "type": "object",
798
+ "properties": {
799
+ "firstname": {"type": "string", "description": "First name"},
800
+ "lastname": {"type": "string", "description": "Last name"},
801
+ "email": {"type": "string", "description": "Email address"},
802
+ "password": {"type": "string", "description": "New password"},
803
+ },
804
+ },
805
+ handler=self._handle_update_current_user,
806
+ )
807
+
808
+ self._register_tool(
809
+ name="list_reviewers",
810
+ description="List all users with reviewer role.",
811
+ parameters={"type": "object", "properties": {}},
812
+ handler=self._handle_list_reviewers,
813
+ )
814
+
815
+ self._register_tool(
816
+ name="get_totp_status",
817
+ description="Get TOTP (2FA) status for current user.",
818
+ parameters={"type": "object", "properties": {}},
819
+ handler=self._handle_get_totp,
820
+ )
821
+
822
+ self._register_tool(
823
+ name="setup_totp",
824
+ description="Setup TOTP (2FA) for current user.",
825
+ parameters={"type": "object", "properties": {}},
826
+ handler=self._handle_setup_totp,
827
+ )
828
+
829
+ self._register_tool(
830
+ name="disable_totp",
831
+ description="Disable TOTP (2FA) for current user.",
832
+ parameters={
833
+ "type": "object",
834
+ "properties": {
835
+ "token": {"type": "string", "description": "TOTP token for verification"}
836
+ },
837
+ "required": ["token"],
838
+ },
839
+ handler=self._handle_disable_totp,
840
+ )
841
+
842
+ # =====================================================================
843
+ # SETTINGS & DATA TOOLS
844
+ # =====================================================================
845
+
846
+ self._register_tool(
847
+ name="list_templates",
848
+ description="List all report templates.",
849
+ parameters={"type": "object", "properties": {}},
850
+ handler=self._handle_list_templates,
851
+ )
852
+
853
+ self._register_tool(
854
+ name="create_template",
855
+ description="Create/upload a report template.",
856
+ parameters={
857
+ "type": "object",
858
+ "properties": {
859
+ "name": {"type": "string", "description": "Template name"},
860
+ "ext": {"type": "string", "description": "File extension (e.g., 'docx')"},
861
+ "file_content": {
862
+ "type": "string",
863
+ "description": "Base64-encoded file content",
864
+ },
865
+ },
866
+ "required": ["name", "ext", "file_content"],
867
+ },
868
+ handler=self._handle_create_template,
869
+ )
870
+
871
+ self._register_tool(
872
+ name="update_template",
873
+ description="Update an existing template.",
874
+ parameters={
875
+ "type": "object",
876
+ "properties": {
877
+ "template_id": {"type": "string", "description": "Template ID"},
878
+ "name": {"type": "string", "description": "Template name"},
879
+ "ext": {"type": "string", "description": "File extension"},
880
+ "file_content": {
881
+ "type": "string",
882
+ "description": "Base64-encoded file content",
883
+ },
884
+ },
885
+ "required": ["template_id"],
886
+ },
887
+ handler=self._handle_update_template,
888
+ )
889
+
890
+ self._register_tool(
891
+ name="delete_template",
892
+ description="Delete a report template.",
893
+ parameters={
894
+ "type": "object",
895
+ "properties": {
896
+ "template_id": {"type": "string", "description": "Template ID to delete"}
897
+ },
898
+ "required": ["template_id"],
899
+ },
900
+ handler=self._handle_delete_template,
901
+ )
902
+
903
+ self._register_tool(
904
+ name="download_template",
905
+ description="Download a template file.",
906
+ parameters={
907
+ "type": "object",
908
+ "properties": {
909
+ "template_id": {"type": "string", "description": "Template ID to download"}
910
+ },
911
+ "required": ["template_id"],
912
+ },
913
+ handler=self._handle_download_template,
914
+ )
915
+
916
+ self._register_tool(
917
+ name="get_settings",
918
+ description="Get system settings.",
919
+ parameters={"type": "object", "properties": {}},
920
+ handler=self._handle_get_settings,
921
+ )
922
+
923
+ self._register_tool(
924
+ name="get_public_settings",
925
+ description="Get public settings (no authentication required).",
926
+ parameters={"type": "object", "properties": {}},
927
+ handler=self._handle_get_public_settings,
928
+ )
929
+
930
+ self._register_tool(
931
+ name="update_settings",
932
+ description="Update system settings (admin only).",
933
+ parameters={
934
+ "type": "object",
935
+ "properties": {"settings": {"type": "object", "description": "Settings to update"}},
936
+ "required": ["settings"],
937
+ },
938
+ handler=self._handle_update_settings,
939
+ )
940
+
941
+ self._register_tool(
942
+ name="export_settings",
943
+ description="Export all system settings.",
944
+ parameters={"type": "object", "properties": {}},
945
+ handler=self._handle_export_settings,
946
+ )
947
+
948
+ self._register_tool(
949
+ name="import_settings",
950
+ description="Import/revert system settings from export.",
951
+ parameters={
952
+ "type": "object",
953
+ "properties": {"settings": {"type": "object", "description": "Settings to import"}},
954
+ "required": ["settings"],
955
+ },
956
+ handler=self._handle_import_settings,
957
+ )
958
+
959
+ self._register_tool(
960
+ name="list_languages",
961
+ description="List all configured languages.",
962
+ parameters={"type": "object", "properties": {}},
963
+ handler=self._handle_list_languages,
964
+ )
965
+
966
+ self._register_tool(
967
+ name="create_language",
968
+ description="Create a new language.",
969
+ parameters={
970
+ "type": "object",
971
+ "properties": {
972
+ "language": {"type": "string", "description": "Language code (e.g., 'en')"},
973
+ "name": {"type": "string", "description": "Language name"},
974
+ },
975
+ "required": ["language", "name"],
976
+ },
977
+ handler=self._handle_create_language,
978
+ )
979
+
980
+ self._register_tool(
981
+ name="update_language",
982
+ description="Update a language.",
983
+ parameters={
984
+ "type": "object",
985
+ "properties": {
986
+ "language_id": {"type": "string", "description": "Language ID"},
987
+ "language": {"type": "string", "description": "Language code"},
988
+ "name": {"type": "string", "description": "Language name"},
989
+ },
990
+ "required": ["language_id"],
991
+ },
992
+ handler=self._handle_update_language,
993
+ )
994
+
995
+ self._register_tool(
996
+ name="delete_language",
997
+ description="Delete a language.",
998
+ parameters={
999
+ "type": "object",
1000
+ "properties": {
1001
+ "language_id": {"type": "string", "description": "Language ID to delete"}
1002
+ },
1003
+ "required": ["language_id"],
1004
+ },
1005
+ handler=self._handle_delete_language,
1006
+ )
1007
+
1008
+ self._register_tool(
1009
+ name="list_audit_types",
1010
+ description="List all audit types.",
1011
+ parameters={"type": "object", "properties": {}},
1012
+ handler=self._handle_list_audit_types,
1013
+ )
1014
+
1015
+ self._register_tool(
1016
+ name="create_audit_type",
1017
+ description="Create a new audit type.",
1018
+ parameters={
1019
+ "type": "object",
1020
+ "properties": {
1021
+ "name": {"type": "string", "description": "Audit type name"},
1022
+ "templates": {
1023
+ "type": "array",
1024
+ "items": {"type": "string"},
1025
+ "description": "Template IDs",
1026
+ },
1027
+ },
1028
+ "required": ["name"],
1029
+ },
1030
+ handler=self._handle_create_audit_type,
1031
+ )
1032
+
1033
+ self._register_tool(
1034
+ name="update_audit_type",
1035
+ description="Update an audit type.",
1036
+ parameters={
1037
+ "type": "object",
1038
+ "properties": {
1039
+ "audit_type_id": {"type": "string", "description": "Audit type ID"},
1040
+ "name": {"type": "string", "description": "Audit type name"},
1041
+ "templates": {"type": "array", "items": {"type": "string"}},
1042
+ },
1043
+ "required": ["audit_type_id"],
1044
+ },
1045
+ handler=self._handle_update_audit_type,
1046
+ )
1047
+
1048
+ self._register_tool(
1049
+ name="delete_audit_type",
1050
+ description="Delete an audit type.",
1051
+ parameters={
1052
+ "type": "object",
1053
+ "properties": {
1054
+ "audit_type_id": {"type": "string", "description": "Audit type ID to delete"}
1055
+ },
1056
+ "required": ["audit_type_id"],
1057
+ },
1058
+ handler=self._handle_delete_audit_type,
1059
+ )
1060
+
1061
+ self._register_tool(
1062
+ name="list_vulnerability_types",
1063
+ description="List all vulnerability types.",
1064
+ parameters={"type": "object", "properties": {}},
1065
+ handler=self._handle_list_vulnerability_types,
1066
+ )
1067
+
1068
+ self._register_tool(
1069
+ name="create_vulnerability_type",
1070
+ description="Create a new vulnerability type.",
1071
+ parameters={
1072
+ "type": "object",
1073
+ "properties": {
1074
+ "name": {"type": "string", "description": "Vulnerability type name"}
1075
+ },
1076
+ "required": ["name"],
1077
+ },
1078
+ handler=self._handle_create_vulnerability_type,
1079
+ )
1080
+
1081
+ self._register_tool(
1082
+ name="update_vulnerability_type",
1083
+ description="Update a vulnerability type.",
1084
+ parameters={
1085
+ "type": "object",
1086
+ "properties": {
1087
+ "vuln_type_id": {"type": "string", "description": "Vulnerability type ID"},
1088
+ "name": {"type": "string", "description": "Vulnerability type name"},
1089
+ },
1090
+ "required": ["vuln_type_id"],
1091
+ },
1092
+ handler=self._handle_update_vulnerability_type,
1093
+ )
1094
+
1095
+ self._register_tool(
1096
+ name="delete_vulnerability_type",
1097
+ description="Delete a vulnerability type.",
1098
+ parameters={
1099
+ "type": "object",
1100
+ "properties": {
1101
+ "vuln_type_id": {
1102
+ "type": "string",
1103
+ "description": "Vulnerability type ID to delete",
1104
+ }
1105
+ },
1106
+ "required": ["vuln_type_id"],
1107
+ },
1108
+ handler=self._handle_delete_vulnerability_type,
1109
+ )
1110
+
1111
+ self._register_tool(
1112
+ name="list_vulnerability_categories",
1113
+ description="List all vulnerability categories.",
1114
+ parameters={"type": "object", "properties": {}},
1115
+ handler=self._handle_list_vulnerability_categories,
1116
+ )
1117
+
1118
+ self._register_tool(
1119
+ name="create_vulnerability_category",
1120
+ description="Create a new vulnerability category.",
1121
+ parameters={
1122
+ "type": "object",
1123
+ "properties": {"name": {"type": "string", "description": "Category name"}},
1124
+ "required": ["name"],
1125
+ },
1126
+ handler=self._handle_create_vulnerability_category,
1127
+ )
1128
+
1129
+ self._register_tool(
1130
+ name="update_vulnerability_category",
1131
+ description="Update a vulnerability category.",
1132
+ parameters={
1133
+ "type": "object",
1134
+ "properties": {
1135
+ "category_id": {"type": "string", "description": "Category ID"},
1136
+ "name": {"type": "string", "description": "Category name"},
1137
+ },
1138
+ "required": ["category_id"],
1139
+ },
1140
+ handler=self._handle_update_vulnerability_category,
1141
+ )
1142
+
1143
+ self._register_tool(
1144
+ name="delete_vulnerability_category",
1145
+ description="Delete a vulnerability category.",
1146
+ parameters={
1147
+ "type": "object",
1148
+ "properties": {
1149
+ "category_id": {"type": "string", "description": "Category ID to delete"}
1150
+ },
1151
+ "required": ["category_id"],
1152
+ },
1153
+ handler=self._handle_delete_vulnerability_category,
1154
+ )
1155
+
1156
+ self._register_tool(
1157
+ name="list_sections",
1158
+ description="List all section definitions.",
1159
+ parameters={"type": "object", "properties": {}},
1160
+ handler=self._handle_list_sections,
1161
+ )
1162
+
1163
+ self._register_tool(
1164
+ name="create_section",
1165
+ description="Create a new section definition.",
1166
+ parameters={
1167
+ "type": "object",
1168
+ "properties": {
1169
+ "field": {"type": "string", "description": "Section field name"},
1170
+ "name": {"type": "string", "description": "Section display name"},
1171
+ },
1172
+ "required": ["field", "name"],
1173
+ },
1174
+ handler=self._handle_create_section,
1175
+ )
1176
+
1177
+ self._register_tool(
1178
+ name="update_section",
1179
+ description="Update a section definition.",
1180
+ parameters={
1181
+ "type": "object",
1182
+ "properties": {
1183
+ "section_id": {"type": "string", "description": "Section ID"},
1184
+ "field": {"type": "string", "description": "Section field name"},
1185
+ "name": {"type": "string", "description": "Section display name"},
1186
+ },
1187
+ "required": ["section_id"],
1188
+ },
1189
+ handler=self._handle_update_section,
1190
+ )
1191
+
1192
+ self._register_tool(
1193
+ name="delete_section",
1194
+ description="Delete a section definition.",
1195
+ parameters={
1196
+ "type": "object",
1197
+ "properties": {
1198
+ "section_id": {"type": "string", "description": "Section ID to delete"}
1199
+ },
1200
+ "required": ["section_id"],
1201
+ },
1202
+ handler=self._handle_delete_section,
1203
+ )
1204
+
1205
+ self._register_tool(
1206
+ name="list_custom_fields",
1207
+ description="List all custom field definitions.",
1208
+ parameters={"type": "object", "properties": {}},
1209
+ handler=self._handle_list_custom_fields,
1210
+ )
1211
+
1212
+ self._register_tool(
1213
+ name="create_custom_field",
1214
+ description="Create a new custom field definition.",
1215
+ parameters={
1216
+ "type": "object",
1217
+ "properties": {
1218
+ "label": {"type": "string", "description": "Field label"},
1219
+ "field_type": {
1220
+ "type": "string",
1221
+ "description": "Field type (text, select, etc.)",
1222
+ },
1223
+ },
1224
+ "required": ["label", "field_type"],
1225
+ },
1226
+ handler=self._handle_create_custom_field,
1227
+ )
1228
+
1229
+ self._register_tool(
1230
+ name="update_custom_field",
1231
+ description="Update a custom field definition.",
1232
+ parameters={
1233
+ "type": "object",
1234
+ "properties": {
1235
+ "field_id": {"type": "string", "description": "Custom field ID"},
1236
+ "label": {"type": "string", "description": "Field label"},
1237
+ "field_type": {"type": "string", "description": "Field type"},
1238
+ },
1239
+ "required": ["field_id"],
1240
+ },
1241
+ handler=self._handle_update_custom_field,
1242
+ )
1243
+
1244
+ self._register_tool(
1245
+ name="delete_custom_field",
1246
+ description="Delete a custom field definition.",
1247
+ parameters={
1248
+ "type": "object",
1249
+ "properties": {
1250
+ "field_id": {"type": "string", "description": "Custom field ID to delete"}
1251
+ },
1252
+ "required": ["field_id"],
1253
+ },
1254
+ handler=self._handle_delete_custom_field,
1255
+ )
1256
+
1257
+ self._register_tool(
1258
+ name="list_roles",
1259
+ description="List all user roles.",
1260
+ parameters={"type": "object", "properties": {}},
1261
+ handler=self._handle_list_roles,
1262
+ )
1263
+
1264
+ # =====================================================================
1265
+ # IMAGE TOOLS
1266
+ # =====================================================================
1267
+
1268
+ self._register_tool(
1269
+ name="get_image",
1270
+ description="Get image metadata.",
1271
+ parameters={
1272
+ "type": "object",
1273
+ "properties": {"image_id": {"type": "string", "description": "Image ID"}},
1274
+ "required": ["image_id"],
1275
+ },
1276
+ handler=self._handle_get_image,
1277
+ )
1278
+
1279
+ self._register_tool(
1280
+ name="download_image",
1281
+ description="Download an image file.",
1282
+ parameters={
1283
+ "type": "object",
1284
+ "properties": {
1285
+ "image_id": {"type": "string", "description": "Image ID to download"}
1286
+ },
1287
+ "required": ["image_id"],
1288
+ },
1289
+ handler=self._handle_download_image,
1290
+ )
1291
+
1292
+ self._register_tool(
1293
+ name="upload_image",
1294
+ description="Upload an image to an audit.",
1295
+ parameters={
1296
+ "type": "object",
1297
+ "properties": {
1298
+ "audit_id": {"type": "string", "description": "Audit ID"},
1299
+ "name": {"type": "string", "description": "Image name"},
1300
+ "value": {"type": "string", "description": "Base64-encoded image data"},
1301
+ },
1302
+ "required": ["audit_id", "name", "value"],
1303
+ },
1304
+ handler=self._handle_upload_image,
1305
+ )
1306
+
1307
+ self._register_tool(
1308
+ name="delete_image",
1309
+ description="Delete an image.",
1310
+ parameters={
1311
+ "type": "object",
1312
+ "properties": {"image_id": {"type": "string", "description": "Image ID to delete"}},
1313
+ "required": ["image_id"],
1314
+ },
1315
+ handler=self._handle_delete_image,
1316
+ )
1317
+
1318
+ # =====================================================================
1319
+ # STATISTICS
1320
+ # =====================================================================
1321
+
1322
+ self._register_tool(
1323
+ name="get_statistics",
1324
+ description="Get comprehensive statistics about audits, findings, clients, and more.",
1325
+ parameters={"type": "object", "properties": {}},
1326
+ handler=self._handle_get_statistics,
1327
+ )
1328
+
1329
+ def _register_tool(
1330
+ self,
1331
+ name: str,
1332
+ description: str,
1333
+ parameters: Dict[str, Any],
1334
+ handler: Callable,
1335
+ required: Optional[List[str]] = None,
1336
+ ):
1337
+ """Register a tool."""
1338
+ self._tools[name] = Tool(
1339
+ name=name,
1340
+ description=description,
1341
+ parameters=parameters,
1342
+ handler=handler,
1343
+ required=required or parameters.get("required", []),
1344
+ )
1345
+
1346
+ # =========================================================================
1347
+ # TOOL HANDLERS
1348
+ # =========================================================================
1349
+
1350
+ def _handle_list_audits(self, finding_title: Optional[str] = None) -> List[Dict]:
1351
+ return self.client.list_audits(finding_title)
1352
+
1353
+ def _handle_get_audit(self, audit_id: str) -> Dict:
1354
+ return self.client.get_audit(audit_id)
1355
+
1356
+ def _handle_create_audit(self, name: str, language: str, audit_type: str) -> Dict:
1357
+ return self.client.create_audit(name, language, audit_type)
1358
+
1359
+ def _handle_update_audit_general(self, audit_id: str, **kwargs) -> Dict:
1360
+ return self.client.update_audit_general(audit_id, **kwargs)
1361
+
1362
+ def _handle_delete_audit(self, audit_id: str) -> Dict:
1363
+ self.client.delete_audit(audit_id)
1364
+ return {"success": True, "message": f"Audit {audit_id} deleted"}
1365
+
1366
+ def _handle_generate_report(self, audit_id: str) -> Dict:
1367
+ content = self.client.generate_report(audit_id)
1368
+ return {"success": True, "size_bytes": len(content)}
1369
+
1370
+ def _handle_get_findings(self, audit_id: str) -> List[Dict]:
1371
+ return self.client.get_findings(audit_id)
1372
+
1373
+ def _handle_get_finding(self, audit_id: str, finding_id: str) -> Dict:
1374
+ return self.client.get_finding(audit_id, finding_id)
1375
+
1376
+ def _handle_create_finding(self, audit_id: str, **kwargs) -> Dict:
1377
+ return self.client.create_finding(audit_id, **kwargs)
1378
+
1379
+ def _handle_update_finding(self, audit_id: str, finding_id: str, **kwargs) -> Dict:
1380
+ return self.client.update_finding(audit_id, finding_id, **kwargs)
1381
+
1382
+ def _handle_delete_finding(self, audit_id: str, finding_id: str) -> Dict:
1383
+ self.client.delete_finding(audit_id, finding_id)
1384
+ return {"success": True, "message": f"Finding {finding_id} deleted"}
1385
+
1386
+ def _handle_search_findings(self, **kwargs) -> List[Dict]:
1387
+ return self.client.search_findings(**kwargs)
1388
+
1389
+ def _handle_get_all_findings_with_context(
1390
+ self, include_failed: bool = False, exclude_categories: Optional[List[str]] = None
1391
+ ) -> List[Dict]:
1392
+ return self.client.get_all_findings_with_context(include_failed, exclude_categories)
1393
+
1394
+ def _handle_list_clients(self) -> List[Dict]:
1395
+ return self.client.list_clients()
1396
+
1397
+ def _handle_create_client(self, **kwargs) -> Dict:
1398
+ return self.client.create_client(**kwargs)
1399
+
1400
+ def _handle_update_client(self, client_id: str, **kwargs) -> Dict:
1401
+ return self.client.update_client(client_id, **kwargs)
1402
+
1403
+ def _handle_delete_client(self, client_id: str) -> Dict:
1404
+ self.client.delete_client(client_id)
1405
+ return {"success": True, "message": f"Client {client_id} deleted"}
1406
+
1407
+ def _handle_list_companies(self) -> List[Dict]:
1408
+ return self.client.list_companies()
1409
+
1410
+ def _handle_create_company(self, **kwargs) -> Dict:
1411
+ return self.client.create_company(**kwargs)
1412
+
1413
+ def _handle_list_vulnerabilities(self) -> List[Dict]:
1414
+ return self.client.list_vulnerabilities()
1415
+
1416
+ def _handle_get_vulnerabilities_by_locale(self, locale: str = "en") -> List[Dict]:
1417
+ return self.client.get_vulnerabilities_by_locale(locale)
1418
+
1419
+ def _handle_create_vulnerability(self, **kwargs) -> Dict:
1420
+ return self.client.create_vulnerability(**kwargs)
1421
+
1422
+ def _handle_list_users(self) -> List[Dict]:
1423
+ return self.client.list_users()
1424
+
1425
+ def _handle_get_current_user(self) -> Dict:
1426
+ return self.client.get_current_user()
1427
+
1428
+ def _handle_list_templates(self) -> List[Dict]:
1429
+ return self.client.list_templates()
1430
+
1431
+ def _handle_list_languages(self) -> List[Dict]:
1432
+ return self.client.list_languages()
1433
+
1434
+ def _handle_list_audit_types(self) -> List[Dict]:
1435
+ return self.client.list_audit_types()
1436
+
1437
+ def _handle_get_statistics(self) -> Dict:
1438
+ return self.client.get_statistics()
1439
+
1440
+ # Audit handlers
1441
+ def _handle_get_audit_general(self, audit_id: str) -> Dict:
1442
+ return self.client.get_audit_general(audit_id)
1443
+
1444
+ def _handle_get_audit_network(self, audit_id: str) -> Dict:
1445
+ return self.client.get_audit_network(audit_id)
1446
+
1447
+ def _handle_update_audit_network(self, audit_id: str, network_data: Dict) -> Dict:
1448
+ return self.client.update_audit_network(audit_id, network_data)
1449
+
1450
+ def _handle_toggle_audit_approval(self, audit_id: str) -> Dict:
1451
+ return self.client.toggle_audit_approval(audit_id)
1452
+
1453
+ def _handle_update_review_status(self, audit_id: str, state: bool) -> Dict:
1454
+ return self.client.update_review_status(audit_id, state)
1455
+
1456
+ # Finding handlers
1457
+ def _handle_sort_findings(self, audit_id: str, finding_order: List[str]) -> Dict:
1458
+ return self.client.sort_findings(audit_id, finding_order)
1459
+
1460
+ def _handle_move_finding(
1461
+ self, audit_id: str, finding_id: str, destination_audit_id: str
1462
+ ) -> Dict:
1463
+ return self.client.move_finding(audit_id, finding_id, destination_audit_id)
1464
+
1465
+ # Company handlers
1466
+ def _handle_update_company(self, company_id: str, **kwargs) -> Dict:
1467
+ return self.client.update_company(company_id, **kwargs)
1468
+
1469
+ def _handle_delete_company(self, company_id: str) -> Dict:
1470
+ self.client.delete_company(company_id)
1471
+ return {"success": True, "message": f"Company {company_id} deleted"}
1472
+
1473
+ # Vulnerability handlers
1474
+ def _handle_update_vulnerability(self, vuln_id: str, **kwargs) -> Dict:
1475
+ return self.client.update_vulnerability(vuln_id, **kwargs)
1476
+
1477
+ def _handle_delete_vulnerability(self, vuln_id: str) -> Dict:
1478
+ self.client.delete_vulnerability(vuln_id)
1479
+ return {"success": True, "message": f"Vulnerability {vuln_id} deleted"}
1480
+
1481
+ def _handle_bulk_delete_vulnerabilities(self, vuln_ids: List[str]) -> Dict:
1482
+ self.client.bulk_delete_vulnerabilities(vuln_ids)
1483
+ return {"success": True, "message": f"Deleted {len(vuln_ids)} vulnerabilities"}
1484
+
1485
+ def _handle_export_vulnerabilities(self) -> Dict:
1486
+ return self.client.export_vulnerabilities()
1487
+
1488
+ def _handle_create_vulnerability_from_finding(self, **kwargs) -> Dict:
1489
+ return self.client.create_vulnerability_from_finding(**kwargs)
1490
+
1491
+ # User handlers
1492
+ def _handle_get_user(self, username: str) -> Dict:
1493
+ return self.client.get_user(username)
1494
+
1495
+ def _handle_create_user(self, **kwargs) -> Dict:
1496
+ return self.client.create_user(**kwargs)
1497
+
1498
+ def _handle_update_user(self, user_id: str, **kwargs) -> Dict:
1499
+ return self.client.update_user(user_id, **kwargs)
1500
+
1501
+ def _handle_update_current_user(self, **kwargs) -> Dict:
1502
+ return self.client.update_current_user(**kwargs)
1503
+
1504
+ def _handle_list_reviewers(self) -> List[Dict]:
1505
+ return self.client.list_reviewers()
1506
+
1507
+ # Template handlers
1508
+ def _handle_create_template(self, name: str, ext: str, file_content: str) -> Dict:
1509
+ return self.client.create_template(name, ext, file_content)
1510
+
1511
+ def _handle_update_template(self, template_id: str, **kwargs) -> Dict:
1512
+ return self.client.update_template(template_id, **kwargs)
1513
+
1514
+ def _handle_delete_template(self, template_id: str) -> Dict:
1515
+ self.client.delete_template(template_id)
1516
+ return {"success": True, "message": f"Template {template_id} deleted"}
1517
+
1518
+ def _handle_download_template(self, template_id: str) -> Dict:
1519
+ content = self.client.download_template(template_id)
1520
+ return {"success": True, "size_bytes": len(content)}
1521
+
1522
+ # Settings handlers
1523
+ def _handle_get_settings(self) -> Dict:
1524
+ return self.client.get_settings()
1525
+
1526
+ def _handle_get_public_settings(self) -> Dict:
1527
+ return self.client.get_public_settings()
1528
+
1529
+ def _handle_update_settings(self, settings: Dict) -> Dict:
1530
+ return self.client.update_settings(settings)
1531
+
1532
+ # Data type handlers
1533
+ def _handle_list_vulnerability_types(self) -> List[Dict]:
1534
+ return self.client.list_vulnerability_types()
1535
+
1536
+ def _handle_list_vulnerability_categories(self) -> List[Dict]:
1537
+ return self.client.list_vulnerability_categories()
1538
+
1539
+ def _handle_list_sections(self) -> List[Dict]:
1540
+ return self.client.list_sections()
1541
+
1542
+ def _handle_list_custom_fields(self) -> List[Dict]:
1543
+ return self.client.list_custom_fields()
1544
+
1545
+ def _handle_list_roles(self) -> List[Dict]:
1546
+ return self.client.list_roles()
1547
+
1548
+ # Image handlers
1549
+ def _handle_get_image(self, image_id: str) -> Dict:
1550
+ return self.client.get_image(image_id)
1551
+
1552
+ def _handle_download_image(self, image_id: str) -> Dict:
1553
+ content = self.client.download_image(image_id)
1554
+ return {"success": True, "size_bytes": len(content)}
1555
+
1556
+ def _handle_upload_image(self, audit_id: str, name: str, value: str) -> Dict:
1557
+ return self.client.upload_image(audit_id, name, value)
1558
+
1559
+ def _handle_delete_image(self, image_id: str) -> Dict:
1560
+ self.client.delete_image(image_id)
1561
+ return {"success": True, "message": f"Image {image_id} deleted"}
1562
+
1563
+ # Audit section handlers
1564
+ def _handle_get_audit_sections(self, audit_id: str) -> Dict:
1565
+ return self.client.get_audit_sections(audit_id)
1566
+
1567
+ def _handle_update_audit_sections(self, audit_id: str, sections: Dict) -> Dict:
1568
+ return self.client.update_audit_sections(audit_id, sections)
1569
+
1570
+ # TOTP handlers
1571
+ def _handle_get_totp(self) -> Dict:
1572
+ return self.client.get_totp()
1573
+
1574
+ def _handle_setup_totp(self) -> Dict:
1575
+ return self.client.setup_totp()
1576
+
1577
+ def _handle_disable_totp(self, token: str) -> Dict:
1578
+ return self.client.disable_totp(token)
1579
+
1580
+ # Settings handlers
1581
+ def _handle_export_settings(self) -> Dict:
1582
+ return self.client.export_settings()
1583
+
1584
+ def _handle_import_settings(self, settings: Dict) -> Dict:
1585
+ return self.client.import_settings(settings)
1586
+
1587
+ # Language handlers
1588
+ def _handle_create_language(self, **kwargs) -> Dict:
1589
+ return self.client.create_language(**kwargs)
1590
+
1591
+ def _handle_update_language(self, language_id: str, **kwargs) -> Dict:
1592
+ return self.client.update_language(language_id, **kwargs)
1593
+
1594
+ def _handle_delete_language(self, language_id: str) -> Dict:
1595
+ self.client.delete_language(language_id)
1596
+ return {"success": True, "message": f"Language {language_id} deleted"}
1597
+
1598
+ # Audit type handlers
1599
+ def _handle_create_audit_type(self, **kwargs) -> Dict:
1600
+ return self.client.create_audit_type(**kwargs)
1601
+
1602
+ def _handle_update_audit_type(self, audit_type_id: str, **kwargs) -> Dict:
1603
+ return self.client.update_audit_type(audit_type_id, **kwargs)
1604
+
1605
+ def _handle_delete_audit_type(self, audit_type_id: str) -> Dict:
1606
+ self.client.delete_audit_type(audit_type_id)
1607
+ return {"success": True, "message": f"Audit type {audit_type_id} deleted"}
1608
+
1609
+ # Vulnerability type handlers
1610
+ def _handle_create_vulnerability_type(self, **kwargs) -> Dict:
1611
+ return self.client.create_vulnerability_type(**kwargs)
1612
+
1613
+ def _handle_update_vulnerability_type(self, vuln_type_id: str, **kwargs) -> Dict:
1614
+ return self.client.update_vulnerability_type(vuln_type_id, **kwargs)
1615
+
1616
+ def _handle_delete_vulnerability_type(self, vuln_type_id: str) -> Dict:
1617
+ self.client.delete_vulnerability_type(vuln_type_id)
1618
+ return {"success": True, "message": f"Vulnerability type {vuln_type_id} deleted"}
1619
+
1620
+ # Vulnerability category handlers
1621
+ def _handle_create_vulnerability_category(self, **kwargs) -> Dict:
1622
+ return self.client.create_vulnerability_category(**kwargs)
1623
+
1624
+ def _handle_update_vulnerability_category(self, category_id: str, **kwargs) -> Dict:
1625
+ return self.client.update_vulnerability_category(category_id, **kwargs)
1626
+
1627
+ def _handle_delete_vulnerability_category(self, category_id: str) -> Dict:
1628
+ self.client.delete_vulnerability_category(category_id)
1629
+ return {"success": True, "message": f"Vulnerability category {category_id} deleted"}
1630
+
1631
+ # Section handlers
1632
+ def _handle_create_section(self, **kwargs) -> Dict:
1633
+ return self.client.create_section(**kwargs)
1634
+
1635
+ def _handle_update_section(self, section_id: str, **kwargs) -> Dict:
1636
+ return self.client.update_section(section_id, **kwargs)
1637
+
1638
+ def _handle_delete_section(self, section_id: str) -> Dict:
1639
+ self.client.delete_section(section_id)
1640
+ return {"success": True, "message": f"Section {section_id} deleted"}
1641
+
1642
+ # Custom field handlers
1643
+ def _handle_create_custom_field(self, **kwargs) -> Dict:
1644
+ return self.client.create_custom_field(**kwargs)
1645
+
1646
+ def _handle_update_custom_field(self, field_id: str, **kwargs) -> Dict:
1647
+ return self.client.update_custom_field(field_id, **kwargs)
1648
+
1649
+ def _handle_delete_custom_field(self, field_id: str) -> Dict:
1650
+ self.client.delete_custom_field(field_id)
1651
+ return {"success": True, "message": f"Custom field {field_id} deleted"}
1652
+
1653
+ # Vulnerability update handlers
1654
+ def _handle_get_vulnerability_updates(self) -> List[Dict]:
1655
+ return self.client.get_vulnerability_updates()
1656
+
1657
+ def _handle_merge_vulnerability(self, vuln_id: str, update_id: str) -> Dict:
1658
+ return self.client.merge_vulnerability(vuln_id, update_id)
1659
+
1660
+ # =========================================================================
1661
+ # PUBLIC API METHODS (for testing and direct use)
1662
+ # =========================================================================
1663
+
1664
+ async def handle_initialize(self, params: Dict) -> Dict:
1665
+ """
1666
+ Handle MCP initialize request (async public method).
1667
+
1668
+ Args:
1669
+ params: Initialize parameters
1670
+
1671
+ Returns:
1672
+ Initialize response with capabilities
1673
+ """
1674
+ return self._handle_initialize(params)
1675
+
1676
+ async def handle_list_tools(self) -> List[Dict]:
1677
+ """
1678
+ Handle list tools request (async public method).
1679
+
1680
+ Returns:
1681
+ List of tool definitions
1682
+ """
1683
+ from typing import cast
1684
+
1685
+ result = self._handle_list_tools({})
1686
+ return cast(List[Dict], result.get("tools", []))
1687
+
1688
+ async def handle_call_tool(self, name: str, arguments: Dict) -> Any:
1689
+ """
1690
+ Handle call tool request (async public method).
1691
+
1692
+ Args:
1693
+ name: Tool name
1694
+ arguments: Tool arguments
1695
+
1696
+ Returns:
1697
+ Tool result
1698
+
1699
+ Raises:
1700
+ ValueError: If tool is unknown
1701
+ """
1702
+ params = {"name": name, "arguments": arguments}
1703
+ return self._handle_call_tool(params)
1704
+
1705
+ def _format_result(self, data: Any) -> str:
1706
+ """
1707
+ Format result data for output.
1708
+
1709
+ Args:
1710
+ data: Data to format
1711
+
1712
+ Returns:
1713
+ Formatted string
1714
+ """
1715
+ if isinstance(data, str):
1716
+ return data
1717
+ elif data is None:
1718
+ return "null"
1719
+ else:
1720
+ return json.dumps(data, indent=2, default=str)
1721
+
1722
+ # =========================================================================
1723
+ # MCP PROTOCOL HANDLING
1724
+ # =========================================================================
1725
+
1726
+ def _handle_initialize(self, params: Dict) -> Dict:
1727
+ """Handle MCP initialize request."""
1728
+ self._initialized = True
1729
+ return {
1730
+ "protocolVersion": self.PROTOCOL_VERSION,
1731
+ "capabilities": {
1732
+ "tools": {"listChanged": True},
1733
+ "logging": {},
1734
+ },
1735
+ "serverInfo": {
1736
+ "name": self.SERVER_NAME,
1737
+ "version": self.SERVER_VERSION,
1738
+ },
1739
+ }
1740
+
1741
+ def _handle_list_tools(self, params: Dict) -> Dict:
1742
+ """Handle tools/list request."""
1743
+ tools = []
1744
+ for tool in self._tools.values():
1745
+ tools.append(
1746
+ {
1747
+ "name": tool.name,
1748
+ "description": tool.description,
1749
+ "inputSchema": tool.parameters,
1750
+ }
1751
+ )
1752
+ return {"tools": tools}
1753
+
1754
+ def _handle_call_tool(self, params: Dict) -> Dict:
1755
+ """Handle tools/call request."""
1756
+ tool_name = params.get("name")
1757
+ arguments = params.get("arguments", {})
1758
+
1759
+ if tool_name not in self._tools:
1760
+ raise ValueError(f"Unknown tool: {tool_name}")
1761
+
1762
+ tool = self._tools[tool_name]
1763
+
1764
+ try:
1765
+ result = tool.handler(**arguments)
1766
+ return {
1767
+ "content": [{"type": "text", "text": json.dumps(result, indent=2, default=str)}]
1768
+ }
1769
+ except PwnDocError as e:
1770
+ return {"content": [{"type": "text", "text": f"Error: {str(e)}"}], "isError": True}
1771
+
1772
+ def _handle_message(self, message: Dict) -> Optional[Dict]:
1773
+ """Process an incoming MCP message."""
1774
+ method = message.get("method")
1775
+ params = message.get("params", {})
1776
+ msg_id = message.get("id")
1777
+
1778
+ handlers = {
1779
+ "initialize": self._handle_initialize,
1780
+ "initialized": lambda p: None, # Notification, no response
1781
+ "tools/list": self._handle_list_tools,
1782
+ "tools/call": self._handle_call_tool,
1783
+ "ping": lambda p: {},
1784
+ }
1785
+
1786
+ if method in handlers:
1787
+ try:
1788
+ result = handlers[method](params)
1789
+ if result is None: # Notification
1790
+ return None
1791
+ return {"jsonrpc": "2.0", "id": msg_id, "result": result}
1792
+ except Exception as e:
1793
+ logger.exception(f"Error handling {method}")
1794
+ return {
1795
+ "jsonrpc": "2.0",
1796
+ "id": msg_id,
1797
+ "error": {"code": -32603, "message": str(e)},
1798
+ }
1799
+ else:
1800
+ return {
1801
+ "jsonrpc": "2.0",
1802
+ "id": msg_id,
1803
+ "error": {"code": -32601, "message": f"Method not found: {method}"},
1804
+ }
1805
+
1806
+ # =========================================================================
1807
+ # TRANSPORT IMPLEMENTATIONS
1808
+ # =========================================================================
1809
+
1810
+ def run_stdio(self):
1811
+ """Run server with stdio transport (for Claude Desktop)."""
1812
+ logger.info("Starting PwnDoc MCP Server (stdio transport)")
1813
+
1814
+ while True:
1815
+ try:
1816
+ line = sys.stdin.readline()
1817
+ if not line:
1818
+ break
1819
+
1820
+ message = json.loads(line)
1821
+ logger.debug(f"Received: {message.get('method', 'response')}")
1822
+
1823
+ response = self._handle_message(message)
1824
+ if response:
1825
+ sys.stdout.write(json.dumps(response) + "\n")
1826
+ sys.stdout.flush()
1827
+
1828
+ except json.JSONDecodeError as e:
1829
+ logger.warning(f"Invalid JSON: {e}")
1830
+ except KeyboardInterrupt:
1831
+ logger.info("Server interrupted")
1832
+ break
1833
+ except Exception as e:
1834
+ logger.exception(f"Error: {e}")
1835
+
1836
+ async def run_sse(self, host: str = "127.0.0.1", port: int = 8080):
1837
+ """Run server with SSE transport."""
1838
+ try:
1839
+ from aiohttp import web
1840
+ except ImportError:
1841
+ raise ImportError("aiohttp required for SSE transport: pip install aiohttp")
1842
+
1843
+ async def handle_sse(request):
1844
+ response = web.StreamResponse(
1845
+ status=200,
1846
+ headers={
1847
+ "Content-Type": "text/event-stream",
1848
+ "Cache-Control": "no-cache",
1849
+ "Connection": "keep-alive",
1850
+ },
1851
+ )
1852
+ await response.prepare(request)
1853
+
1854
+ # Read messages from POST body
1855
+ data = await request.json()
1856
+ result = self._handle_message(data)
1857
+
1858
+ if result:
1859
+ await response.write(f"data: {json.dumps(result)}\n\n".encode())
1860
+
1861
+ return response
1862
+
1863
+ app = web.Application()
1864
+ app.router.add_post("/mcp", handle_sse)
1865
+
1866
+ runner = web.AppRunner(app)
1867
+ await runner.setup()
1868
+ site = web.TCPSite(runner, host, port)
1869
+ await site.start()
1870
+
1871
+ logger.info(f"SSE server running at http://{host}:{port}/mcp")
1872
+
1873
+ # Keep running
1874
+ while True:
1875
+ await asyncio.sleep(3600)
1876
+
1877
+ def run(self, transport: Optional[str] = None):
1878
+ """
1879
+ Run the MCP server.
1880
+
1881
+ Args:
1882
+ transport: Transport type (stdio, sse, websocket). Defaults to config value.
1883
+ """
1884
+ transport = transport or self.config.mcp_transport
1885
+
1886
+ if transport == "stdio":
1887
+ self.run_stdio()
1888
+ elif transport == "sse":
1889
+ asyncio.run(self.run_sse(self.config.mcp_host, self.config.mcp_port))
1890
+ else:
1891
+ raise ValueError(f"Unsupported transport: {transport}")
1892
+
1893
+
1894
+ # Module-level constant for tool definitions (for compatibility)
1895
+ TOOL_DEFINITIONS: Optional[List[Dict]] = None
1896
+
1897
+
1898
+ def _get_tool_definitions() -> List[Dict]:
1899
+ """
1900
+ Get tool definitions from server instance.
1901
+
1902
+ Returns:
1903
+ List of tool definitions
1904
+ """
1905
+ # Create a temporary server to extract tool definitions
1906
+ # Use _silent=True to prevent logging during module import
1907
+ config = Config(url="http://temp", token="temp")
1908
+ server = PwnDocMCPServer(config, _silent=True)
1909
+ tools = []
1910
+ for tool in server._tools.values():
1911
+ tools.append(
1912
+ {
1913
+ "name": tool.name,
1914
+ "description": tool.description,
1915
+ "inputSchema": tool.parameters,
1916
+ }
1917
+ )
1918
+ return tools
1919
+
1920
+
1921
+ def get_tool_definitions() -> List[Dict]:
1922
+ """
1923
+ Get tool definitions, initializing them lazily on first access.
1924
+
1925
+ Returns:
1926
+ List of tool definitions
1927
+ """
1928
+ global TOOL_DEFINITIONS
1929
+ if TOOL_DEFINITIONS is None:
1930
+ TOOL_DEFINITIONS = _get_tool_definitions()
1931
+ return TOOL_DEFINITIONS
1932
+
1933
+
1934
+ def create_server(config: Optional[Config] = None, **kwargs) -> PwnDocMCPServer:
1935
+ """
1936
+ Create and configure a PwnDoc MCP server.
1937
+
1938
+ Args:
1939
+ config: Configuration object (if provided, kwargs are ignored)
1940
+ **kwargs: Configuration parameters (url, token, etc.)
1941
+
1942
+ Returns:
1943
+ Configured PwnDocMCPServer instance
1944
+
1945
+ Raises:
1946
+ ValueError: If configuration is invalid
1947
+
1948
+ Example:
1949
+ >>> server = create_server(url="https://pwndoc.com", token="...")
1950
+ >>> server = create_server(config)
1951
+ """
1952
+ if config is None:
1953
+ config = Config(**kwargs)
1954
+
1955
+ # Validate configuration
1956
+ errors = config.validate()
1957
+ if errors:
1958
+ raise ValueError(f"Invalid configuration: {'; '.join(errors)}")
1959
+
1960
+ return PwnDocMCPServer(config)
1961
+
1962
+
1963
+ def main():
1964
+ """Main entry point."""
1965
+ import argparse
1966
+
1967
+ parser = argparse.ArgumentParser(description="PwnDoc MCP Server")
1968
+ parser.add_argument("--transport", choices=["stdio", "sse"], default="stdio")
1969
+ parser.add_argument("--host", default="127.0.0.1")
1970
+ parser.add_argument("--port", type=int, default=8080)
1971
+ parser.add_argument("--log-level", default="INFO")
1972
+ args = parser.parse_args()
1973
+
1974
+ logging.basicConfig(level=getattr(logging, args.log_level))
1975
+
1976
+ config = load_config(
1977
+ mcp_transport=args.transport,
1978
+ mcp_host=args.host,
1979
+ mcp_port=args.port,
1980
+ )
1981
+
1982
+ server = PwnDocMCPServer(config)
1983
+ server.run()
1984
+
1985
+
1986
+ if __name__ == "__main__":
1987
+ main()