oclaw/runtime/operations/mcp_registry_export.py
oliver 4a23b715a2 重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。

Made-with: Cursor
2026-04-25 01:24:23 +08:00

109 lines
4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Build a reinstallable MCP JSON from SQLite and persist under ``oclaw/_local/`` for migration."""
from __future__ import annotations
import json
import os
import warnings
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from oclaw.platform.config.paths import PROJECT_ROOT
from oclaw.platform.persistence.sqlite_store import SqliteStore
from oclaw.runtime.tools.mcp.registry import McpRegistry
_EXPORT_FILENAME = "mcp_registry_migrated.json"
_LOCAL_DIR = (PROJECT_ROOT / "oclaw" / "_local").resolve()
_EXPORT_PATH = (_LOCAL_DIR / _EXPORT_FILENAME).resolve()
def mcp_migrated_json_path() -> Path:
return _EXPORT_PATH
def _collapse_entry_arg(arg: str, root: Path) -> str:
"""Rewrite absolute / repo-relative paths to ``__REPO_ROOT__/...`` when they exist on disk under root.
Skips npx flags (``-y``) and tokens that are not an existing file or directory, so package names
(e.g. ``mcp-sqlite``, ``@upstash/foo``) are not mistaken for paths.
"""
t = (arg or "").strip()
if not t or t.startswith("__REPO_ROOT__") or t.startswith("-"):
return str(arg)
r = root.resolve()
a = Path(os.path.expanduser(t))
if not a.is_absolute():
a = (r / t).resolve()
else:
a = a.resolve()
if not a.exists():
return str(arg)
try:
rel = a.relative_to(r)
except ValueError:
return str(arg)
return "__REPO_ROOT__/" + rel.as_posix()
def _collapse_entry_args(args: list[str], root: Path) -> list[str]:
return [_collapse_entry_arg(x, root) for x in args]
def build_mcp_install_export_document(store: SqliteStore) -> dict[str, Any]:
rows = McpRegistry(store).list_servers(enabled_only=False)
root = PROJECT_ROOT.resolve()
servers: list[dict[str, Any]] = []
for r in rows:
if not isinstance(r, dict):
continue
raw_args = r.get("entry_args")
if isinstance(raw_args, list):
entry_args = _collapse_entry_args([str(x) for x in raw_args if str(x).strip()], root)
else:
entry_args = []
es = r.get("env_schema")
env_schema = es if isinstance(es, dict) else {}
perms = r.get("required_permissions")
if not isinstance(perms, list):
perms = []
servers.append(
{
"server_id": str(r.get("server_id") or ""),
"source_type": str(r.get("source_type") or ""),
"source_ref": str(r.get("source_ref") or ""),
"version": str(r.get("version") or ""),
"entry_command": str(r.get("entry_command") or ""),
"entry_args": entry_args,
"env_schema": env_schema,
"required_permissions": [str(x) for x in perms if str(x).strip()],
"risk_level": str(r.get("risk_level") or "high"),
"enabled": bool(r.get("enabled")),
"timeout_s": float(r.get("timeout_s") or 30.0),
"dry_run": False,
}
)
return {
"_comment": "管理台导出 / 新安装后自动落盘。可粘到「Install from JSON」;`__REPO_ROOT__/` 在管理台与 seed 中展开为仓库根。已存在路径会写成占位符,其余参数保持原样。",
"exported_at": datetime.now(timezone.utc).isoformat(),
"servers": servers,
}
def persist_mcp_migrated_file(store: SqliteStore) -> str | None:
"""Write ``oclaw/_local/mcp_registry_migrated.json``. Fails open (warn only). Returns path or None."""
try:
mcp_migrated_json_path().parent.mkdir(parents=True, exist_ok=True)
doc = build_mcp_install_export_document(store)
path = mcp_migrated_json_path()
path.write_text(json.dumps(doc, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return str(path)
except OSError as exc:
warnings.warn(f"mcp_registry_migrated write failed: {exc}", RuntimeWarning, stacklevel=1)
return None
__all__ = [
"build_mcp_install_export_document",
"mcp_migrated_json_path",
"persist_mcp_migrated_file",
]