mirror of
https://github.com/Av0idD4rk/packmate.git
synced 2026-10-23 14:31:01 +00:00
62 lines
3.5 KiB
Python
62 lines
3.5 KiB
Python
"""Run inside MCP container of a disposable production-Compose deployment in VIEW mode."""
|
|
import asyncio
|
|
import json
|
|
import os
|
|
|
|
import httpx
|
|
from mcp import ClientSession
|
|
from mcp.client.streamable_http import streamable_http_client
|
|
|
|
|
|
async def main():
|
|
backend = os.environ["PACKMATE_BACKEND_URL"]
|
|
token = os.environ["PACKMATE_MCP_TOKEN"]
|
|
endpoint = "http://127.0.0.1:" + os.getenv("PACKMATE_MCP_PORT", "65002")
|
|
auth = (os.environ["PACKMATE_WEB_LOGIN"], os.environ["PACKMATE_WEB_PASSWORD"])
|
|
async with httpx.AsyncClient(auth=auth, timeout=10) as rest:
|
|
overview = None
|
|
for _ in range(60):
|
|
try:
|
|
overview = await rest.get(backend + "/api/analysis/overview")
|
|
if overview.status_code == 200:
|
|
break
|
|
except httpx.HTTPError:
|
|
pass
|
|
await asyncio.sleep(1)
|
|
assert overview is not None and overview.status_code == 200 and overview.json()["mode"] == "VIEW", "use a disposable VIEW deployment"
|
|
page = await rest.get(backend + "/")
|
|
assert page.status_code == 200 and "text/html" in page.headers["content-type"]
|
|
async with httpx.AsyncClient(headers={"Authorization": "Bearer " + token}, timeout=15) as transport:
|
|
assert (await transport.get(endpoint + "/healthz")).json()["enabled"]
|
|
assert (await transport.post(endpoint + "/mcp", headers={"Authorization": "Bearer wrong"}, json={})).status_code == 401
|
|
assert (await transport.post(endpoint + "/mcp", headers={"Host": "invalid.test"}, json={})).status_code == 421
|
|
assert (await transport.post(endpoint + "/mcp", headers={"Origin": "https://invalid.test"}, json={})).status_code == 403
|
|
async with streamable_http_client(endpoint + "/mcp", http_client=transport) as (read, write, _):
|
|
async with ClientSession(read, write) as client:
|
|
await client.initialize()
|
|
tools = (await client.list_tools()).tools
|
|
assert len(tools) == 22 and all(t.outputSchema is not None for t in tools)
|
|
async def call(name, args=None):
|
|
result = await client.call_tool(name, args or {})
|
|
assert not result.isError, (name, result.content)
|
|
return result.structuredContent
|
|
assert (await call("get_overview"))["mode"] == "VIEW"
|
|
valid = {"name": "deployment regex", "match": {"value": r"FLAG\{[^}]+\}"}}
|
|
assert (await call("validate_pattern", {"settings": valid}))["valid"]
|
|
bad = await client.call_tool("validate_pattern", {"settings": {"name": "unsupported byte escape", "match": {"value": r"\C"}}})
|
|
assert bad.isError and "RE2J" in str(bad.content), bad
|
|
service = await call("create_service", {"port": 18083, "settings": {"name": "Deployment smoke"}})
|
|
pattern = await call("create_pattern", {"settings": valid | {"service_port": 18083}})
|
|
assert pattern["pattern"]["regexEngine"] == "RE2J"
|
|
if pattern["created"]:
|
|
await call("delete_pattern", {"pattern_id": pattern["pattern"]["id"]})
|
|
if service["created"]:
|
|
await call("delete_service", {"port": 18083})
|
|
print(json.dumps({"production_compose": True, "web_html": True, "mcp_tools": 22,
|
|
"real_sdk": True, "shared_ui_writes": True, "both_regex_engines_validated": True,
|
|
"invalid_token_host_origin_rejected": True}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|