kmcp 0.1.5.dev0__tar.gz → 0.1.7.dev0__tar.gz
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.
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/.github/workflows/release.yml +0 -3
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/PKG-INFO +1 -1
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/_version.py +3 -3
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/execution.py +2 -2
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/monitor/sse_client.py +22 -21
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/file_read.py +1 -1
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/utils/process.py +1 -1
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/PKG-INFO +1 -1
- kmcp-0.1.7.dev0/src/kmcp.egg-info/scm_version.json +8 -0
- kmcp-0.1.7.dev0/tests/e2e/test_mcp_prompts_resources.py +142 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_db_manager_extended.py +1 -3
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_vault_extended.py +1 -3
- kmcp-0.1.5.dev0/src/kmcp.egg-info/scm_version.json +0 -8
- kmcp-0.1.5.dev0/tests/e2e/test_mcp_prompts_resources.py +0 -139
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/.env.example +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/.gitignore +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/Justfile +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/README.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/FEATURE_ROADMAP.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/IMPLEMENTATION_PLAN.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/credential-evidence-vault-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/interactive-monitor-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/orchestration-playbooks-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/parallel-execution-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/result-normalization-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/superpowers/specs/2026-10-31-foundation-design.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/target-scope-management-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/docs/tool-wrappers-plan.md +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/pyproject.toml +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/requirements.txt +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/setup.cfg +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/__init__.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/__main__.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/cli.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/config.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/db/__init__.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/db/manager.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/logging_utils.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/models.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/monitor/__init__.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/monitor/sessions.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/monitor/tui.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/notifications.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/prompts.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/registry.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/resources.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/responses.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/security.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/server.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/__init__.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/amass.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/arjun.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/base.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/bettercap.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/bloodhound.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/commix.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/crackmapexec.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/dirb.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/dursgo.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/enum4linux.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/farsight.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/ffuf.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/file_download.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/file_write.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/flowlyt.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/generic_command.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/gobuster.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/hashcat.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/httpx.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/hydra.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/john.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/katana.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/masscan.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/medusa.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/msfconsole.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/naabu.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/netexec.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/nmap.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/normalization.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/nuclei.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/parallel_execute.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/persist.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/playbook_ad.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/playbook_engine.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/playbook_engine_tool.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/playbook_recon.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/playbook_tools.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/playbook_webapp.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/python_command.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/responder.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/searchsploit.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/spiderfoot.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/sqlmap.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/subfinder.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/theharvester.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/vault_tool.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/wfuzz.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/whatweb.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/wpscan.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/zap.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/zighound.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/tools/zizmor.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/utils/__init__.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/utils/vault.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kali_mcp/validation.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/SOURCES.txt +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/dependency_links.txt +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/entry_points.txt +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/requires.txt +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/scm_file_list.json +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/src/kmcp.egg-info/top_level.txt +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/conftest.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/e2e/mcp_smoke.sh +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/e2e/test_mcp_protocol.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_execution_advanced.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_execution_engine.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_file_tools.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_generic_command.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_monitor_endpoints.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_parallel_execute.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_playbook_engine.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_playbook_tools.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_process_and_download.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_python_command.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_resources.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_resources_more.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_safe_execute.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/integration/test_vault_tools.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_cli.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_config.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_config_extended.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_db_edge.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_db_manager.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_execution_edge.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_execution_engine_unit.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_logging_utils.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_main_entry.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_models.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_monitor_actions.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_monitor_sessions.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_monitor_snapshot.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_monitor_tui.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_notifications.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_persist.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_process_extended.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_prompts_extended.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_registry.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_resources_extended.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_responses.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_security.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_security_edge.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_security_helpers.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_server.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_support_modules.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_tool_commands_matrix.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_tool_schemas.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_validation.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_validation_edge.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_vault.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_vault_edge.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_vault_sqlite.py +0 -0
- {kmcp-0.1.5.dev0 → kmcp-0.1.7.dev0}/tests/unit/test_wrapper_normalize.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.7.dev0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 7, 'dev0')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g5916172e7'
|
|
@@ -275,7 +275,7 @@ class ExecutionEngine:
|
|
|
275
275
|
heartbeat_task.cancel()
|
|
276
276
|
try:
|
|
277
277
|
await heartbeat_task
|
|
278
|
-
except (asyncio.CancelledError, Exception):
|
|
278
|
+
except (asyncio.CancelledError, Exception): # noqa: S110, BLE001
|
|
279
279
|
pass
|
|
280
280
|
if progress_cb is not None:
|
|
281
281
|
try:
|
|
@@ -313,7 +313,7 @@ class ExecutionEngine:
|
|
|
313
313
|
heartbeat_task.cancel()
|
|
314
314
|
try:
|
|
315
315
|
await heartbeat_task
|
|
316
|
-
except (asyncio.CancelledError, Exception):
|
|
316
|
+
except (asyncio.CancelledError, Exception): # noqa: S110, BLE001
|
|
317
317
|
pass
|
|
318
318
|
return result
|
|
319
319
|
|
|
@@ -39,28 +39,29 @@ class SseClient:
|
|
|
39
39
|
async def _read_loop(self) -> None:
|
|
40
40
|
while not self._closed:
|
|
41
41
|
try:
|
|
42
|
-
async with
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
async with (
|
|
43
|
+
httpx.AsyncClient(timeout=None) as http,
|
|
44
|
+
http.stream("GET", self.url) as response,
|
|
45
|
+
):
|
|
46
|
+
response.raise_for_status()
|
|
47
|
+
async for line in response.aiter_lines():
|
|
48
|
+
if self._closed:
|
|
49
|
+
return
|
|
50
|
+
if line.startswith("data: "):
|
|
51
|
+
try:
|
|
52
|
+
payload = json.loads(line[len("data: ") :])
|
|
53
|
+
except json.JSONDecodeError:
|
|
54
|
+
continue
|
|
55
|
+
self._snapshot = payload
|
|
56
|
+
try:
|
|
57
|
+
self._queue.put_nowait(payload)
|
|
58
|
+
except asyncio.QueueFull:
|
|
59
|
+
# Slow consumer — drop this snapshot
|
|
50
60
|
try:
|
|
51
|
-
|
|
52
|
-
except
|
|
53
|
-
|
|
54
|
-
self.
|
|
55
|
-
try:
|
|
56
|
-
self._queue.put_nowait(payload)
|
|
57
|
-
except asyncio.QueueFull:
|
|
58
|
-
# Slow consumer — drop this snapshot
|
|
59
|
-
try:
|
|
60
|
-
self._queue.get_nowait()
|
|
61
|
-
except asyncio.QueueEmpty:
|
|
62
|
-
pass
|
|
63
|
-
self._queue.put_nowait(payload)
|
|
61
|
+
self._queue.get_nowait()
|
|
62
|
+
except asyncio.QueueEmpty:
|
|
63
|
+
pass
|
|
64
|
+
self._queue.put_nowait(payload)
|
|
64
65
|
except (httpx.ConnectError, httpx.ReadError, OSError):
|
|
65
66
|
# Server went down — retry after a short pause
|
|
66
67
|
await asyncio.sleep(2.0)
|
|
@@ -32,5 +32,5 @@ async def kill_process_tree(proc: asyncio.subprocess.Process) -> None:
|
|
|
32
32
|
except TimeoutError:
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
|
-
except Exception as e:
|
|
35
|
+
except Exception as e: # noqa: BLE001
|
|
36
36
|
logger.warning("Error killing process %s: %s", getattr(proc, "pid", "?"), e)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""E2E: MCP prompts and resources over the live protocol (server spawned for real)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
import pytest
|
|
11
|
+
from mcp.client.session import ClientSession
|
|
12
|
+
from mcp.client.streamable_http import streamable_http_client
|
|
13
|
+
|
|
14
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(scope="module")
|
|
18
|
+
def server_url():
|
|
19
|
+
import os
|
|
20
|
+
import socket
|
|
21
|
+
|
|
22
|
+
with socket.socket() as s:
|
|
23
|
+
s.bind(("127.0.0.1", 0))
|
|
24
|
+
port = s.getsockname()[1]
|
|
25
|
+
env = {
|
|
26
|
+
**os.environ,
|
|
27
|
+
"MCP_HOST": "127.0.0.1",
|
|
28
|
+
"MCP_PORT": str(port),
|
|
29
|
+
"MCP_TRANSPORT": "streamable-http",
|
|
30
|
+
"MCP_DEBUG": "false",
|
|
31
|
+
"MCP_DB_ENABLED": "false",
|
|
32
|
+
}
|
|
33
|
+
proc = subprocess.Popen(
|
|
34
|
+
[sys.executable, str(REPO_ROOT / "src" / "kali_mcp" / "server.py")],
|
|
35
|
+
cwd=REPO_ROOT,
|
|
36
|
+
env=env,
|
|
37
|
+
stdout=subprocess.DEVNULL,
|
|
38
|
+
stderr=subprocess.DEVNULL,
|
|
39
|
+
)
|
|
40
|
+
base = f"http://127.0.0.1:{port}"
|
|
41
|
+
try:
|
|
42
|
+
last_err = None
|
|
43
|
+
for _ in range(60):
|
|
44
|
+
if proc.poll() is not None:
|
|
45
|
+
raise RuntimeError("server exited early")
|
|
46
|
+
try:
|
|
47
|
+
httpx.get(f"{base}/mcp", timeout=1)
|
|
48
|
+
last_err = None
|
|
49
|
+
break
|
|
50
|
+
except Exception as exc: # noqa: BLE001
|
|
51
|
+
last_err = exc
|
|
52
|
+
import time
|
|
53
|
+
|
|
54
|
+
time.sleep(0.5)
|
|
55
|
+
if last_err is not None:
|
|
56
|
+
raise RuntimeError(f"server not ready: {last_err}")
|
|
57
|
+
yield f"{base}/mcp"
|
|
58
|
+
finally:
|
|
59
|
+
proc.terminate()
|
|
60
|
+
try:
|
|
61
|
+
proc.wait(timeout=10)
|
|
62
|
+
except subprocess.TimeoutExpired:
|
|
63
|
+
proc.kill()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.e2e
|
|
67
|
+
async def test_prompts_listed_and_rendered(server_url):
|
|
68
|
+
async with (
|
|
69
|
+
streamable_http_client(server_url) as (read, write, _sid),
|
|
70
|
+
ClientSession(read, write) as session,
|
|
71
|
+
):
|
|
72
|
+
await session.initialize()
|
|
73
|
+
|
|
74
|
+
prompts = await session.list_prompts()
|
|
75
|
+
names = {p.name for p in prompts.prompts}
|
|
76
|
+
assert {
|
|
77
|
+
"recon_workflow",
|
|
78
|
+
"webapp_assessment",
|
|
79
|
+
"ad_assessment",
|
|
80
|
+
"osint_harvest",
|
|
81
|
+
"quick_scan",
|
|
82
|
+
"report_template",
|
|
83
|
+
} <= names
|
|
84
|
+
|
|
85
|
+
result = await session.get_prompt("recon_workflow", {"target": "example.com"})
|
|
86
|
+
assert len(result.messages) == 2
|
|
87
|
+
assert "example.com" in result.messages[0].content.text
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.e2e
|
|
91
|
+
async def test_resources_listed_and_read(server_url):
|
|
92
|
+
async with (
|
|
93
|
+
streamable_http_client(server_url) as (read, write, _sid),
|
|
94
|
+
ClientSession(read, write) as session,
|
|
95
|
+
):
|
|
96
|
+
await session.initialize()
|
|
97
|
+
|
|
98
|
+
resources = await session.list_resources()
|
|
99
|
+
uris = {str(r.uri) for r in resources.resources}
|
|
100
|
+
assert {
|
|
101
|
+
"kali://config",
|
|
102
|
+
"kali://tools",
|
|
103
|
+
"kali://health",
|
|
104
|
+
"kali://db/stats",
|
|
105
|
+
"kali://vault/index",
|
|
106
|
+
"kali://artifacts",
|
|
107
|
+
} <= uris
|
|
108
|
+
|
|
109
|
+
# Read config + health resources
|
|
110
|
+
config_res = await session.read_resource("kali://config")
|
|
111
|
+
assert config_res.contents[0].mimeType == "application/json"
|
|
112
|
+
import json
|
|
113
|
+
|
|
114
|
+
cfg = json.loads(config_res.contents[0].text)
|
|
115
|
+
# The server runs on a random port — config must reflect reality
|
|
116
|
+
assert cfg["port"] == int(server_url.split(":")[2].split("/")[0])
|
|
117
|
+
|
|
118
|
+
health_res = await session.read_resource("kali://health")
|
|
119
|
+
health = json.loads(health_res.contents[0].text)
|
|
120
|
+
assert len(health) > 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.mark.e2e
|
|
124
|
+
async def test_orchestration_tools_callable_via_mcp(server_url):
|
|
125
|
+
"""playbook_list / playbook_status / parallel_status must round-trip."""
|
|
126
|
+
import json
|
|
127
|
+
|
|
128
|
+
async with (
|
|
129
|
+
streamable_http_client(server_url) as (read, write, _sid),
|
|
130
|
+
ClientSession(read, write) as session,
|
|
131
|
+
):
|
|
132
|
+
await session.initialize()
|
|
133
|
+
|
|
134
|
+
result = await session.call_tool("playbook_list", {})
|
|
135
|
+
assert result.isError is False
|
|
136
|
+
payload = json.loads(result.content[0].text)
|
|
137
|
+
assert payload["success"] is False or payload # ExecutionResult or error dict
|
|
138
|
+
|
|
139
|
+
result = await session.call_tool("playbook_status", {"name": "nope"})
|
|
140
|
+
payload = json.loads(result.content[0].text)
|
|
141
|
+
assert payload["success"] is False
|
|
142
|
+
assert "Unknown playbook" in payload["error"]
|
|
@@ -92,9 +92,7 @@ class TestDBManagerEdgeCases:
|
|
|
92
92
|
mock_config.db_enabled = True
|
|
93
93
|
import tempfile
|
|
94
94
|
|
|
95
|
-
mock_config.db_path = (
|
|
96
|
-
str(tempfile.mkdtemp()) + "/singleton.sqlite"
|
|
97
|
-
)
|
|
95
|
+
mock_config.db_path = str(tempfile.mkdtemp()) + "/singleton.sqlite"
|
|
98
96
|
db1 = get_db()
|
|
99
97
|
db2 = get_db()
|
|
100
98
|
assert db1 is db2
|
|
@@ -153,7 +153,5 @@ class TestVaultRowSizeLimit:
|
|
|
153
153
|
from kali_mcp import config as cfg
|
|
154
154
|
|
|
155
155
|
small_cfg = replace(cfg.config, vault_max_row_bytes=100)
|
|
156
|
-
with patch("kali_mcp.utils.vault.config", small_cfg), pytest.raises(
|
|
157
|
-
ValueError
|
|
158
|
-
):
|
|
156
|
+
with patch("kali_mcp.utils.vault.config", small_cfg), pytest.raises(ValueError):
|
|
159
157
|
vault_write("runs/big", "big", {"data": "x" * 1000})
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"""E2E: MCP prompts and resources over the live protocol (server spawned for real)."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import subprocess
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
import httpx
|
|
10
|
-
import pytest
|
|
11
|
-
from mcp.client.session import ClientSession
|
|
12
|
-
from mcp.client.streamable_http import streamable_http_client
|
|
13
|
-
|
|
14
|
-
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@pytest.fixture(scope="module")
|
|
18
|
-
def server_url():
|
|
19
|
-
import os
|
|
20
|
-
import socket
|
|
21
|
-
|
|
22
|
-
with socket.socket() as s:
|
|
23
|
-
s.bind(("127.0.0.1", 0))
|
|
24
|
-
port = s.getsockname()[1]
|
|
25
|
-
env = {
|
|
26
|
-
**os.environ,
|
|
27
|
-
"MCP_HOST": "127.0.0.1",
|
|
28
|
-
"MCP_PORT": str(port),
|
|
29
|
-
"MCP_TRANSPORT": "streamable-http",
|
|
30
|
-
"MCP_DEBUG": "false",
|
|
31
|
-
"MCP_DB_ENABLED": "false",
|
|
32
|
-
}
|
|
33
|
-
proc = subprocess.Popen(
|
|
34
|
-
[sys.executable, str(REPO_ROOT / "src" / "kali_mcp" / "server.py")],
|
|
35
|
-
cwd=REPO_ROOT,
|
|
36
|
-
env=env,
|
|
37
|
-
stdout=subprocess.DEVNULL,
|
|
38
|
-
stderr=subprocess.DEVNULL,
|
|
39
|
-
)
|
|
40
|
-
base = f"http://127.0.0.1:{port}"
|
|
41
|
-
try:
|
|
42
|
-
last_err = None
|
|
43
|
-
for _ in range(60):
|
|
44
|
-
if proc.poll() is not None:
|
|
45
|
-
raise RuntimeError("server exited early")
|
|
46
|
-
try:
|
|
47
|
-
httpx.get(f"{base}/mcp", timeout=1)
|
|
48
|
-
last_err = None
|
|
49
|
-
break
|
|
50
|
-
except Exception as exc: # noqa: BLE001
|
|
51
|
-
last_err = exc
|
|
52
|
-
import time
|
|
53
|
-
|
|
54
|
-
time.sleep(0.5)
|
|
55
|
-
if last_err is not None:
|
|
56
|
-
raise RuntimeError(f"server not ready: {last_err}")
|
|
57
|
-
yield f"{base}/mcp"
|
|
58
|
-
finally:
|
|
59
|
-
proc.terminate()
|
|
60
|
-
try:
|
|
61
|
-
proc.wait(timeout=10)
|
|
62
|
-
except subprocess.TimeoutExpired:
|
|
63
|
-
proc.kill()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@pytest.mark.e2e
|
|
67
|
-
async def test_prompts_listed_and_rendered(server_url):
|
|
68
|
-
async with streamable_http_client(server_url) as (read, write, _sid), ClientSession(
|
|
69
|
-
read, write
|
|
70
|
-
) as session:
|
|
71
|
-
await session.initialize()
|
|
72
|
-
|
|
73
|
-
prompts = await session.list_prompts()
|
|
74
|
-
names = {p.name for p in prompts.prompts}
|
|
75
|
-
assert {
|
|
76
|
-
"recon_workflow",
|
|
77
|
-
"webapp_assessment",
|
|
78
|
-
"ad_assessment",
|
|
79
|
-
"osint_harvest",
|
|
80
|
-
"quick_scan",
|
|
81
|
-
"report_template",
|
|
82
|
-
} <= names
|
|
83
|
-
|
|
84
|
-
result = await session.get_prompt("recon_workflow", {"target": "example.com"})
|
|
85
|
-
assert len(result.messages) == 2
|
|
86
|
-
assert "example.com" in result.messages[0].content.text
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@pytest.mark.e2e
|
|
90
|
-
async def test_resources_listed_and_read(server_url):
|
|
91
|
-
async with streamable_http_client(server_url) as (read, write, _sid), ClientSession(
|
|
92
|
-
read, write
|
|
93
|
-
) as session:
|
|
94
|
-
await session.initialize()
|
|
95
|
-
|
|
96
|
-
resources = await session.list_resources()
|
|
97
|
-
uris = {str(r.uri) for r in resources.resources}
|
|
98
|
-
assert {
|
|
99
|
-
"kali://config",
|
|
100
|
-
"kali://tools",
|
|
101
|
-
"kali://health",
|
|
102
|
-
"kali://db/stats",
|
|
103
|
-
"kali://vault/index",
|
|
104
|
-
"kali://artifacts",
|
|
105
|
-
} <= uris
|
|
106
|
-
|
|
107
|
-
# Read config + health resources
|
|
108
|
-
config_res = await session.read_resource("kali://config")
|
|
109
|
-
assert config_res.contents[0].mimeType == "application/json"
|
|
110
|
-
import json
|
|
111
|
-
|
|
112
|
-
cfg = json.loads(config_res.contents[0].text)
|
|
113
|
-
# The server runs on a random port — config must reflect reality
|
|
114
|
-
assert cfg["port"] == int(server_url.split(":")[2].split("/")[0])
|
|
115
|
-
|
|
116
|
-
health_res = await session.read_resource("kali://health")
|
|
117
|
-
health = json.loads(health_res.contents[0].text)
|
|
118
|
-
assert len(health) > 0
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@pytest.mark.e2e
|
|
122
|
-
async def test_orchestration_tools_callable_via_mcp(server_url):
|
|
123
|
-
"""playbook_list / playbook_status / parallel_status must round-trip."""
|
|
124
|
-
import json
|
|
125
|
-
|
|
126
|
-
async with streamable_http_client(server_url) as (read, write, _sid), ClientSession(
|
|
127
|
-
read, write
|
|
128
|
-
) as session:
|
|
129
|
-
await session.initialize()
|
|
130
|
-
|
|
131
|
-
result = await session.call_tool("playbook_list", {})
|
|
132
|
-
assert result.isError is False
|
|
133
|
-
payload = json.loads(result.content[0].text)
|
|
134
|
-
assert payload["success"] is False or payload # ExecutionResult or error dict
|
|
135
|
-
|
|
136
|
-
result = await session.call_tool("playbook_status", {"name": "nope"})
|
|
137
|
-
payload = json.loads(result.content[0].text)
|
|
138
|
-
assert payload["success"] is False
|
|
139
|
-
assert "Unknown playbook" in payload["error"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|