"""市場情報 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, ) from services.market_intel.ai_controlled_service_compat import ( compatibility_flag, ) 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": f"{compatibility_flag('fetch')}_gate_present", "passed": fetch_gate_present, "label": "AI-controlled 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 ), f"ready_for_{compatibility_flag('fetch')}": bool( fetch_gate.get(compatibility_flag("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"), compatibility_flag("fetch_gate_open"): bool( fetch_gate.get(compatibility_flag("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", "AI-controlled 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