Skip to main content
Glama

nexus-mods-mcp

MCP (Model Context Protocol) server exposing the Nexus Mods API to an AI agent (GitHub Copilot, Claude, Junie...). Written in TypeScript, stdio transport.

Covers the read APIs (v1 REST, v2 GraphQL) and v3 API writes, so an agent can publish a mod file or update a mod page's summary and description.

Design goal: answer a question in as few tool calls as possible. Instead of a thin 1:1 mapping over REST endpoints, the high-value tools aggregate several requests and return compacted results.

1. Concepts

A MCP server is a process speaking JSON-RPC 2.0 over stdin/stdout and declaring:

Primitive

Role

Here

Tools

actions the model can call

23 Nexus tools (nexus_*)

Resources

documents the client can read

nexus://cheatsheet, nexus://graphql-cheatsheet, nexus://upload-guide

Prompts

reusable prompt templates

nexus_mod_report, nexus_release_update

Instructions

global routing hints sent at initialize

"name -> find_mods -> mod_overview"

The agent never sees the URL nor the API key: it calls a typed tool, the server translates it into HTTP calls, handles authentication, caching and quotas, then returns a compact result.

Related MCP server: modrinth-mcp

2. Install

Clone the repository, install the locked dependencies, and build the server:

git clone https://github.com/PHLemp/nexus-mods-mcp.git
cd nexus-mods-mcp
npm ci
npm run build

The commands above assume Node.js and Git are installed. If you already have a checkout, run git pull, then repeat npm ci and npm run build after updating the repository.

Get an API key: https://www.nexusmods.com/users/myaccount?tab=api%20access (section "Personal API Key"), then:

Copy-Item .env.example .env
# edit .env and paste the key into NEXUS_API_KEY

Check that everything works:

npm run smoke        # offline: lists tools, resources and prompts
npm run smoke:live   # calls the read-only tools against the live API
npm run inspect      # MCP Inspector web UI

Node gives precedence to the process environment over .env. If NEXUS_API_KEY is already set in the shell, that value wins.

3. Environment variables

Variable

Required

Description

NEXUS_API_KEY

yes

Required by the server; supply it through MCP env or the repository .env file

NEXUS_OAUTH_TOKEN

no

Bearer token for the v2 GraphQL / v3 REST APIs

NEXUS_DEFAULT_GAME

no

Default domain (mountandblade2bannerlord)

NEXUS_USER_AGENT

no

Identifiable User-Agent, required by Nexus

NEXUS_ALLOW_WRITES

no

true to allow endorse / track / changelog / rename / mod page edits / mutations

NEXUS_ALLOW_UPLOADS

no

true to allow publishing files (also needs NEXUS_ALLOW_WRITES)

NEXUS_UPLOAD_ROOT

no

Restricts which directory archives may be uploaded from

NEXUS_UPLOAD_TIMEOUT_SECONDS

no

Timeout of each presigned transfer (default 900)

NEXUS_CACHE_TTL_SECONDS

no

Read cache lifetime (default 300, 0 disables it)

NEXUS_TIMEOUT_SECONDS

no

Per-request timeout (default 20)

NEXUS_MAX_RETRIES

no

Retries on network errors / 5xx (default 2)

4. Wiring it into a MCP client

Recommended: for this stdio server, provide credentials as environment variables. Do not hardcode a real key in a versioned MCP configuration. Use the client's secure secret input or an untracked environment file when supported, or inject NEXUS_API_KEY through the process environment in CI. This server also loads <checkout>/.env itself for MCP clients without an envFile feature. Node's process.loadEnvFile never overrides a variable already set in the process environment, so a client-provided env value always wins over .env.

JetBrains Rider + GitHub Copilot plugin (setup used here)

The Copilot plugin for JetBrains reads its MCP configuration from:

%LOCALAPPDATA%\github-copilot\intellij\mcp.json

Block to add under servers:

"nexus-mods": {
  "type": "stdio",
  "command": "C:\\Program Files\\nodejs\\node.exe",
  "args": ["C:\\path\\to\\nexus-mods-mcp\\dist\\index.js"],
  "env": {
    "NEXUS_API_KEY": "<your_api_key>",
    "NEXUS_DEFAULT_GAME": "mountandblade2bannerlord",
    "NEXUS_ALLOW_WRITES": "false",
    "NEXUS_ALLOW_UPLOADS": "false"
  }
}

