@creeperhost/modlens-mcp 1.6.18 → 1.6.20

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 (194) hide show
  1. package/README.md +112 -15
  2. package/TESTING.md +69 -0
  3. package/dist/cli.js +49 -13
  4. package/dist/cli.js.map +1 -1
  5. package/dist/curseforge.d.ts +9 -7
  6. package/dist/curseforge.d.ts.map +1 -1
  7. package/dist/curseforge.js +48 -55
  8. package/dist/curseforge.js.map +1 -1
  9. package/dist/db.d.ts.map +1 -1
  10. package/dist/db.js +31 -1
  11. package/dist/db.js.map +1 -1
  12. package/dist/embeddings.d.ts.map +1 -1
  13. package/dist/embeddings.js +1 -0
  14. package/dist/embeddings.js.map +1 -1
  15. package/dist/feed-the-beast.d.ts +5 -5
  16. package/dist/feed-the-beast.d.ts.map +1 -1
  17. package/dist/feed-the-beast.js +5 -21
  18. package/dist/feed-the-beast.js.map +1 -1
  19. package/dist/java-tools.d.ts +1 -0
  20. package/dist/java-tools.d.ts.map +1 -1
  21. package/dist/java-tools.js +31 -7
  22. package/dist/java-tools.js.map +1 -1
  23. package/dist/launcher.js +15 -0
  24. package/dist/launcher.js.map +1 -1
  25. package/dist/mappings.d.ts +4 -1
  26. package/dist/mappings.d.ts.map +1 -1
  27. package/dist/mappings.js +120 -52
  28. package/dist/mappings.js.map +1 -1
  29. package/dist/minecraft.d.ts +0 -4
  30. package/dist/minecraft.d.ts.map +1 -1
  31. package/dist/minecraft.js +7 -23
  32. package/dist/minecraft.js.map +1 -1
  33. package/dist/mod-artifacts.d.ts +4 -0
  34. package/dist/mod-artifacts.d.ts.map +1 -0
  35. package/dist/mod-artifacts.js +58 -0
  36. package/dist/mod-artifacts.js.map +1 -0
  37. package/dist/modpacks-ch.d.ts +72 -46
  38. package/dist/modpacks-ch.d.ts.map +1 -1
  39. package/dist/modpacks-ch.js +134 -20
  40. package/dist/modpacks-ch.js.map +1 -1
  41. package/dist/modrinth.d.ts +14 -6
  42. package/dist/modrinth.d.ts.map +1 -1
  43. package/dist/modrinth.js +70 -55
  44. package/dist/modrinth.js.map +1 -1
  45. package/dist/platform-adapter.d.ts +1 -0
  46. package/dist/platform-adapter.d.ts.map +1 -1
  47. package/dist/platform.d.ts.map +1 -1
  48. package/dist/platform.js +20 -44
  49. package/dist/platform.js.map +1 -1
  50. package/dist/primer-catalog.d.ts +15 -0
  51. package/dist/primer-catalog.d.ts.map +1 -0
  52. package/dist/primer-catalog.js +43 -0
  53. package/dist/primer-catalog.js.map +1 -0
  54. package/dist/primer-content.d.ts +7 -0
  55. package/dist/primer-content.d.ts.map +1 -0
  56. package/dist/primer-content.js +185 -0
  57. package/dist/primer-content.js.map +1 -0
  58. package/dist/processor.d.ts +5 -0
  59. package/dist/processor.d.ts.map +1 -1
  60. package/dist/processor.js +166 -46
  61. package/dist/processor.js.map +1 -1
  62. package/dist/registry-submission.d.ts +34 -0
  63. package/dist/registry-submission.d.ts.map +1 -0
  64. package/dist/registry-submission.js +128 -0
  65. package/dist/registry-submission.js.map +1 -0
  66. package/dist/repositories/array-fields.d.ts +3 -0
  67. package/dist/repositories/array-fields.d.ts.map +1 -0
  68. package/dist/repositories/array-fields.js +8 -0
  69. package/dist/repositories/array-fields.js.map +1 -0
  70. package/dist/repositories/embeddings-sqlite.d.ts +1 -0
  71. package/dist/repositories/embeddings-sqlite.d.ts.map +1 -1
  72. package/dist/repositories/embeddings-sqlite.js +26 -58
  73. package/dist/repositories/embeddings-sqlite.js.map +1 -1
  74. package/dist/repositories/embeddings.d.ts.map +1 -1
  75. package/dist/repositories/embeddings.js +26 -3
  76. package/dist/repositories/embeddings.js.map +1 -1
  77. package/dist/repositories/index.d.ts +1 -1
  78. package/dist/repositories/index.d.ts.map +1 -1
  79. package/dist/repositories/mod.d.ts +8 -5
  80. package/dist/repositories/mod.d.ts.map +1 -1
  81. package/dist/repositories/mod.js +25 -11
  82. package/dist/repositories/mod.js.map +1 -1
  83. package/dist/repositories/packs.d.ts.map +1 -1
  84. package/dist/repositories/packs.js +10 -2
  85. package/dist/repositories/packs.js.map +1 -1
  86. package/dist/search-adapter.d.ts +2 -0
  87. package/dist/search-adapter.d.ts.map +1 -1
  88. package/dist/search-adapter.js +21 -9
  89. package/dist/search-adapter.js.map +1 -1
  90. package/dist/security.d.ts +2 -0
  91. package/dist/security.d.ts.map +1 -1
  92. package/dist/security.js +5 -1
  93. package/dist/security.js.map +1 -1
  94. package/dist/server.js +80 -35
  95. package/dist/server.js.map +1 -1
  96. package/dist/setup.js +9 -4
  97. package/dist/setup.js.map +1 -1
  98. package/dist/sqlite-json.d.ts +3 -0
  99. package/dist/sqlite-json.d.ts.map +1 -0
  100. package/dist/sqlite-json.js +78 -0
  101. package/dist/sqlite-json.js.map +1 -0
  102. package/dist/sqlite-parameters.d.ts +3 -0
  103. package/dist/sqlite-parameters.d.ts.map +1 -0
  104. package/dist/sqlite-parameters.js +19 -0
  105. package/dist/sqlite-parameters.js.map +1 -0
  106. package/dist/sqlite-schema.d.ts +4 -0
  107. package/dist/sqlite-schema.d.ts.map +1 -0
  108. package/dist/sqlite-schema.js +76 -0
  109. package/dist/sqlite-schema.js.map +1 -0
  110. package/dist/tools/catalog.d.ts +1 -6
  111. package/dist/tools/catalog.d.ts.map +1 -1
  112. package/dist/tools/catalog.js +10 -14
  113. package/dist/tools/catalog.js.map +1 -1
  114. package/dist/tools/compat-check.d.ts.map +1 -1
  115. package/dist/tools/compat-check.js +18 -30
  116. package/dist/tools/compat-check.js.map +1 -1
  117. package/dist/tools/diagnostics.js.map +1 -1
  118. package/dist/tools/docs.d.ts.map +1 -1
  119. package/dist/tools/docs.js +8 -3
  120. package/dist/tools/docs.js.map +1 -1
  121. package/dist/tools/embed-registry.d.ts +20 -4
  122. package/dist/tools/embed-registry.d.ts.map +1 -1
  123. package/dist/tools/embed-registry.js +30 -19
  124. package/dist/tools/embed-registry.js.map +1 -1
  125. package/dist/tools/graphify.d.ts +19 -4
  126. package/dist/tools/graphify.d.ts.map +1 -1
  127. package/dist/tools/graphify.js +61 -13
  128. package/dist/tools/graphify.js.map +1 -1
  129. package/dist/tools/ingest.d.ts.map +1 -1
  130. package/dist/tools/ingest.js +4 -4
  131. package/dist/tools/ingest.js.map +1 -1
  132. package/dist/tools/kubejs.js +1 -1
  133. package/dist/tools/kubejs.js.map +1 -1
  134. package/dist/tools/mc-fts.d.ts.map +1 -1
  135. package/dist/tools/mc-fts.js +9 -4
  136. package/dist/tools/mc-fts.js.map +1 -1
  137. package/dist/tools/mcmeta.d.ts +10 -1
  138. package/dist/tools/mcmeta.d.ts.map +1 -1
  139. package/dist/tools/mcmeta.js +43 -29
  140. package/dist/tools/mcmeta.js.map +1 -1
  141. package/dist/tools/mixin-scan.d.ts +12 -6
  142. package/dist/tools/mixin-scan.d.ts.map +1 -1
  143. package/dist/tools/mixin-scan.js +12 -7
  144. package/dist/tools/mixin-scan.js.map +1 -1
  145. package/dist/tools/mod-data.d.ts.map +1 -1
  146. package/dist/tools/mod-data.js +40 -27
  147. package/dist/tools/mod-data.js.map +1 -1
  148. package/dist/tools/modpacks-ch.d.ts +9 -5
  149. package/dist/tools/modpacks-ch.d.ts.map +1 -1
  150. package/dist/tools/modpacks-ch.js +54 -33
  151. package/dist/tools/modpacks-ch.js.map +1 -1
  152. package/dist/tools/packtools.js +3 -3
  153. package/dist/tools/packtools.js.map +1 -1
  154. package/dist/tools/platform.d.ts +2 -2
  155. package/dist/tools/platform.d.ts.map +1 -1
  156. package/dist/tools/platform.js +33 -23
  157. package/dist/tools/platform.js.map +1 -1
  158. package/dist/tools/primers.d.ts +133 -22
  159. package/dist/tools/primers.d.ts.map +1 -1
  160. package/dist/tools/primers.js +391 -386
  161. package/dist/tools/primers.js.map +1 -1
  162. package/dist/tools/project.d.ts +342 -0
  163. package/dist/tools/project.d.ts.map +1 -0
  164. package/dist/tools/project.js +519 -0
  165. package/dist/tools/project.js.map +1 -0
  166. package/dist/tools/reports.d.ts.map +1 -1
  167. package/dist/tools/reports.js +5 -3
  168. package/dist/tools/reports.js.map +1 -1
  169. package/dist/tools/vanilla-data.d.ts.map +1 -1
  170. package/dist/tools/vanilla-data.js +3 -33
  171. package/dist/tools/vanilla-data.js.map +1 -1
  172. package/dist/tools/vanilla.d.ts +3 -1
  173. package/dist/tools/vanilla.d.ts.map +1 -1
  174. package/dist/tools/vanilla.js +26 -12
  175. package/dist/tools/vanilla.js.map +1 -1
  176. package/dist/tools/version-diff.d.ts.map +1 -1
  177. package/dist/tools/version-diff.js +4 -24
  178. package/dist/tools/version-diff.js.map +1 -1
  179. package/dist/version-ranges.d.ts +5 -0
  180. package/dist/version-ranges.d.ts.map +1 -0
  181. package/dist/version-ranges.js +93 -0
  182. package/dist/version-ranges.js.map +1 -0
  183. package/package.json +9 -1
  184. package/prisma/backends/template.db +0 -0
  185. package/scripts/build-template-db.mjs +3 -1
  186. package/scripts/enable-sqlite-vec.mjs +2 -106
  187. package/scripts/gradle/modlens.init.gradle +154 -0
  188. package/scripts/project-upload.mjs +74 -0
  189. package/scripts/test-minecraft-eras.mjs +202 -0
  190. package/scripts/test-package.mjs +105 -5
  191. package/scripts/test-primers-live.mjs +93 -0
  192. package/scripts/test-project-gradle.mjs +76 -0
  193. package/scripts/test-project-http.mjs +98 -0
  194. package/scripts/test-setup.mjs +18 -0
