renovate-mcp 1.6.8 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +80 -34
  2. package/dist/data/datasources.generated.js +92 -0
  3. package/dist/index.js +6 -3
  4. package/dist/lib/configLinter.js +152 -23
  5. package/dist/lib/configLocations.js +14 -0
  6. package/dist/lib/dependencyExplainer.js +3 -1
  7. package/dist/lib/endpointValidator.js +1 -1
  8. package/dist/lib/externalPresetFetcher.js +81 -9
  9. package/dist/lib/gitRemote.js +24 -11
  10. package/dist/lib/packageRulesAnalysis.js +3 -1
  11. package/dist/lib/packageRulesWorker.js +4 -3
  12. package/dist/lib/packageRulesWorkerImpl.js +249 -2
  13. package/dist/lib/presetResolver.js +36 -17
  14. package/dist/lib/renovateCli.js +1 -1
  15. package/dist/lib/renovateWorker.js +2 -1
  16. package/dist/lib/scheduleWorker.js +34 -0
  17. package/dist/lib/scheduleWorkerImpl.js +102 -0
  18. package/dist/lib/setupCheck.js +46 -20
  19. package/dist/lib/toolInputs.js +19 -1
  20. package/dist/lib/version.js +1 -1
  21. package/dist/prompts/workflows.js +31 -5
  22. package/dist/resources/options.js +28 -0
  23. package/dist/tools/annotateDryRun.js +6 -4
  24. package/dist/tools/checkSetup.js +2 -0
  25. package/dist/tools/dryRun.js +3 -2
  26. package/dist/tools/dryRunDiff.js +2 -0
  27. package/dist/tools/explainConfig.js +6 -4
  28. package/dist/tools/explainDependency.js +2 -1
  29. package/dist/tools/explainSchedule.js +88 -0
  30. package/dist/tools/getVersion.js +2 -0
  31. package/dist/tools/lintConfig.js +2 -0
  32. package/dist/tools/migrateConfig.js +2 -0
  33. package/dist/tools/previewCustomManager.js +2 -1
  34. package/dist/tools/readConfig.js +4 -3
  35. package/dist/tools/resolveConfig.js +8 -7
  36. package/dist/tools/resolveConfigDiff.js +6 -5
  37. package/dist/tools/suggestPresets.js +2 -0
  38. package/dist/tools/testPackageRules.js +35 -11
  39. package/dist/tools/validateConfig.js +2 -0
  40. package/dist/tools/writeConfig.js +1 -0
  41. package/package.json +2 -1