Replace the example path with your checkout location, then set NEXUS_API_KEY in <your checkout>/.env. The example keeps writes and uploads disabled explicitly; set the corresponding value to true only when needed. Do not commit the .env file. Values supplied through the client's env object take precedence over .env. The GitHub Copilot plugin for JetBrains does not document a secure input-variable equivalent to VS Code's ${input:...} syntax, so use the checkout-local .env file for the key. After changing the code, run npm run build and restart the server (or Rider) so the client picks up the new tools.

JetBrains AI Assistant / Junie

Settings > Tools > AI Assistant > Model Context Protocol (MCP) > +, button As JSON:

{
  "mcpServers": {
    "nexus-mods": {
      "command": "C:\\Program Files\\nodejs\\node.exe",
      "args": ["C:\\path\\to\\nexus-mods-mcp\\dist\\index.js"],
      "env": {
        "NEXUS_API_KEY": "<your_api_key>",
        "NEXUS_DEFAULT_GAME": "mountandblade2bannerlord",
        "NEXUS_ALLOW_WRITES": "false",
        "NEXUS_ALLOW_UPLOADS": "false"
      }
    }
  }
}

For a configuration versioned with the project (Junie): .junie/mcp/mcp.json at the repo root. Since this file is typically committed, prefer leaving the key out of it and relying on .env (git-ignored) for that particular workflow, or on a per-user Junie secrets mechanism if available.

VS Code / GitHub Copilot — .vscode/mcp.json (optional)

The key is not required in this JSON. VS Code supports a password-protected input for a personal configuration, or envFile for an untracked .env file. This server also loads <your checkout>/.env itself, so omitting both properties works for a checkout-local configuration. An env value supplied by VS Code takes precedence.

{
  "servers": {
    "nexus-mods": {
      "type": "stdio",
      "command": "node",
      "args": ["E:\\Modding\\Tools\\nexus-mods-mcp\\dist\\index.js"],
      "env": {
        "NEXUS_API_KEY": "${input:nexusApiKey}",
        "NEXUS_DEFAULT_GAME": "mountandblade2bannerlord",
        "NEXUS_ALLOW_WRITES": "false",
        "NEXUS_ALLOW_UPLOADS": "false"
      }
    }
  },
  "inputs": [
    { "id": "nexusApiKey", "type": "promptString", "description": "Nexus Mods API key", "password": true }
  ]
}

5. Troubleshooting

Symptom

Likely cause / fix

Server "failed to start" in Rider

dist/index.js missing -> npm run build

"NEXUS_API_KEY is missing"

Set NEXUS_API_KEY in the client's env block (preferred) or in .env

node not found

Use the absolute path C:\Program Files\nodejs\node.exe

Tools missing from the chat

Switch the chat to Agent mode, then enable nexus-mods in the tool list

HTTP 401 on every call

Invalid key -> test with nexus_validate_user

New tools not showing up

Rebuild, then restart the MCP server from the client

HTTP 429

Quota exhausted; the error states the reset time. Reuse cached results

"Publishing is disabled"

Set NEXUS_ALLOW_UPLOADS=true (and NEXUS_ALLOW_WRITES=true)

Upload refused, "outside NEXUS_UPLOAD_ROOT"

The archive is not under the configured sandbox directory

HTTP 403 on a publishing call

The API key does not belong to an author of that mod

HTTP 422 on publish

name/version or a flag broke a Nexus constraint; the field is named in the error

Storage SignatureDoesNotMatch

Presigned transfers go through multipart with the exact headers Nexus' own action sends; if this ever returns, do not retry blindly, check nexus_upload_status first

Upload succeeded but publishing failed

Reuse the returned upload_id with nexus_publish_upload, do not re-upload

6. Tools

Aggregating tools (prefer these)

Tool

What it returns

Calls saved

nexus_find_mods

name/keywords -> mod_id, version, author, downloads, endorsements, URL

replaces GraphQL introspection + search

nexus_mod_overview

