packmate/mcp/tests/deployment.py
2026-10-23 11:42:44 +03:00

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())