From 3b29d4b66ab31b4d2b2263d9501c5d03c85fc1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey?= Date: Fri, 6 Mar 2026 21:52:08 -0800 Subject: [PATCH] feat(glyph): setup context7 mcp --- hosts/glyph/secrets/context7-api-key.age | 7 ++ hosts/glyph/services/default.nix | 11 +++ lib/secrets/glyph.nix | 1 + modules/home/development.nix | 2 + modules/nixos/llm/mcpjungle.nix | 85 +++++++++++++++++------- 5 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 hosts/glyph/secrets/context7-api-key.age diff --git a/hosts/glyph/secrets/context7-api-key.age b/hosts/glyph/secrets/context7-api-key.age new file mode 100644 index 00000000..0c3ef6aa --- /dev/null +++ b/hosts/glyph/secrets/context7-api-key.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 rSr+rA Rv+SeOdkxUpeduGJu8ydAC4Jm0QgAyKIndsPzvpYC1U +VuUbqTAMHXcCMsZqSCEXYK52Hepkf2NBxqg7zbIpRu0 +-> ssh-ed25519 3EWhnQ 2WfchLD0XNCwTjp96o8cX+QQiPXLh/Lan9vshqHx4kQ +esRIsLZvl/KbbtY2L5mYBg4n39KlYgjU4X7E//FeLII +--- ouqSSPZWNv85Y1FVCsPkA0Bn7y2M4rrNNl0ecIb6bIg +FÚ¹f!æÙ^Íø¤sorå~”N$´Þ]´Ä0½ŒëŒ÷öÑkzª6p1,Í_žÈQ¶Æ'è W½šM|Êœ­Xóý«Éž¦IóµÌ(–hŠœÁï\2J™`$Æ�Kïù \ No newline at end of file diff --git a/hosts/glyph/services/default.nix b/hosts/glyph/services/default.nix index 0a96bcbf..01df5392 100644 --- a/hosts/glyph/services/default.nix +++ b/hosts/glyph/services/default.nix @@ -67,6 +67,11 @@ group = "kagi-mcp"; }; + age.secrets.context7-api-key = { + file = ./../secrets/context7-api-key.age; + mode = "440"; + }; + services.basic-memory.enable = true; services.mcp-nixos.enable = true; services.kagi-mcp = { @@ -87,5 +92,11 @@ url = "http://127.0.0.1:8093/mcp"; description = "Kagi web search and page summarization"; }; + servers.context7 = { + url = "https://mcp.context7.com/mcp"; + description = "Up-to-date library documentation and code examples"; + headers.CONTEXT7_API_KEY = "$CONTEXT7_API_KEY"; + environmentFile = config.age.secrets.context7-api-key.path; + }; }; } diff --git a/lib/secrets/glyph.nix b/lib/secrets/glyph.nix index 8ab50744..7c5b4dea 100644 --- a/lib/secrets/glyph.nix +++ b/lib/secrets/glyph.nix @@ -5,4 +5,5 @@ in { "hosts/glyph/secrets/pushover-app-token.age".publicKeys = keys; "hosts/glyph/secrets/pushover-user-token.age".publicKeys = keys; "hosts/glyph/secrets/kagi-api-key.age".publicKeys = keys; + "hosts/glyph/secrets/context7-api-key.age".publicKeys = keys; } diff --git a/modules/home/development.nix b/modules/home/development.nix index d7c1a9c6..5eff87b1 100644 --- a/modules/home/development.nix +++ b/modules/home/development.nix @@ -90,6 +90,8 @@ in { "mcp__glyph__basic-memory__search_by_metadata" "mcp__glyph__basic-memory__search_notes" "mcp__glyph__basic-memory__view_note" + "mcp__glyph__context7__resolve-library-id" + "mcp__glyph__context7__get-library-docs" ]; deny = []; }; diff --git a/modules/nixos/llm/mcpjungle.nix b/modules/nixos/llm/mcpjungle.nix index 6c5fa466..f1c855e1 100644 --- a/modules/nixos/llm/mcpjungle.nix +++ b/modules/nixos/llm/mcpjungle.nix @@ -31,6 +31,20 @@ in { default = ""; description = "Description of the MCP server."; }; + headers = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = '' + Custom HTTP headers to send with requests to this server. + Values may reference environment variables (e.g. "$MY_API_KEY") + which will be substituted from the environmentFile. + ''; + }; + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Path to environment file for variable substitution in headers."; + }; }; }); default = {}; @@ -82,36 +96,61 @@ in { Type = "oneshot"; RemainAfterExit = true; }; - path = [pkgs.curl]; + path = [pkgs.curl pkgs.envsubst]; script = let registry = "http://127.0.0.1:${toString cfg.port}"; bin = lib.getExe cfg.package; - registrations = lib.concatStringsSep "\n" (lib.mapAttrsToList (name: server: '' - if ${bin} list servers --registry ${registry} 2>/dev/null | grep -q '${name}'; then - echo "${name} already registered, skipping." - else - # Wait for server to be reachable before registering - ready=false - for i in $(seq 1 30); do - http_code=$(curl -s -o /dev/null -w '%{http_code}' "${server.url}" 2>/dev/null) - if [ "$http_code" != "000" ]; then - ready=true - break - fi - echo "Waiting for ${name} at ${server.url} (attempt $i/30)..." - sleep 2 - done - - if [ "$ready" = true ]; then + mkRegistration = name: server: let + hasHeaders = server.headers != {}; + configJson = builtins.toJSON { + inherit name; + inherit (server) url description headers; + transport = "streamable-http"; + }; + registerCmd = + if hasHeaders + then '' + ${lib.optionalString (server.environmentFile != null) '' + set -a + source "${server.environmentFile}" + set +a + ''} + tmpfile=$(mktemp --suffix=.json) + echo '${configJson}' | envsubst > "$tmpfile" + ${bin} register --conf "$tmpfile" --registry ${registry} || echo "ERROR: failed to register ${name}" + rm -f "$tmpfile" + '' + else '' ${bin} register --name '${name}' --description '${server.description}' --url '${server.url}' --registry ${registry} || echo "ERROR: failed to register ${name}" - else - echo "WARNING: ${name} at ${server.url} not reachable after 60s, skipping registration." - fi + ''; + in '' + if ${bin} list servers --registry ${registry} 2>/dev/null | grep -q '${name}'; then + echo "${name} already registered, skipping." + else + # Wait for server to be reachable before registering + ready=false + for i in $(seq 1 30); do + http_code=$(curl -s -o /dev/null -w '%{http_code}' "${server.url}" 2>/dev/null) + if [ "$http_code" != "000" ]; then + ready=true + break fi - '') - cfg.servers); + echo "Waiting for ${name} at ${server.url} (attempt $i/30)..." + sleep 2 + done + + if [ "$ready" = true ]; then + ${registerCmd} + else + echo "WARNING: ${name} at ${server.url} not reachable after 60s, skipping registration." + fi + + fi + ''; + + registrations = lib.concatStringsSep "\n" (lib.mapAttrsToList mkRegistration cfg.servers); in '' ${registrations} '';