MCP Nexus
Sends messages to Discord via webhooks as an outbound destination, with named destinations that keep webhook URLs away from the model and an Approve/Deny gate before anything leaves the machine.
Connects Google Gemini models as a chat backend in the unified LLM hub, supporting streaming and tool calling, with API keys created in Google AI Studio and entered via the dashboard or .env.
Delivers push notifications through ntfy as an outbound destination, gated by the same approval flow that applies to all outbound sends.
Connects a locally running Ollama instance as a chat backend so the hub works fully offline, auto-detecting the local server and listing available tool-capable models.
Connects OpenAI models — and any OpenAI-compatible endpoint such as LM Studio, vLLM or OpenRouter — as a chat backend with streaming, tool calling, live model listing and a one-click connection test.
Posts messages to Slack as an outbound destination, using saved named destinations instead of exposing webhook URLs to the model, and requiring dashboard approval unless 'send without asking' is enabled.
Provides a read-only SQLite connector that lets the agent run queries against local SQLite databases, with the connector toggleable live from the dashboard.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MCP Nexussummarize README.md in my workspace and send it to Slack"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP Nexus
A local Model Context Protocol hub with a web dashboard. Chat with Claude, Gemini, OpenAI or a local Ollama model, give it tools written as plain Python classes, approve anything it wants to send off your machine, keep reusable prompt templates, and watch every JSON-RPC message between client and server as it happens.