metadata + files grouped by category + recent changelogs + page URL

replaces get_mod + get_mod_files + get_mod_changelogs

nexus_list_author_mods

full catalogue of an author (or "my mods"), with download/endorsement totals

replaces one call per mod

nexus_upload_mod_file

local archive -> published file or new version (+ optional changelog)

replaces the 5-step v3 upload dance

Publishing and mod page tools (v3 API)

Tool

Nexus endpoint

Write

nexus_mod_file_targets

/v3/games/{domain}/mods/{id} + /v3/mods/{uid}/files + versions

no

nexus_upload_mod_file

full upload session + /v3/mod-files[/{id}/versions]

yes*

nexus_publish_upload

/v3/mod-files[/{id}/versions] from an existing upload_id

yes*

nexus_upload_status

/v3/uploads/{id}

no

nexus_add_changelog

/v3/mods/{uid}/changelogs

yes

nexus_edit_mod

PATCH /v3/mods/{uid} (summary and/or description)

yes

nexus_rename_mod_file

PUT /v3/mod-files/{id}

yes

* also requires NEXUS_ALLOW_UPLOADS=true.

Supporting tools

Tool

Nexus endpoint

Write

nexus_validate_user

/v1/users/validate.json

no

nexus_rate_limit_status

x-rl-* headers + server stats + cache reset

no

nexus_list_games

/v1/games.json (+ local filter)

no

nexus_get_game

/v1/games/{domain}.json

no

nexus_get_mod

/v1/games/{domain}/mods/{id}.json

no

nexus_get_mod_files

.../files[.json|/{file_id}.json]

no

nexus_get_mod_changelogs

.../changelogs.json

no

nexus_list_mods

latest_added / latest_updated / trending / updated

no

nexus_search_md5

.../md5_search/{hash}.json

no

nexus_get_download_link

.../download_link.json (Premium)

no

nexus_tracked_mods

/v1/user/tracked_mods.json

yes*

nexus_endorse_mod

.../endorse.json | abstain.json

yes

nexus_graphql

POST /v2/graphql

yes*

* only for mutating actions, blocked unless NEXUS_ALLOW_WRITES=true.

Publishing / updating a mod

Nexus vocabulary matters here, because the two operations are different endpoints:

Term

Meaning

mod file

one entry of the Files tab, i.e. a whole update chain

mod file version

one release inside that chain

Updating a mod = adding a new version to an existing mod file. Adding a second entry to the Files tab = creating a new mod file.

nexus_find_mods            -> mod_id
nexus_mod_file_targets     -> mod_file_id of the file to update
nexus_upload_mod_file      -> dry_run: true, review the plan
nexus_upload_mod_file      -> for real
nexus_mod_overview         -> refresh: true, verify

nexus_upload_mod_file runs the whole v3 sequence in one call:

  1. POST /v3/uploads/multipart with size and filename — used for every size, not only above 100 MiB, because that is what Nexus' own GitHub Action does.

  2. PUT each part to its presigned storage URL with Content-Type: application/octet-stream and Content-Length, keeping each ETag, then POST the ETag list to the completion URL.

  3. POST /v3/uploads/{id}/finalise, then polls GET /v3/uploads/{id} until state: available.

  4. POST /v3/mod-files/{mod_file_id}/versions (update) or POST /v3/mod-files (new file).

  5. Optionally POST /v3/mods/{uid}/changelogs.

The single-part POST /v3/uploads route is deliberately unused: its presigned signature also covers Content-Disposition (and Content-MD5 when a digest is supplied), and storage answers 403 SignatureDoesNotMatch on the slightest mismatch. Nexus-Mods/upload-action dropped that route in March 2026 and multiparts everything; this server mirrors it.

Content integrity (the md5 question)

Nexus badges md5 as required from 2026-12-01. That badge sits on POST /v3/uploads only, where the digest is folded into the presigned signature. The multipart request body (CreateUploadRequest) has no md5 field, and GET /v3/uploads/{id} returns no checksum, so the deadline does not reach the route this server uses — and the server does not pretend otherwise.

If the single-part route is ever reinstated, treat the digest as mandatory from that date: hex in the request body, and the same digest base64-encoded in Content-MD5 on the PUT.

