diff --git a/10-systems/applications/gnexus-book.md b/10-systems/applications/gnexus-book.md index fbd0a50..799a8cf 100644 --- a/10-systems/applications/gnexus-book.md +++ b/10-systems/applications/gnexus-book.md @@ -19,6 +19,7 @@ ## Runtime Components - Backend: FastAPI under `server/`. +- Agent interface: MCP server under `server/app/mcp_server.py`. - UI: Vue/Vite under `ui/`. - UI dependency: `gnexus-ui-kit`. @@ -33,4 +34,6 @@ Agents should prefer structured inventory changes, validation, pending changes, and local commits through the maintenance API. +For agent-facing MCP usage rules, see [../../90-maintenance/agent-mcp-usage.md](../../90-maintenance/agent-mcp-usage.md). + No raw credentials are documented here. diff --git a/90-maintenance/agent-mcp-usage.md b/90-maintenance/agent-mcp-usage.md new file mode 100644 index 0000000..0e3ae33 --- /dev/null +++ b/90-maintenance/agent-mcp-usage.md @@ -0,0 +1,124 @@ +--- +owner: gmikcon +status: active +last_reviewed: 2026-05-11 +review_interval: 30d +confidence: high +source_of_truth: repository +--- + +# Agent MCP Usage + +This document explains what the Gnexus Book MCP server is for and how an AI agent should use it. + +## Purpose + +Gnexus Book is the canonical knowledge base for the owner's digital and server infrastructure. + +The MCP server is the agent-facing interface to that knowledge base. It lets an agent read and maintain documentation without knowing the repository layout, raw REST API, or Git workflow. + +The MCP server is valuable because it gives agents: + +- stable access to canonical Markdown documentation; +- structured YAML inventory for hosts, services, domains, networks, endpoints, projects, databases, backups, hardware, and virtual machines; +- relationship graph data across inventory records; +- freshness and validation checks; +- controlled write operations through pending changes, validation, and local Git commits. + +## Proactive Usage Rule + +Agents should not wait for the user to explicitly say "look in the knowledge base". + +If a task mentions or depends on the owner's infrastructure, the agent should consult Gnexus Book proactively before giving a confident answer or making changes. + +Use MCP proactively for questions about: + +- hosts, servers, VPS, VMs, hardware, routers, or local devices; +- IP addresses, domains, ports, routes, VPNs, proxies, nginx upstreams, or network topology; +- deployed services, web applications, smart-home components, GitBucket repositories, or project ownership; +- whether something is local, VPN-only, public, stale, undocumented, or dependent on another component; +- where a project is hosted, deployed, or documented; +- what an agent should update after discovering new infrastructure facts. + +## When Not To Use It + +Do not use MCP for unrelated general knowledge. + +Do not use MCP to store secrets. Raw passwords, access tokens, private keys, recovery codes, session cookies, and secret config values must not be written to documentation or pending changes. + +It is acceptable to document non-secret operational identifiers: + +- hostnames; +- usernames; +- internal and public IP addresses; +- domains; +- ports; +- credential references such as `owner-managed` or `ssh-key`; +- access model notes such as `auth required` or `read-only SSH key`. + +## Read Workflow + +Start broad, then narrow. + +1. Use `search_docs(query)` for natural-language lookup. +2. Use `read_doc(path)` for canonical narrative context. +3. Use `list_inventory(inventory_type)` for structured records. +4. Use `get_inventory_item(inventory_type, item_id)` when the id is known. +5. Use `get_relationships()` when dependencies, routes, upstreams, domains, or unresolved references matter. +6. Use `check_freshness()` when deciding whether documentation may be stale. +7. Use `validate_repository()` before trusting the repository state for maintenance work. + +Examples: + +- For `git.gnexus.space`, search docs for `git.gnexus.space`, then inspect `domains`, `services`, `endpoints`, and relationships. +- For `smart-home-server`, read the server and automation docs, then inspect `hosts`, `hardware`, `services`, `databases`, and `backups`. +- For a repository question, read the GitBucket catalog first and pay attention to last commit dates. + +## Write Workflow + +Documentation writes should be factual, focused, and committed. + +For non-trivial changes: + +1. Use `propose_doc_change` or `propose_inventory_item_change`. +2. Use `apply_pending_change`. +3. Use `validate_repository`. +4. Use `commit_changes`. + +For simple full-document changes, `update_doc` can create the pending change, apply it, validate it, and commit it in one operation. + +Every write should include: + +- a short `summary`; +- a clear `reason`; +- no raw secrets; +- `last_reviewed` updates only when the agent actually verified the fact. + +## Interpreting Repository Activity + +For GitBucket repositories, last commit date matters. + +Default interpretation: + +- repositories touched in 2026 are potentially active; +- repositories last touched in 2025 are dormant unless deployment evidence says otherwise; +- repositories last touched in 2024 are old/archive candidates until confirmed. + +Do not call a repository active only because it exists in GitBucket. + +## Conflict Handling + +If live observations and documentation disagree: + +- say what conflicts; +- prefer live observations for immediate operational decisions; +- update Gnexus Book with the new fact if it is relevant and safe to document; +- keep uncertainty explicit with `confidence`, notes, or discovery-observation docs. + +## Agent Prompt Guidance + +When this MCP server is listed in an agent's available tools, the agent should treat it as infrastructure memory. + +A good system prompt rule is: + +> Before answering infrastructure-specific questions or making infrastructure-related changes, consult Gnexus Book MCP proactively. Use it to verify documented facts, discover relationships, and update documentation after new facts are found. diff --git a/nginx/gnexus-book.conf b/nginx/gnexus-book.conf new file mode 100644 index 0000000..a263ee8 --- /dev/null +++ b/nginx/gnexus-book.conf @@ -0,0 +1,50 @@ +server { + listen 80; + server_name _; + + root /var/www/gnexus-book; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://127.0.0.1:8000/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /api-docs { + proxy_pass http://127.0.0.1:8000/api-docs; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /api-redoc { + proxy_pass http://127.0.0.1:8000/api-redoc; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /mcp/ { + proxy_pass http://127.0.0.1:8001/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + } +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..abdd658 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -euo pipefail + +cd "$(dirname "$0")/.." +REPO_ROOT="$(pwd)" + +export PATH="$HOME/.local/bin:$PATH" +export NVM_DIR="$HOME/.nvm" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +echo "==> Updating source..." +git pull + +echo "==> Rebuilding gnexus-ui-kit..." +cd "$HOME/gnexus-ui-kit" +git pull +npm install +npm run build + +echo "==> Rebuilding UI..." +cd "$REPO_ROOT/ui" +npm install +npm run build + +echo "==> Updating server dependencies..." +cd "$REPO_ROOT/server" +uv pip install -e ".[dev]" + +echo "==> Syncing dist to /var/www..." +sudo cp -r "$REPO_ROOT/ui/dist/"* /var/www/gnexus-book/ +sudo chown -R www-data:www-data /var/www/gnexus-book + +echo "==> Restarting services..." +systemctl --user daemon-reload +systemctl --user restart gnexus-book-backend.service + +echo "==> Reloading nginx..." +sudo nginx -t && sudo systemctl reload nginx + +echo "==> Deploy complete." +systemctl --user status gnexus-book-backend.service --no-pager diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..409ca39 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -euo pipefail + +cd "$(dirname "$0")/.." +REPO_ROOT="$(pwd)" + +echo "==> Installing uv..." +curl -LsSf https://astral.sh/uv/install.sh | sh +export PATH="$HOME/.local/bin:$PATH" + +echo "==> Installing Python 3.11..." +uv python install 3.11 + +echo "==> Installing nvm + Node LTS..." +if [[ ! -d "$HOME/.nvm" ]]; then + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash +fi +export NVM_DIR="$HOME/.nvm" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +nvm install --lts + +echo "==> Cloning gnexus-ui-kit..." +if [[ ! -d "$HOME/gnexus-ui-kit" ]]; then + git clone https://git.gnexus.space/git/root/gnexus-ui-kit.git "$HOME/gnexus-ui-kit" +fi + +echo "==> Building gnexus-ui-kit..." +cd "$HOME/gnexus-ui-kit" +npm install +npm run build + +echo "==> Installing server dependencies..." +cd "$REPO_ROOT/server" +uv venv --python 3.11 +uv pip install -e ".[dev]" + +echo "==> Building UI..." +cd "$REPO_ROOT/ui" +npm install +npm run build + +echo "==> Installing systemd user units..." +mkdir -p "$HOME/.config/systemd/user" +cp "$REPO_ROOT/systemd/gnexus-book-backend.service" "$HOME/.config/systemd/user/" +cp "$REPO_ROOT/systemd/gnexus-book-frontend.service" "$HOME/.config/systemd/user/" + +echo "==> Enabling lingering for boot autostart..." +loginctl enable-linger "$(whoami)" + +systemctl --user daemon-reload +systemctl --user enable gnexus-book-backend.service gnexus-book-frontend.service +systemctl --user start gnexus-book-backend.service +sleep 3 +systemctl --user start gnexus-book-frontend.service + +echo "==> Done. Services:" +systemctl --user status gnexus-book-backend.service --no-pager +systemctl --user status gnexus-book-frontend.service --no-pager diff --git a/server/README.md b/server/README.md index 8b7aae2..9899e25 100644 --- a/server/README.md +++ b/server/README.md @@ -18,6 +18,8 @@ The MCP server exposes the same repository, inventory, validation, pending-change, and Git commit logic through MCP tools and resources. +It should be treated by AI agents as infrastructure memory. Agents should consult it proactively for infrastructure-specific work rather than waiting for the user to explicitly ask them to "look in the knowledge base". + Run over stdio: ```bash @@ -48,6 +50,16 @@ - `commit_changes(summary, files, details?)` - `update_doc(path, content, summary, reason?)` +Agent usage policy: + +- Use MCP before answering questions about hosts, VPS, VMs, networks, domains, routes, services, deployments, GitBucket repositories, or smart-home infrastructure. +- Use `search_docs` first for broad lookup, then `read_doc`, inventory tools, and `get_relationships` for authoritative context. +- Use `check_freshness` when stale information matters. +- Use write tools to update documentation after discovering relevant new facts. +- Never store raw passwords, tokens, private keys, recovery codes, session cookies, or secret config values. + +Detailed rules: `../90-maintenance/agent-mcp-usage.md`. + Useful resources: - `gnexus-book://docs` diff --git a/server/app/main.py b/server/app/main.py index 711e535..7b4afe5 100644 --- a/server/app/main.py +++ b/server/app/main.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + from fastapi import Depends, FastAPI, HTTPException, Query from .config import Settings, get_settings @@ -179,3 +181,17 @@ return git.commit(request) except GitAdapterError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc + + +@app.get("/mcp/info") +def mcp_info() -> dict[str, Any]: + from .mcp_server import mcp + tools = [ + {"name": t.name, "description": t.description} + for t in mcp._tool_manager.list_tools() + ] + return { + "name": mcp.name, + "instructions": mcp.instructions, + "tools": tools, + } diff --git a/server/app/mcp_server.py b/server/app/mcp_server.py index 0d32d45..3327768 100644 --- a/server/app/mcp_server.py +++ b/server/app/mcp_server.py @@ -17,6 +17,45 @@ from .validation import validate_repository +MCP_INSTRUCTIONS = """ +Gnexus Book is the canonical knowledge base for the owner's digital, server, +network, project, smart-home, and local infrastructure. + +This MCP server is not a generic file browser. It is the preferred operational +interface for AI agents that need infrastructure context or need to maintain the +knowledge base. Use it proactively when a user request mentions, depends on, or +may be affected by documented infrastructure. + +Use Gnexus Book before answering or acting when the task involves: +- hosts, VPS, VMs, servers, routers, networks, domains, ports, traffic routes, or VPNs; +- services such as GitBucket, nginx proxies, smart-home, auth, storage, media, or project deployments; +- repository/project ownership, activity, deployment mapping, or source-of-truth questions; +- credentials/access methods, but never request or store raw secrets in documentation; +- checking whether information is stale, incomplete, already documented, or safe to change; +- updating documentation after discovering new facts. + +Typical read workflow: +1. search_docs(query) for natural-language lookup. +2. read_doc(path) for canonical narrative context. +3. list_inventory(type) or get_inventory_item(type, id) for structured facts. +4. get_relationships() when dependencies, upstreams, domains, hosts, or unresolved references matter. +5. check_freshness() and validate_repository() when making maintenance decisions. + +Typical write workflow: +1. Prefer propose_doc_change or propose_inventory_item_change for non-trivial changes. +2. Use apply_pending_change to validate and apply. +3. Use commit_changes with a short factual summary. +4. For simple full-document replacements, update_doc may create, apply, validate, and commit in one call. + +Rules: +- Do not store raw passwords, tokens, private keys, recovery codes, session cookies, or secret config values. +- IP addresses, hostnames, usernames, domains, routes, ports, and credential references may be documented when useful. +- Treat Git history as the rollback mechanism; still keep changes focused and commit summaries clear. +- If documentation conflicts with live observations, say so and update the knowledge base when appropriate. +- If the user asks about infrastructure and you have this MCP server available, do not wait for the user to say "look in the knowledge base"; consult it yourself. +""".strip() + + def _settings() -> Settings: repo_root = os.environ.get("GNEXUS_BOOK_REPO_ROOT") return Settings() if not repo_root else Settings(repo_root=Path(repo_root)) @@ -152,13 +191,7 @@ mcp = FastMCP( "gnexus-book", - instructions=( - "Gnexus Book MCP exposes infrastructure documentation, inventory, " - "validation, pending changes, and local Git commit tools. " - "Do not store raw secrets in documentation." - ), - host=os.environ.get("GNEXUS_BOOK_MCP_HOST", "127.0.0.1"), - port=int(os.environ.get("GNEXUS_BOOK_MCP_PORT", "8001")), + instructions=MCP_INSTRUCTIONS, ) diff --git a/server/tests/test_mcp_server.py b/server/tests/test_mcp_server.py index 6a31bdf..c7dfd9f 100644 --- a/server/tests/test_mcp_server.py +++ b/server/tests/test_mcp_server.py @@ -23,6 +23,14 @@ assert "commit_changes" in names +def test_mcp_instructions_tell_agents_to_use_knowledge_base_proactively() -> None: + assert mcp.instructions is not None + assert "canonical knowledge base" in mcp.instructions + assert "Use it proactively" in mcp.instructions + assert "do not wait for the user" in mcp.instructions + assert "Do not store raw passwords" in mcp.instructions + + def test_mcp_tool_functions_read_repository() -> None: doc = read_doc("10-systems/applications/gitbucket.md") assert doc["path"] == "10-systems/applications/gitbucket.md" diff --git a/systemd/gnexus-book-backend.service b/systemd/gnexus-book-backend.service new file mode 100644 index 0000000..4b09556 --- /dev/null +++ b/systemd/gnexus-book-backend.service @@ -0,0 +1,13 @@ +[Unit] +Description=Gnexus Book API (FastAPI) +After=network.target + +[Service] +Type=simple +WorkingDirectory=/home/ubuntu/gnexus-book/server +ExecStart=/home/ubuntu/gnexus-book/server/.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/systemd/gnexus-book-frontend.service b/systemd/gnexus-book-frontend.service new file mode 100644 index 0000000..4a3b651 --- /dev/null +++ b/systemd/gnexus-book-frontend.service @@ -0,0 +1,14 @@ +[Unit] +Description=Gnexus Book UI (Vite preview) +After=network.target gnexus-book-backend.service + +[Service] +Type=simple +WorkingDirectory=/home/ubuntu/gnexus-book/ui +ExecStart=/home/ubuntu/.nvm/versions/node/v24.15.0/bin/node /home/ubuntu/gnexus-book/ui/node_modules/.bin/vite preview --host 0.0.0.0 --port 3006 +Restart=on-failure +RestartSec=5 +Environment="HOME=/home/ubuntu" + +[Install] +WantedBy=default.target diff --git a/systemd/gnexus-book-mcp.service b/systemd/gnexus-book-mcp.service new file mode 100644 index 0000000..cf1afea --- /dev/null +++ b/systemd/gnexus-book-mcp.service @@ -0,0 +1,17 @@ +[Unit] +Description=Gnexus Book MCP Server (SSE) +After=network.target + +[Service] +Type=simple +WorkingDirectory=/home/ubuntu/gnexus-book/server +Environment="GNEXUS_BOOK_REPO_ROOT=/home/ubuntu/gnexus-book" +Environment="GNEXUS_BOOK_MCP_TRANSPORT=sse" +Environment="GNEXUS_BOOK_MCP_HOST=0.0.0.0" +Environment="GNEXUS_BOOK_MCP_PORT=8001" +ExecStart=/home/ubuntu/gnexus-book/server/.venv/bin/python -m app.mcp_server +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target