feat: print remote config on MCP service start
Make scripts/mcp_service.sh start create the token env file when missing, then start the user service and print the remote MCP URL, Authorization header, and client JSON. Document the one-command startup flow and cover automatic token creation with tests.
This commit is contained in:
+2
-1
@@ -41,8 +41,9 @@ OBSIDIAN_MCP_EXPOSE_PATHS=false
|
||||
# Run locally:
|
||||
# OBSIDIAN_MCP_TOKEN='<long-random-token>' obsidian-wiki-mcp --transport streamable-http
|
||||
# Or manage a user-level service:
|
||||
# bash scripts/mcp_service.sh new-token
|
||||
# bash scripts/mcp_service.sh start
|
||||
# Rotate token later with:
|
||||
# bash scripts/mcp_service.sh new-token
|
||||
OBSIDIAN_MCP_TOKEN=
|
||||
OBSIDIAN_MCP_HOST=127.0.0.1
|
||||
OBSIDIAN_MCP_PORT=8765
|
||||
|
||||
@@ -364,11 +364,22 @@ Client configuration example:
|
||||
For long-running access from another machine, use the managed user service:
|
||||
|
||||
```bash
|
||||
bash scripts/mcp_service.sh new-token
|
||||
bash scripts/mcp_service.sh start
|
||||
```
|
||||
|
||||
`mcp_service.sh new-token` writes `.runtime/obsidian-wiki-mcp.env` with `0600` permissions and prints the remote URL, `Authorization` header, and client JSON. `mcp_service.sh start` writes `~/.config/systemd/user/obsidian-wiki-mcp.service` and starts it. The service defaults to `0.0.0.0:8765/mcp` because it is intended for remote clients, but it still requires the bearer token. Prefer SSH tunnel, Tailscale, WireGuard, or another trusted network boundary instead of directly exposing the port to the public internet.
|
||||
`mcp_service.sh start` creates `.runtime/obsidian-wiki-mcp.env` with `0600` permissions if it does not exist, writes `~/.config/systemd/user/obsidian-wiki-mcp.service`, starts the service, and prints the remote URL, `Authorization` header, and client JSON to copy into another agent. The service defaults to `0.0.0.0:8765/mcp` because it is intended for remote clients, but it still requires the bearer token. Prefer SSH tunnel, Tailscale, WireGuard, or another trusted network boundary instead of directly exposing the port to the public internet.
|
||||
|
||||
Customize the first startup if needed:
|
||||
|
||||
```bash
|
||||
bash scripts/mcp_service.sh start --host 0.0.0.0 --port 9876 --path /wiki-mcp
|
||||
```
|
||||
|
||||
Rotate the token later with:
|
||||
|
||||
```bash
|
||||
bash scripts/mcp_service.sh new-token
|
||||
```
|
||||
|
||||
Useful service commands:
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ def write_token_env_file(
|
||||
|
||||
def read_service_env_file(env_path: Path) -> dict[str, str]:
|
||||
if not env_path.exists():
|
||||
raise RuntimeError(f"Missing MCP service env file: {env_path}. Run scripts/mcp_service.sh new-token first.")
|
||||
raise RuntimeError(f"Missing MCP service env file: {env_path}. Run scripts/mcp_service.sh start to create it.")
|
||||
|
||||
values: dict[str, str] = {}
|
||||
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
||||
@@ -262,9 +262,19 @@ def new_token(
|
||||
def start_service(
|
||||
*,
|
||||
project_root: Path,
|
||||
host: str = DEFAULT_HOST,
|
||||
port: int = DEFAULT_PORT,
|
||||
path: str = DEFAULT_PATH,
|
||||
token_bytes: int = DEFAULT_TOKEN_BYTES,
|
||||
) -> str:
|
||||
paths = resolve_service_paths(project_root)
|
||||
token, host, port, path = saved_service_config(project_root)
|
||||
try:
|
||||
token, host, port, path = saved_service_config(project_root)
|
||||
except RuntimeError:
|
||||
if paths.env_path.exists():
|
||||
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)
|
||||
|
||||
run_systemctl(["daemon-reload"])
|
||||
@@ -307,8 +317,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
new_token_parser.add_argument("--path", default=DEFAULT_PATH)
|
||||
new_token_parser.add_argument("--token-bytes", default=DEFAULT_TOKEN_BYTES, type=int)
|
||||
|
||||
start_parser = subparsers.add_parser("start", help="Install or update the user service using the saved token.")
|
||||
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("--host", default=DEFAULT_HOST)
|
||||
start_parser.add_argument("--port", default=DEFAULT_PORT, type=int)
|
||||
start_parser.add_argument("--path", default=DEFAULT_PATH)
|
||||
start_parser.add_argument("--token-bytes", default=DEFAULT_TOKEN_BYTES, type=int)
|
||||
|
||||
subparsers.add_parser("stop", help="Stop the user service without deleting service files.")
|
||||
|
||||
@@ -335,7 +349,16 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
)
|
||||
return 0
|
||||
if args.command == "start":
|
||||
print(start_service(project_root=args.project_root), end="")
|
||||
print(
|
||||
start_service(
|
||||
project_root=args.project_root,
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
path=args.path,
|
||||
token_bytes=args.token_bytes,
|
||||
),
|
||||
end="",
|
||||
)
|
||||
return 0
|
||||
if args.command == "stop":
|
||||
stop_service()
|
||||
|
||||
@@ -152,6 +152,36 @@ class McpServiceConfigTests(unittest.TestCase):
|
||||
]
|
||||
)
|
||||
|
||||
def test_start_service_generates_token_when_missing_and_prints_config(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
project_root = Path(tmp) / "project"
|
||||
project_root.mkdir()
|
||||
config_home = Path(tmp) / "config"
|
||||
with (
|
||||
patch.dict("os.environ", {"XDG_CONFIG_HOME": str(config_home)}),
|
||||
patch.object(manage_mcp_service, "generate_token", return_value="created-token"),
|
||||
patch.object(manage_mcp_service, "detect_remote_hosts", return_value=["10.0.0.4"]),
|
||||
patch.object(manage_mcp_service, "find_uv_path", return_value=Path("/home/nac/.local/bin/uv")),
|
||||
patch.object(manage_mcp_service, "run_systemctl") as run_systemctl,
|
||||
):
|
||||
output = manage_mcp_service.start_service(project_root=project_root, host="0.0.0.0", port=9876, path="/wiki-mcp")
|
||||
|
||||
env_path = project_root / ".runtime" / "obsidian-wiki-mcp.env"
|
||||
env_content = env_path.read_text(encoding="utf-8")
|
||||
self.assertIn("OBSIDIAN_MCP_TOKEN=created-token\n", env_content)
|
||||
self.assertIn("OBSIDIAN_MCP_PORT=9876\n", env_content)
|
||||
self.assertIn("OBSIDIAN_MCP_PATH=/wiki-mcp\n", env_content)
|
||||
self.assertIn("http://10.0.0.4:9876/wiki-mcp", output)
|
||||
self.assertIn("Authorization: Bearer created-token", output)
|
||||
self.assertIn('"type": "streamable-http"', output)
|
||||
run_systemctl.assert_has_calls(
|
||||
[
|
||||
call(["daemon-reload"]),
|
||||
call(["enable", manage_mcp_service.SERVICE_NAME]),
|
||||
call(["restart", manage_mcp_service.SERVICE_NAME]),
|
||||
]
|
||||
)
|
||||
|
||||
def test_uninstall_service_removes_only_managed_files(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
project_root = Path(tmp) / "project"
|
||||
|
||||
Reference in New Issue
Block a user