Independently of that API rule, each part is compared with the MD5 S3 returns as its ETag. This is a local integrity check on an irreversible publication, reported as integrity; an opaque ETag (SSE-KMS) is reported as unverifiable rather than treated as a failure.

If anything fails once the session exists, the error carries [upload_id: ...]: the bytes may already be on Nexus, so do not re-upload. Check nexus_upload_status, then reuse that upload_id with nexus_publish_upload.

Guardrails:

  • two independent switches, NEXUS_ALLOW_WRITES and NEXUS_ALLOW_UPLOADS;

  • NEXUS_UPLOAD_ROOT confines which directory archives may be read from, so the model cannot ask the server to publish an arbitrary file;

  • name / version are validated against the Nexus patterns before any byte is sent;

  • dry_run: true reports the plan (size, transfer mode, target and category) without contacting Nexus;

  • an inconsistent part plan from Nexus aborts before a single byte is sent, and the server never retries an upload automatically after a storage rejection.

The nexus_release_update prompt drives the whole sequence and asks for confirmation before the real upload. nexus://upload-guide documents it for the model.

7. Applied best practices

  • Guided routing: server instructions, cross-referencing tool descriptions and a next_step field in results, so the model chains the right tools instead of probing.

  • No introspection needed: nexus://graphql-cheatsheet documents ModsFilter, ModsSort and the operators, verified against the live API. Introspection queries are rejected with a pointer to that resource.

  • Bounded responses: markup stripped (HTML/BBCode), archived files and upload history hidden by default, 45 000 character cap with an actionable message.

  • Quota aware: every reply ends with the remaining quota. The v2/GraphQL endpoint sends no x-rl-* headers, so the last known v1 snapshot is kept instead of showing ?/?.

  • Cache: reads are cached in memory (default 5 minutes, one hour for /games.json and the profile), so a repeated call costs nothing. refresh=true forces a fresh read.

  • Resilience: 20 s timeout, exponential-backoff retries on network errors and 5xx, HTTP status codes translated into actionable messages (401/403/404/429).

  • Protected writes: explicit opt-in via NEXUS_ALLOW_WRITES, a second opt-in (NEXUS_ALLOW_UPLOADS + NEXUS_UPLOAD_ROOT) for anything that publishes to a mod page, plus MCP annotations (readOnlyHint / destructiveHint) so clients can display the risk.

  • Resumable publishing: the upload id is always returned, so a failed publishing step never forces a re-upload (nexus_publish_upload).

  • Secrets out of the model: the key lives in the environment, never in tool arguments.

  • stderr-only logging: stdout is reserved for JSON-RPC (writing to it breaks the session).

8. Extending the server

File

Role

src/nexus-client.ts

HTTP transport: auth, retry, timeout, cache, rate limit

src/format.ts

Compaction: markup stripping, compactMod, compactFile

src/queries.ts

GraphQL documents + schema crib sheet

src/upload.ts

v3 upload session: MD5, presigned PUT, multipart, polling

src/index.ts

Tool/resource/prompt registration

scripts/smoke.mjs

Capability checks + live read-only test suite

Adding a tool means adding a server.registerTool(...) block in src/index.ts, then npm run build. Before doing so, ask whether an existing tool could return the information in the same call - that is usually the better change.

Add a check to scripts/smoke.mjs for anything you add, then run npm run smoke:live.

Available Tools

22 tools
nexus_add_changelogAdd changelog entriesA
Idempotent

Append changelog text for a version of one of your mods. Additive only: calling it twice for the same version appends, it does not replace. Write operation (NEXUS_ALLOW_WRITES=true).

Inputs: mod_id (required), version (required), changelog (required), game_domain_name.

nexus_endorse_modEndorse a modA
Idempotent

Endorse a mod or withdraw the endorsement. Write operation: requires NEXUS_ALLOW_WRITES=true. The version must match the one Nexus knows (nexus_get_mod returns it).

Inputs: action (required), mod_id (required), version, game_domain_name.

nexus_find_modsFind mods by nameA
Read-only