Why
MCP clients usually hide the protocol. MCP Nexus puts it on the table: the same tools serve the built-in chat, Claude Desktop and any other MCP client, every request and reply is visible in a live inspector, and nothing leaves your computer without your say-so. It runs on 127.0.0.1, stores everything in one SQLite file, and works fully offline with Ollama.
Related MCP server: AKIMCP
Features
LLM Hub | Anthropic Claude, Google Gemini, OpenAI (and any OpenAI-compatible server such as LM Studio, vLLM or OpenRouter) and Ollama behind one interface, with streaming and tool calling. Keys entered in the UI are encrypted at rest; keys in |
Local tools ("Connect stuff") | Built-in connectors: filesystem (sandboxed workspace), web (fetch pages as markdown, extract links, read JSON; private-network requests blocked), system (CPU, memory, disk, processes), sqlite (read-only queries). Toggle each one on or off live; settings forms are generated from each connector's config model. |
Outbound dispatcher ("Send stuff") | Webhooks, Discord, Slack, email (SMTP) and push notifications (ntfy). Every send waits for Approve / Deny in the dashboard, including requests from Claude Desktop, unless you switch on Send without asking. Named destinations keep webhook URLs away from the model. |
Template Studio | Prompt templates with blanks ( |
Live Inspector | A two-lane timeline of genuine JSON-RPC 2.0 traffic: requests in teal, replies in violet, with round-trip times, request/response pairing, filters, pause and JSON export. Covers the dashboard, HTTP clients and stdio clients. |
Three ways in | The web dashboard, Streamable HTTP at |
Plugins in under 20 lines | Drop a Python file into |
Architecture
flowchart TB
subgraph Browser["Dashboard (Tailwind · HTMX · Alpine.js)"]
direction LR
Chat["Chat & Execution"] ~~~ Conn["Connections"] ~~~ Tpl["Template Studio"] ~~~ Insp["Live Inspector"]
end
subgraph Host["FastAPI host (nexus serve)"]
direction LR
API["REST + SSE API"] --> Agent["Agent loop"] <--> Router["LLM Router"]
Bus["Event bus"]
DB[("SQLite<br/>keys · chats · templates<br/>approvals · RPC log")]
end
subgraph Engine["MCP engine (mcp 2.x MCPServer)"]
direction LR
Tap["RPC tap"] --> Server["MCP server"] --> Registry["Connector registry"] --> Tools["filesystem · web · system<br/>sqlite · outbound · plugins/"]
Gate["Approval gate"]
end
LLMs["Anthropic · Gemini · OpenAI · Ollama"]
Ext["Claude Desktop · Cursor · any MCP client"]
Browser -- "HTTP + SSE" --> API
Router <--> LLMs
Agent -- "JSON-RPC 2.0" --> Tap
Tap -- "every message" --> Bus
Bus -- "/api/events" --> Insp
Tools -. "send webhook / email" .-> Gate
Gate -. "Approve / Deny" .-> Chat
Ext -- "stdio or HTTP /mcp" --> Server
Host --- DBThe dashboard's agent never calls connector code directly: it is an MCP client of its own server over an in-memory transport that relays real JSON-RPC, so what you see in the Inspector is exactly what went over the wire. External clients are recorded by server middleware in the same format. The approval gate sits inside the server and coordinates through SQLite, so it also protects calls from a separate nexus stdio process. The full design, with 17 recorded decisions, is in docs/ARCHITECTURE.md; the build history is in docs/DEVLOG.md.
Quickstart
You need Python 3.12 or newer (tested on 3.12, 3.13 and 3.15) and git.
1. Install
git clone https://github.com/BMKoscak/mcp-nexus.git
cd mcp-nexus
python -m venv .venvActivate the environment, then install:
# Windows (PowerShell)
.venv\Scripts\Activate.ps1
# macOS / Linux
source .venv/bin/activate
pip install -e .2. Configure (optional)
cp .env.example .env # Windows: copy .env.example .envEvery setting is optional. You can paste API keys straight into the dashboard instead (they're stored encrypted in data/). Gemini keys are created in Google AI Studio. For a fully local setup, install Ollama, then run ollama pull llama3.2 (or any model that supports tools).
3. Run
nexus serveOpen http://127.0.0.1:8765. Go to Connect, add a key or check that Ollama is found, pick a model in the chat box, and try "How much memory and disk space is free on this computer?"
Other commands:
Command | What it does |
| Dashboard, REST API and the MCP endpoint at |
| MCP over stdin/stdout, for Claude Desktop and similar clients |
| List every connector and tool (handy for checking a plugin loads) |
| Run as if started from |
Use it from Claude Desktop
Add this to claude_desktop_config.json (on Windows: %APPDATA%\Claude\claude_desktop_config.json), adjusting the paths to where you cloned the repo:
{
"mcpServers": {
"nexus": {
"command": "C:\\path\\to\\mcp-nexus\\.venv\\Scripts\\nexus.exe",
"args": ["--project-dir", "C:\\path\\to\\mcp-nexus", "stdio"]
}
}
}On macOS/Linux use /path/to/mcp-nexus/.venv/bin/nexus. Restart Claude Desktop; the Nexus tools and templates appear there. Keep nexus serve running if you want to approve outbound actions in the dashboard and see Claude Desktop's traffic in the Inspector, since both processes share the same SQLite file.
Use it from other MCP clients
Point any Streamable HTTP client at http://127.0.0.1:8765/mcp. For example, with the Python SDK:
from mcp.client import Client
async with Client("http://127.0.0.1:8765/mcp") as client:
tools = await client.list_tools()
result = await client.call_tool("system_stats", {})Write your own connector
Create plugins/dice.py:
from nexus.connectors import Connector, tool
class Dice(Connector):
name = "dice"
description = "Roll dice"
@tool()
async def roll(self, sides: int = 6) -> int:
"""Roll one die with the given number of sides."""
import random
return random.randint(1, sides)Choose Reload plugins under Connect → Local tools (or restart). dice_roll is now available to the chat, to Claude Desktop and to every MCP client. Type hints become the JSON Schema and the docstring becomes the description.
Going further, in the same class:
from typing import Annotated
from pydantic import BaseModel, Field, SecretStr
from nexus.connectors import Connector, ToolError, resource, tool
class Weather(Connector):
name = "weather"
description = "Current weather"
class Config(BaseModel): # rendered as a settings form in the dashboard
api_key: SecretStr = SecretStr("") # SecretStr fields are stored encrypted
units: str = "metric"
@tool(read_only=True, open_world=True)
async def now(self, city: Annotated[str, Field(description="City name")]) -> dict:
if not self.config.api_key.get_secret_value():
# ToolError messages are shown to the model as-is
raise ToolError("Add an API key in the weather connector settings.")
r = await self.ctx.http.get("https://api.example.com/weather", params={"q": city})
return r.json()
@tool(requires_approval=True, summary="Text {to}: {message}") # waits for Approve / Deny
async def send_sms(self, to: str, message: str) -> str: ...
@resource("weather://cities") # an MCP resource
async def cities(self) -> str:
return "Ljubljana\nMaribor"self.ctx gives you settings, db, crypto, bus, approvals and a shared httpx client. Packages can also publish connectors through the mcp_nexus.connectors entry-point group. See plugins/README.md.
Plugins run inside the Nexus process with your user's permissions. Only install code you trust.
Configuration
All settings come from environment variables or .env (see .env.example for the full, commented list):
Variable | Default | Purpose |
|
| Where the server listens. There is no login, so keep it on localhost. |
|
| The only folder the filesystem and sqlite connectors can touch |
|
| Custom connectors |
|
| SQLite database and the encryption key |
| Provider keys (override keys saved in the UI). | |
|
| Local models |
|
| First model selected, as |
|
| Initial value of Send without asking |
|
| Unanswered approvals are denied after this |
| Ready-made | |
| Email delivery | |
|
| Stop the web connector reaching localhost or your LAN |
API
Interactive docs are served at /docs while Nexus runs. The main routes:
Area | Routes |
System |
|
Models |
|
Tools |
|
Approvals |
|
Templates |
|
Chat |
|
MCP |
|
Security model
Listens on localhost only, with no authentication layer. Don't expose it to a network as-is.
API keys, webhook URLs and connector secrets are encrypted with Fernet (
data/secret.keyorNEXUS_SECRET_KEY) and only ever shown masked.The filesystem connector resolves every path and refuses anything outside the workspace, including
..and symlink escapes.The web connector refuses loopback, private, link-local and reserved addresses, and re-checks every redirect.
The SQLite connector opens databases read-only and runs one statement at a time.
Outbound tools wait for approval by default, for every client.
Templates render in a Jinja2 sandbox.
Tool errors reach the model as clean messages; unexpected exceptions are masked.
Development
pip install -e ".[dev]"
pytest # 85 tests: connectors, engine, providers, API end to end
ruff check . && ruff format --check .
python scripts/smoke_test.py # 54 live checks against a running `nexus serve`Layout:
nexus/
├── mcp_engine.py MCPServer, tapped transport, RPC tap, external-client middleware
├── registry.py connector discovery, lifecycle, config, approval wrapping
├── connectors/ base API + filesystem, outbound, web_fetch, system, sqlite_query
├── providers/ anthropic, gemini, openai, ollama (direct httpx streaming clients)
├── llm_router.py credentials, model discovery, routing
├── agent.py LLM ⇄ MCP tool-calling loop
├── templates.py sandboxed templates → MCP prompts
├── approvals.py cross-process approval gate
├── db.py · crypto.py · events.py · netguard.py · config.py
├── api/ FastAPI routers (+ HTMX fragments in ui.py)
├── main.py · cli.py
web/ dashboard (index.html, partials/, app.js, app.css)
plugins/ your connectors
docs/ ARCHITECTURE.md, DEVLOG.md, screenshots/Roadmap
Connect to other MCP servers and re-expose their tools (Nexus as an MCP proxy)
Optional login for non-localhost use
Edit arguments on the approval card before sending
Vendored front-end assets for fully offline dashboards
License
MIT © 2026 Benjamin Koščak
This server cannot be deployed
Maintenance
Related MCP Connectors
Real-time chat hub for AI agents — Claude Code, Cursor, Cline, Codex over MCP or REST.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
- QuallaaOAuthcom.quallaa
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
One MCP endpoint for Claude, GPT & Gemini: 100+ tools + no-code connectors + agent workers.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceLocal MCP server that exposes fixed tools for GPT, Claude, and Gemini while routing to any OpenAI-compatible chat completions backend with independent configuration per target.1-
- FlicenseNot gradedqualityAmaintenanceExposes a governed suite of 38 tools over a single OAuth-gated MCP endpoint, letting web-based AI clients such as Claude, ChatGPT, and Grok read and edit files, run whitelisted shell commands, search, query SQLite, use Git, automate browsers and DevTools, manage background tasks, reach localhost services, and control the clipboard, notifications, ports, and Postman. This turns remote AI chats into secure operators of a local machine without granting unrestricted shell access.-
- FlicenseNot gradedqualityAmaintenanceExposes a governed suite of 38 tools over one OAuth-gated MCP endpoint, letting web-based AI clients such as Claude, ChatGPT, and Grok read and edit files, run whitelisted shell commands, search content, use Git, inspect SQLite databases and localhost APIs, automate browsers, manage background tasks, and control Postman. It runs locally with a tailnet or public ingress so browser-based chats can act on your own machine without granting unrestricted shell access.-
- FlicenseNot gradedqualityAmaintenanceExposes a governed suite of 38 tools over a single OAuth-gated MCP endpoint, letting web AI chat clients such as Claude, ChatGPT, and Grok operate on a local machine — reading and editing files, running whitelisted shell commands, searching, querying SQLite, driving browsers and DevTools, managing background jobs, ports, and clipboard. Local IDEs and Postman can also connect directly over localhost for offline, low-latency access.-