320 lines
13 KiB
Python
320 lines
13 KiB
Python
"""市場情報 MCP 完整度稽核 preview。
|
||
|
||
本模組只彙整既有 MCP readiness、tool contract、deploy preflight、activation
|
||
runbook 與 fetch gate;不呼叫 MCP server、不執行 docker/SSH、不連 DB、不寫入。
|
||
"""
|
||
|
||
from services.market_intel.mcp_activation_runbook import (
|
||
build_mcp_activation_runbook_preview,
|
||
)
|
||
from services.market_intel.mcp_contract import build_mcp_tool_contract_preview
|
||
from services.market_intel.mcp_deploy_preflight import build_mcp_deploy_preflight_plan
|
||
from services.market_intel.mcp_fetch_gate import build_mcp_fetch_gate_preview
|
||
from services.market_intel.mcp_readiness import (
|
||
EXPECTED_EXTERNAL_SERVERS,
|
||
build_mcp_readiness_plan,
|
||
)
|
||
|
||
|
||
def _blocked_reasons(gates):
|
||
return [item["key"] for item in gates if not item["passed"]]
|
||
|
||
|
||
def _none_executed(payload, keys):
|
||
return all(not payload.get(key) for key in keys)
|
||
|
||
|
||
def build_mcp_completion_audit_preview(
|
||
*,
|
||
runtime_status,
|
||
readiness=None,
|
||
tool_contract=None,
|
||
deploy_preflight=None,
|
||
activation_runbook=None,
|
||
fetch_gate=None,
|
||
):
|
||
"""建立 MCP 外部/內部完整度稽核;預設全程 read-only planned preview。"""
|
||
readiness = readiness or build_mcp_readiness_plan(execute_requested=False)
|
||
tool_contract = tool_contract or build_mcp_tool_contract_preview()
|
||
deploy_preflight = deploy_preflight or build_mcp_deploy_preflight_plan()
|
||
activation_runbook = activation_runbook or build_mcp_activation_runbook_preview(
|
||
preflight=deploy_preflight,
|
||
readiness=readiness,
|
||
)
|
||
fetch_gate = fetch_gate or build_mcp_fetch_gate_preview(
|
||
runtime_status,
|
||
fetch_requested=False,
|
||
execute_readiness=False,
|
||
readiness=readiness,
|
||
)
|
||
|
||
external_design_checks = {
|
||
"expected_servers_declared": set(EXPECTED_EXTERNAL_SERVERS)
|
||
<= {item.get("server") for item in readiness.get("server_statuses", [])},
|
||
"compose_file_present": bool(
|
||
deploy_preflight.get("checks", {}).get("compose_file_present")
|
||
),
|
||
"expected_services_declared": bool(
|
||
deploy_preflight.get("checks", {}).get("all_expected_services_declared")
|
||
),
|
||
"expected_containers_declared": bool(
|
||
deploy_preflight.get("checks", {}).get("all_expected_containers_declared")
|
||
),
|
||
"localhost_ports_only": bool(
|
||
deploy_preflight.get("checks", {}).get(
|
||
"all_public_mcp_ports_localhost_only"
|
||
)
|
||
),
|
||
"readonly_postgres_design": bool(
|
||
deploy_preflight.get("checks", {}).get("postgres_readonly_role_required")
|
||
and deploy_preflight.get("checks", {}).get(
|
||
"postgres_allowed_tables_limited"
|
||
)
|
||
),
|
||
"readonly_filesystem_mounts": bool(
|
||
deploy_preflight.get("checks", {}).get("filesystem_mounts_read_only")
|
||
),
|
||
}
|
||
external_design_complete = all(external_design_checks.values())
|
||
external_runtime_complete = bool(readiness.get("external_mcp_complete"))
|
||
internal_contract_complete = bool(tool_contract.get("contract_ready"))
|
||
internal_runtime_complete = bool(readiness.get("internal_mcp_complete"))
|
||
market_intel_mcp_integrated = bool(readiness.get("market_intel_mcp_integrated"))
|
||
activation_runbook_present = bool(
|
||
activation_runbook.get("mode") == "mcp_activation_runbook_preview"
|
||
and activation_runbook.get("stages")
|
||
)
|
||
fetch_gate_present = bool(fetch_gate.get("mode") == "mcp_fetch_gate_planned")
|
||
preview_side_effect_free = bool(
|
||
_none_executed(
|
||
readiness,
|
||
(
|
||
"database_session_created",
|
||
"database_write_executed",
|
||
"database_commit_executed",
|
||
"external_network_executed",
|
||
"scheduler_attached",
|
||
"writes_executed",
|
||
"would_write_database",
|
||
),
|
||
)
|
||
and _none_executed(
|
||
tool_contract,
|
||
(
|
||
"database_session_created",
|
||
"database_write_executed",
|
||
"database_commit_executed",
|
||
"external_network_executed",
|
||
"scheduler_attached",
|
||
"writes_executed",
|
||
"would_write_database",
|
||
),
|
||
)
|
||
and _none_executed(
|
||
deploy_preflight,
|
||
(
|
||
"deployment_actions_executed",
|
||
"docker_command_executed",
|
||
"ssh_command_executed",
|
||
"database_session_created",
|
||
"database_write_executed",
|
||
"database_commit_executed",
|
||
"external_network_executed",
|
||
"scheduler_attached",
|
||
"writes_executed",
|
||
"would_write_database",
|
||
),
|
||
)
|
||
and _none_executed(
|
||
activation_runbook,
|
||
(
|
||
"deployment_actions_executed",
|
||
"docker_command_executed",
|
||
"ssh_command_executed",
|
||
"database_session_created",
|
||
"database_write_executed",
|
||
"database_commit_executed",
|
||
"external_network_executed",
|
||
"scheduler_attached",
|
||
"writes_executed",
|
||
"would_write_database",
|
||
),
|
||
)
|
||
and _none_executed(
|
||
fetch_gate,
|
||
(
|
||
"network_request_allowed",
|
||
"database_session_created",
|
||
"database_write_executed",
|
||
"database_commit_executed",
|
||
"external_network_executed",
|
||
"scheduler_attached",
|
||
"writes_executed",
|
||
"would_write_database",
|
||
),
|
||
)
|
||
)
|
||
|
||
gates = [
|
||
{
|
||
"key": "external_mcp_design_complete",
|
||
"passed": external_design_complete,
|
||
"label": "外部 MCP compose、localhost port、唯讀邊界與 server 清單已定義",
|
||
},
|
||
{
|
||
"key": "external_mcp_runtime_complete",
|
||
"passed": external_runtime_complete,
|
||
"label": "外部 MCP health 已執行且 router 已啟用",
|
||
},
|
||
{
|
||
"key": "internal_mcp_contract_complete",
|
||
"passed": internal_contract_complete,
|
||
"label": "market_intel 內部 MCP tool contract 已被 router 白名單覆蓋",
|
||
},
|
||
{
|
||
"key": "internal_mcp_runtime_complete",
|
||
"passed": internal_runtime_complete,
|
||
"label": "mcp_calls telemetry / read-only 查詢鏈路已在執行態確認",
|
||
},
|
||
{
|
||
"key": "market_intel_mcp_integrated",
|
||
"passed": market_intel_mcp_integrated,
|
||
"label": "市場情報呼叫端已納入 MCP registry",
|
||
},
|
||
{
|
||
"key": "activation_runbook_present",
|
||
"passed": activation_runbook_present,
|
||
"label": "外部 MCP 啟用順序、health gate 與 fallback 已建立",
|
||
},
|
||
{
|
||
"key": "manual_fetch_gate_present",
|
||
"passed": fetch_gate_present,
|
||
"label": "人工 fetch 安全閘門已建立且預設關閉",
|
||
},
|
||
{
|
||
"key": "preview_side_effect_free",
|
||
"passed": preview_side_effect_free,
|
||
"label": "本 API 不啟動 MCP、不連 DB、不寫入、不抓外站",
|
||
},
|
||
]
|
||
passed_gate_count = sum(1 for item in gates if item["passed"])
|
||
|
||
return {
|
||
"mode": "mcp_completion_audit_preview",
|
||
"audit_ready_for_operator_review": True,
|
||
"audit_preview_safe": preview_side_effect_free,
|
||
"overall_mcp_completion_percent": round(passed_gate_count / len(gates) * 100),
|
||
"external_mcp_complete": external_runtime_complete,
|
||
"external_mcp_design_complete": external_design_complete,
|
||
"external_mcp_runtime_complete": external_runtime_complete,
|
||
"internal_mcp_complete": internal_runtime_complete,
|
||
"internal_mcp_contract_complete": internal_contract_complete,
|
||
"internal_mcp_runtime_complete": internal_runtime_complete,
|
||
"market_intel_mcp_integrated": market_intel_mcp_integrated,
|
||
"ready_for_external_mcp_activation": bool(
|
||
activation_runbook.get("ready_for_operator_activation")
|
||
),
|
||
"ready_for_internal_mcp_use": bool(
|
||
internal_contract_complete and market_intel_mcp_integrated
|
||
),
|
||
"ready_for_manual_fetch": bool(fetch_gate.get("manual_fetch_gate_open")),
|
||
"gates": gates,
|
||
"blocked_reasons": _blocked_reasons(gates),
|
||
"external_mcp_summary": {
|
||
"expected_servers": list(EXPECTED_EXTERNAL_SERVERS),
|
||
"configured_servers": [
|
||
item["server"]
|
||
for item in readiness.get("server_statuses", [])
|
||
if item.get("configured")
|
||
],
|
||
"health_checked": bool(readiness.get("execute_requested")),
|
||
"router_enabled": bool(readiness.get("router_enabled")),
|
||
"design_checks": external_design_checks,
|
||
"runtime_blocked_reasons": readiness.get("blocked_reasons", []),
|
||
},
|
||
"internal_mcp_summary": {
|
||
"caller": tool_contract.get("caller"),
|
||
"tool_count": int(tool_contract.get("tool_count") or 0),
|
||
"expected_tool_names": tool_contract.get("expected_tool_names", []),
|
||
"contract_ready": internal_contract_complete,
|
||
"telemetry_mode": readiness.get("telemetry", {}).get("mode"),
|
||
"telemetry_table_exists": bool(
|
||
readiness.get("telemetry", {}).get("table_exists")
|
||
),
|
||
"read_only_query_executed": bool(
|
||
readiness.get("telemetry", {}).get("read_only_query_executed")
|
||
),
|
||
},
|
||
"activation_summary": {
|
||
"mode": activation_runbook.get("mode"),
|
||
"ready_for_operator_activation": bool(
|
||
activation_runbook.get("ready_for_operator_activation")
|
||
),
|
||
"stage_count": len(activation_runbook.get("stages", [])),
|
||
"blocked_reasons": activation_runbook.get("blocked_reasons", []),
|
||
"fallback_count": len(activation_runbook.get("fallback_plan", [])),
|
||
},
|
||
"fetch_gate_summary": {
|
||
"mode": fetch_gate.get("mode"),
|
||
"manual_fetch_gate_open": bool(fetch_gate.get("manual_fetch_gate_open")),
|
||
"network_request_allowed": bool(fetch_gate.get("network_request_allowed")),
|
||
"blocked_reasons": fetch_gate.get("blocked_reasons", []),
|
||
},
|
||
"next_operator_steps": [
|
||
"補齊外部 MCP 必要 env 後,再由操作員依 runbook 啟動 docker-compose.mcp.yml",
|
||
"四個 localhost health endpoint 全部 200 後,才允許開 MCP_ROUTER_ENABLED",
|
||
"router 開啟後以 execute=true 跑 read-only MCP readiness smoke",
|
||
"人工 fetch 仍需先通過 MCP fetch gate,且不得寫 DB 或掛 scheduler",
|
||
],
|
||
"mcp_readiness": readiness,
|
||
"mcp_tool_contract": tool_contract,
|
||
"mcp_deploy_preflight": deploy_preflight,
|
||
"mcp_activation_runbook": activation_runbook,
|
||
"mcp_fetch_gate": fetch_gate,
|
||
"api_executes_health_check": False,
|
||
"api_executes_docker": False,
|
||
"api_executes_ssh": False,
|
||
"api_opens_database_connection": False,
|
||
"api_writes_database": False,
|
||
"api_uses_external_network": False,
|
||
"database_session_created": False,
|
||
"database_write_executed": False,
|
||
"database_commit_executed": False,
|
||
"external_network_executed": False,
|
||
"scheduler_attached": False,
|
||
"writes_executed": False,
|
||
"would_write_database": False,
|
||
}
|
||
|
||
|
||
def build_mcp_completion_audit_for_runtime(*, runtime_status, phase):
|
||
"""以 service runtime 狀態組裝完整度稽核,並在子 payload 標記 phase。"""
|
||
readiness = build_mcp_readiness_plan(execute_requested=False)
|
||
readiness["phase"] = phase
|
||
tool_contract = build_mcp_tool_contract_preview()
|
||
tool_contract["phase"] = phase
|
||
deploy_preflight = build_mcp_deploy_preflight_plan()
|
||
deploy_preflight["phase"] = phase
|
||
activation_runbook = build_mcp_activation_runbook_preview(
|
||
preflight=deploy_preflight,
|
||
readiness=readiness,
|
||
)
|
||
activation_runbook["phase"] = phase
|
||
fetch_gate = build_mcp_fetch_gate_preview(
|
||
runtime_status,
|
||
fetch_requested=False,
|
||
execute_readiness=False,
|
||
readiness=readiness,
|
||
)
|
||
fetch_gate["phase"] = phase
|
||
audit = build_mcp_completion_audit_preview(
|
||
runtime_status=runtime_status,
|
||
readiness=readiness,
|
||
tool_contract=tool_contract,
|
||
deploy_preflight=deploy_preflight,
|
||
activation_runbook=activation_runbook,
|
||
fetch_gate=fetch_gate,
|
||
)
|
||
audit["phase"] = phase
|
||
return audit
|