START HERE when you only know a mod's name/keywords: resolves it to a mod_id with version, author, downloads, endorsements, last update and page URL, in ONE call. Backed by the v2 search index - never write a raw GraphQL search yourself. If the name search finds nothing it automatically retries against mod descriptions, so a single call is normally enough. Follow up with nexus_mod_overview only if you need files or changelogs.

Inputs: tag, sort, limit, match, +10 more.

nexus_get_gameGame detailsA
Read-only

Return details for one game, including its category list (category_id values needed when publishing a mod).

Inputs: game_domain_name.

nexus_get_modMod metadataA
Read-only

Raw metadata for a single mod (name, version, author, counters, status). Narrow follow-up tool: if you also need files or changelogs, call nexus_mod_overview instead of chaining calls.

Inputs: mod_id (required), compact, refresh, game_domain_name.

nexus_get_mod_changelogsMod changelogsA
Read-only

Per-version changelogs for a mod, newest first. Narrow follow-up tool: nexus_mod_overview already returns the most recent versions.

Inputs: mod_id (required), refresh, versions, game_domain_name.

nexus_get_mod_filesMod filesA
Read-only

List the files published for a mod. By default archived/old versions and the upload history are filtered out. Pass file_id for a single file. For a complete picture (metadata + files + changelogs) prefer nexus_mod_overview.

Inputs: mod_id (required), compact, file_id, refresh, +4 more.

nexus_graphqlRaw GraphQL query (API v2)A
Read-only

Escape hatch for the v2 GraphQL API (collections, media, advanced filters). Read the nexus://graphql-cheatsheet resource first: the schema is already documented, so introspection queries are unnecessary. For plain mod searches use nexus_find_mods. Filters look like {"gameDomainName":[{"value":"","op":"EQUALS"}],"nameStemmed":[{"value":""}]}; sorts look like [{"updatedAt":{"direction":"DESC"}}].

Inputs: query (required), refresh, variables.

nexus_list_author_modsMods of an authorA
Read-only

List every mod published by a Nexus account, newest update first, in ONE call. With no argument it uses the authenticated account ('my mods'). Ideal for 'how are my mods doing?' - returns downloads, endorsements, version and last update for each mod.

Inputs: limit, user_id, uploader, all_games, +1 more.

nexus_list_gamesList gamesA
Read-only

List the games supported by Nexus Mods. Always pass 'filter' (case-insensitive, matches name or domain): the unfiltered catalogue has 2000+ entries. Use it to resolve a game domain before any other tool.

Inputs: limit, filter.

nexus_list_modsRecent / trending modsA
Read-only

Browse a game's feeds: latest_added, latest_updated, trending, or updated (needs period=1d|1w|1m). Use it for discovery; to look for a specific mod use nexus_find_mods instead.

Inputs: feed (required), limit, period, compact, +1 more.

nexus_mod_file_targetsUpload targets of a modA
Read-only

START HERE before updating a mod: lists the v3 identifiers needed to publish. Returns the internal mod id plus every mod file (the update chain shown on the Files tab) with its mod_file_id and latest versions. Pass the mod_file_id to nexus_upload_mod_file to release a new version of that file; omit it to create a brand new file. The numeric mod_id is the one in the page URL.

Inputs: mod_id (required), refresh, game_domain_name, include_versions, +1 more.

nexus_mod_overviewFull mod report (one call)A
Read-only

ONE-CALL mod report: metadata, currently published files grouped by category, recent changelogs and the page URL. Prefer this over chaining nexus_get_mod + nexus_get_mod_files + nexus_get_mod_changelogs. Archived/old files and the upload history are hidden unless you ask for them.

Inputs: mod_id (required), refresh, description, game_domain_name, +2 more.

nexus_publish_uploadPublish an existing uploadA

Recovery tool: turns an already finalised upload_id into a mod file or a new version, without re-sending the archive. Use it when nexus_upload_mod_file uploaded the bytes but the publishing step failed, or when the upload was still processing. Check nexus_upload_status shows state=available first.

Inputs: name (required), mod_id (required), version (required), upload_id (required), +10 more.

nexus_rate_limit_statusQuota and server statusA
Idempotent

Return the remaining Nexus quota (hourly/daily) plus this server's request and cache counters. Free of charge unless refresh=true. Use it before a batch of calls, or to decide whether to back off after an HTTP 429.

