diff --git a/nixos/host/seminar/mcp-nixos.nix b/nixos/host/seminar/mcp-nixos.nix index 0a6ecb4f..1da7f456 100644 --- a/nixos/host/seminar/mcp-nixos.nix +++ b/nixos/host/seminar/mcp-nixos.nix @@ -5,6 +5,13 @@ `https://mcp-nixos.ncaq.net/mcp` 読み取り専用のMCPサーバなので認証情報は不要です。 万が一の危険を減らすために仮想マシンで隔離しています。 + + mcp-nixos(fastmcp)自体はfaviconを提供しないため、 + Caddyを前段に置いてfaviconとトップページを直接配信しています。 + 以前はCloudflareのリダイレクトルールでGitHub上のfaviconに302で誘導していましたが、 + MCPクライアントのfaviconリゾルバ(Google s2など)はクロスホストのリダイレクトを追跡せず、 + サブドメインにfaviconが無いと判定して親ドメインncaq.netのfaviconに、 + フォールバックしてしまうため、200で直接返す方式に変更しました。 */ { pkgs, @@ -13,6 +20,37 @@ }: let addr = config.machineAddresses.mcp-nixos; + # mcp-nixosリポジトリのwebサイトで使われているfaviconです。 + # コミットをpinして取得の再現性を保証します。 + favicon = pkgs.fetchurl { + url = "https://raw.githubusercontent.com/utensils/mcp-nixos/30530114f777483d2443ebc8347c3330387e66ea/website/public/favicon/favicon.ico"; + hash = "sha256-uapx8X64FusNDFjj1NN0nLm9cbXMP04TVvaJVpoWJuk="; + }; + indexHtml = pkgs.writeText "mcp-nixos-index.html" '' + + + + + + mcp-nixos.ncaq.net + + + +

mcp-nixos.ncaq.net

+

+ This host serves + mcp-nixos + as a remote MCP server. +

+

MCP endpoint: https://mcp-nixos.ncaq.net/mcp

+ + + ''; + webRoot = pkgs.runCommand "mcp-nixos-webroot" { } '' + mkdir -p $out + cp ${favicon} $out/favicon.ico + cp ${indexHtml} $out/index.html + ''; in { # 仮に脆弱性があった場合の被害を最小限に抑えるため仮想マシンで動かします。 @@ -24,7 +62,7 @@ in hypervisor = "cloud-hypervisor"; vsock.cid = config.microvmCid.mcp-nixos; vcpu = 1; - # 実測ピークが約494MiB(NixOS基盤 + Python + mcp-nixos込み)なので、 + # ピークが約500MiB(NixOS基盤 + Python + mcp-nixos + caddy込み)なので、 # 約30%の余裕を持って640MiBにしています。 mem = 640; interfaces = [ @@ -49,6 +87,25 @@ in hostName = "mcp-nixos"; firewall.allowedTCPPorts = [ 8080 ]; # Cloudflare Tunnelから転送されるポートです。 }; + # faviconとトップページを直接配信し、 + # MCPエンドポイントはmcp-nixosへ転送します。 + # TLSはCloudflareが終端するのでHTTPのみです。 + # ホスト名なしのポート指定にすることでCaddyの自動HTTPSを無効にしています。 + # なおCaddyのreverse_proxyはtext/event-streamを検出すると、 + # 自動でバッファリングを無効にするため、 + # MCPのStreamable HTTP(SSE)向けの追加設定は不要です。 + services.caddy = { + enable = true; + virtualHosts.":8080".extraConfig = '' + handle /mcp* { + reverse_proxy http://127.0.0.1:8081 + } + handle { + root * ${webRoot} + file_server + } + ''; + }; systemd = { network = { enable = true; @@ -73,11 +130,12 @@ in serviceConfig = { # mcp-nixosはHTTPトランスポートをネイティブにサポートしているので、 # 環境変数で設定して直接起動します。 - # パスは未指定だとデフォルトの`/mcp`になり、Cloudflare Tunnelの転送先と一致します。 + # パスは未指定だとデフォルトの`/mcp`になり、前段Caddyの転送先と一致します。 + # 外部からはCaddy経由でアクセスするのでループバックのみで待ち受けます。 Environment = [ "MCP_NIXOS_TRANSPORT=http" - "MCP_NIXOS_HOST=0.0.0.0" - "MCP_NIXOS_PORT=8080" + "MCP_NIXOS_HOST=127.0.0.1" + "MCP_NIXOS_PORT=8081" ]; ExecStart = "${pkgs.mcp-nixos}/bin/mcp-nixos"; DynamicUser = true;