Files
nocobase-mcp/scripts/agent_installer/flow.py
T

108 lines
3.6 KiB
Python

from __future__ import annotations
from .auth import connect_with_personal_key, normalize_api_base_url
from .common import (
DEFAULT_ENV_NAME,
ENV_NAME_PATTERN,
SERVER_NAME,
Any,
Callable,
InstallerError,
Path,
Runner,
Which,
_project_root,
shutil,
subprocess,
sys,
)
from .diagnostics import doctor_project
from .project import inspect_project, prepare_project, render_client_config
def install_project(
project_root: str | Path | None,
api_base_url: str,
*,
env_name: str = DEFAULT_ENV_NAME,
enable_writes: bool = False,
runner: Runner = subprocess.run,
which: Which = shutil.which,
python_version: tuple[int, int, int] | None = None,
stdin_isatty: Callable[[], bool] = sys.stdin.isatty,
) -> dict[str, Any]:
"""Prepare, authenticate, diagnose, and describe one local installation.
The returned client configuration is intentionally not written here: the
installation Agent must merge it into its own client-specific format after
showing the destination and creating a recoverable backup.
"""
root = _project_root(project_root)
normalized_url = normalize_api_base_url(api_base_url)
if not ENV_NAME_PATTERN.fullmatch(env_name):
raise InstallerError(
"Environment name must start with a letter or digit and contain only letters, digits, dot, underscore, or dash"
)
if not stdin_isatty():
raise InstallerError(
"Secure API Key entry requires a user-controlled local terminal. "
"Do not paste the Key into Agent chat or pass it with --access-token."
)
inspection = inspect_project(root, which=which, python_version=python_version)
failed_prerequisites = [check["detail"] for check in inspection["checks"] if check["required"] and not check["ok"]]
if failed_prerequisites:
raise InstallerError("Installation prerequisites are not ready: " + "; ".join(failed_prerequisites))
prepared = prepare_project(
root,
runner=runner,
which=which,
python_version=python_version,
)
authentication = connect_with_personal_key(
root,
normalized_url,
env_name=env_name,
runner=runner,
which=which,
stdin_isatty=stdin_isatty,
)
diagnosis = doctor_project(
root,
runner=runner,
which=which,
python_version=python_version,
)
client_config = render_client_config(root, enable_writes=enable_writes, which=which)
local_ready = bool(diagnosis["ok"])
return {
"ok": local_ready,
"status": "ready-for-client-registration" if local_ready else "local-checks-failed",
"projectRoot": str(root),
"mode": "project-writes" if enable_writes else "read-only",
"prepared": {
"environmentPath": prepared["environmentPath"],
"dependencyProfile": prepared["dependencies"],
"configCreated": prepared["configCreated"],
},
"authentication": authentication,
"checks": diagnosis["checks"],
"clientConfig": client_config,
"clientRegistration": {
"required": True,
"serverName": SERVER_NAME,
"backupRequired": True,
"action": "merge the generated server entry, reload the client, then call crm_health",
},
"remainingActions": [
"Merge clientConfig into the current MCP client without replacing other servers.",
"Reload or restart the MCP client.",
"Call crm_health and require ok=true before reporting installation complete.",
],
}
__all__ = ["install_project"]