package/README.md CHANGED
@@ -49,6 +49,80 @@ npm run start # start the server
49
49
 
50
50
  ---
51
51
 
52
+ ## Gradle project environments
53
+
54
+ ModLens can import the **actual compile classpath** of a Gradle project. The ModDevGradle adapter also exports the matching prepared Minecraft sources and hashes of the AT files used by its artifact task. This lets the AI see project-specific access changes, loader patches and mapped names.
55
+
56
+ Imports are private, immutable snapshots, separate from the shared vanilla/mod caches and database. Re-export and import after changing ATs, dependencies or mappings. The returned `environmentId` selects the new snapshot; earlier IDs still describe their original inputs. This is compile-time context, not a simulation of runtime mixins/coremods.
57
+
58
+ ### Export from Gradle
59
+
60
+ In a checkout, run your mod project's wrapper with the supplied init script:
61
+
62
+ ```bash
63
+ ./gradlew -I /path/to/modlens-mcp/scripts/gradle/modlens.init.gradle :modlensExport
64
+ ```
65
+
66
+ For an installed package, locate its script first:
67
+
68
+ ```bash
69
+ npx @creeperhost/modlens-mcp --gradle-init-script
70
+ # Pass the printed path to your project's ./gradlew -I command.
71
+ ```
72
+
73
+ On Windows, use `gradlew.bat -I "H:\Git\modlens-mcp\scripts\gradle\modlens.init.gradle" :modlensExport`.
74
+
75
+ The task produces `build/modlens/main/environment.zip` and creates a persistent private key at `.gradle/modlens/project-key.txt` in the selected project. The key is not included in the bundle or printed. Keep it out of source control and share it only with clients that should access this project.
76
+
77
+ Use `:subproject:modlensExport` for a module, and `-PmodlensSourceSet=client` to select a different source set. The task runs the classpath's prerequisite tasks, including Minecraft artifact preparation, without launching the game. Referenced project/source-set outputs may need compilation. It does not support Gradle's configuration cache; use `--no-configuration-cache` if necessary.
78
+
79
+ Supported adapter: **ModDevGradle 2.x**, exercised with 2.0.141 / NeoForge 21.1.209 / Minecraft 1.21.1 and Gradle 8.14. Other Java projects can export their resolved compile classpath, including directory dependencies. Automatic source discovery for ForgeGradle/NeoGradle/Loom is not implemented; absent supplied sources, individual classes are decompiled from the exported binaries on demand. Do not interpret generic exports as proof that runtime-only transformations have been applied.
80
+
81
+ Optional metadata overrides: `-PmodlensMinecraftVersion=...`, `-PmodlensMappings=...`. The exporter also reads the common `minecraft_version`, `neo_version` and `forge_version` properties. It exports artifact names/content hashes and selected version metadata, not the project's entire Gradle configuration, repository credentials or absolute dependency paths.
82
+
83
+ ### Import locally or remotely
84
+
85
+ Local import and query examples:
86
+
87
+ ```bash
88
+ npx @creeperhost/modlens-mcp --project import-local build/modlens/main/environment.zip --key-file=.gradle/modlens/project-key.txt
89
+ npx @creeperhost/modlens-mcp --project list --key-file=.gradle/modlens/project-key.txt
90
+ npx @creeperhost/modlens-mcp --project members --environment-id=<returned-id> --class-name=net.minecraft.world.level.Level --key-file=.gradle/modlens/project-key.txt
91
+ ```
92
+
93
+ From a checkout, the equivalent is `node dist/cli.js project ...`.
94
+
95
+ For a remote server, run the uploader **on the developer's machine**, where the bundle exists:
96
+
97
+ ```bash
98
+ npx @creeperhost/modlens-mcp --project-upload https://modlens.example/mcp build/modlens/main/environment.zip .gradle/modlens/project-key.txt
99
+ # Checkout equivalent:
100
+ node /path/to/modlens-mcp/scripts/project-upload.mjs https://modlens.example/mcp build/modlens/main/environment.zip .gradle/modlens/project-key.txt
101
+ ```
102
+
103
+ Set `MODLENS_AUTH_TOKEN` if the deployment's reverse proxy requires a bearer token. Remote uploads require HTTPS; loopback HTTP is supported for local testing. The uploader transfers 1 MiB chunks, verifies the full bundle hash, retries acknowledged chunks safely and prints the imported snapshot metadata. The remote server never runs Gradle or needs the developer's local paths. Host-local import is disabled when `MCP_PORT` is set.
104
+
105
+ Configure the AI to use the MCP **`project` tool** with `projectKey` (the key file's contents) and `environmentId`. Use `project` source/member/search queries for this environment; the existing `mc_source` and `mod` tools continue to describe their shared inputs. Key possession grants access to that project's snapshots; use the deployment's normal authentication to control access to the service and its upload resources.
106
+
107
+ | Action | Additional arguments | Result |
108
+ | --- | --- | --- |
109
+ | `list` | none | Snapshots belonging to this project key only |
110
+ | `info` | `environmentId` | Versions, artifacts, counts and snapshot provenance |
111
+ | `classes` | `environmentId`, optional `query`, `offset`, `limit` | Classes in compile classpath order of precedence |
112
+ | `source` | `environmentId`, `className`, optional `startLine`, `maxLines` | Supplied Gradle source or an on-demand binary decompile |
113
+ | `members` / `bytecode` | `environmentId`, `className` | Inspection of the project's prepared binary |
114
+ | `search` | `environmentId`, `query`, optional `limit` | Literal, case-insensitive search of supplied sources and cached decompiles |
115
+ | `import_local` | `bundlePath` | Import an absolute host-local ZIP path (stdio only) |
116
+ | `upload_begin` | `size`, `sha256` | Upload ID and chunk size |
117
+ | `upload_chunk` | `uploadId`, `offset`, `data` | Next offset; `data` is base64 |
118
+ | `upload_finish` / `upload_abort` | `uploadId` | Commit a complete upload or remove upload state |
119
+
120
+ Search reports its coverage: a missing match does not prove absence from binaries without sources. Duplicate classes follow the first compile classpath entry; sources from shadowed dependencies are excluded. Multi-release JARs use the compile Java release, with stale base sources excluded when a versioned class overrides them. Limits are 512 MiB per bundle, 1 GiB expanded content, 2 MiB per Java source, and four unfinished uploads per project. Abandoned uploads expire after 24 hours and are cleaned up when the next upload begins. Snapshot files live under `MODLENS_CACHE_ROOT/projects` and work with all database backends.
121
+
122
+ Contributor validation: `npm run test:project:http` exercises the real uploader and HTTP transport. Set `JAVA_HOME` and `MODLENS_TEST_GRADLE_HOME`, then run `npm run test:project:gradle` for a Java dependency fixture, or `npm run test:project:gradle -- moddev` for a real AT before/after check. The Gradle tests use disposable projects and may download build dependencies; they never launch Minecraft.
123
+
124
+ Minecraft compatibility validation: `npm run test:minecraft:eras` exercises the local MCP server across Beta 1.7.3 through 26.1.2, plus real legacy Forge, Fabric and NeoForge mods, using temporary databases and caches. See [TESTING.md](TESTING.md) for the version matrix, offline format coverage and upstream gaps.
125
+
52
126
  ## Prerequisites
53
127
 
54
128
  | Requirement | Notes |
@@ -63,7 +137,7 @@ npm run start # start the server
63
137
 
64
138
  | Variable | Purpose |
65
139
  |----------|---------|
66
- | `CURSEFORGE_API_KEY` | CurseForge platform sync (`sync_curseforge` action) |
140
+ | `CURSEFORGE_API_KEY` | Optional direct fingerprint lookup and authenticated CurseForge downloads; normal metadata/sync uses modpacks.ch |
67
141
  | `MODRINTH_TOKEN` | Modrinth API — increases rate limit for batch operations |
68
142
  | `JAVA_HOME` | Override Java discovery (falls back to PATH if not set) |
69
143
 
@@ -154,10 +228,24 @@ node dist/cli.js backfill-embeddings
154
228
  node dist/cli.js backfill-embeddings --type source --version 26.1.2
155
229
  ```
156
230
 
157
- After this, the `docs` and `primers` MCP tools will have a `semantic_search` action, and the `mc_source` tool will have `search_semantic` and `index_semantic`. Semantic search is **opt-in** — if `OLLAMA_URL` is not reachable, tools fall back to keyword search automatically.
231
+ After this, the `docs` and `primers` MCP tools provide `semantic_search`, and `mc_source` provides `search_semantic` and `index_semantic`. Semantic search requires a reachable Ollama service and the configured embedding model. Keyword search remains available separately. SQLite uses the bundled `sqlite-vec` dependency; `db:vector` above is for PostgreSQL.
158
232
 
159
233
  > **Note:** The `npm run db:vector` script is safe to re-run. It creates the `pgvector` extension and `embedding` columns using `IF NOT EXISTS` guards.
160
234
 
235
+ ## Data sources and shared bundles
236
+
237
+ ModLens uses [modpacks.ch](https://modpacks.ch/api/openapi.json) for supported mod and pack searches, provider records, release histories, hash lookups, parsed pack manifests and loader versions. Maven artifacts and metadata come from [CreeperHost Maven](https://maven.creeperhost.net/). Download URLs supplied by modpacks.ch may point to the original provider's CDN. An API failure or missing result does not silently switch to another metadata service.
238
+
239
+ When a mod file has no loader label, loader-filtered searches, version listings and update checks automatically download and inspect its JAR. Existing modpacks.ch labels take precedence. Inspection supports Forge/FML, Fabric, Quilt and NeoForge, including multiple loader declarations in one JAR; unknown or mismatched loaders are excluded. Recovered labels report `loaderSource: "jar"`. The first lookup can download several candidate files; subsequent queries and ingestion reuse the artifact cache. `mod_info` defaults to the latest 20 matches and reports `versionLimit`; increase `limit` (CLI: `--limit`) for more history. Download, checksum and unreadable-JAR failures are reported as errors.
240
+
241
+ Existing sources remain for capabilities these services do not cover: Mojang manifests, game files and official mappings; mcmeta's extracted data and history; RetroMCP legacy mappings; Adoptium runtime provisioning; the ModLens indexer release; metadata-provided source repositories; and the selected GitHub graph and embedding registries. Existing local Java installations are preferred.
242
+
243
+ `mod graph_build` can build a class and inheritance graph directly from the mod JAR, without Graphify or an AI service. Set `backend=ast-only` to select this mode explicitly. `graph_enrich_next` and `graph_enrich_submit` add relationships supplied by the client. Automatic semantic extraction still needs a configured Graphify backend.
244
+
245
+ `graph_export` and `embed_export` produce portable gzip bundles. Pass the resulting `bundlePath` to `graph_submit` or `embed_submit` to propose the bundle and its index entry together in a draft pull request. The defaults are [Mattabase/modlens-graphs](https://github.com/Mattabase/modlens-graphs) and [Mattabase/modlens-embeddings](https://github.com/Mattabase/modlens-embeddings). Submissions require `MODLENS_REGISTRY_TOKEN` or `GITHUB_TOKEN` with access to write the branch and open the pull request; a personal fork is used when the account cannot push to the registry. Submission does not merge the pull request.
246
+
247
+ Custom registries use `MODLENS_GRAPH_REGISTRY_URL` or `MODLENS_EMBED_REGISTRY_URL`. For submission, the corresponding `MODLENS_GRAPH_REGISTRY_REPO` / `MODLENS_EMBED_REGISTRY_REPO`, `_BRANCH` and `_INDEX_PATH` settings override the destination inferred from a GitHub raw-content URL.
248
+
161
249
  ---
162
250
 
163
251
  ## MCP Configuration
@@ -456,10 +544,10 @@ Run without arguments (or `--help`) to print the full command list. Every MCP to
456
544
  | `modpacks manifest <packId> <verId>` | Pack manifest |
457
545
  | MCP `modpacks_ch action=resolve_pack` | Resolve FTB/CurseForge/Modrinth/Feed The Beast pack names, IDs, version IDs, or version names |
458
546
  | MCP `modpacks_ch action=list_versions` | List remote pack versions and optionally mark matches for a short `versionRef` like `7.1` |
459
- | MCP `modpacks_ch action=ingest_pack` | Download and ingest a resolved pack version from modpacks.ch, Modrinth, or the official Feed The Beast API |
547
+ | MCP `modpacks_ch action=ingest_pack` | Resolve metadata through modpacks.ch, then download and ingest the selected pack version |
460
548
  | `modpacks list-versions` | Pack version list |
461
549
  | `modpacks list-files` | Pack file list |
462
- | `modpacks ftb-mod-info <modId>` | FTB mod info |
550
+ | `modpacks mod-info <modId>` | Mod metadata from modpacks.ch (`ftb-mod-info` remains an alias) |
463
551
  | `modpacks find-mod` | Find a mod across packs |
464
552
 
465
553
  **Diagnostics**
@@ -568,8 +656,8 @@ All tool actions have been consolidated into **24 grouped tools** to stay within
568
656
 
569
657
  | action | Key params | Description |
570
658
  |--------|-----------|-------------|
571
- | `sync_modrinth` | dbId | SHA-512 lookup → store project ID + source URL |
572
- | `sync_curseforge` | dbId | Murmur2 fingerprint lookup (needs `CURSEFORGE_API_KEY`) |
659
+ | `sync_modrinth` | dbId | Resolve a known project or JAR SHA-1 through modpacks.ch; store project ID and source URL |
660
+ | `sync_curseforge` | dbId | Resolve a known project or JAR SHA-1 through modpacks.ch; no CurseForge API key required |
573
661
  | `check_updates` | dbId | Check both platforms for newer version |
574
662
  | `batch_sync` | syncModrinth, syncCurseforge, downloadSources, modIdFilter, limit | Bulk sync all unmatched mods |
575
663
  | `download_source` | dbId | Download GitHub/GitLab source ZIP |
@@ -630,13 +718,23 @@ All tool actions have been consolidated into **24 grouped tools** to stay within
630
718
  | action | Key params | Description |
631
719
  |--------|-----------|-------------|
632
720
  | `ingest` | entries[] | Add migration guide entries |
633
- | `seed` | — | Populate built-in NeoForge/Forge/Fabric guides |
634
- | `get` | id | Get primer by DB id |
635
- | `by_version` | fromVersion, toVersion, modloader | All guides covering a version span |
721
+ | `seed` | fetchContent=true | Populate the official Minecraft/Forge/NeoForge primer catalogue and cache missing guide bodies |
722
+ | `get` | id, startLine=1, maxLines=400, fetchContent=true, refresh=false | Read cached Markdown; fetch missing content automatically |
723
+ | `by_version` | fromVersion, toVersion, modloader, includeContent=false, maxChars=60000, cursor, fetchContent=true | Ordered migration steps; optionally bundle their Markdown, including vanilla changes for the selected loader |
636
724
  | `search` | query, modloader, fromVersion, toVersion, limit | Full-text search |
637
725
  | `list` | modloader, limit | List all primers |
638
726
  | `delete` | id | Remove by DB id |
639
727
 
728
+ Start with `{"action":"seed"}` on a fresh database, then use `{"action":"by_version","fromVersion":"1.21.1","toVersion":"1.21.5","modloader":"neoforge"}`. This returns the vanilla and NeoForge guides for the intervening transitions. Call `{"action":"get","id":<returned id>}` for each guide, following `nextStartLine` until it is null. Guides retain headings, tables, links and code blocks; pagination does not truncate the cached document. The catalogue contains the steps published upstream, so a result is not a guarantee of coverage for every release or loader.
729
+
730
+ To read a whole migration range, request `{"action":"by_version","fromVersion":"1.21.1","toVersion":"26.1","modloader":"neoforge","includeContent":true}`. The server fetches missing bodies and returns the original Markdown with each guide's ID, title, source URL, loader and version boundaries. Small ranges fit in one response. Larger ranges return `nextCursor`; repeat the same request with `cursor` set to that value until it is null. `count` is the total number of matching guides; `primers` contains the current page. Existing calls without `includeContent` retain their metadata-only response.
731
+
732
+ Bundled pages share a `maxChars` text budget (1,000–200,000, default 60,000) and contain at most 20 guides. Long guides may span pages: concatenate `content` chunks for the same ID directly, without inserting separators. `startOffset`, `endOffset` (exclusive), `totalChars` and `contentChars` count UTF-16 code units; pagination preserves Unicode characters. Each guide reports `contentStatus` and `truncated`; the outer `truncated` reports whether another page remains. Cursors detect changes to the selected catalogue or partly read content and ask you to restart. `fetchContent:false` reads only cached bodies and reports missing ones with `contentStatus:"missing"` and a page-level `missing` count. Fetch failures retain successful guides, report `contentStatus:"fetch_failed"` and an error per failed guide, and set the page-level `failed` count and MCP error flag. Retry those IDs with `get`. The CLI equivalent is `primers by-version 1.21.1 26.1 --modloader=neoforge --include-content`, with optional `--max-chars=`, `--cursor=` and `--fetch-content=false`; fetch failures set a nonzero exit code after printing the page.
733
+
734
+ `get` and `seed` fetch missing content on the MCP server and store it in its configured database, so this also works with a remote server. Cached reads work without Internet access. Use `fetchContent:false` for metadata-only reads/seeding, or `refresh:true` on `get` to replace cached content. Fetch failures return an error; failed refreshes preserve the previous content. `ingest` still accepts supplied content or an explicit `entries[].fetchContent:true`, and reports failed entries without saving them. The CLI equivalents are `primers seed --fetch-content=false` and `primers get <id> --refresh --start-line=401`.
735
+
736
+ Existing installations repair the old built-in placeholder URLs on the first primer operation; no database reset is needed. IDs are retained where possible, and obsolete duplicates or placeholders are marked superseded with replacement guides. Custom entries and existing content are preserved.
737
+
640
738
  ### 10. `mc_registry` — MC Registry & Meta Data
641
739
 
642
740
  | action | Key params | Description |
@@ -836,7 +934,7 @@ modpacks_ch action=ingest_pack namespace=modrinth packRef=<slug-or-project-id
836
934
  modpacks_ch action=ingest_pack namespace=modrinth packRef="https://modrinth.com/modpack/fabulously-optimized" versionRef=6.4.0
837
935
 
838
936
  # Official Feed The Beast API packs use namespace=feedthebeast.
839
- # Use namespace=ftb for the modpacks.ch FTB namespace; use feedthebeast for api.feed-the-beast.com.
937
+ # Both use modpacks.ch: namespace=ftb selects /modpack; feedthebeast selects /ftb.
840
938
  modpacks_ch action=list_versions namespace=feedthebeast packRef="Architect's" versionRef=1.1
841
939
  modpacks_ch action=ingest_pack namespace=feedthebeast packRef="Architect's" versionRef=1.1
842
940
 
@@ -942,16 +1040,16 @@ node dist/cli.js check-updates 2
942
1040
 
943
1041
  ### Services & APIs
944
1042
  - **[CreeperHost](https://www.creeperhost.net)** — for the free [modpacks.ch](https://www.modpacks.ch) public API powering modpack search, sync, and mod downloads.
945
- - **[Feed The Beast](https://www.feed-the-beast.com)** — for the public Feed The Beast modpack API powering official FTB pack lookup and ingest.
1043
+ - **[Feed The Beast](https://www.feed-the-beast.com)** — for the official FTB pack catalog, accessed through modpacks.ch.
946
1044
  - **[Modrinth](https://modrinth.com)** — for the free [Modrinth API](https://docs.modrinth.com) powering mod search, metadata lookup, and version sync.
947
- - **[CurseForge](https://www.curseforge.com)** — for the [CurseForge API](https://docs.curseforge.com) powering mod and modpack browsing and sync.
1045
+ - **[CurseForge](https://www.curseforge.com)** — for mod and modpack hosting; metadata and release history are accessed through modpacks.ch.
948
1046
  - **[misode](https://github.com/misode)** — for [mcmeta](https://github.com/misode/mcmeta), the version-controlled Minecraft data repository that powers the `mc_data`, `mc_files`, and `mc_registry` tools.
949
1047
  - **[Mojang](https://www.minecraft.net)** — for publishing official Mojmap mappings and the Piston Meta API used for version discovery and JAR downloads.
950
1048
 
951
1049
  ### Modloader teams
952
- - **[NeoForged team](https://github.com/neoforged/NeoForge)** — for NeoForge, the [NeoForge documentation](https://docs.neoforged.net) seeded into the docs database, and the migration changelogs seeded into the primers database.
1050
+ - **[NeoForged team](https://github.com/neoforged/NeoForge)** — for NeoForge, the [NeoForge documentation](https://docs.neoforged.net) seeded into the docs database, and the [migration primer catalogue](https://docs.neoforged.net/primer/docs/) used by the primers tool.
953
1051
  - **[FabricMC team](https://github.com/FabricMC)** — for the [Fabric Wiki](https://fabricmc.net/wiki) and [Yarn mappings](https://github.com/FabricMC/yarn) seeded into the docs database, Intermediary mappings used by the `mappings` tool, and [mcsrc.dev](https://mcsrc.dev) whose source browsing and class analysis features inspired our `mc_source` tool.
954
- - **[MinecraftForge team](https://github.com/MinecraftForge)** — for the pre-fork Forge migration changelogs (1.18.2 → 1.20.1) seeded into the primers database.
1052
+ - **[MinecraftForge team](https://github.com/MinecraftForge)** — for Forge and the API changes documented in the Forge migration primers.
955
1053
 
956
1054
  ### Community contributors
957
1055
  - **[MCPHackers](https://mcphackers.org/)** — for [RetroMCP](https://github.com/MCPHackers/RetroMCP-Java), providing the Tiny v2 mappings that enable decompilation of legacy Minecraft versions (Alpha, Beta, and pre-1.7.10 releases).
@@ -977,4 +1075,3 @@ node dist/cli.js check-updates 2
977
1075
  ### Special thanks
978
1076
 
979
1077
  A heartfelt thank you to all the members of the **ForgeCraft Discord and Minecraft server** for being a genuinely great community, for your feedback, and for supporting my development endeavours over the years. This project wouldn't be what it is without you.
980
-
package/TESTING.md ADDED
@@ -0,0 +1,69 @@
1
+ # Compatibility testing
2
+
3
+ Run the deterministic regressions with `npm test`. Run `npm run build` before the MCP and package checks below.
4
+
5
+ `npm test` works directly after `npm ci`, before a build. Vitest generates the SQLite client before importing the tests. SQLite integration tests create a temporary template from the Prisma schema, use a fresh database for each test, and isolate their artifact cache. They do not depend on `dist/`, the packaged `template.db`, or earlier test results.
6
+
7
+ ## Minecraft era matrix
8
+
9
+ `npm run test:minecraft:eras` starts a local MCP server with a fresh temporary SQLite database, home and cache. It downloads public artifacts using the application's configured source policy. Every interaction with game/mod source goes through MCP. Results and the server log are retained in the printed temporary directory.
10
+
11
+ The vanilla checks verify named class discovery, decompiled source, methods/fields, bytecode and inheritance:
12
+
13
+ | Mapping generation | Minecraft versions |
14
+ | --- | --- |
15
+ | RetroMCP, separate client/server namespaces | Beta 1.7.3, 1.2.5 |
16
+ | RetroMCP, joined mappings | 1.5.2 |
17
+ | Legacy SRG, with MCP member names where available | 1.6.4, 1.7.10, 1.8.9, 1.12.2 |
18
+ | MCPConfig TSRG | 1.13.2 |
19
+ | Official Mojang mappings | 1.16.5, 1.18.2, 1.20.1, 1.20.6, 1.21.1 |
20
+ | Unobfuscated game artifacts | 26.1.2 |
21
+
22
+ The 1.5.2 checks also verify the explicit `MinecraftServer.tick` and `IntegratedServer.tickIntegrated` mappings. This catches differences between the joined mapping's declared names and TinyRemapper's inherited-name guesses.
23
+
24
+ Real-mod checks cover metadata resolution, the compatibility action alias, download/ingestion, database identity, language discovery, indexing, members, bytecode and single-class decompilation:
25
+
26
+ | Mod | Minecraft / loader |
27
+ | --- | --- |
28
+ | Waila | 1.6.4 Forge, 1.7.10 Forge |
29
+ | JEI | 1.8.9 Forge, 1.12.2 Forge, 1.16.5 Forge, 1.20.1 Forge, 1.21.1 NeoForge |
30
+ | Sodium | 1.16.5 Fabric |
31
+
32
+ Select individual game versions or run only the mod checks:
33
+
34
+ ```sh
35
+ npm run test:minecraft:eras -- b1.7.3 1.7.10 1.12.2
36
+ npm run test:minecraft:eras -- --mods
37
+ ```
38
+
39
+ Set `JAVA_HOME` for the Java runtime used by remapping. Public services must be reachable. Optional `MODLENS_TEST_TOOL_CACHE` and `MODLENS_TEST_ARTIFACT_CACHE` directories are read-only seeds: the harness copies their files into its temporary cache. Credentials, live database settings, MCP server settings and automatic graph/embedding settings are removed from the test server environment. The harness retains evidence and never starts Minecraft gameplay.
40
+
41
+ ## Offline format regressions
42
+
43
+ The unit suite builds small disposable JARs and mapping archives. Assertions cover the behavior that changed between eras:
44
+
45
+ - `mcmod.info` arrays, wrapper objects and bare objects from 1.2.5 through 1.12.2.
46
+ - Java 6/7/8 Forge annotations, accepted Minecraft ranges, required versus optional dependencies, version bounds and duplicate declarations.
47
+ - Legacy `FMLAT` files and folded JAR manifest headers.
48
+ - Forge `mods.toml`, JAR implementation-version substitution, early NeoForge `mods.toml` and later `neoforge.mods.toml` dependency types.
49
+ - Fabric declared mixin filenames, optional dependencies, array predicates and access wideners; Quilt metadata, dependencies and its `mixin` key.
50
+ - SRG/TSRG archives, MCP CSV names, object descriptors, CRLF mappings and consistency between version listing and source access.
51
+ - Pre-1.13 resources under `assets/`, later data packs under `data/`, and the 1.21 plural-to-singular directory changes.
52
+ - Legacy `.lang` files, JSON translations, namespace filtering and resource deduplication.
53
+ - Automatic loader inspection for unlabeled files, provider-label precedence, multiple loader declarations, pagination after filtering, artifact reuse, forced refresh, checksum failures and range-filtered update checks.
54
+
55
+ ## Package validation
56
+
57
+ `npm run test:package` installs the packed npm artifact into an isolated consumer. It checks setup, the MCP handshake, SQLite bootstrap, database types, transactions and restart persistence. Run this after changes to public commands, packaging or database setup.
58
+
59
+ ## Current coverage and upstream gaps
60
+
61
+ The 2026-09-13 housekeeping and loader-fallback passes verified the matrix above. These are selected compatibility boundaries, not an exhaustive test of every Minecraft release or mod.
62
+
63
+ Clean-install validation on 2026-09-13 passed `npm ci` → `npm test` (523 tests across 28 files) → `npm run build` on Ubuntu 24.04 and Windows using Node 22.21.1. The SQLite suite also passed in shuffled order, and the schema-repair test passed on its own.
64
+
65
+ The final mod matrix passed 116 MCP checks across all eight builds, including filtered search, mismatch rejection and database isolation. The installed npm-consumer checks also passed.
66
+
67
+ Some modpacks.ch historical file records, including the tested Waila releases and JEI 1.8.9, include the Minecraft version but omit the loader target. The matrix now supplies the loader filter for every release. ModLens keeps the API's version order, uses supplied loader labels, and downloads and inspects unlabeled candidate JARs before accepting a match. Result limits apply after filtering, including across pages. This also recovers a newer unlabeled file when older labeled versions exist.
68
+
69
+ The fallback is automatic for mod searches, details, downloads and provider update checks. Recovered labels carry `loaderSource: "jar"`; the artifact cache is shared with ingestion. `mod_info` returns up to 20 matches by default; its `limit` parameter allows a larger history. The MCP matrix also checks loader-filtered Waila search, rejects Waila for Fabric, and verifies that metadata inspection does not ingest candidates into the database. JARs with no recognised loader are excluded, and failed downloads or unreadable artifacts produce errors. API errors still surface, and no other metadata service supplies replacement records. Upstream labels can be populated later; they take precedence immediately when present.
package/dist/cli.js CHANGED
@@ -23,13 +23,14 @@ import { getModGradleFiles, searchGradleFiles, compareGradleDeps } from "./tools
23
23
  import { generateReport } from "./tools/reports.js";
24
24
  import { findAssetConflicts, findVanillaOverrides, analyzeModSidedness, analyzePackSidedness, computeModComplexity, computePackChangelog, findDataConflicts } from "./tools/packtools.js";
25
25
  import { indexKubeJsScripts, searchKubeJsScripts } from "./tools/kubejs.js";
26
- import { searchPacksAction, featuredPacksAction, packInfoAction, packManifestAction, ftbModInfoAction, listPackVersionsAction, listPackFilesAction, findModInPacksAction, } from "./tools/modpacks-ch.js";
26
+ import { searchPacksAction, featuredPacksAction, packInfoAction, packManifestAction, modInfoAction, listPackVersionsAction, listPackFilesAction, findModInPacksAction, } from "./tools/modpacks-ch.js";
27
27
  import { analyzeCrashLog, findMissingDeps } from "./tools/diagnostics.js";
28
28
  import { checkModCompat } from "./tools/compat-check.js";
29
29
  import { listModJarFiles, getModJarFile, getModLang, getModSounds, getModAtlas, getModManifest, listModConfigs, getModConfig, listModData, getModData, listModDataTags, getModDataTag, getModModel as getModModelData, diffModData, traceRecipeChain, } from "./tools/mod-data.js";
30
30
  import { mcPaths } from "./minecraft.js";
31
31
  import { findModById } from "./repositories/mod.js";
32
32
  import { CACHE_ROOT } from "./cache.js";
33
+ import { projectAction } from "./tools/project.js";
33
34
  import { getDb, disconnect } from "./db.js";
34
35
  import { readdir, readFile } from "fs/promises";
35
36
  import { join, resolve } from "path";
@@ -211,9 +212,11 @@ DOCS
211
212
  docs semantic-search <query> Semantic search (requires Ollama) [--limit=10]
212
213
 
213
214
  PRIMERS (version migration guides)
214
- primers seed Seed built-in porting primers
215
- primers get <id> Get primer by ID
215
+ primers seed Seed and fetch built-in primers [--fetch-content=false]
216
+ primers get <id> Read cached/fetched Markdown [--refresh] [--fetch-content=false]
217
+ [--start-line=1] [--max-lines=400]
216
218
  primers by-version <from> <to> Get primers for a version range [--modloader=]
219
+ [--include-content] [--max-chars=60000] [--cursor=] [--fetch-content=false]
217
220
  primers search <query> Keyword search [--modloader=] [--limit=]
218
221
  primers list List all primers [--modloader=] [--limit=]
219
222
  primers delete <id> Delete a primer by ID
@@ -291,6 +294,10 @@ MOD TAGS (cross-mod data-pack tags)
291
294
  mod-tags search <query> Search tags [--registry=] [--limit=]
292
295
 
293
296
  GRADLE
297
+ project import-local <bundle.zip> --key-file=<file> Import a private Gradle environment
298
+ project list --key-file=<file> List this project's snapshots
299
+ project info|classes|source|search|members|bytecode --key-file=<file> --environment-id=<id>
300
+ [--class-name=<name>] [--query=<text>] [--limit=20] [--offset=0] [--start-line=1] [--max-lines=200]
294
301
  gradle get-files <modId> Extract Gradle build files
295
302
  gradle search <query> Search Gradle files across mods [--mod-id-filter=] [--limit=]
296
303
  gradle compare-deps Compare dependency versions across mods [--group-filter=] [--mod-id-filter=]
@@ -327,7 +334,7 @@ MODPACKS.CH
327
334
  modpacks manifest <packId> <verId> Pack manifest [--namespace=]
328
335
  modpacks list-versions Pack version list [--namespace=] [--pack-id=]
329
336
  modpacks list-files Pack file list [--namespace=] [--pack-id=] [--version-id=] [--file-type=]
330
- modpacks ftb-mod-info <modId> FTB mod info [--mc-version=] [--loader=]
337
+ modpacks mod-info <modId> Mod metadata [--mc-version=] [--loader=] [--limit=20]
331
338
  modpacks find-mod Find a mod across packs [--mod-db-id=] [--cf-project=]
332
339
 
333
340
  DIAGNOSTICS
@@ -862,15 +869,33 @@ try {
862
869
  case "primers": {
863
870
  const sub = requireArg(positional[0], "primers action");
864
871
  switch (sub) {
865
- case "seed":
866
- out(await seedDefaultPrimers());
872
+ case "seed": {
873
+ const seeded = await seedDefaultPrimers(flags.fetchContent !== "false" && flags.fetchContent !== false);
874
+ out(seeded);
875
+ if (seeded.failed)
876
+ process.exitCode = 1;
867
877
  break;
878
+ }
868
879
  case "get":
869
- out(await getPrimer(numArg(positional[1], "id")));
870
- break;
871
- case "by-version":
872
- out(await getPrimersByVersionRange(requireArg(positional[1], "fromVersion"), requireArg(positional[2], "toVersion"), flags.modloader));
880
+ out(await getPrimer(numArg(positional[1], "id"), {
881
+ fetchContent: flags.fetchContent !== "false" && flags.fetchContent !== false,
882
+ refresh: flags.refresh === true || flags.refresh === "true",
883
+ startLine: flags.startLine,
884
+ maxLines: flags.maxLines,
885
+ }));
886
+ break;
887
+ case "by-version": {
888
+ const range = await getPrimersByVersionRange(requireArg(positional[1], "fromVersion"), requireArg(positional[2], "toVersion"), flags.modloader, {
889
+ includeContent: flags.includeContent === true || flags.includeContent === "true",
890
+ fetchContent: flags.fetchContent !== "false" && flags.fetchContent !== false,
891
+ maxChars: flags.maxChars,
892
+ cursor: flags.cursor,
893
+ });
894
+ out(range);
895
+ if ("failed" in range && range.failed)
896
+ process.exitCode = 1;
873
897
  break;
898
+ }
874
899
  case "search":
875
900
  out(await searchPrimers(requireArg(positional[1], "query"), flags.modloader, undefined, undefined, flags.limit));
876
901
  break;
@@ -1144,6 +1169,16 @@ try {
1144
1169
  break;
1145
1170
  }
1146
1171
  // ── Gradle ────────────────────────────────────────────────────────────
1172
+ case "project": {
1173
+ const action = requireArg(positional[0], "project action").replace(/-/g, "_");
1174
+ const keyFile = requireArg(typeof flags.keyFile === "string" ? flags.keyFile : undefined, "--key-file=/path/to/project-key.txt");
1175
+ const projectKey = (await readFile(resolve(keyFile), "utf8")).trim();
1176
+ const { keyFile: ignored, ...options } = flags;
1177
+ out(await projectAction({ ...options, action, projectKey,
1178
+ ...(action === "import_local" ? { bundlePath: resolve(requireArg(positional[1], "bundle.zip")) } : {}),
1179
+ }));
1180
+ break;
1181
+ }
1147
1182
  case "gradle": {
1148
1183
  const sub = requireArg(positional[0], "gradle action");
1149
1184
  switch (sub) {
@@ -1253,14 +1288,15 @@ try {
1253
1288
  case "list-files":
1254
1289
  out(await listPackFilesAction({ namespace: flags.namespace, packId: flags.packId, versionId: flags.versionId, fileType: flags.fileType }));
1255
1290
  break;
1256
- case "ftb-mod-info":
1257
- out(await ftbModInfoAction(modIdArg(positional[1], "modId"), { mcVersion: flags.mcVersion, loader: flags.loader }));
1291
+ case "mod-info":
1292
+ case "ftb-mod-info": // Compatibility with older CLI clients.
1293
+ out(await modInfoAction(modIdArg(positional[1], "modId"), { mcVersion: flags.mcVersion, loader: flags.loader, limit: flags.limit }));
1258
1294
  break;
1259
1295
  case "find-mod":
1260
1296
  out(await findModInPacksAction({ modDbId: flags.modDbId, cfProject: flags.cfProject }));
1261
1297
  break;
1262
1298
  default:
1263
- die(`Unknown modpacks action: ${sub}. Use: search|featured|info|manifest|list-versions|list-files|ftb-mod-info|find-mod`);
1299
+ die(`Unknown modpacks action: ${sub}. Use: search|featured|info|manifest|list-versions|list-files|mod-info|find-mod`);
1264
1300
  }
1265
1301
  break;
1266
1302
  }