Skip to content

feat: Nexus chat in the DAppNode UI + MCP server#2438

Open
hcastc00 wants to merge 3 commits into
mateu/ai-basefrom
feat/nexus-chat
Open

feat: Nexus chat in the DAppNode UI + MCP server#2438
hcastc00 wants to merge 3 commits into
mateu/ai-basefrom
feat/nexus-chat

Conversation

@hcastc00
Copy link
Copy Markdown
Contributor

Nexus chat in the DAppNode UI + MCP server

Summary

Adds a chat panel that talks to a Nexus gateway through this DAppNode, plus an MCP server that exposes the same tool registry to external clients (Claude Desktop, Cursor, etc.).

  • Backend (dappmanager) — new /nexus/* routes proxy chat completions to a Nexus gateway with the API key held server-side (NEXUS_API_KEY env), persist chat history, and stream SSE back to the browser. A multi-step tool loop dispatches DAppNode-specific tools (list packages, fetch logs, diagnose, restart/start/stop/update package, search/fetch docs) and surfaces mutating calls to the UI for explicit confirmation before running.
  • MCP server — same tool registry mounted at POST/GET/DELETE /mcp behind auth.onlyAdmin using the streamable HTTP transport from @modelcontextprotocol/sdk. External MCP clients can authenticate as admin and call the same tools the embedded chat uses.
  • Docs cache — in-process cache of docs.dappnode.io driven by llms.txt. The chat is prompted to consult docs over training-data memory for any DAppNode-specific question; pages are pre-warmed on first chat so latency is negligible.
  • Frontend (admin-ui)ChatPanel rendered both on /ai/nexus (full-page) and as a floating launcher mounted app-wide. Includes model picker (last-used persisted in localStorage), confirmation dialog for mutating tool calls, persisted conversation history (server-side via dbCache), markdown rendering with GFM tables, and a smooth-stream pacer so token deltas read at a steady rate. The NexusPage marketing landing is replaced by the chat surface; the existing "About Nexus" link stays in the footer.

Notable design choices

  • System prompt every turn. The proxy injects a fresh system prompt with (a) the docs index from llms.txt, (b) this node's installed-package snapshot (60 s in-mem cache), and (c) the current admin-UI page (passed by the browser as dappmanager_page). The model is instructed to consult dappnode_search_docs / dappnode_fetch_doc before answering DAppNode-specific questions.
  • Mutating tools require confirmation. restart_package, start_package, stop_package, and update_package go through createPendingConfirmation → SSE confirm_required event → UI dialog → POST /nexus/chat/confirm. A 5-minute server-side timeout auto-denies abandoned prompts.
  • Single in-process tool registry. mcp/tools.ts is consumed by both the chat's in-process dispatcher (mcp/dispatch.ts) and the MCP server (mcp/server.ts), so behaviour is identical whether the caller is the embedded chat or an external MCP client.

New environment variables

All three are read by the dappmanager container (see docker-compose.yml and docker-compose-dev.yml). The chat is disabled until NEXUS_API_KEY is set.

Variable Required Default Purpose
NEXUS_API_KEY yes (empty) Bearer token sent to the Nexus gateway. Held server-side; never reaches the browser. With it unset, /nexus/status reports configured: false and the UI shows the "not configured" panel.
NEXUS_GATEWAY_URL no https://nexus-api.dappnode.com/v1 Base URL of the Nexus-compatible OpenAI endpoint. Override to point at a staging/self-hosted gateway.
NEXUS_DEFAULT_MODEL no nexus/router Model used when the request body doesn't specify one. Whatever you set must be present in the gateway's /models list.

Set them on the dappmanager service (docker-compose.ymlservices.dappmanager.environment) and restart the container. There are no other config touchpoints — the key is read fresh from process.env per request, so changing it requires a container restart.

How to test

Prerequisites: NEXUS_API_KEY exported in the dappmanager container's environment; container restarted.

  1. Status & model list. Open /ai/nexus → header shows the model picker populated from /nexus/models. With NEXUS_API_KEY unset, the page shows the "not configured" panel and the floating bubble's panel does the same.
  2. Streaming chat. Pick a model, send "List the packages installed on this DAppNode" → answer streams smoothly and reflects this node's actual listPackages() output (model called dappnode_list_packages under the hood).
  3. Docs grounding. Ask "How do I set up Wireguard?" → model should call dappnode_search_docs then dappnode_fetch_doc and cite a docs.dappnode.io/... URL (no trailing .md).
  4. Mutating-tool confirmation. Ask "restart my-package" → confirmation card renders with tool name + args. Click Deny → assistant continues with a _skipped restart_package — …_ line. Repeat and Approve → the package is actually restarted, assistant continues.
  5. History. Send a couple of messages, reload the page, open the history dropdown → conversation is listed and restorable. Delete it from the dropdown → entry disappears and reload confirms it's gone.
  6. Floating launcher + page context. Navigate to /packages, click the bubble, ask "what is this page?" → answer references the packages page. Navigate to /system while the panel stays open, ask again → answer reflects the new path (the panel samples location lazily on each send).
  7. MCP from an external client. From Claude Desktop / Cursor configured with this DAppNode's admin cookie at POST/GET/DELETE https://<dappnode>/mcp, list tools → matches dappnodeToolList from mcp/tools.ts. Calling dappnode_list_packages returns the same JSON the embedded chat sees.
  8. Override knobs.
    • Set NEXUS_DEFAULT_MODEL=openai/gpt-4o, restart → header dropdown defaults to that model on fresh visits.
    • Point NEXUS_GATEWAY_URL at a stub server and confirm requests hit it instead of nexus-api.dappnode.com.
  9. Secret hygiene. git grep "sk-" against the branch returns no committed API keys.

@hcastc00 hcastc00 requested a review from a team as a code owner May 27, 2026 09:11
@github-actions github-actions Bot temporarily deployed to commit May 27, 2026 09:14 Inactive
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 27, 2026

@github-actions github-actions Bot temporarily deployed to commit May 29, 2026 11:45 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant