This commit is contained in:
Farouk Brown 2025-11-05 19:09:38 +01:00 committed by GitHub
commit 9df682b3c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 488 additions and 1 deletions

View file

@ -246,6 +246,7 @@ isMaximal: {
};
assistant = {
mcphub-nvim.enable = isMaximal;
chatgpt.enable = false;
copilot = {
enable = false;

View file

@ -386,6 +386,10 @@
- Fix [render-markdown.nvim] file_types option type to list, to accept merging.
- Add [avante.nvim] plugin under `vim.assistant.avante-nvim`.
[mcphub-nvim]: https://github.com/ravitemer/mcphub.nvim
- Add [mcphub.nvim] plugin under `vim.assistant.mcphub-nvim`.
[poz](https://poz.pet):
[everforest]: https://github.com/sainnhe/everforest

View file

@ -0,0 +1,25 @@
{
pins,
pkgs,
...
}: let
pin = pins.mcp-hub;
src = pkgs.fetchFromGitHub {
inherit (pin.repository) owner repo;
tag = pin.version;
sha256 = pin.hash;
};
inherit (pkgs) nodejs;
in
pkgs.buildNpmPackage {
pname = "mcp-hub";
inherit (pin) version;
inherit src nodejs;
nativeBuildInputs = [nodejs];
npmDeps = pkgs.importNpmLock {
npmRoot = src;
};
inherit (pkgs.importNpmLock) npmConfigHook;
}

View file

@ -0,0 +1,26 @@
{
pins,
vimUtils,
pkgs,
...
}: let
mcp-hub = import ./bin.nix {inherit pins pkgs;};
pin = pins.mcphub-nvim;
version = pin.branch;
src = pkgs.fetchFromGitHub {
inherit (pin.repository) owner repo;
rev = pin.revision;
sha256 = pin.hash;
};
in
vimUtils.buildVimPlugin {
pname = "mcphub-nvim";
inherit src version;
doCheck = false;
postInstall = ''
mkdir -p $out/bundled/mcp-hub/node_modules/.bin
ln -s ${mcp-hub}/bin/mcp-hub $out/bundled/mcp-hub/node_modules/.bin/mcp-hub
'';
}

View file

@ -5,5 +5,6 @@
./codecompanion
./supermaven-nvim
./avante
./mcphub
];
}

View file

@ -0,0 +1,104 @@
{
config,
lib,
...
}: let
inherit (lib.modules) mkIf;
cfg = config.vim.assistant.mcphub-nvim;
in {
config = mkIf cfg.enable {
vim = {
startPlugins = [
"plenary-nvim"
];
lazy.plugins = {
mcphub-nvim = {
package = "mcphub-nvim";
setupModule = "mcphub";
inherit (cfg) setupOpts;
event = ["DeferredUIEnter"];
};
};
# avante-nvim
assistant.avante-nvim.setupOpts = {
system_prompt = lib.generators.mkLuaInline ''
function()
local hub = require("mcphub").get_hub_instance()
return hub and hub:get_active_servers_prompt() or ""
end
'';
custom_tools = lib.generators.mkLuaInline ''
function()
return {
require("mcphub.extensions.avante").mcp_tool(),
}
end
'';
};
# codecompanion-nvim
assistant.codecompanion-nvim.setupOpts.extensions.mcphub = {
callback = "mcphub.extensions.codecompanion";
opts = {
## MCP Tools
make_tools = true;
show_server_tools_in_chat = true;
add_mcp_prefix_to_tool_names = false;
show_result_in_chat = true;
format_tool = null;
## MCP Resources
make_vars = true;
## MCP Prompts
make_slash_commands = true;
};
};
# lualine
statusline.lualine.setupOpts.sections.lualine_x = lib.generators.mkLuaInline ''
{{
function()
-- Check if MCPHub is loaded
if not vim.g.loaded_mcphub then
return "󰐻 -"
end
local count = vim.g.mcphub_servers_count or 0
local status = vim.g.mcphub_status or "stopped"
local executing = vim.g.mcphub_executing
-- Show "-" when stopped
if status == "stopped" then
return "󰐻 -"
end
-- Show spinner when executing, starting, or restarting
if executing or status == "starting" or status == "restarting" then
local frames = { "", "", "", "", "", "", "", "", "", "" }
local frame = math.floor(vim.loop.now() / 100) % #frames + 1
return "󰐻 " .. frames[frame]
end
return "󰐻 " .. count
end,
color = function()
if not vim.g.loaded_mcphub then
return { fg = "#6c7086" } -- Gray for not loaded
end
local status = vim.g.mcphub_status or "stopped"
if status == "ready" or status == "restarted" then
return { fg = "#50fa7b" } -- Green for connected
elseif status == "starting" or status == "restarting" then
return { fg = "#ffb86c" } -- Orange for connecting
else
return { fg = "#ff5555" } -- Red for error/stopped
end
end,
},}
'';
};
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./config.nix
./mcphub-nvim.nix
];
}

View file

@ -0,0 +1,291 @@
{lib, ...}: let
inherit (lib.options) mkOption mkEnableOption literalMD;
inherit (lib.types) int bool enum str nullOr attrs listOf either attrsOf anything;
inherit (lib.strings) toUpper;
inherit (lib.nvim.types) mkPluginSetupOption luaInline;
inherit (lib.generators) mkLuaInline;
in {
options.vim.assistant = {
mcphub-nvim = {
enable = mkEnableOption "MCPHub";
setupOpts = mkPluginSetupOption "mcphub-nvim" {
use_bundled_binary =
mkEnableOption "Use local `mcp-hub` binary."
// {
default = true;
};
port = mkOption {
type = int;
default = 37373;
description = "The port for the mcp-hub Express server.";
};
server_url = mkOption {
type = nullOr str;
default = null;
description = "The URL for the mcp-hub server in cases where it is hosted somewhere else.";
example = "http://mydomain.com:8080";
};
config_path = mkOption {
type = str;
default = "~/.config/mcphub/servers.json";
description = "The absolute path to your mcpservers.json configuration file. Defaults to ~/.config/mcphub/servers.json in Lua.";
example = "~/.config/nvim/mcpservers.json";
};
shutdown_delay = mkOption {
type = int;
default = 300000; # 5 minutes in milliseconds
description = "The delay in milliseconds before the server shuts down when the last client disconnects.";
};
request_timeout = mkOption {
type = int;
default = 60000;
description = "Timeout for MCP requests in milliseconds.";
};
auto_approve = mkOption {
type = either luaInline bool;
default = false;
description = ''
How to approve MCP calls.
The system checks auto-approval in this order:
1. Function: Custom auto_approve function (if provided)
1. Server-specific: autoApprove field in server config
1. Default: Show confirmation dialog
'';
};
auto_toggle_servers =
mkEnableOption "Let LLMs start and stop MCP servers automatically."
// {
default = true;
};
cmd = mkOption {
type = nullOr str;
default = null;
description = "Custom command to start the mcp-hub binary.";
};
cmd_args = mkOption {
type = nullOr (listOf str);
default = null;
description = "Custom arguments for the mcp-hub command.";
};
global_env = mkOption {
type = nullOr (either luaInline (attrsOf anything));
default = null;
description = ''
Global environment variables available to all MCP servers.
You can use either a table or a function that returns a table.
'';
};
log = {
level = mkOption {
description = "Logging level, e.g., 'vim.log.levels.WARN'.";
type = enum ["debug" "info" "warn" "error" "trace"];
default = "info";
apply = filter: mkLuaInline "vim.log.levels.${toUpper filter}";
};
to_file = mkEnableOption "log to a file.";
file_path = mkOption {
type = nullOr str;
default = null;
description = "Path to the log file.";
};
prefix = mkOption {
type = str;
default = "MCPHub";
description = "The prefix for log messages.";
};
};
ui = {
window = mkOption {
type = attrs;
default = {
width = 0.8;
height = 0.8;
align = "center";
relative = "editor";
zindex = 50;
border = "rounded";
};
description = "Options for the UI window.";
};
wo = mkOption {
type = attrs;
default = {
winhl = "Normal:MCPHubNormal,FloatBorder:MCPHubBorder";
};
description = "Window options.";
};
};
extensions = {
avante = {
enabled =
mkEnableOption "the Avante extension."
// {
default = true;
};
make_slash_commands =
mkEnableOption "create slash commands for Avante."
// {
default = true;
};
};
copilotchat = {
enabled =
mkEnableOption "the CopilotChat extension."
// {
default = true;
};
convert_tools_to_functions =
mkEnableOption "convert tools to functions."
// {
default = true;
};
convert_resources_to_functions =
mkEnableOption "convert resources to functions."
// {
default = true;
};
add_mcp_prefix = mkEnableOption "add an mcp prefix.";
};
};
builtin_tools = {
edit_file = {
parser = {
track_issues =
mkEnableOption "track issues during parsing."
// {
default = true;
};
extract_inline_content =
mkEnableOption "extract inline content."
// {
default = true;
};
};
locator = {
fuzzy_threshold = mkOption {
type = nullOr int;
default = null;
description = "Fuzzy matching threshold.";
};
enable_fuzzy_matching =
mkEnableOption "fuzzy matching."
// {
default = true;
};
};
ui = {
go_to_origin_on_complete =
mkEnableOption "go to the origin on complete."
// {
default = true;
};
keybindings = mkOption {
type = attrs;
default = {
accept = ".";
reject = ",";
next = "n";
prev = "p";
accept_all = "ga";
reject_all = "gr";
};
description = "Keybindings for the edit file UI.";
};
};
};
};
workspace = {
enabled =
mkEnableOption "workspace-specific hubs."
// {
default = true;
};
look_for = mkOption {
type = listOf str;
default = [".mcphub/servers.json" ".vscode/mcp.json" ".cursor/mcp.json"];
description = "Files to search for in order.";
};
reload_on_dir_changed =
mkEnableOption "listen to DirChanged events to reload workspace config."
// {
default = true;
};
port_range = mkOption {
type = attrs;
default = {
min = 40000;
max = 41000;
};
description = "Port range for workspace hubs, with `min` and `max` attributes.";
};
get_port = mkOption {
type = nullOr luaInline;
default = null;
description = "Function that returns the port.";
};
};
on_ready = mkOption {
type = luaInline;
default = mkLuaInline ''
function() end
'';
description = ''
A Lua function to be executed once the mcp-hub server is ready.
It receives the hub object as an argument.
'';
example = literalMD ''
```lua
function(hub)
vim.notify('MCPHub is ready', vim.log.levels.INFO)
end
```
'';
};
on_error = mkOption {
type = luaInline;
default = mkLuaInline ''
function(msg) end
'';
description = ''
A Lua function to be executed when an error occurs.
It receives the error message as an argument.
'';
example = literalMD ''
```lua
function(msg)
vim.notify('An error occurred in MCPHub: ' .. msg, vim.log.levels.ERROR)
end
```
'';
};
json_decode = mkOption {
type = nullOr luaInline;
default = null;
description = ''
Custom JSON parser function for configuration files.
This is particularly useful for supporting JSON5 syntax (comments and trailing commas).
'';
};
};
};
};
}

View file

@ -48,7 +48,7 @@
doCheck = false;
};
inherit (inputs.self.packages.${pkgs.stdenv.system}) blink-cmp avante-nvim;
inherit (inputs.self.packages.${pkgs.stdenv.system}) blink-cmp avante-nvim mcphub-nvim;
};
buildConfigPlugins = plugins:

View file

@ -961,6 +961,35 @@
"url": "https://github.com/OXY2DEV/markview.nvim/archive/de79a7626d54d7785436105ef72f37ee8fe8fa16.tar.gz",
"hash": "032i6m9pld1zyhd7lq49dg4qh98w6vmmzqp2f46drhq0ds26hs4h"
},
"mcp-hub": {
"type": "GitRelease",
"repository": {
"type": "GitHub",
"owner": "ravitemer",
"repo": "mcp-hub"
},
"pre_releases": false,
"version_upper_bound": null,
"release_prefix": null,
"submodules": false,
"version": "v4.2.1",
"revision": "aac67ba3e163712d07674c154532a71965015e57",
"url": "https://api.github.com/repos/ravitemer/mcp-hub/tarball/v4.2.1",
"hash": "127d91xnfqaxqvk76682n8cghjhw1b02xzi4rxm3ggpljxfjza99"
},
"mcphub-nvim": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "ravitemer",
"repo": "mcphub.nvim"
},
"branch": "main",
"submodules": false,
"revision": "8ff40b5edc649959bb7e89d25ae18e055554859a",
"url": "https://github.com/ravitemer/mcphub.nvim/archive/8ff40b5edc649959bb7e89d25ae18e055554859a.tar.gz",
"hash": "1saw3xfrbnwpjklcffp144q2y100kd51yrhvmxnhgc7niy0ip893"
},
"mind-nvim": {
"type": "Git",
"repository": {