Skip to content

NuGet and dnx

GitLab MCP Server is published on NuGet.org as gitlab-mcp-server. It is a .NET tool in the layout the .NET 10 SDK introduced for tools that ship a platform-specific executable: the entry point is the same native Go binary every other channel ships, dotnet tool install and dnx pick the package for your operating system and architecture, and no .NET code runs once the server is up.

Seven packages, published together at every release, each with a SLSA build provenance attestation made before it was pushed (from the first release after 3.1.0):

  • gitlab-mcp-server is the pointer package. It declares the gitlab-mcp-server command and names, for each runtime identifier, the package that carries it. It also holds the package README and an .mcp/server.json, which is what NuGet.org renders its install snippet from.
  • gitlab-mcp-server.<rid> packages carry the binary itself, one per runtime identifier: linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64 and win-arm64. The SDK installs exactly one of them, the one matching your machine, and runs the binary directly (Runner="executable" in the tool manifest, so there is no .NET host in between).
PlatformRuntime identifier
Linux x64 (glibc)linux-x64
Linux arm64 (glibc)linux-arm64
macOS Intelosx-x64
macOS Apple Siliconosx-arm64
Windows x64win-x64
Windows ARM64win-arm64

Requirements: the .NET 10 SDK or newer. The runtime-identifier layout is tool manifest version 2, which older SDKs do not read, and dnx itself arrived with .NET 10. On Linux the binaries need glibc; on musl systems such as Alpine, use the Docker image instead.

Nothing to install: dnx downloads the two packages into the NuGet cache on the first run and executes the tool. Clients can launch it this way directly.

Terminal window
dnx gitlab-mcp-server

Two things about dnx that are easy to trip over:

  • It parses its own options anywhere on the line. Arguments meant for the server go after --: dnx gitlab-mcp-server -- --version prints the server’s version, while dnx gitlab-mcp-server --version is read as dnx’s own --version <VERSION> option and prints dnx’s usage.
  • It asks nothing when its standard input is not a terminal. Microsoft’s documentation shows dnx <package> --yes to skip the “download this tool?” prompt; an MCP client connects pipes, so the prompt never appears and the flag is unnecessary (the .NET 10.0.400 and 10.0.401 SDKs accept it all the same, so a client template that adds it does no harm). In a terminal, the first run asks once.

Check the install:

Terminal window
gitlab-mcp-server --version
# gitlab-mcp-server <version> (commit: ...)

With dnx no command lands on your PATH; dnx gitlab-mcp-server -- --version runs the same check. Started in a terminal without both GITLAB_URL and GITLAB_TOKEN set, the binary prints what it is and the two values it needs, then waits for Enter. That is the first-run screen, not an error: an MCP client connects pipes rather than a terminal and never sees it. There is no setup wizard; configuration lives in your client’s JSON, below.

The portable form needs no install at all. Point the client at dnx:

{
"mcpServers": {
"gitlab": {
"command": "dnx",
"args": ["gitlab-mcp-server"],
"env": {
"GITLAB_URL": "https://gitlab.com",
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
}
}
}
}

After dotnet tool install -g, use the command instead:

{
"mcpServers": {
"gitlab": {
"command": "gitlab-mcp-server",
"env": { "GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx" }
}
}
}

GITLAB_TOKEN is the only required value: a personal access token with the api scope (read_api also works; the server detects it at startup and serves a read-only surface). GITLAB_URL defaults to https://gitlab.com; add it to env only for a self-managed instance. A client that does not inherit your shell’s PATH may need the full path to the command; which gitlab-mcp-server (or where gitlab-mcp-server on Windows) prints it. To pin a release rather than follow the newest one with dnx, name it in the argument: "gitlab-mcp-server@<version>", with a version NuGet.org lists. Pinning does not let dnx start without NuGet.org (see Upgrade and uninstall).

One caution for a machine on which the .NET CLI has never run: its first-use welcome text goes to standard output, which is the stream the MCP protocol runs on. Run dotnet --version once in a terminal before the client’s first launch, or add "DOTNET_NOLOGO": "1" to the env block.