package/README.md CHANGED
@@ -9,24 +9,15 @@ An MCP server for designing [Renovate](https://github.com/renovatebot/renovate)
9
9
 
10
10
  ## Quick start
11
11
 
12
- ```bash
13
- curl -fsSL https://raw.githubusercontent.com/tibuntu/renovate-mcp/main/install.sh | bash
14
- ```
15
-
16
- Or add this entry manually to your client's `mcpServers` config:
12
+ Needs Node.js ≥ 24.11 on Linux or macOS. With [Claude Code](https://docs.claude.com/en/docs/claude-code):
17
13
 
18
- ```json
19
- {
20
- "mcpServers": {
21
- "renovate": {
22
- "command": "npx",
23
- "args": ["-y", "renovate-mcp"]
24
- }
25
- }
26
- }
14
+ ```bash
15
+ npm install -g renovate-mcp
16
+ claude mcp add renovate -s user -- renovate-mcp
17
+ claude mcp list # renovate should show as connected
27
18
  ```
28
19
 
29
- Restart your client and try the prompt: *"List the namespaces available under `renovate://presets`."* A response listing `config`, `docker`, `npm`, … confirms the server is reachable. Long-form install options are under [Install](#install) below.
20
+ Then try the prompt: *"List the namespaces available under `renovate://presets`."* A response listing `config`, `docker`, `npm`, … confirms the server is reachable. No tokens are needed for the offline tools. Other clients, tokens and upgrades are under [Install](#install) below.
30
21
 
31
22
  ## What you can do
32
23
 
@@ -39,7 +30,7 @@ Restart your client and try the prompt: *"List the namespaces available under `r
39
30
 
40
31
  ## Tools & resources
41
32
 
42
- Seventeen tools, six resources, and three workflow prompts. Each tool name below links to its full reference in [`docs/tools.md`](docs/tools.md).
33
+ Eighteen tools, seven resources, and four workflow prompts. Each tool name below links to its full reference in [`docs/tools.md`](docs/tools.md).
43
34
 
44
35
  | Tool | Purpose |
45
36
  | --- | --- |
@@ -47,10 +38,10 @@ Seventeen tools, six resources, and three workflow prompts. Each tool name below
47
38
  | [`get_version`](docs/tools.md#get_version) | Report the renovate-mcp server version and whether it's a released or local/dev build. |
48
39
  | [`read_config`](docs/tools.md#read_config) | Locate and parse a repo's Renovate config in Renovate's own discovery order. |
49
40
  | [`suggest_presets`](docs/tools.md#suggest_presets) | Search built-in (and optional local-repo) presets by natural-language intent; ranks matches and sketches an unvalidated draft config skeleton for multi-facet or weakly-covered intents. Offline. |
50
- | [`resolve_config`](docs/tools.md#resolve_config) | Expand every `extends` preset offline. Opt in to fetching `github>` / `gitlab>` presets over HTTPS. |
41
+ | [`resolve_config`](docs/tools.md#resolve_config) | Expand every `extends` preset offline. Opt in to fetching `github>` / `gitlab>` / `gitea>` / `forgejo>` presets over HTTPS, or Bitbucket Cloud presets via `local>` + `platform`. |
51
42
  | [`explain_config`](docs/tools.md#explain_config) | Inverse of `resolve_config`: annotate every leaf field with the chain of presets that set it. |
52
43
  | [`resolve_config_diff`](docs/tools.md#resolve_config_diff) | Offline structural diff of two fully-resolved configs — changed fields plus an order-insensitive set diff of array keys (`packageRules`, `customManagers`, …). The refactor-friendly counterpart to `dry_run_diff`. |
53
- | [`test_package_rules`](docs/tools.md#test_package_rules) | Offline what-if: which `packageRules` match a hypothetical dependency, which matcher decided each, and what each contributes. Faithful (Renovate's real matchers in a worker); fields you don't supply are reported as unevaluatable, not non-matches. |
44
+ | [`test_package_rules`](docs/tools.md#test_package_rules) | Offline what-if: which `packageRules` match a hypothetical dependency, which matcher decided each, and what each contributes — plus the `branchName` / `commitMessage` / `prTitle` the matched config would render for it. Faithful matching (Renovate's real matchers in a worker); fields you don't supply are reported as unevaluatable, not non-matches, and the rendered names list the post-lookup template variables they could not fill. |
54
45
  | [`preview_custom_manager`](docs/tools.md#preview_custom_manager) | Preview a `customManagers` entry (regex or JSONata) against a local repo. Offline. |
55
46
  | [`validate_config`](docs/tools.md#validate_config) | Run `renovate-config-validator` against a file or inline object. |
56
47
  | [`lint_config`](docs/tools.md#lint_config) | Semantic lint pass for Renovate-specific footguns the schema validator declares valid. Offline. |
@@ -58,6 +49,7 @@ Seventeen tools, six resources, and three workflow prompts. Each tool name below
58
49
  | [`dry_run_diff`](docs/tools.md#dry_run_diff) | Stateless semantic diff between two `dry_run` reports — added / removed / changed updates. |
59
50
  | [`annotate_dry_run`](docs/tools.md#annotate_dry_run) | Attribute each proposed update in a `dry_run` report to the `packageRules` that caused it; flags rules that never matched and report fields the matchers needed but the report lacked. Stateless + offline. |
60
51
  | [`explain_dependency`](docs/tools.md#explain_dependency) | Answer "why was/wasn't dependency X updated?" from a `dry_run` report: per-occurrence `skipReason`, proposed `updates`, `warnings`, a one-line verdict and remedy hints; optionally the `packageRules` that matched it. Stateless + offline. |
52
+ | [`explain_schedule`](docs/tools.md#explain_schedule) | Answer "when will this `schedule` actually run?": active-now plus the next run windows in UTC and the config's `timezone`, computed with Renovate's own schedule matcher; invalid schedules return Renovate's message and the fact that it then runs at any time. Offline. |
61
53
  | [`migrate_config`](docs/tools.md#migrate_config) | Apply Renovate's built-in migrations and return the migrated config plus a unified diff. Does not write. |
62
54
  | [`write_config`](docs/tools.md#write_config) | Validate, then atomically write a config to disk. Preserves comments/key order on existing JSON-with-comments files; for `package.json` edits only the nested `renovate` key. |
63
55
  | [`renovate://presets`](docs/tools.md#renovatepresets) (resource) | Markdown index of all built-in presets grouped by namespace. |
@@ -66,21 +58,23 @@ Seventeen tools, six resources, and three workflow prompts. Each tool name below
66
58
  | [`renovate://options`](docs/tools.md#renovateoptions) (resource) | Markdown index of all Renovate config options, split into repository and global/self-hosted-only sections. |
67
59
  | [`renovate://option/{name}`](docs/tools.md#renovateoptionname) (resource) | Full definition of one config option as JSON. |
68
60
  | [`renovate://managers`](docs/tools.md#renovatemanagers) (resource) | Markdown list of all manager names, including custom managers. |
61
+ | [`renovate://datasources`](docs/tools.md#renovatedatasources) (resource) | Markdown list of all built-in datasource ids. |
69
62
 
70
- **Prompts.** Three workflow prompts package the tool sequences above into ready-to-run starting points. Claude Code surfaces MCP prompts as slash commands (e.g. `/mcp__renovate__design-renovate-config`); other MCP clients expose them through their own prompt picker.
63
+ **Prompts.** Four workflow prompts package the tool sequences above into ready-to-run starting points. Claude Code surfaces MCP prompts as slash commands (e.g. `/mcp__renovate__design-renovate-config`); other MCP clients expose them through their own prompt picker.
71
64
 
72
65
  | Prompt | Drives |
73
66
  | --- | --- |
74
67
  | [`design-renovate-config`](docs/tools.md#design-renovate-config) | `check_setup` → `read_config` → `suggest_presets` → `resolve_config`/`explain_config` → `validate_config`/`lint_config` → `dry_run` → `write_config` on confirmation. |
75
- | [`debug-package-rule`](docs/tools.md#debug-package-rule) | `check_setup` → `read_config` → `test_package_rules` → `dry_run` (with `reportOutputPath`) → `annotate_dry_run` → `explain_dependency` → a verdict naming rule indices and matchers. |
68
+ | [`debug-package-rule`](docs/tools.md#debug-package-rule) | `check_setup` → `read_config` → `test_package_rules` (matches + rendered names) → `dry_run` (with `reportOutputPath`) → `annotate_dry_run` → `explain_dependency` → a verdict naming rule indices and matchers. |
76
69
  | [`author-custom-manager`](docs/tools.md#author-custom-manager) | Draft a `customManagers` entry → `preview_custom_manager` iteratively → `validate_config`/`lint_config` → `dry_run` → `write_config` on confirmation. |
70
+ | [`onboard-repo`](docs/tools.md#onboard-repo) | For repos without a config: `check_setup` → `read_config` (expect none) → `dry_run` (extract mode) → `suggest_presets` → `lint_config`/`validate_config` → `write_config` on confirmation → full `dry_run`. |
77
71
 
78
72
  ## Requirements
79
73
 
80
74
  - **Linux or macOS.** Windows is not supported — `package.json` declares `"os": ["darwin", "linux"]`, so `npm i` surfaces an `EBADPLATFORM` warning on Windows and the server exits with a clear stderr message at startup. Use WSL2 or a Linux/macOS host instead.
81
75
  - **Node.js ≥ 24.11** (aligns with Renovate's own engine requirement).
82
76
 
83
- Renovate ships bundled — the `renovate` package is a runtime dependency, so `validate_config`, `dry_run`, and `write_config` work out of the box with no separate install. The offline tools (`read_config`, `suggest_presets`, `resolve_config`, `explain_config`, `resolve_config_diff`, `test_package_rules`, `preview_custom_manager`, `lint_config`, `dry_run_diff`, `annotate_dry_run`, `explain_dependency`, `migrate_config`) never spawn Renovate at all — nor do `check_setup` (by default it reads version metadata from the bundled install instead of spawning) or `get_version` (it only reports the renovate-mcp server's own version).
77
+ Renovate ships bundled — the `renovate` package is a runtime dependency, so `validate_config`, `dry_run`, and `write_config` work out of the box with no separate install. The offline tools (`read_config`, `suggest_presets`, `resolve_config`, `explain_config`, `resolve_config_diff`, `test_package_rules`, `preview_custom_manager`, `lint_config`, `dry_run_diff`, `annotate_dry_run`, `explain_dependency`, `explain_schedule`, `migrate_config`) never spawn Renovate at all — nor do `check_setup` (by default it reads version metadata from the bundled install instead of spawning) or `get_version` (it only reports the renovate-mcp server's own version).
84
78
 
85
79
  **Optional env vars:**
86
80
 
@@ -93,30 +87,76 @@ Renovate ships bundled — the `renovate` package is a runtime dependency, so `v
93
87
 
94
88
  ## Install
95
89
 
96
- ### Quick install
90
+ ### 1. Install the package
91
+
92
+ ```bash
93
+ npm install -g renovate-mcp
94
+ ```
95
+
96
+ Install before you register the server. The package bundles Renovate (about 670 packages, 350 MB), so a client that starts `npx -y renovate-mcp` against a cold npm cache downloads all of that during server startup and can time out.
97
+
98
+ Upgrade with `npm install -g renovate-mcp@latest` and restart your client. With nvm or fnm, global packages belong to one Node version — reinstall after switching.
99
+
100
+ ### 2. Register it with your client
101
+
102
+ **Claude Code:**
103
+
104
+ ```bash
105
+ claude mcp add renovate -s user -- renovate-mcp
106
+ ```
107
+
108
+ `-s user` makes the server available in every project. Leave it out to register it for the current project only, or use `-s project` to write a shared `.mcp.json`.
109
+
110
+ **Claude Desktop and other GUI clients** don't load your shell's PATH, so with nvm, fnm or Homebrew they may find no Node or an older one. Use absolute paths:
97
111
 
98
112
  ```bash
99
- curl -fsSL https://raw.githubusercontent.com/tibuntu/renovate-mcp/main/install.sh | bash
113
+ which node # → "command"
114
+ echo "$(npm root -g)/renovate-mcp/dist/index.js" # → "args"
100
115
  ```
101
116
 
102
- The script checks Node ≥ 24, asks whether you want `npx`-on-demand or a global install, runs an MCP `initialize` handshake to verify the binary works, and — when the Claude Code CLI is on PATH — auto-registers `renovate` in user scope via `claude mcp add`. Useful flags (pass after `bash -s --`): `--global`, `--npx`, `--no-mcp-add`, `--mcp-scope=user|project|local`, `--version=X.Y.Z`. The same flags are also accepted as env vars (`RENOVATE_MCP_GLOBAL=1`, `RENOVATE_MCP_NO_MCP_ADD=1`, `RENOVATE_MCP_VERSION=…`).
117
+ Claude Desktop reads `~/Library/Application Support/Claude/claude_desktop_config.json`:
103
118
 
104
- ### Manual install
119
+ ```json
120
+ {
121
+ "mcpServers": {
122
+ "renovate": {
123
+ "command": "/Users/you/.nvm/versions/node/v24.14.1/bin/node",
124
+ "args": ["/Users/you/.nvm/versions/node/v24.14.1/lib/node_modules/renovate-mcp/dist/index.js"]
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ Any other client that can launch a stdio MCP server works with the same command.
105
131
 
106
- `npx` fetches the [published package](https://www.npmjs.com/package/renovate-mcp) on demand — no manual install needed. For local development, clone and build first:
132
+ ### 3. Add tokens (optional)
133
+
134
+ The offline tools need nothing. Add env vars only for what you use:
135
+
136
+ | Use case | Env vars |
137
+ | --- | --- |
138
+ | Local `dry_run` on a repo with github.com dependencies | `GITHUB_COM_TOKEN` (read-only PAT, avoids anonymous rate limits) |
139
+ | `dry_run` against GitHub, or private `github>` presets | `RENOVATE_PLATFORM=github` + `GITHUB_TOKEN` |
140
+ | `dry_run` against GitLab, or private `gitlab>` presets | `RENOVATE_PLATFORM=gitlab` + `GITLAB_TOKEN` |
141
+
142
+ Self-hosted GitLab and GitHub Enterprise also need `RENOVATE_ENDPOINT` — see [Platform setup](#platform-setup).
143
+
144
+ In Claude Code, re-register with `-e`:
107
145
 
108
146
  ```bash
109
- npm install
110
- npm run build
147
+ claude mcp remove renovate -s user
148
+ claude mcp add renovate -s user -e GITHUB_COM_TOKEN=ghp_xxx -- renovate-mcp
111
149
  ```
112
150
 
113
- ### Client config locations
151
+ - `-e` stores the value in plain text in `~/.claude.json`. Keep tokens in `user` or `local` scope, never in a `project`-scope `.mcp.json` that gets committed.
152
+ - `-e` accepts several values, so keep the `--` before `renovate-mcp`.
153
+ - Other clients take the same variables in an `"env": { … }` block next to `"args"`.
114
154
 
115
- - **Claude Code** — `.mcp.json` (project) or `~/.claude.json` (user).
116
- - **Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json`.
117
- - **Other MCP clients** — any client that can launch a stdio MCP server works; point it at the same `npx -y renovate-mcp` command.
155
+ ### Alternatives
118
156
 
119
- For local development, swap to `"command": "node"` with `"args": ["/absolute/path/to/renovate-mcp/dist/index.js"]`.
157
+ - **npx without a global install** — register `npx -y renovate-mcp` instead, after warming the npm cache once with `npm exec --yes --package=renovate-mcp -- node -e 0`.
158
+ - **Installer script** — `curl -fsSL https://raw.githubusercontent.com/tibuntu/renovate-mcp/main/install.sh | bash` runs steps 1 and 2 interactively: it checks Node, asks for `npx` or global mode, verifies the server with an MCP `initialize` handshake, and registers it via `claude mcp add` when the Claude Code CLI is on PATH. Flags (after `bash -s --`): `--global`, `--npx`, `--no-mcp-add`, `--mcp-scope=user|project|local`, `--version=X.Y.Z`; also accepted as env vars (`RENOVATE_MCP_GLOBAL=1`, `RENOVATE_MCP_NO_MCP_ADD=1`, `RENOVATE_MCP_VERSION=…`).
159
+ - **From source** — clone, run `npm install && npm run build`, and point your client at `node /absolute/path/to/renovate-mcp/dist/index.js`.
120
160
 
121
161
  ## Platform setup
122
162
 
@@ -128,6 +168,11 @@ For `dry_run` against a remote platform or `resolve_config` with `externalPreset
128
168
  | GitHub Enterprise | `github` | `https://github.example.com/api/v3/` | `RENOVATE_TOKEN` *or* `GITHUB_TOKEN` |
129
169
  | gitlab.com | `gitlab` | (omit) | `RENOVATE_TOKEN` *or* `GITLAB_TOKEN` |
130
170
  | Self-hosted GitLab | `gitlab` | `https://gitlab.example.com/api/v4/` | `RENOVATE_TOKEN` *or* `GITLAB_TOKEN` |
171
+ | gitea.com / Forgejo (code.forgejo.org) | — | (omit) | `RENOVATE_TOKEN` *or* `GITEA_TOKEN` / `FORGEJO_TOKEN` |
172
+ | Self-hosted Gitea / Forgejo | — | `https://forgejo.example.com/api/v1/` | `RENOVATE_TOKEN` *or* `GITEA_TOKEN` / `FORGEJO_TOKEN` |
173
+ | Bitbucket Cloud | — | (omit) | `RENOVATE_TOKEN` *or* `BITBUCKET_TOKEN` |
174
+
175
+ The Gitea / Forgejo / Bitbucket Cloud rows apply to the `resolve_config` family only: `dry_run` runs Renovate as `local` / `github` / `gitlab` and never reads those tokens. Pass `platform` (`gitea` / `forgejo` / `bitbucket`) and `endpoint` as tool inputs instead of `RENOVATE_PLATFORM` / `RENOVATE_ENDPOINT`.
131
176
 
132
177
  See [`docs/platform-setup.md`](docs/platform-setup.md) for a worked self-hosted GitLab example, `local>` preset handling, and private-registry credentials.
133
178
 
@@ -208,6 +253,7 @@ npm run generate:presets # regenerate src/data/presets.generated.ts
208
253
  npm run generate:managers # regenerate src/data/managers.generated.ts
209
254
  npm run generate:migrations # regenerate src/data/migrations.generated.ts
210
255
  npm run generate:options # regenerate src/data/options.generated.ts
256
+ npm run generate:datasources # regenerate src/data/datasources.generated.ts
211
257
  npm run sync:jsonata-pin # sync package.json's jsonata pin to Renovate's own
212
258
  npm run check:snapshot-versions # fail if any src/data/*.generated.ts is stale vs installed renovate
213
259
  ```
@@ -228,4 +274,4 @@ See [`docs/development.md`](docs/development.md) for snapshot-file mechanics, CI
228
274
  - **Not a Renovate replacement.** This server doesn't open PRs, run scheduled updates, or execute in CI — it's a design-time companion for a local `renovate.json`. Use the real Renovate for the actual dependency-update pipeline.
229
275
  - **`resolve_config` merges faithfully.** Preset expansion runs offline against a committed snapshot, then folds with Renovate's real `mergeChildConfig` in a worker thread — `mergeQuality: "faithful"` (it falls back to an approximate in-process merge as `"preview"` only if the worker is unavailable). Template substitution still implements only positional `{{argN}}` placeholders — non-positional tokens and Handlebars helpers are flagged in `warnings` and pass through verbatim. For full config resolution (datasource lookups, etc.), run `dry_run`.
230
276
  - **`preview_custom_manager` is a subset of Renovate's custom managers.** It covers `customType: "regex"` (with `matchStringsStrategy` of `any` / `combination` / `recursive`) and `customType: "jsonata"` (with `fileFormat: "json" | "yaml" | "toml"`). File selection follows Renovate's `managerFilePatterns` semantics (globs or `/regex/`, entries unioned like Renovate's extract phase); the deprecated `fileMatch` is still accepted with a warning. Template substitution is `{{groupName}}` only — full Handlebars helpers/conditionals are not implemented. Other custom types (e.g. `html`) are out of scope. Use it for fast iteration; confirm with `dry_run`. Full coverage matrix in [`docs/tools.md#preview_custom_manager`](docs/tools.md#preview_custom_manager).
231
- - **`dry_run` still uses fake binaries in CI.** The bundled Renovate is available (it's a runtime dependency), and `validate_config` / `write_config` now have one real-validator integration test (`test/integration/realValidator.test.ts`) exercising the actual bundled `renovate-config-validator` — but `dry_run` itself still runs per-PR against fake binaries via `RENOVATE_BIN` / `RENOVATE_CONFIG_VALIDATOR_BIN` env overrides, for determinism and speed. A separate nightly workflow re-runs the full suite against `renovate@latest` end-to-end. Run `dry_run` locally against a real config to validate behaviour.
277
+ - **`dry_run` still uses fake binaries in CI.** The bundled Renovate is available (it's a runtime dependency), and `validate_config` / `write_config` have real-validator integration tests (`test/integration/realValidator.test.ts`) exercising the actual bundled `renovate-config-validator` — but `dry_run` itself still runs per-PR against fake binaries via `RENOVATE_BIN` / `RENOVATE_CONFIG_VALIDATOR_BIN` env overrides, for determinism and speed. A separate nightly workflow re-runs the full suite against `renovate@latest` end-to-end. Run `dry_run` locally against a real config to validate behaviour.
@@ -0,0 +1,92 @@
1
+ // AUTO-GENERATED by scripts/generate-datasources.mjs from renovate@44.181.5.
2
+ // Do not edit by hand. Run `npm run generate:datasources` to refresh.
3
+ export const RENOVATE_VERSION = "44.181.5";
4
+ /**
5
+ * All built-in datasource ids accepted by Renovate's `matchDatasources` and
6
+ * `customManagers[].datasourceTemplate`. Custom datasources (`custom.<name>`)
7
+ * are not listed.
8
+ */
9
+ export const DATASOURCES = Object.freeze([
10
+ "apk",
11
+ "artifactory",
12
+ "aws-eks-addon",
13
+ "aws-machine-image",
14
+ "aws-rds",
15
+ "azure-bicep-resource",
16
+ "azure-pipelines-tasks",
17
+ "azure-tags",
18
+ "bazel",
19
+ "bitbucket-server-tags",
20
+ "bitbucket-tags",
21
+ "bitrise",
22
+ "buildpacks-registry",
23
+ "cdnjs",
24
+ "clojure",
25
+ "conan",
26
+ "conda",
27
+ "cpan",
28
+ "crate",
29
+ "custom",
30
+ "dart",
31
+ "dart-version",
32
+ "deb",
33
+ "deno",
34
+ "devbox",
35
+ "docker",
36
+ "dotnet-version",
37
+ "elm-package",
38
+ "endoflife-date",
39
+ "flutter-version",
40
+ "forgejo-releases",
41
+ "forgejo-tags",
42
+ "galaxy",
43
+ "galaxy-collection",
44
+ "git-refs",
45
+ "git-tags",
46
+ "gitea-releases",
47
+ "gitea-tags",
48
+ "github-digest",
49
+ "github-release-attachments",
50
+ "github-releases",
51
+ "github-runners",
52
+ "github-tags",
53
+ "gitlab-packages",
54
+ "gitlab-releases",
55
+ "gitlab-tags",
56
+ "glasskube-packages",
57
+ "go",
58
+ "golang-version",
59
+ "gradle-version",
60
+ "hackage",
61
+ "helm",
62
+ "hermit",
63
+ "hex",
64
+ "hexpm-bob",
65
+ "java-version",
66
+ "jenkins-plugins",
67
+ "jsr",
68
+ "kubernetes-api",
69
+ "maven",
70
+ "nextcloud",
71
+ "node-version",
72
+ "npm",
73
+ "nuget",
74
+ "orb",
75
+ "packagist",
76
+ "pod",
77
+ "puppet-forge",
78
+ "pypi",
79
+ "python-version",
80
+ "repology",
81
+ "rpm",
82
+ "ruby-version",
83
+ "rubygems",
84
+ "rust-version",
85
+ "sbt-package",
86
+ "sbt-plugin",
87
+ "terraform-module",
88
+ "terraform-provider",
89
+ "typst",
90
+ "unity3d",
91
+ "unity3d-packages"
92
+ ]);
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { registerDryRunDiff } from "./tools/dryRunDiff.js";
15
15
  import { registerTestPackageRules } from "./tools/testPackageRules.js";
16
16
  import { registerAnnotateDryRun } from "./tools/annotateDryRun.js";
17
17
  import { registerExplainDependency } from "./tools/explainDependency.js";
18
+ import { registerExplainSchedule } from "./tools/explainSchedule.js";
18
19
  import { registerWriteConfig } from "./tools/writeConfig.js";
19
20
  import { registerMigrateConfig } from "./tools/migrateConfig.js";
20
21
  import { registerGetVersion } from "./tools/getVersion.js";
@@ -50,13 +51,14 @@ const BASE_INSTRUCTIONS = [
50
51
  " 11. dry_run_diff — semantic diff between two dry_run reports (added/removed/changed updates)",
51
52
  " 12. annotate_dry_run — attribute each proposed update in a dry_run report to the packageRules that caused it (and flag rules that never matched)",
52
53
  " 13. explain_dependency — why was/wasn't a given dependency updated: skipReason, updates, warnings and matched rules, read from a dry_run report (offline)",
53
- " 14. migrate_config — apply Renovate's built-in migrations (deprecated keys → current schema) and return the migrated config",
54
- " 15. write_config — save the agreed-upon config (validates first)",
54
+ " 14. explain_schedule — when will a `schedule` actually run: active-now plus the next windows in UTC and the config's timezone, or Renovate's own invalid-schedule message (offline)",
55
+ " 15. migrate_config — apply Renovate's built-in migrations (deprecated keys → current schema) and return the migrated config",
56
+ " 16. write_config — save the agreed-upon config (validates first)",
55
57
  "",
56
58
  "Before the first repo-touching tool call in a session (read_config, resolve_config, dry_run, write_config, …), call check_setup with the same repoPath. It surfaces token / endpoint / connectivity problems up front (e.g. \"set GITHUB_TOKEN or github-actions deps will be skipped\") instead of waiting for dry_run to fail. Skip if the user has already confirmed setup or is doing offline-only work that doesn't depend on git origin or registries.",
57
59
  "If any tool fails unexpectedly, call check_setup to diagnose CLI availability.",
58
60
  "Built-in preset reference: renovate://presets (namespace index), renovate://presets/{namespace} (one namespace), renovate://preset/{name} (one preset's expanded JSON).",
59
- "Config option reference: renovate://options (index of all config options), renovate://option/{name} (one option's full definition). Manager names: renovate://managers.",
61
+ "Config option reference: renovate://options (index of all config options), renovate://option/{name} (one option's full definition). Manager names: renovate://managers. Datasource ids: renovate://datasources.",
60
62
  ].join("\n");
61
63
  if (process.platform === "win32") {
62
64
  process.stderr.write("renovate-mcp does not support Windows. Supported platforms are Linux and macOS — see the `os` field in package.json. Run on WSL2 or a Linux/macOS host instead.\n");
@@ -84,6 +86,7 @@ registerDryRunDiff(server);
84
86
  registerTestPackageRules(server);
85
87
  registerAnnotateDryRun(server);
86
88
  registerExplainDependency(server);
89
+ registerExplainSchedule(server);
87
90
  registerMigrateConfig(server);
88
91
  registerWriteConfig(server);
89
92
  registerGetVersion(server);
@@ -1,4 +1,5 @@
1
1
  import { ALL_MANAGERS, CUSTOM_MANAGERS } from "../data/managers.generated.js";
2
+ import { DATASOURCES } from "../data/datasources.generated.js";
2
3
  import { DEPRECATED_KEYS, } from "../data/migrations.generated.js";
3
4
  import { isRecord } from "./util.js";
4
5
  const REGEX_AWARE_FIELDS = new Set([
@@ -12,23 +13,28 @@ const VALID_MANAGER_NAMES = new Set([
12
13
  ...ALL_MANAGERS,
13
14
  ...CUSTOM_MANAGERS.map((m) => `custom.${m}`),
14
15
  ]);
16
+ const VALID_DATASOURCES = new Set(DATASOURCES);
15
17
  const DEPRECATED_KEY_LOOKUP = new Map(DEPRECATED_KEYS.map((e) => [e.oldKey, e]));
16
18
  // Hand-maintained snapshot of keys that count as "actions" on a packageRules entry
17
19
  // (i.e. keys whose presence means the rule actually *does* something to the matched
18
20
  // deps). Source of truth: `node_modules/renovate/dist/config/options/index.js` —
19
21
  // entries whose `parents` array includes `"packageRules"`, plus the broader set of
20
22
  // inheritable config options Renovate allows inside a packageRules entry
21
- // (groupName, automerge, labels, schedule, …). Reconciled against renovate@43.150.0.
22
- // Bumping the `renovate` devDep should prompt a manual re-check. We deliberately do
23
- // NOT generate this list at build time: per CLAUDE.md, the linter must stay
24
- // independent of the runtime `renovate` import, so this stays a curated snapshot
25
- // — analogous to the other allow-lists under `src/data/`.
23
+ // (groupName, automerge, labels, schedule, …). We deliberately do NOT generate this
24
+ // list at build time: per CLAUDE.md, the linter must stay independent of the runtime
25
+ // `renovate` import, so this stays a curated snapshot — analogous to the other
26
+ // allow-lists under `src/data/`. Drift guard: test/unit/allowListDrift.test.ts checks
27
+ // every entry against the installed Renovate's option registry (real option, not
28
+ // global-only, `scopes` allow packageRules) and that every packageRules-only option
29
+ // is listed, so a `renovate` bump fails there instead of leaving this stale.
26
30
  //
27
31
  // Explicitly NOT in this set (kept here as documentation):
28
32
  // - `description` (metadata)
29
33
  // - every `match*` / `exclude*` key (selectors)
30
34
  // - `paths` / `excludePaths` (path-scope selectors)
31
35
  // - `matchJsonata`, `matchNewValue`, `matchCurrentAge` etc. (selectors)
36
+ // - `vulnerabilityAlerts`, `dependencyDashboardLabels` (Renovate's option `scopes`
37
+ // restrict them to the top level; its validator warns inside a packageRule)
32
38
  // - `changelogUrl`, `sourceDirectory`, `sourceUrl` (metadata/locator overrides —
33
39
  // treated as actions below since they actively override resolved fields)
34
40
  export const PACKAGE_RULE_ACTION_KEYS = new Set([
@@ -51,7 +57,6 @@ export const PACKAGE_RULE_ACTION_KEYS = new Set([
51
57
  "commitMessageSuffix",
52
58
  "commitMessageTopic",
53
59
  "dependencyDashboardApproval",
54
- "dependencyDashboardLabels",
55
60
  "draftPR",
56
61
  "enabled",
57
62
  "extends",
@@ -102,7 +107,6 @@ export const PACKAGE_RULE_ACTION_KEYS = new Set([
102
107
  "sourceUrl",
103
108
  "stopUpdatingLabel",
104
109
  "versioning",
105
- "vulnerabilityAlerts",
106
110
  ]);
107
111
  const DEPRECATED_KEY_CONTAINERS = ["packageRules", "hostRules", "customManagers"];
108
112
  // Schedule-validation heuristic. Behavioral source of truth:
@@ -121,6 +125,9 @@ const DEPRECATED_KEY_CONTAINERS = ["packageRules", "hostRules", "customManagers"
121
125
  // a deliberate bias toward false negatives over false positives: a non-later
122
126
  // string that happens to contain one of these anchor tokens gets a free pass,
123
127
  // but every plausible later-text input will contain at least one of them.
128
+ // Drift guard: test/unit/allowListDrift.test.ts parses each token (in a minimal
129
+ // phrase) with the `@breejs/later` Renovate ships, so a token the grammar does
130
+ // not know can't sneak in or linger after a bump.
124
131
  // Special-cased sentinels meaning "no schedule". Renovate treats these as
125
132
  // always-on.
126
133
  const SCHEDULE_ANY_TIME = new Set(["", "at any time"]);
@@ -132,12 +139,10 @@ const SCHEDULE_MAPPING_LITERALS = new Set([
132
139
  ]);
133
140
  // Lowercase tokens. Presence of at least one (whole-word, case-insensitive)
134
141
  // makes a non-cron string plausibly-later-text. Curated from later's grammar.
135
- const LATER_TEXT_ANCHOR_TOKENS = new Set([
142
+ export const LATER_TEXT_ANCHOR_TOKENS = new Set([
136
143
  // Time anchors
137
144
  "am",
138
145
  "pm",
139
- "noon",
140
- "midnight",
141
146
  "before",
142
147
  "after",
143
148
  "between",
@@ -181,10 +186,7 @@ const LATER_TEXT_ANCHOR_TOKENS = new Set([
181
186
  "feb",
182
187
  "mar",
183
188
  "apr",
184
- "jun",
185
- "jul",
186
189
  "aug",
187
- "sep",
188
190
  "sept",
189
191
  "oct",
190
192
  "nov",
@@ -195,9 +197,7 @@ const LATER_TEXT_ANCHOR_TOKENS = new Set([
195
197
  "week",
196
198
  "weeks",
197
199
  "weekday",
198
- "weekdays",
199
200
  "weekend",
200
- "weekends",
201
201
  "month",
202
202
  "months",
203
203
  "year",
@@ -206,9 +206,6 @@ const LATER_TEXT_ANCHOR_TOKENS = new Set([
206
206
  "hours",
207
207
  "minute",
208
208
  "minutes",
209
- "morning",
210
- "evening",
211
- "night",
212
209
  ]);
213
210
  // Cron-shape: 5 or 6 whitespace-separated fields, every char in `[0-9*/,\-]`,
214
211
  // FIRST field is exactly `*` (Renovate explicitly rejects non-`*` minutes).
@@ -241,6 +238,9 @@ export function lintConfig(config) {
241
238
  checkAutomergeIncludesMajor(config, findings);
242
239
  checkDuplicatePackageRuleMatchers(config, findings);
243
240
  checkHostRuleInlineSecret(config, findings);
241
+ checkDatasourceTemplates(config, findings);
242
+ checkScheduleWithoutTimezone(config, findings);
243
+ checkPackageRuleLabelsOverrides(config, findings);
244
244
  return findings;
245
245
  }
246
246
  function isSelectorKey(key) {
@@ -422,6 +422,76 @@ function checkDuplicatePackageRuleMatchers(config, findings) {
422
422
  }
423
423
  }
424
424
  }
425
+ // First `schedule` entry (anywhere in the tree, same reach as `invalid-schedule`)
426
+ // that actually restricts timing, as [path, value].
427
+ function findRestrictingSchedule(node, pathStr) {
428
+ if (Array.isArray(node)) {
429
+ for (let i = 0; i < node.length; i++) {
430
+ const hit = findRestrictingSchedule(node[i], `${pathStr}[${i}]`);
431
+ if (hit)
432
+ return hit;
433
+ }
434
+ return null;
435
+ }
436
+ if (!isRecord(node))
437
+ return null;
438
+ for (const [key, value] of Object.entries(node)) {
439
+ const childPath = pathStr ? `${pathStr}.${key}` : key;
440
+ if (key === "schedule") {
441
+ if (typeof value === "string" && !SCHEDULE_ANY_TIME.has(value)) {
442
+ return [childPath, value];
443
+ }
444
+ if (Array.isArray(value)) {
445
+ const i = value.findIndex((v) => typeof v === "string" && !SCHEDULE_ANY_TIME.has(v));
446
+ if (i !== -1)
447
+ return [`${childPath}[${i}]`, value[i]];
448
+ }
449
+ continue;
450
+ }
451
+ const hit = findRestrictingSchedule(value, childPath);
452
+ if (hit)
453
+ return hit;
454
+ }
455
+ return null;
456
+ }
457
+ function checkScheduleWithoutTimezone(config, findings) {
458
+ if (!isRecord(config) || config.timezone)
459
+ return;
460
+ const hit = findRestrictingSchedule(config, "");
461
+ if (!hit)
462
+ return;
463
+ const [path, value] = hit;
464
+ // One finding per config: the fix (a root `timezone`) is the same for all.
465
+ findings.push({
466
+ ruleId: "schedule-without-timezone",
467
+ severity: "warn",
468
+ path,
469
+ value,
470
+ message: `${path} sets a schedule but the root config has no \`timezone\`, so Renovate evaluates every schedule in UTC. ` +
471
+ "Ignore this if an extended preset sets the timezone.",
472
+ suggestion: 'Set a top-level "timezone" to an IANA name, e.g. "Europe/Berlin".',
473
+ });
474
+ }
475
+ function checkPackageRuleLabelsOverrides(config, findings) {
476
+ if (!isRecord(config))
477
+ return;
478
+ if (!Array.isArray(config.labels) || config.labels.length === 0)
479
+ return;
480
+ if (!Array.isArray(config.packageRules))
481
+ return;
482
+ config.packageRules.forEach((entry, i) => {
483
+ if (!isRecord(entry) || !("labels" in entry))
484
+ return;
485
+ findings.push({
486
+ ruleId: "package-rule-labels-overrides",
487
+ severity: "warn",
488
+ path: `packageRules[${i}].labels`,
489
+ value: JSON.stringify(entry.labels),
490
+ message: `packageRules[${i}].labels replaces the root labels (${JSON.stringify(config.labels)}) for matched deps instead of adding to them.`,
491
+ suggestion: `Use addLabels in packageRules[${i}] to append to the root labels.`,
492
+ });
493
+ });
494
+ }
425
495
  const HOST_RULE_SECRET_FIELDS = ["token", "password"];
426
496
  function checkHostRuleInlineSecret(config, findings) {
427
497
  if (!isRecord(config))
@@ -537,6 +607,18 @@ function walk(node, pathStr, findings) {
537
607
  });
538
608
  }
539
609
  }
610
+ else if (key === "matchDatasources") {
611
+ if (Array.isArray(value)) {
612
+ value.forEach((entry, i) => {
613
+ if (typeof entry === "string") {
614
+ checkDatasource(entry, `${childPath}[${i}]`, findings);
615
+ }
616
+ });
617
+ }
618
+ else if (typeof value === "string") {
619
+ checkDatasource(value, childPath, findings);
620
+ }
621
+ }
540
622
  else if (MANAGER_FIELDS.has(key)) {
541
623
  if (Array.isArray(value)) {
542
624
  value.forEach((entry, i) => {
@@ -604,26 +686,73 @@ function checkSchedule(value, path, findings) {
604
686
  "preset such as `schedule:earlyMondays`.",
605
687
  });
606
688
  }
689
+ // Renovate evaluates matchManagers and matchDatasources with
690
+ // matchRegexOrGlobList: `/…/` (optionally `/…/i`) is a regex and anything with
691
+ // glob metachars is a glob — neither is a literal id to check against a list.
692
+ // Deliberately permissive: a typo that happens to contain a glob char (e.g.
693
+ // `docker[`) is skipped rather than risk an error-severity finding on a valid glob.
694
+ function isRegexOrGlobEntry(name) {
695
+ return /^\/.+\/i?$/.test(name) || /[*?[\]{}]/.test(name);
696
+ }
607
697
  function checkManager(raw, path, findings) {
608
- if (VALID_MANAGER_NAMES.has(raw))
698
+ // `!` is Renovate's negation prefix for every matchRegexOrGlobList field.
699
+ const name = raw.startsWith("!") ? raw.slice(1) : raw;
700
+ if (VALID_MANAGER_NAMES.has(name) || isRegexOrGlobEntry(name))
609
701
  return;
610
- const suggestion = nearestManager(raw);
702
+ const suggestion = nearestName(name, VALID_MANAGER_NAMES);
611
703
  const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
612
704
  const finding = {
613
705
  ruleId: "matchManagers-unknown-name",
614
706
  severity: "error",
615
707
  path,
616
708
  value: raw,
617
- message: `'${raw}' is not a known Renovate manager. Renovate will silently apply this rule to zero packages.${hint}`,
709
+ message: `'${name}' is not a known Renovate manager. Renovate will silently apply this rule to zero packages.${hint}`,
710
+ };
711
+ if (suggestion)
712
+ finding.suggestion = suggestion;
713
+ findings.push(finding);
714
+ }
715
+ function checkDatasourceTemplates(config, findings) {
716
+ if (!isRecord(config) || !Array.isArray(config.customManagers))
717
+ return;
718
+ config.customManagers.forEach((entry, i) => {
719
+ if (!isRecord(entry))
720
+ return;
721
+ const tpl = entry.datasourceTemplate;
722
+ // A templated value is resolved per match; only a literal id can be checked.
723
+ // A literal template is an id, never a pattern, so no `!` / regex / glob leniency.
724
+ if (typeof tpl !== "string" || tpl.includes("{{"))
725
+ return;
726
+ checkDatasource(tpl, `customManagers[${i}].datasourceTemplate`, findings, false);
727
+ });
728
+ }
729
+ function checkDatasource(raw, path, findings, isPattern = true) {
730
+ // Renovate matches `matchDatasources` via matchRegexOrGlobList: `!` negates,
731
+ // `/…/` is a regex and anything with glob metachars is a glob — none of those
732
+ // are literal ids. `custom.<name>` routes to the user's customDatasources.
733
+ const name = isPattern && raw.startsWith("!") ? raw.slice(1) : raw;
734
+ if (VALID_DATASOURCES.has(name) ||
735
+ name.startsWith("custom.") ||
736
+ (isPattern && isRegexOrGlobEntry(name))) {
737
+ return;
738
+ }
739
+ const suggestion = nearestName(name, VALID_DATASOURCES);
740
+ const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
741
+ const finding = {
742
+ ruleId: "matchDatasources-unknown-name",
743
+ severity: "error",
744
+ path,
745
+ value: raw,
746
+ message: `'${name}' is not a known Renovate datasource. Renovate does not validate datasource ids, so this silently matches (or looks up) nothing.${hint}`,
618
747
  };
619
748
  if (suggestion)
620
749
  finding.suggestion = suggestion;
621
750
  findings.push(finding);
622
751
  }
623
- function nearestManager(name) {
752
+ function nearestName(name, candidates) {
624
753
  let best = null;
625
754
  let bestDistance = Infinity;
626
- for (const candidate of VALID_MANAGER_NAMES) {
755
+ for (const candidate of candidates) {
627
756
  const d = damerauLevenshtein(name, candidate);
628
757
  if (d < bestDistance) {
629
758
  bestDistance = d;