Inputs: refresh, clear_cache.

nexus_rename_mod_fileRename a mod fileA
Idempotent

Rename an existing mod file (the whole update chain, not a single version). Get the mod_file_id from nexus_mod_file_targets. Write operation (NEXUS_ALLOW_WRITES=true).

Inputs: name (required), mod_file_id (required).

nexus_search_md5Identify a file by MD5A
Read-only

Resolve an MD5 hash to its Nexus mod and file: the reliable way to identify an unknown local archive.

Inputs: md5_hash (required), game_domain_name.

nexus_tracked_modsTracked modsA
Idempotent

Manage the mods tracked by the authenticated user. action=list is read-only; track/untrack are writes and require NEXUS_ALLOW_WRITES=true.

Inputs: action (required), mod_id, game_domain_name.

nexus_upload_mod_fileUpload and publish a mod fileA

Uploads a local archive to Nexus and publishes it, in ONE call (v3 Upload API): creates the upload session, sends the bytes to the presigned storage URL, finalises, waits for processing, then either appends a new version to an existing file (pass mod_file_id, from nexus_mod_file_targets) or creates a new file on the mod page. Multipart is used automatically above 100 MiB. Optionally posts a changelog entry. Requires NEXUS_ALLOW_WRITES=true and NEXUS_ALLOW_UPLOADS=true. Always run with dry_run=true first to confirm the plan before the real upload.

Inputs: name (required), mod_id (required), version (required), file_path (required), +13 more.

nexus_upload_statusUpload session statusA
Read-only

Return the state of an upload session: 'created' means Nexus is still processing the archive, 'available' means it can be published with nexus_publish_upload.

Inputs: upload_id (required).

nexus_validate_userValidate API keyA
Read-only

Validate the Nexus API key and return the profile (name, user id, premium/supporter status). Call this first when authentication looks broken, or to learn the current user before nexus_list_author_mods.

Inputs: refresh.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 22 tools

    Disambiguation4/5

    There is real overlap: nexus_get_mod vs nexus_mod_overview, nexus_get_mod_files, and nexus_get_mod_changelogs all target the same mod resource, and nexus_find_mods vs nexus_list_mods vs nexus_graphql all cover search/discovery. The descriptions work hard to disambiguate ('prefer this over chaining', 'narrow follow-up tool', 'START HERE'), which largely resolves the boundaries for an attentive agent.

    Naming Consistency4/5

    Every tool uses a consistent nexus_ snake_case prefix with mostly verb_noun patterns (nexus_find_mods, nexus_upload_mod_file, nexus_endorse_mod). A few are noun-only (nexus_mod_overview, nexus_upload_status, nexus_mod_file_targets), a minor deviation but still readable and predictable.

    Tool Count4/5

    22 tools sits at the upper edge of the comfortable band, but each maps to a distinct capability across auth, discovery, mod lifecycle, uploads/publishing, and a GraphQL escape hatch. The upload chain (targets → upload → status → publish) justifies several tools, though a few read-only report tools could arguably be merged.

    Completeness5/5

    The surface is unusually thorough: authentication, quota checks, game/category resolution, search (name, MD5, feeds), mod metadata and files, changelogs, endorsements, tracking, the full upload/publish/recover workflow, file rename, and a documented GraphQL escape hatch. No obvious lifecycle gaps or dead ends for managing mods on Nexus.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI assistants to search, browse, and manage mods across Nexus Mods, mod.io, Thunderstore, and Modrinth, as well as perform local diagnostics like detecting games and parsing crash logs.
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI agents to search, read, and publish Minecraft mods on Modrinth, including creating projects and uploading jar files as new versions.
      7
      310 npm
      1
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables any MCP client to search, browse, download, and manage Nexus Mods content—games, mods, files, comments, collections, and user preferences—through a unified REST and GraphQL interface with caching and OAuth2 support.
      135
      48 PyPI
      3
      MIT
    • A
      license
      B
      quality
      B
      maintenance
      Enables interacting with Nexus Mods: search and download mods, manage endorsements and uploads, and use a browser session for comments, forums, and private messages.
      22
      57
      Creative Commons Attribution Non Commercial 4.0 International