fix: make remote MCP service start reliably
Use the Python module entrypoint in the user service, prepare the MCP optional runtime before restart, and serve the configured vault path instead of the repo root. Verify binary asset resources use an octet-stream MIME type and cover service vault resolution in tests.
This commit is contained in:
@@ -10,10 +10,17 @@ import shlex
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from obsidian_wiki.config import resolve_vault_path
|
||||
|
||||
|
||||
SERVICE_NAME = "obsidian-wiki-mcp.service"
|
||||
ENV_RELATIVE_PATH = Path(".runtime") / "obsidian-wiki-mcp.env"
|
||||
@@ -54,9 +61,19 @@ def resolve_service_paths(project_root: Path, config_home: Path | None = None) -
|
||||
return ServicePaths(project_root=root, unit_path=unit_path, env_path=root / ENV_RELATIVE_PATH)
|
||||
|
||||
|
||||
def resolve_vault_for_service(project_root: Path, vault_path: Path | str | None = None) -> Path:
|
||||
if vault_path is not None:
|
||||
return Path(vault_path).expanduser().resolve()
|
||||
configured = resolve_vault_path(None, start=project_root)
|
||||
if configured:
|
||||
return Path(configured).expanduser().resolve()
|
||||
return project_root.expanduser().resolve()
|
||||
|
||||
|
||||
def render_service_unit(
|
||||
*,
|
||||
project_root: Path,
|
||||
vault_path: Path,
|
||||
env_file: Path,
|
||||
host: str = DEFAULT_HOST,
|
||||
port: int = DEFAULT_PORT,
|
||||
@@ -70,9 +87,11 @@ def render_service_unit(
|
||||
systemd_quote(resolved_uv_path),
|
||||
"run",
|
||||
"--no-sync",
|
||||
"obsidian-wiki-mcp",
|
||||
"python",
|
||||
"-m",
|
||||
"obsidian_wiki.mcp_server",
|
||||
"--vault",
|
||||
systemd_quote(project_root),
|
||||
systemd_quote(vault_path),
|
||||
"--transport",
|
||||
"streamable-http",
|
||||
"--host",
|
||||
@@ -103,6 +122,31 @@ def render_service_unit(
|
||||
)
|
||||
|
||||
|
||||
def prepare_runtime(
|
||||
project_root: Path,
|
||||
*,
|
||||
uv_path: Path | None = None,
|
||||
uv_cache_dir: str = DEFAULT_UV_CACHE_DIR,
|
||||
) -> None:
|
||||
resolved_uv_path = uv_path or find_uv_path()
|
||||
env = {**os.environ, "UV_CACHE_DIR": uv_cache_dir}
|
||||
subprocess.run(
|
||||
[
|
||||
str(resolved_uv_path),
|
||||
"run",
|
||||
"--extra",
|
||||
"mcp",
|
||||
"python",
|
||||
"-c",
|
||||
"import mcp.server.fastmcp",
|
||||
],
|
||||
cwd=project_root,
|
||||
env=env,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def write_token_env_file(
|
||||
env_path: Path,
|
||||
token: str,
|
||||
@@ -152,17 +196,21 @@ def saved_service_config(project_root: Path) -> tuple[str, str, int, str]:
|
||||
def write_service_unit(
|
||||
paths: ServicePaths,
|
||||
*,
|
||||
vault_path: Path,
|
||||
host: str = DEFAULT_HOST,
|
||||
port: int = DEFAULT_PORT,
|
||||
path: str = DEFAULT_PATH,
|
||||
uv_path: Path | None = None,
|
||||
) -> None:
|
||||
paths.unit_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
unit = render_service_unit(
|
||||
project_root=paths.project_root,
|
||||
vault_path=vault_path,
|
||||
env_file=paths.env_path,
|
||||
host=host,
|
||||
port=port,
|
||||
path=path,
|
||||
uv_path=uv_path,
|
||||
)
|
||||
paths.unit_path.write_text(unit, encoding="utf-8")
|
||||
|
||||
@@ -262,6 +310,7 @@ def new_token(
|
||||
def start_service(
|
||||
*,
|
||||
project_root: Path,
|
||||
vault_path: Path | str | None = None,
|
||||
host: str = DEFAULT_HOST,
|
||||
port: int = DEFAULT_PORT,
|
||||
path: str = DEFAULT_PATH,
|
||||
@@ -275,7 +324,10 @@ def start_service(
|
||||
raise
|
||||
token = generate_token(token_bytes)
|
||||
write_token_env_file(paths.env_path, token, host=host, port=port, path=path)
|
||||
write_service_unit(paths, host=host, port=port, path=path)
|
||||
uv_path = find_uv_path()
|
||||
prepare_runtime(paths.project_root, uv_path=uv_path)
|
||||
resolved_vault = resolve_vault_for_service(paths.project_root, vault_path=vault_path)
|
||||
write_service_unit(paths, vault_path=resolved_vault, host=host, port=port, path=path, uv_path=uv_path)
|
||||
|
||||
run_systemctl(["daemon-reload"])
|
||||
run_systemctl(["enable", SERVICE_NAME])
|
||||
@@ -319,6 +371,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
|
||||
start_parser = subparsers.add_parser("start", help="Install or update the user service and print remote MCP config.")
|
||||
start_parser.add_argument("--project-root", default=Path(__file__).resolve().parent.parent, type=Path)
|
||||
start_parser.add_argument("--vault", dest="vault_path", type=Path, help="Vault path to serve. Defaults to OBSIDIAN_VAULT_PATH config.")
|
||||
start_parser.add_argument("--host", default=DEFAULT_HOST)
|
||||
start_parser.add_argument("--port", default=DEFAULT_PORT, type=int)
|
||||
start_parser.add_argument("--path", default=DEFAULT_PATH)
|
||||
@@ -352,6 +405,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
print(
|
||||
start_service(
|
||||
project_root=args.project_root,
|
||||
vault_path=args.vault_path,
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
path=args.path,
|
||||
|
||||
Reference in New Issue
Block a user