pwndoc-mcp-server 1.0.2__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.
Potentially problematic release.
This version of pwndoc-mcp-server might be problematic. Click here for more details.
- pwndoc_mcp_server/__init__.py +57 -0
- pwndoc_mcp_server/cli.py +441 -0
- pwndoc_mcp_server/client.py +870 -0
- pwndoc_mcp_server/config.py +411 -0
- pwndoc_mcp_server/logging_config.py +329 -0
- pwndoc_mcp_server/server.py +950 -0
- pwndoc_mcp_server/version.py +26 -0
- pwndoc_mcp_server-1.0.2.dist-info/METADATA +110 -0
- pwndoc_mcp_server-1.0.2.dist-info/RECORD +11 -0
- pwndoc_mcp_server-1.0.2.dist-info/WHEEL +4 -0
- pwndoc_mcp_server-1.0.2.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PwnDoc MCP Server
|
|
3
|
+
|
|
4
|
+
Model Context Protocol server for PwnDoc penetration testing documentation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pwndoc_mcp_server.version import get_version
|
|
8
|
+
|
|
9
|
+
__version__ = get_version()
|
|
10
|
+
__author__ = "Walid Faour"
|
|
11
|
+
|
|
12
|
+
# Export main classes and functions
|
|
13
|
+
from pwndoc_mcp_server.client import (
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
NotFoundError,
|
|
16
|
+
PwnDocClient,
|
|
17
|
+
PwnDocError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
)
|
|
20
|
+
from pwndoc_mcp_server.config import (
|
|
21
|
+
Config,
|
|
22
|
+
get_config_path,
|
|
23
|
+
init_config_interactive,
|
|
24
|
+
load_config,
|
|
25
|
+
save_config,
|
|
26
|
+
)
|
|
27
|
+
from pwndoc_mcp_server.logging_config import LogLevel, get_logger, setup_logging
|
|
28
|
+
from pwndoc_mcp_server.server import (
|
|
29
|
+
TOOL_DEFINITIONS,
|
|
30
|
+
PwnDocMCPServer,
|
|
31
|
+
create_server,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Version
|
|
36
|
+
"__version__",
|
|
37
|
+
# Config
|
|
38
|
+
"Config",
|
|
39
|
+
"get_config_path",
|
|
40
|
+
"init_config_interactive",
|
|
41
|
+
"load_config",
|
|
42
|
+
"save_config",
|
|
43
|
+
# Client
|
|
44
|
+
"PwnDocClient",
|
|
45
|
+
"PwnDocError",
|
|
46
|
+
"AuthenticationError",
|
|
47
|
+
"RateLimitError",
|
|
48
|
+
"NotFoundError",
|
|
49
|
+
# Server
|
|
50
|
+
"PwnDocMCPServer",
|
|
51
|
+
"TOOL_DEFINITIONS",
|
|
52
|
+
"create_server",
|
|
53
|
+
# Logging
|
|
54
|
+
"LogLevel",
|
|
55
|
+
"setup_logging",
|
|
56
|
+
"get_logger",
|
|
57
|
+
]
|
pwndoc_mcp_server/cli.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PwnDoc MCP Server - Command Line Interface.
|
|
3
|
+
|
|
4
|
+
Provides a rich CLI for managing the MCP server, configuration,
|
|
5
|
+
and PwnDoc interactions.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
pwndoc-mcp serve # Start MCP server (stdio)
|
|
9
|
+
pwndoc-mcp serve --transport sse # Start SSE server
|
|
10
|
+
pwndoc-mcp config init # Interactive configuration
|
|
11
|
+
pwndoc-mcp config show # Show current config
|
|
12
|
+
pwndoc-mcp test # Test connection
|
|
13
|
+
pwndoc-mcp version # Show version
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional, Union
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import typer # type: ignore[import-not-found]
|
|
24
|
+
from rich.console import Console # type: ignore[import-not-found]
|
|
25
|
+
from rich.panel import Panel # type: ignore[import-not-found]
|
|
26
|
+
from rich.prompt import Prompt # type: ignore[import-not-found]
|
|
27
|
+
from rich.syntax import Syntax # type: ignore[import-not-found]
|
|
28
|
+
from rich.table import Table # type: ignore[import-not-found]
|
|
29
|
+
|
|
30
|
+
HAS_RICH = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
HAS_RICH = False
|
|
33
|
+
typer = None # type: ignore[assignment]
|
|
34
|
+
Prompt = None # type: ignore[assignment,misc]
|
|
35
|
+
|
|
36
|
+
from pwndoc_mcp_server import __version__
|
|
37
|
+
from pwndoc_mcp_server.client import PwnDocClient, PwnDocError
|
|
38
|
+
from pwndoc_mcp_server.config import (
|
|
39
|
+
DEFAULT_CONFIG_FILE,
|
|
40
|
+
init_config_interactive,
|
|
41
|
+
load_config,
|
|
42
|
+
save_config,
|
|
43
|
+
)
|
|
44
|
+
from pwndoc_mcp_server.server import PwnDocMCPServer
|
|
45
|
+
|
|
46
|
+
# Create CLI app
|
|
47
|
+
if HAS_RICH:
|
|
48
|
+
app = typer.Typer(
|
|
49
|
+
name="pwndoc-mcp",
|
|
50
|
+
help="PwnDoc MCP Server - Model Context Protocol for Pentest Documentation",
|
|
51
|
+
add_completion=True,
|
|
52
|
+
)
|
|
53
|
+
console = Console()
|
|
54
|
+
|
|
55
|
+
def version_callback(value: bool):
|
|
56
|
+
"""Callback for --version flag."""
|
|
57
|
+
if value:
|
|
58
|
+
console.print(f"pwndoc-mcp-server version {__version__}")
|
|
59
|
+
raise typer.Exit()
|
|
60
|
+
|
|
61
|
+
@app.callback()
|
|
62
|
+
def cli_callback(
|
|
63
|
+
version: Optional[bool] = typer.Option(
|
|
64
|
+
None,
|
|
65
|
+
"--version",
|
|
66
|
+
"-v",
|
|
67
|
+
help="Show version and exit",
|
|
68
|
+
callback=version_callback,
|
|
69
|
+
is_eager=True,
|
|
70
|
+
)
|
|
71
|
+
):
|
|
72
|
+
"""PwnDoc MCP Server CLI."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
app = None # type: ignore[assignment]
|
|
77
|
+
console = None # type: ignore[assignment]
|
|
78
|
+
version_callback = None # type: ignore[assignment]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def setup_logging(level: str = "INFO", log_file: Optional[str] = None):
|
|
82
|
+
"""Configure logging."""
|
|
83
|
+
handlers: list[logging.Handler] = [logging.StreamHandler()]
|
|
84
|
+
if log_file:
|
|
85
|
+
handlers.append(logging.FileHandler(log_file))
|
|
86
|
+
|
|
87
|
+
logging.basicConfig(
|
|
88
|
+
level=getattr(logging, level.upper()),
|
|
89
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
90
|
+
handlers=handlers,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# =============================================================================
|
|
95
|
+
# VERSION COMMAND
|
|
96
|
+
# =============================================================================
|
|
97
|
+
|
|
98
|
+
if HAS_RICH:
|
|
99
|
+
|
|
100
|
+
@app.command()
|
|
101
|
+
def version():
|
|
102
|
+
"""Show version information."""
|
|
103
|
+
console.print(
|
|
104
|
+
Panel(
|
|
105
|
+
f"[bold green]PwnDoc MCP Server[/bold green]\n"
|
|
106
|
+
f"Version: [cyan]{__version__}[/cyan]\n"
|
|
107
|
+
f"Python: [cyan]{sys.version.split()[0]}[/cyan]",
|
|
108
|
+
title="Version Info",
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# =============================================================================
|
|
114
|
+
# SERVE COMMAND
|
|
115
|
+
# =============================================================================
|
|
116
|
+
|
|
117
|
+
if HAS_RICH:
|
|
118
|
+
|
|
119
|
+
@app.command()
|
|
120
|
+
def serve(
|
|
121
|
+
transport: str = typer.Option("stdio", help="Transport type (stdio, sse)"),
|
|
122
|
+
host: str = typer.Option("127.0.0.1", help="Host for SSE/WebSocket"),
|
|
123
|
+
port: int = typer.Option(8080, help="Port for SSE/WebSocket"),
|
|
124
|
+
log_level: str = typer.Option("INFO", help="Log level"),
|
|
125
|
+
log_file: Optional[str] = typer.Option(None, help="Log file path"),
|
|
126
|
+
config_file: Optional[Path] = typer.Option(None, "--config", "-c", help="Config file path"),
|
|
127
|
+
):
|
|
128
|
+
"""Start the MCP server."""
|
|
129
|
+
setup_logging(log_level, log_file)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
config = load_config(
|
|
133
|
+
config_file=config_file,
|
|
134
|
+
mcp_transport=transport,
|
|
135
|
+
mcp_host=host,
|
|
136
|
+
mcp_port=port,
|
|
137
|
+
log_level=log_level,
|
|
138
|
+
log_file=log_file,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if not config.is_configured:
|
|
142
|
+
console.print(
|
|
143
|
+
"[red]Error:[/red] Server not configured. Run 'pwndoc-mcp config init' first."
|
|
144
|
+
)
|
|
145
|
+
raise typer.Exit(1)
|
|
146
|
+
|
|
147
|
+
if transport != "stdio":
|
|
148
|
+
console.print("[green]Starting PwnDoc MCP Server[/green]")
|
|
149
|
+
console.print(f" Transport: [cyan]{transport}[/cyan]")
|
|
150
|
+
console.print(f" URL: [cyan]{config.url}[/cyan]")
|
|
151
|
+
if transport == "sse":
|
|
152
|
+
console.print(f" Endpoint: [cyan]http://{host}:{port}/mcp[/cyan]")
|
|
153
|
+
|
|
154
|
+
server = PwnDocMCPServer(config)
|
|
155
|
+
server.run(transport)
|
|
156
|
+
|
|
157
|
+
except KeyboardInterrupt:
|
|
158
|
+
console.print("\n[yellow]Server stopped[/yellow]")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
161
|
+
raise typer.Exit(1)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# =============================================================================
|
|
165
|
+
# CONFIG COMMANDS
|
|
166
|
+
# =============================================================================
|
|
167
|
+
|
|
168
|
+
if HAS_RICH:
|
|
169
|
+
config_app = typer.Typer(help="Configuration management")
|
|
170
|
+
app.add_typer(config_app, name="config")
|
|
171
|
+
|
|
172
|
+
@config_app.command("init")
|
|
173
|
+
def config_init():
|
|
174
|
+
"""Interactive configuration wizard."""
|
|
175
|
+
try:
|
|
176
|
+
init_config_interactive()
|
|
177
|
+
console.print("[green]✓ Configuration complete![/green]")
|
|
178
|
+
except KeyboardInterrupt:
|
|
179
|
+
console.print("\n[yellow]Configuration cancelled[/yellow]")
|
|
180
|
+
|
|
181
|
+
@config_app.command("show")
|
|
182
|
+
def config_show(
|
|
183
|
+
reveal_secrets: bool = typer.Option(False, "--reveal", help="Show sensitive values"),
|
|
184
|
+
):
|
|
185
|
+
"""Show current configuration."""
|
|
186
|
+
config = load_config()
|
|
187
|
+
|
|
188
|
+
table = Table(title="Current Configuration")
|
|
189
|
+
table.add_column("Setting", style="cyan")
|
|
190
|
+
table.add_column("Value", style="green")
|
|
191
|
+
|
|
192
|
+
table.add_row("URL", config.url or "[dim]not set[/dim]")
|
|
193
|
+
table.add_row("Username", config.username or "[dim]not set[/dim]")
|
|
194
|
+
table.add_row(
|
|
195
|
+
"Password",
|
|
196
|
+
(
|
|
197
|
+
("*" * 8 if config.password else "[dim]not set[/dim]")
|
|
198
|
+
if not reveal_secrets
|
|
199
|
+
else config.password
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
table.add_row(
|
|
203
|
+
"Token",
|
|
204
|
+
(
|
|
205
|
+
("*" * 20 if config.token else "[dim]not set[/dim]")
|
|
206
|
+
if not reveal_secrets
|
|
207
|
+
else (config.token or "")
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
table.add_row("Verify SSL", str(config.verify_ssl))
|
|
211
|
+
table.add_row("Timeout", str(config.timeout))
|
|
212
|
+
table.add_row("Log Level", config.log_level)
|
|
213
|
+
table.add_row("MCP Transport", config.mcp_transport)
|
|
214
|
+
table.add_row("Auth Method", config.auth_method)
|
|
215
|
+
table.add_row(
|
|
216
|
+
"Configured",
|
|
217
|
+
"[green]Yes[/green]" if config.is_configured else "[red]No[/red]",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
console.print(table)
|
|
221
|
+
console.print(f"\nConfig file: [dim]{DEFAULT_CONFIG_FILE}[/dim]")
|
|
222
|
+
|
|
223
|
+
@config_app.command("set")
|
|
224
|
+
def config_set(
|
|
225
|
+
key: str = typer.Argument(..., help="Configuration key"),
|
|
226
|
+
value: str = typer.Argument(..., help="Configuration value"),
|
|
227
|
+
):
|
|
228
|
+
"""Set a configuration value."""
|
|
229
|
+
config = load_config()
|
|
230
|
+
|
|
231
|
+
if hasattr(config, key):
|
|
232
|
+
# Convert value to appropriate type
|
|
233
|
+
current = getattr(config, key)
|
|
234
|
+
converted_value: Union[bool, int, str]
|
|
235
|
+
if isinstance(current, bool):
|
|
236
|
+
converted_value = value.lower() in ("true", "1", "yes")
|
|
237
|
+
elif isinstance(current, int):
|
|
238
|
+
converted_value = int(value)
|
|
239
|
+
else:
|
|
240
|
+
converted_value = value
|
|
241
|
+
|
|
242
|
+
setattr(config, key, converted_value)
|
|
243
|
+
save_config(config)
|
|
244
|
+
console.print(f"[green]✓[/green] Set {key} = {value}")
|
|
245
|
+
else:
|
|
246
|
+
console.print(f"[red]Error:[/red] Unknown configuration key: {key}")
|
|
247
|
+
raise typer.Exit(1)
|
|
248
|
+
|
|
249
|
+
@config_app.command("path")
|
|
250
|
+
def config_path():
|
|
251
|
+
"""Show configuration file path."""
|
|
252
|
+
console.print(f"[cyan]{DEFAULT_CONFIG_FILE}[/cyan]")
|
|
253
|
+
if DEFAULT_CONFIG_FILE.exists():
|
|
254
|
+
console.print("[green] (exists)[/green]")
|
|
255
|
+
else:
|
|
256
|
+
console.print("[yellow] (not created)[/yellow]")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# =============================================================================
|
|
260
|
+
# TEST COMMAND
|
|
261
|
+
# =============================================================================
|
|
262
|
+
|
|
263
|
+
if HAS_RICH:
|
|
264
|
+
|
|
265
|
+
@app.command()
|
|
266
|
+
def test(
|
|
267
|
+
config_file: Optional[Path] = typer.Option(None, "--config", "-c", help="Config file path"),
|
|
268
|
+
):
|
|
269
|
+
"""Test connection to PwnDoc server."""
|
|
270
|
+
config = load_config(config_file=config_file)
|
|
271
|
+
|
|
272
|
+
if not config.is_configured:
|
|
273
|
+
console.print(
|
|
274
|
+
"[red]Error:[/red] Server not configured. Run 'pwndoc-mcp config init' first."
|
|
275
|
+
)
|
|
276
|
+
raise typer.Exit(1)
|
|
277
|
+
|
|
278
|
+
console.print(f"Testing connection to [cyan]{config.url}[/cyan]...")
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
with PwnDocClient(config) as client:
|
|
282
|
+
client.authenticate()
|
|
283
|
+
console.print("[green]✓ Authentication successful[/green]")
|
|
284
|
+
|
|
285
|
+
user = client.get_current_user()
|
|
286
|
+
console.print(f"[green]✓ Logged in as:[/green] {user.get('username', 'unknown')}")
|
|
287
|
+
|
|
288
|
+
audits = client.list_audits()
|
|
289
|
+
console.print(f"[green]✓ Found {len(audits)} audits[/green]")
|
|
290
|
+
|
|
291
|
+
console.print("\n[bold green]All tests passed![/bold green]")
|
|
292
|
+
|
|
293
|
+
except PwnDocError as e:
|
|
294
|
+
console.print(f"[red]✗ Connection failed:[/red] {e}")
|
|
295
|
+
raise typer.Exit(1)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
console.print(f"[red]✗ Error:[/red] {e}")
|
|
298
|
+
raise typer.Exit(1)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# =============================================================================
|
|
302
|
+
# QUERY COMMAND (for quick queries)
|
|
303
|
+
# =============================================================================
|
|
304
|
+
|
|
305
|
+
if HAS_RICH:
|
|
306
|
+
|
|
307
|
+
@app.command()
|
|
308
|
+
def query(
|
|
309
|
+
tool: str = typer.Argument(..., help="Tool name to call"),
|
|
310
|
+
params: Optional[str] = typer.Option(None, "--params", "-p", help="JSON parameters"),
|
|
311
|
+
config_file: Optional[Path] = typer.Option(None, "--config", "-c", help="Config file path"),
|
|
312
|
+
):
|
|
313
|
+
"""Execute a tool query directly."""
|
|
314
|
+
config = load_config(config_file=config_file)
|
|
315
|
+
|
|
316
|
+
if not config.is_configured:
|
|
317
|
+
console.print("[red]Error:[/red] Server not configured.")
|
|
318
|
+
raise typer.Exit(1)
|
|
319
|
+
|
|
320
|
+
server = PwnDocMCPServer(config)
|
|
321
|
+
|
|
322
|
+
if tool not in server._tools:
|
|
323
|
+
console.print(f"[red]Error:[/red] Unknown tool: {tool}")
|
|
324
|
+
console.print("\nAvailable tools:")
|
|
325
|
+
for t in sorted(server._tools.keys()):
|
|
326
|
+
console.print(f" • {t}")
|
|
327
|
+
raise typer.Exit(1)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
arguments = json.loads(params) if params else {}
|
|
331
|
+
result = server._tools[tool].handler(**arguments)
|
|
332
|
+
|
|
333
|
+
# Pretty print result
|
|
334
|
+
syntax = Syntax(json.dumps(result, indent=2, default=str), "json", theme="monokai")
|
|
335
|
+
console.print(syntax)
|
|
336
|
+
|
|
337
|
+
except json.JSONDecodeError as e:
|
|
338
|
+
console.print(f"[red]Error:[/red] Invalid JSON parameters: {e}")
|
|
339
|
+
raise typer.Exit(1)
|
|
340
|
+
except Exception as e:
|
|
341
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
342
|
+
raise typer.Exit(1)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# =============================================================================
|
|
346
|
+
# TOOLS COMMAND
|
|
347
|
+
# =============================================================================
|
|
348
|
+
|
|
349
|
+
if HAS_RICH:
|
|
350
|
+
|
|
351
|
+
@app.command()
|
|
352
|
+
def tools():
|
|
353
|
+
"""List all available MCP tools."""
|
|
354
|
+
config = load_config()
|
|
355
|
+
server = PwnDocMCPServer(config)
|
|
356
|
+
|
|
357
|
+
table = Table(title="Available Tools")
|
|
358
|
+
table.add_column("Tool", style="cyan")
|
|
359
|
+
table.add_column("Description", style="white")
|
|
360
|
+
|
|
361
|
+
for name, tool in sorted(server._tools.items()):
|
|
362
|
+
table.add_row(
|
|
363
|
+
name,
|
|
364
|
+
tool.description[:60] + "..." if len(tool.description) > 60 else tool.description,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
console.print(table)
|
|
368
|
+
console.print(f"\nTotal: [cyan]{len(server._tools)}[/cyan] tools")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# =============================================================================
|
|
372
|
+
# MAIN ENTRY POINT
|
|
373
|
+
# =============================================================================
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def main():
|
|
377
|
+
"""Main entry point for CLI."""
|
|
378
|
+
if not HAS_RICH:
|
|
379
|
+
# Fallback to simple argparse if rich/typer not available
|
|
380
|
+
import argparse
|
|
381
|
+
|
|
382
|
+
parser = argparse.ArgumentParser(
|
|
383
|
+
description="PwnDoc MCP Server",
|
|
384
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
385
|
+
epilog="""
|
|
386
|
+
Examples:
|
|
387
|
+
pwndoc-mcp serve Start MCP server (stdio)
|
|
388
|
+
pwndoc-mcp serve --transport sse Start SSE server
|
|
389
|
+
|
|
390
|
+
For rich CLI experience, install: pip install typer[all] rich
|
|
391
|
+
""",
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
395
|
+
|
|
396
|
+
# Serve command
|
|
397
|
+
serve_parser = subparsers.add_parser("serve", help="Start MCP server")
|
|
398
|
+
serve_parser.add_argument("--transport", default="stdio", choices=["stdio", "sse"])
|
|
399
|
+
serve_parser.add_argument("--host", default="127.0.0.1")
|
|
400
|
+
serve_parser.add_argument("--port", type=int, default=8080)
|
|
401
|
+
serve_parser.add_argument("--log-level", default="INFO")
|
|
402
|
+
serve_parser.add_argument("--config", "-c", type=Path)
|
|
403
|
+
|
|
404
|
+
# Version command
|
|
405
|
+
subparsers.add_parser("version", help="Show version")
|
|
406
|
+
|
|
407
|
+
# Test command
|
|
408
|
+
test_parser = subparsers.add_parser("test", help="Test connection")
|
|
409
|
+
test_parser.add_argument("--config", "-c", type=Path)
|
|
410
|
+
|
|
411
|
+
args = parser.parse_args()
|
|
412
|
+
|
|
413
|
+
if args.command == "version":
|
|
414
|
+
print(f"PwnDoc MCP Server v{__version__}")
|
|
415
|
+
elif args.command == "serve":
|
|
416
|
+
setup_logging(args.log_level)
|
|
417
|
+
config = load_config(
|
|
418
|
+
config_file=args.config,
|
|
419
|
+
mcp_transport=args.transport,
|
|
420
|
+
mcp_host=args.host,
|
|
421
|
+
mcp_port=args.port,
|
|
422
|
+
)
|
|
423
|
+
server = PwnDocMCPServer(config)
|
|
424
|
+
server.run()
|
|
425
|
+
elif args.command == "test":
|
|
426
|
+
config = load_config(config_file=args.config)
|
|
427
|
+
try:
|
|
428
|
+
with PwnDocClient(config) as client:
|
|
429
|
+
client.authenticate()
|
|
430
|
+
print("✓ Connection successful!")
|
|
431
|
+
except Exception as e:
|
|
432
|
+
print(f"✗ Connection failed: {e}")
|
|
433
|
+
sys.exit(1)
|
|
434
|
+
else:
|
|
435
|
+
parser.print_help()
|
|
436
|
+
else:
|
|
437
|
+
app()
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
if __name__ == "__main__":
|
|
441
|
+
main()
|