Per-client file locations and shapes (VS Code uses servers with "type": "stdio", Zed uses context_servers) are in the Quick Start’s client tabs.

The binary the SDK runs is the release asset byte for byte, so the build provenance attestation GitHub holds for that asset verifies it with the GitHub CLI, wherever it was installed from:

Terminal window
# dnx: the NuGet package cache (<rid> and <version> as in the table above)
gh attestation verify ~/.nuget/packages/gitlab-mcp-server.<rid>/<version>/tools/any/<rid>/gitlab-mcp-server \
-R jmrplens/gitlab-mcp-server --signer-workflow jmrplens/gitlab-mcp-server/.github/workflows/release.yml
# dotnet tool install -g: on Linux and macOS the shim links into the tool store
gh attestation verify "$(readlink -f "$(command -v gitlab-mcp-server)")" \
-R jmrplens/gitlab-mcp-server --signer-workflow jmrplens/gitlab-mcp-server/.github/workflows/release.yml

--signer-workflow holds the attestation to the release workflow, since -R alone accepts one any workflow of the repository minted; add --source-ref refs/tags/v<version> to hold it to the release you run.

On Windows the cache is under %USERPROFILE%\.nuget\packages and the file is gitlab-mcp-server.exe. dnx gitlab-mcp-server -- --version prints the version you run. Both paths were checked on 3.1.0 against the release’s signed checksums.txt.

From the first release after 3.1.0 the seven packages are attested too, as they were before NuGet.org added the repository signature (.signature.p7s) it puts on every package it serves. Remove that entry from a downloaded copy and verify what is left:

Terminal window
v=<version>
curl -sSLO "https://api.nuget.org/v3-flatcontainer/gitlab-mcp-server/$v/gitlab-mcp-server.$v.nupkg"
zip -q -d "gitlab-mcp-server.$v.nupkg" .signature.p7s
gh attestation verify "gitlab-mcp-server.$v.nupkg" -R jmrplens/gitlab-mcp-server \
--signer-workflow jmrplens/gitlab-mcp-server/.github/workflows/release.yml --source-ref "refs/tags/v$v"

The same works for each gitlab-mcp-server.<rid> package. Info-ZIP’s zip -d (zip 3.0) leaves every other byte where it was, which is NuGet’s own definition of the unsigned package: on all seven 3.1.0 packages it gives back exactly what a rebuild from the tag produces. A tool that rewrites the archive gives other bytes, and gh then finds nothing. Rebuilding from the tag with scripts/build_nuget.py and the signed binaries matches byte for byte only on a Python linked against stock zlib: zlib-ng, which is what zlib is on Fedora 40 and later, compresses the same input to different bytes, so a rebuild there differs without saying anything about the published packages.

The server never checks for updates and never replaces its own binary; the SDK owns it here, as each channel owns what it installed. Every release moves the pointer and its six runtime packages together at one version, so a pointer never resolves a binary from another release.

dnx resolves the version against NuGet.org on every launch, so an unpinned dnx gitlab-mcp-server follows each release as it is published, and the download is skipped when the resolved version is already in the NuGet cache. To stay on one release, name it: dnx gitlab-mcp-server@<version> (@latest is not a NuGet version and is refused). The other side of that coin: every launch contacts NuGet.org, pinned or not, cached or not. Measured on the 10.0.401 SDK with 3.1.0 already in the cache and no network, both dnx gitlab-mcp-server@3.1.0 and dnx gitlab-mcp-server exit with Resource temporarily unavailable (api.nuget.org:443), while the command dotnet tool install -g had placed starts. A machine that is offline at times, or a host that cannot reach NuGet.org at all, is served by dotnet tool install (with --source <dir> pointing at downloaded packages where the feed is out of reach).

Nothing was installed, so there is nothing to uninstall beyond the entry in your client’s configuration (and the cached packages under ~/.nuget, if you want them gone).

Other channels are compared in the installation overview.