@creeperhost/modlens-mcp 1.6.20 → 1.6.22
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.
- package/ATTRIBUTION.md +8 -1
- package/README.md +77 -2
- package/RUNTIME.md +262 -0
- package/TESTING.md +8 -0
- package/dist/hosted-mod-license.d.ts +36 -0
- package/dist/hosted-mod-license.d.ts.map +1 -0
- package/dist/hosted-mod-license.js +65 -0
- package/dist/hosted-mod-license.js.map +1 -0
- package/dist/hosted-oauth.d.ts +36 -0
- package/dist/hosted-oauth.d.ts.map +1 -0
- package/dist/hosted-oauth.js +522 -0
- package/dist/hosted-oauth.js.map +1 -0
- package/dist/hosted-policy.d.ts +36 -0
- package/dist/hosted-policy.d.ts.map +1 -0
- package/dist/hosted-policy.js +354 -0
- package/dist/hosted-policy.js.map +1 -0
- package/dist/launcher.js +104 -89
- package/dist/launcher.js.map +1 -1
- package/dist/license-notices.d.ts +8 -0
- package/dist/license-notices.d.ts.map +1 -0
- package/dist/license-notices.js +10 -0
- package/dist/license-notices.js.map +1 -0
- package/dist/license-source-references.d.ts +12 -0
- package/dist/license-source-references.d.ts.map +1 -0
- package/dist/license-source-references.js +18 -0
- package/dist/license-source-references.js.map +1 -0
- package/dist/license-templates.d.ts +6 -0
- package/dist/license-templates.d.ts.map +1 -0
- package/dist/license-templates.js +55 -0
- package/dist/license-templates.js.map +1 -0
- package/dist/local-mod.d.ts +31 -0
- package/dist/local-mod.d.ts.map +1 -0
- package/dist/local-mod.js +71 -0
- package/dist/local-mod.js.map +1 -0
- package/dist/minecraft.d.ts +2 -0
- package/dist/minecraft.d.ts.map +1 -1
- package/dist/minecraft.js +16 -0
- package/dist/minecraft.js.map +1 -1
- package/dist/mod-license.d.ts +53 -0
- package/dist/mod-license.d.ts.map +1 -0
- package/dist/mod-license.js +617 -0
- package/dist/mod-license.js.map +1 -0
- package/dist/repositories/mcVersion.d.ts +1 -0
- package/dist/repositories/mcVersion.d.ts.map +1 -1
- package/dist/repositories/mcVersion.js +11 -0
- package/dist/repositories/mcVersion.js.map +1 -1
- package/dist/runtime/cli.d.ts +2 -0
- package/dist/runtime/cli.d.ts.map +1 -0
- package/dist/runtime/cli.js +163 -0
- package/dist/runtime/cli.js.map +1 -0
- package/dist/runtime/companion.d.ts +32 -0
- package/dist/runtime/companion.d.ts.map +1 -0
- package/dist/runtime/companion.js +135 -0
- package/dist/runtime/companion.js.map +1 -0
- package/dist/runtime/guidance.d.ts +73 -0
- package/dist/runtime/guidance.d.ts.map +1 -0
- package/dist/runtime/guidance.js +40 -0
- package/dist/runtime/guidance.js.map +1 -0
- package/dist/runtime/hub.d.ts +150 -0
- package/dist/runtime/hub.d.ts.map +1 -0
- package/dist/runtime/hub.js +561 -0
- package/dist/runtime/hub.js.map +1 -0
- package/dist/runtime/modlens-agent.jar +0 -0
- package/dist/runtime/protocol.d.ts +202 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +91 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/requests.d.ts +367 -0
- package/dist/runtime/requests.d.ts.map +1 -0
- package/dist/runtime/requests.js +57 -0
- package/dist/runtime/requests.js.map +1 -0
- package/dist/server.js +186 -39
- package/dist/server.js.map +1 -1
- package/dist/tools/hosted-mc-index.d.ts +8 -0
- package/dist/tools/hosted-mc-index.d.ts.map +1 -0
- package/dist/tools/hosted-mc-index.js +92 -0
- package/dist/tools/hosted-mc-index.js.map +1 -0
- package/dist/tools/mc-fts.d.ts +2 -0
- package/dist/tools/mc-fts.d.ts.map +1 -1
- package/dist/tools/mc-fts.js +8 -0
- package/dist/tools/mc-fts.js.map +1 -1
- package/dist/tools/project.d.ts +7 -3
- package/dist/tools/project.d.ts.map +1 -1
- package/dist/tools/project.js +45 -3
- package/dist/tools/project.js.map +1 -1
- package/dist/tools/report-issue.d.ts +77 -0
- package/dist/tools/report-issue.d.ts.map +1 -0
- package/dist/tools/report-issue.js +92 -0
- package/dist/tools/report-issue.js.map +1 -0
- package/dist/tools/runtime.d.ts +6 -0
- package/dist/tools/runtime.d.ts.map +1 -0
- package/dist/tools/runtime.js +18 -0
- package/dist/tools/runtime.js.map +1 -0
- package/dist/tools/source.d.ts +1 -1
- package/dist/tools/source.d.ts.map +1 -1
- package/dist/tools/source.js +5 -1
- package/dist/tools/source.js.map +1 -1
- package/dist/tools/vanilla.d.ts +30 -1
- package/dist/tools/vanilla.d.ts.map +1 -1
- package/dist/tools/vanilla.js +184 -25
- package/dist/tools/vanilla.js.map +1 -1
- package/package.json +5 -1
- package/scripts/build-runtime-agent.mjs +64 -0
- package/scripts/fixtures/mod-license-corpus.json +154 -0
- package/scripts/gradle/modlens-runtime.init.gradle +17 -0
- package/scripts/runtime-test-client.mjs +36 -0
- package/scripts/test-hosted-oauth.mjs +186 -0
- package/scripts/test-hosted-policy.mjs +182 -0
- package/scripts/test-mod-licenses-live.mjs +86 -0
- package/scripts/test-package.mjs +44 -0
- package/scripts/test-project-http.mjs +46 -3
- package/scripts/test-runtime-gradle.mjs +107 -0
- package/scripts/test-runtime-minecraft.mjs +190 -0
- package/scripts/test-runtime-native.mjs +120 -0
- package/scripts/test-runtime-remote.mjs +209 -0
- package/scripts/update-license-templates.mjs +14 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { RUNTIME_HELP } from "./hub.js";
|
|
3
|
+
import { runtimeCommand, runtimeMode } from "./protocol.js";
|
|
4
|
+
export const runtimeToolSchema = {
|
|
5
|
+
action: z.enum(["help", "setup", "status", "sessions", "launch", "events", "command", "artifact"]),
|
|
6
|
+
projectDir: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Absolute local mod project directory for setup; requires an existing Gradle dev run."),
|
|
10
|
+
projectId: z.string().uuid().optional(),
|
|
11
|
+
sessionId: z.string().uuid().optional(),
|
|
12
|
+
mcVersion: z.literal("26.3").optional(),
|
|
13
|
+
mode: runtimeMode
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("interactive=human input, observe=visible/MCP input only, hidden=invisible/MCP input only"),
|
|
16
|
+
gradleTask: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Existing client JavaExec task, defaults to runClient; e.g. :fabric:runClient"),
|
|
20
|
+
minecraftHooks: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Enable optional 26.3 screenshot/state/crash hooks (default true). SDL input and JVM monitoring are independent."),
|
|
24
|
+
javaHome: z.string().optional().describe("JDK home for the Gradle launch; use JDK 25 for 26.3"),
|
|
25
|
+
afterCursor: z.number().int().min(0).optional(),
|
|
26
|
+
waitMs: z.number().int().min(0).max(30_000).optional(),
|
|
27
|
+
command: runtimeCommand.optional(),
|
|
28
|
+
artifactName: z.string().optional(),
|
|
29
|
+
};
|
|
30
|
+
export const runtimeRequest = z.object(runtimeToolSchema);
|
|
31
|
+
function need(v, name) {
|
|
32
|
+
if (v === undefined)
|
|
33
|
+
throw new Error(`${name} is required for this action`);
|
|
34
|
+
return v;
|
|
35
|
+
}
|
|
36
|
+
export async function executeRuntime(hub, raw) {
|
|
37
|
+
const a = runtimeRequest.parse(raw);
|
|
38
|
+
if (a.action === "help")
|
|
39
|
+
return RUNTIME_HELP;
|
|
40
|
+
switch (a.action) {
|
|
41
|
+
case "setup":
|
|
42
|
+
return hub.setup(need(a.projectDir, "projectDir"), a.mode, a.gradleTask, a.minecraftHooks);
|
|
43
|
+
case "status":
|
|
44
|
+
return hub.status(a.sessionId);
|
|
45
|
+
case "sessions":
|
|
46
|
+
return hub.list();
|
|
47
|
+
case "launch":
|
|
48
|
+
return hub.launch(need(a.projectId, "projectId"), a.javaHome);
|
|
49
|
+
case "events":
|
|
50
|
+
return hub.events(a.afterCursor, a.waitMs, a.sessionId);
|
|
51
|
+
case "command":
|
|
52
|
+
return hub.command(need(a.sessionId, "sessionId"), need(a.command, "command"));
|
|
53
|
+
case "artifact":
|
|
54
|
+
return hub.artifact(need(a.sessionId, "sessionId"), need(a.artifactName, "artifactName"));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/runtime/requests.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -17,7 +17,7 @@ import { searchModClass, getModClassMembers, getModClassBytecode, findModReferen
|
|
|
17
17
|
import { getMixinTargets, getMixinConflicts, getAtEntries, getAwEntries, resolveMixinTargets, getMixinsTargetingPackage, findAtAwConflicts } from "./tools/mixin-scan.js";
|
|
18
18
|
import { syncModrinth, syncCurseforge, checkUpdates, downloadSource, batchSyncSources, searchPlatforms, batchCheckUpdates } from "./tools/platform.js";
|
|
19
19
|
import { listMcVersions, listNeoForgeVersions, listFabricApiVersions, listForgeVersions, downloadNeoForge, downloadFabricApi, downloadForge } from "./platform.js";
|
|
20
|
-
import { searchMinecraftClass, getMinecraftSource, getMcClassBytecode, getMcClassMembers, findMcReferences, getMcInheritance, diffMcVersions, decompileMcVersion, decompileMcVersionStatus, searchMcCode, validateAccessWidener, analyzeMixin, searchEvents, } from "./tools/vanilla.js";
|
|
20
|
+
import { searchMinecraftClass, getMinecraftSource, getMinecraftSourceInfo, prepareMinecraftSource, getMcClassBytecode, getMcClassMembers, findMcReferences, getMcInheritance, diffMcVersions, decompileMcVersion, decompileMcVersionStatus, searchMcCode, validateAccessWidener, analyzeMixin, searchEvents, } from "./tools/vanilla.js";
|
|
21
21
|
import { diffMcVersionsDetailed, diffModVersionsDetailed } from "./tools/version-diff.js";
|
|
22
22
|
import { indexMcVersion, searchMcIndexed, indexMcSourceSemantic, searchMcSourceSemantic, indexModSourceFts, indexModSourceSemantic, searchModSourceIndexed, searchModSourceSemantic } from "./tools/mc-fts.js";
|
|
23
23
|
import { findMapping, remapModJar, getParchment, listParchmentVersions, getParchmentSummary } from "./tools/mappings.js";
|
|
@@ -39,6 +39,12 @@ import { mcPaths } from "./minecraft.js";
|
|
|
39
39
|
import { findModById, resolveModRefSlim } from "./repositories/mod.js";
|
|
40
40
|
import { CACHE_ROOT } from "./cache.js";
|
|
41
41
|
import { projectAction, projectToolSchema } from "./tools/project.js";
|
|
42
|
+
import { hostedMinecraftVersionIndexStatus, scheduleHostedMinecraftVersionIndex } from "./tools/hosted-mc-index.js";
|
|
43
|
+
import { runtimeAction, runtimeToolSchema, runtimeHub } from "./tools/runtime.js";
|
|
44
|
+
import { reportIssue, reportIssueSchema } from "./tools/report-issue.js";
|
|
45
|
+
import { hostedActions, hostedMinecraftSourceAccess, hostedMinecraftSourceTeams, HostedBudget, HostedPolicyError, hostedLimits, hostedPrincipal, runHostedTool } from "./hosted-policy.js";
|
|
46
|
+
import { HostedOAuth, HostedOAuthError } from "./hosted-oauth.js";
|
|
47
|
+
import { reviewHostedMod, localModPlan } from "./hosted-mod-license.js";
|
|
42
48
|
// Load .env — try ~/.modlens/.env first (npx/installed users), then local .env (git-clone users)
|
|
43
49
|
import { readFileSync, existsSync } from "fs";
|
|
44
50
|
import { join, dirname } from "path";
|
|
@@ -158,10 +164,82 @@ class JsonBodyParseError extends Error {
|
|
|
158
164
|
}
|
|
159
165
|
class HttpBodyTooLargeError extends Error {
|
|
160
166
|
}
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
const hostedBudget = new HostedBudget();
|
|
168
|
+
function createMcpServer(principal, allowMinecraftSource = false) {
|
|
169
|
+
const server = new McpServer({ name: "modlens", version: pkg.version }, {
|
|
170
|
+
instructions: "ModLens supports optional Minecraft 26.3 runtime control. Call runtime help/setup. On remote MCP, runtime returns executed:false and a local execution plan: use your local terminal/file tools to run the --runtime helper on the Minecraft PC. No second MCP connection or tunnel is needed. Local stdio executes directly. Inspect sessions/capabilities after launch; use events/command/artifact for input, screenshots, crashes and allocation reports. Never describe a returned plan as an executed action. Idle AI wake-up is not provided. To report a ModLens bug, use report_issue help/prepare for a GitHub draft and submission instructions; the caller files it using existing GitHub access when requested by the user.",
|
|
171
|
+
});
|
|
172
|
+
const limits = principal === undefined ? undefined : hostedLimits();
|
|
173
|
+
function registerTool(name, description, schema, handler) {
|
|
174
|
+
if (!limits || principal === undefined) {
|
|
175
|
+
server.tool(name, description, schema, handler);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const actions = hostedActions(name, allowMinecraftSource);
|
|
179
|
+
if (!actions)
|
|
180
|
+
return;
|
|
181
|
+
const actionSchema = schema.action;
|
|
182
|
+
let publicSchema = actionSchema instanceof z.ZodEnum
|
|
183
|
+
? { ...schema, action: z.enum(actions.filter(a => actionSchema.options.includes(a))) }
|
|
184
|
+
: schema;
|
|
185
|
+
if (name === "mod_bytecode")
|
|
186
|
+
publicSchema = { ...publicSchema,
|
|
187
|
+
startLine: z.number().int().positive().optional().describe("First bytecode line, 1-based"),
|
|
188
|
+
maxLines: z.number().int().positive().optional().describe("Bytecode lines to return (hosted cap applies)"),
|
|
189
|
+
};
|
|
190
|
+
// Do not advertise host paths, cache jobs, registry exports or unavailable operations.
|
|
191
|
+
const publicDescription = `${description.split(". ")[0]}. Hosted access: ${actions.filter(Boolean).join(", ") || "analysis"}. Responses and cumulative usage are bounded; use focused queries and source ranges.`;
|
|
192
|
+
server.tool(name, publicDescription, publicSchema, (async (args, extra) => runHostedTool(name, args, principal, limits, hostedBudget, async (bounded) => {
|
|
193
|
+
const result = await handler(bounded, extra);
|
|
194
|
+
if (name === "mc_source" && (!result.isError || ["search_indexed", "search_code"].includes(String(bounded.action)))) {
|
|
195
|
+
for (const version of [bounded.version, bounded.mcVersion, bounded.versionA, bounded.versionB]) {
|
|
196
|
+
scheduleHostedMinecraftVersionIndex(version);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}, allowMinecraftSource)));
|
|
201
|
+
}
|
|
202
|
+
registerTool("report_issue", "Prepare a ModLens GitHub issue and direct the coding agent to submit it using its own GitHub tools or gh CLI; this tool does not publish anything. " +
|
|
203
|
+
"Use help for guidance or prepare with title/summary and sanitized reproduction details. Available locally and remotely; always returns executed:false. The caller separately reports the GitHub URL after submission.", reportIssueSchema, safe(async (args) => out(reportIssue(args, pkg.version))));
|
|
204
|
+
registerTool("mod_license", "Inspect a mod's license evidence before hosted source access, or request consent for local decompilation. " +
|
|
205
|
+
"Precedence: applicable GitHub version, mod JAR, CurseForge. action=check|local_plan. " +
|
|
206
|
+
"Provide modId/dbId or projectKey+environmentId+className. Local plans must run on the user's computer; never claim a plan has executed.", {
|
|
207
|
+
action: z.enum(["check", "local_plan"]),
|
|
208
|
+
modId: z.union([z.string(), z.number()]).optional(), dbId: z.number().int().positive().optional(),
|
|
209
|
+
projectKey: z.string().optional(), environmentId: z.string().optional(), className: z.string().optional(),
|
|
210
|
+
operation: z.enum(["source", "bytecode"]).optional(),
|
|
211
|
+
startLine: z.number().int().positive().optional(), maxLines: z.number().int().positive().max(10000).optional(),
|
|
212
|
+
}, safe(async (args) => {
|
|
213
|
+
const decision = await reviewHostedMod(args);
|
|
214
|
+
if (args.action === "check")
|
|
215
|
+
return out(decision);
|
|
216
|
+
let accepted = false;
|
|
217
|
+
if (server.server.getClientCapabilities()?.elicitation?.form !== undefined) {
|
|
218
|
+
const consent = await server.server.elicitInput({
|
|
219
|
+
mode: "form",
|
|
220
|
+
message: `Allow local decompilation of ${args.className ?? "this mod"} from JAR SHA-256 ${decision.sha256}? ${decision.reason} This does not grant redistribution rights; source stays on your computer.`,
|
|
221
|
+
requestedSchema: { type: "object", properties: {
|
|
222
|
+
acceptLocal: { type: "boolean", title: "I accept local decompilation of this artifact", default: false },
|
|
223
|
+
}, required: ["acceptLocal"] },
|
|
224
|
+
});
|
|
225
|
+
if (consent.action !== "accept" || consent.content?.acceptLocal !== true)
|
|
226
|
+
return out({ executed: false, declined: true });
|
|
227
|
+
accepted = true;
|
|
228
|
+
}
|
|
229
|
+
return out({ licenseReview: decision, local: localModPlan(decision, { ...args, action: args.operation ?? "source" }, accepted) });
|
|
230
|
+
}));
|
|
231
|
+
registerTool("runtime", "Optional Minecraft 26.3 live dev runtime: help/setup/status/sessions/launch/events/command/artifact. " +
|
|
232
|
+
"The AI can set up the Java agent and IntelliJ Gradle launch itself with setup(projectDir), then launch or detect a user launch. Remote MCP returns a local execution plan for the --runtime CLI helper; execute it using local terminal/file tools. " +
|
|
233
|
+
"Monitor JVM metrics/crashes and allocation hotspots filtered by mod package, control keyboard/mouse internally, capture screenshots, and switch visible watch-only/hidden/human modes. " +
|
|
234
|
+
"No desktop control or extra local MCP connection required. Plans have executed:false; verify local results. Call help for setup. Poll events with a cursor to actively monitor.", runtimeToolSchema, safe(async (args) => {
|
|
235
|
+
const result = await runtimeAction(args);
|
|
236
|
+
if (args.action === "artifact" && result && typeof result === "object" && "mimeType" in result && "data" in result) {
|
|
237
|
+
return { content: [{ type: "image", mimeType: "image/png", data: result.data }] };
|
|
238
|
+
}
|
|
239
|
+
return out(result);
|
|
240
|
+
}));
|
|
163
241
|
// ── 1. mod ────────────────────────────────────────────────────────────────────
|
|
164
|
-
|
|
242
|
+
registerTool("mod", "Mod database, decompile, and source browser. action=ingest|list|get|search|stats|dependencies|dep_graph|version_conflicts|source_urls|decompile|decompile_status|decompile_class|source|search_source|reindex|batch_ingest|batch_decompile|refresh_metadata|index_fts|search_indexed|index_semantic|search_semantic|get_paths|delete|graph_build|graph_status|graph_query|graph_report|graph_enrich_next|graph_enrich_submit|graph_download|graph_export|graph_submit|embed_export|embed_download|embed_download_all|embed_status|embed_submit. index_fts/search_indexed: BM25-ranked FTS over source code. index_semantic/search_semantic: vector search (requires Ollama). refresh_metadata: re-parse all mods with degraded metadata (filename/@Mod annotation) and upgrade if a higher-quality manifest is found. graph_enrich_next: get next un-enriched chunk for chat enrichment. graph_enrich_submit: submit enriched nodes/edges (chunkIndex, nodes, edges). graph_download: download pre-built graph from registry (targetType=mod|vanilla|modloader with targetId/targetVersion). graph_export: export a mod's local graph as a shareable gzipped bundle. graph_submit: validate an exported bundle and open a draft GitHub registry PR (bundlePath required). embed_export/embed_download/embed_status: targetType-aware embeddings actions for mod/vanilla/modloader. embed_download: downloads registry embeddings; protects local embeddings by default (use force=true to overwrite). embed_download_all: download embeddings for all mods. embed_submit: validate an exported bundle and open a draft GitHub registry PR (bundlePath required). Auto-behaviors: decompile_status auto-queues embedding (MODLENS_AUTO_EMBED) and graph build (MODLENS_AUTO_GRAPH). Pass autoEmbed/autoGraph to override per-call.", {
|
|
165
243
|
action: z.enum([
|
|
166
244
|
"ingest", "list", "get", "search", "stats", "dependencies", "dep_graph",
|
|
167
245
|
"version_conflicts", "source_urls", "decompile", "decompile_status",
|
|
@@ -176,6 +254,8 @@ function createMcpServer() {
|
|
|
176
254
|
dbId: z.number().optional().describe("DB id"),
|
|
177
255
|
query: z.string().optional(),
|
|
178
256
|
path: z.string().optional(),
|
|
257
|
+
startLine: z.number().int().positive().optional().describe("First source line, 1-based"),
|
|
258
|
+
maxLines: z.number().int().positive().optional().describe("Source lines to return"),
|
|
179
259
|
className: z.string().optional(),
|
|
180
260
|
loader: z.string().optional().describe("fabric|neoforge|forge|quilt"),
|
|
181
261
|
mcVersion: z.string().optional(),
|
|
@@ -208,7 +288,7 @@ function createMcpServer() {
|
|
|
208
288
|
autoEmbed: z.boolean().optional().describe("Override auto-embed on decompile completion (default: env MODLENS_AUTO_EMBED, true)"),
|
|
209
289
|
autoGraph: z.boolean().optional().describe("Override auto-graph-build on decompile/source-download (default: env MODLENS_AUTO_GRAPH, true)"),
|
|
210
290
|
provenance: z.enum(["local", "registry", "community"]).optional().describe("Filter search_semantic results by embedding source (default: all sources)"),
|
|
211
|
-
}, safe(async ({ action, jarPath, modId, dbId: rawDbId, query, path, className, loader, mcVersion, hasMixins, decompiled, recursive, skipSource, isRegex, force, limit, directory, indexClasses, replace, budget, backend, chunkIndex, nodes, edges, outputDir, modVersion, targetType, targetId, targetVersion, targetLoader, targetMcVersion, model, autoEmbed, autoGraph, provenance, bundlePath, contributor, note }) => {
|
|
291
|
+
}, safe(async ({ action, jarPath, modId, dbId: rawDbId, query, path, startLine, maxLines, className, loader, mcVersion, hasMixins, decompiled, recursive, skipSource, isRegex, force, limit, directory, indexClasses, replace, budget, backend, chunkIndex, nodes, edges, outputDir, modVersion, targetType, targetId, targetVersion, targetLoader, targetMcVersion, model, autoEmbed, autoGraph, provenance, bundlePath, contributor, note }) => {
|
|
212
292
|
const dbId = await resolveDbIdAsync(rawDbId, modId);
|
|
213
293
|
const resolvedTargetType = targetType ?? "mod";
|
|
214
294
|
const resolvedTargetId = targetId ?? (typeof modId === "string" ? modId : undefined) ?? (resolvedTargetType === "vanilla" ? "minecraft" : undefined);
|
|
@@ -260,7 +340,7 @@ function createMcpServer() {
|
|
|
260
340
|
case "source":
|
|
261
341
|
if (dbId == null)
|
|
262
342
|
throw new Error(`Mod not found: ${String(modId ?? rawDbId ?? "")}`);
|
|
263
|
-
result = await getModSource(dbId, path);
|
|
343
|
+
result = await getModSource(dbId, path, startLine, maxLines);
|
|
264
344
|
break;
|
|
265
345
|
case "search_source":
|
|
266
346
|
result = await searchSource(query, dbId, isRegex ?? false, limit ?? 50);
|
|
@@ -381,7 +461,7 @@ function createMcpServer() {
|
|
|
381
461
|
return out(result);
|
|
382
462
|
}));
|
|
383
463
|
// ── 2. mod_bytecode ───────────────────────────────────────────────────────────
|
|
384
|
-
|
|
464
|
+
registerTool("mod_bytecode", "Mod JAR bytecode and class analysis — for INGESTED MODS only (requires dbId or modId of an ingested mod). For vanilla Minecraft classes use mc_source instead. action=search_class|class_members|bytecode|find_refs|cross_refs|inheritance|diff|diff_detailed|cache_diff|find_implementors|scan_registrations|annotated_by|event_listeners|optional_integrations|network_payloads|config_schema. Most actions require dbId or modId. cross_refs/find_implementors/annotated_by/event_listeners can omit modId to search all mods. diff/diff_detailed/cache_diff require dbIdA+dbIdB. diff_detailed gives AST-level method/field changes with breaking-change flags (add semantic=true for cosine similarity, requires mod index_semantic). cache_diff forces a (re)compute and writes to DB. Set env AUTO_CACHE_MOD_DIFFS=1 to auto-cache all diff_detailed calls.", {
|
|
385
465
|
action: z.enum(["search_class", "class_members", "bytecode", "find_refs", "cross_refs", "inheritance", "diff", "diff_detailed", "cache_diff", "find_implementors", "scan_registrations", "annotated_by", "event_listeners", "optional_integrations", "network_payloads", "config_schema"]),
|
|
386
466
|
dbId: z.number().optional().describe("DB id"),
|
|
387
467
|
dbIdA: z.number().optional().describe("older DB id"),
|
|
@@ -457,7 +537,7 @@ function createMcpServer() {
|
|
|
457
537
|
return out(result);
|
|
458
538
|
}));
|
|
459
539
|
// ── 3. mod_mixins ─────────────────────────────────────────────────────────────
|
|
460
|
-
|
|
540
|
+
registerTool("mod_mixins", "Mod mixin and access transformer analysis. action=targets|resolve|conflicts|targets_in_package|at_conflicts|at_entries|aw_entries.", {
|
|
461
541
|
action: z.enum(["targets", "resolve", "conflicts", "targets_in_package", "at_conflicts", "at_entries", "aw_entries"]),
|
|
462
542
|
modId: z.union([z.string(), z.number()]).optional().describe("mod ID or DB id"),
|
|
463
543
|
dbId: z.number().optional().describe("DB id"),
|
|
@@ -494,7 +574,7 @@ function createMcpServer() {
|
|
|
494
574
|
return out(result);
|
|
495
575
|
}));
|
|
496
576
|
// ── 4. platform ───────────────────────────────────────────────────────────────
|
|
497
|
-
|
|
577
|
+
registerTool("platform", "Modrinth/CurseForge platform sync and source download. action=sync_modrinth|sync_curseforge|check_updates|batch_sync|download_source|search|batch_check_updates. " +
|
|
498
578
|
"action=search: unified mod search across both Modrinth and CurseForge — deduplicated, sorted by downloads (query required, loader, mcVersion, limit). Missing modpacks.ch loader labels are recovered by inspecting cached/downloaded JARs. " +
|
|
499
579
|
"action=batch_check_updates: check ALL linked mods for newer versions on Modrinth/CurseForge in one pass (mcVersion, loader, modIdFilter, limit). " +
|
|
500
580
|
"download_source downloads GitHub source without decompilation — auto-queues graph build (override with autoGraph). Env: MODLENS_AUTO_GRAPH (default: 1).", {
|
|
@@ -540,7 +620,7 @@ function createMcpServer() {
|
|
|
540
620
|
return out(result);
|
|
541
621
|
}));
|
|
542
622
|
// ── 5. modpacks_ch ───────────────────────────────────────────────────────────
|
|
543
|
-
|
|
623
|
+
registerTool("modpacks_ch", "Search, resolve, list versions for, and ingest arbitrary modpacks so other ModLens tools can analyze the exact pack contents. General workflow: when the user names a modpack and possibly a fuzzy version like 7.1, call action=list_versions with packRef/versionRef, choose the best matching version, then call action=ingest_pack before using analyze_crash_log, reports, pack_tools, dependency checks, mixin scans, source search, or other pack-aware analysis. Crash triage is one common use, not the only use. " +
|
|
544
624
|
"Metadata comes from modpacks.ch for all providers; downloads use the supplied URLs and Modrinth .mrpack archives. Loader-filtered mod queries automatically download and inspect unlabeled JARs; recovered labels report loaderSource=jar. No CurseForge API key is required. Modrinth packRef may be a slug/project ID, a modrinth.com modpack/version URL, an api.modrinth.com project/version URL, a cdn.modrinth.com .mrpack URL, or any direct HTTPS .mrpack URL. " +
|
|
545
625
|
"action=search|featured|info|manifest|resolve_pack|list_versions|ingest_pack|sync_pack_mods|search_mods|mod_info|download_mod|download_overrides|list_pack_versions|list_pack_files|find_mod_in_packs. mod_info returns up to limit versions (default 20). search_ftb_mods and ftb_mod_info remain compatibility aliases. " +
|
|
546
626
|
"namespace=ftb|curseforge|modrinth|feedthebeast (default: ftb). feedthebeast uses the modpacks.ch /ftb catalog; ftb uses /modpack. resolve_pack/list_versions/ingest_pack accept packRef=name/slug/id and versionRef=version id/name/number/partial text. list_versions returns matches for fuzzy refs; list_pack_versions is DB-backed synced-pack history. " +
|
|
@@ -648,7 +728,7 @@ function createMcpServer() {
|
|
|
648
728
|
return out(result);
|
|
649
729
|
}));
|
|
650
730
|
// ── 6. mc_versions ────────────────────────────────────────────────────────────
|
|
651
|
-
|
|
731
|
+
registerTool("mc_versions", "Minecraft and mod loader version management. action=list_mc|list_neoforge|list_forge|list_fabric|ingest_neoforge|ingest_forge|ingest_fabric.", {
|
|
652
732
|
action: z.enum(["list_mc", "list_neoforge", "list_forge", "list_fabric", "ingest_neoforge", "ingest_forge", "ingest_fabric"]),
|
|
653
733
|
type: z.enum(["release", "snapshot", "all"]).optional(),
|
|
654
734
|
mcVersion: z.string().optional(),
|
|
@@ -712,8 +792,8 @@ function createMcpServer() {
|
|
|
712
792
|
return out(result);
|
|
713
793
|
}));
|
|
714
794
|
// ── 6. mc_source ─────────────────────────────────────────────────────────────
|
|
715
|
-
|
|
716
|
-
action: z.enum(["search_class", "get_source", "bytecode", "class_members", "find_refs", "inheritance", "diff", "diff_detailed", "decompile", "decompile_status", "search_code", "index", "search_indexed", "search_events", "validate_aw", "analyze_mixin", "index_semantic", "search_semantic", "get_paths"]),
|
|
795
|
+
registerTool("mc_source", "Vanilla Minecraft source code, decompilation, and validation — use this for vanilla MC classes (net/minecraft/...), NOT mod_bytecode. Requires version param. action=search_class|source_info|index_status|get_source|bytecode|class_members|find_refs|inheritance|diff|diff_detailed|decompile|decompile_status|search_code|index|search_indexed|search_events|validate_aw|analyze_mixin|index_semantic|search_semantic|get_paths. source_info returns cached line count without source text or decompilation. index_status reports full-version index readiness. get_paths returns the on-disk jar, decompiled source directory, and index paths so the agent can grep/search files natively. diff_detailed gives AST-level method/field changes with breaking-change flags per class; add semantic=true for cosine similarity scoring (requires Ollama + index_semantic run first). index_semantic/search_semantic require Ollama running.", {
|
|
796
|
+
action: z.enum(["search_class", "source_info", "index_status", "get_source", "bytecode", "class_members", "find_refs", "inheritance", "diff", "diff_detailed", "decompile", "decompile_status", "search_code", "index", "search_indexed", "search_events", "validate_aw", "analyze_mixin", "index_semantic", "search_semantic", "get_paths"]),
|
|
717
797
|
version: z.string().optional(),
|
|
718
798
|
versionA: z.string().optional(),
|
|
719
799
|
versionB: z.string().optional(),
|
|
@@ -742,8 +822,16 @@ function createMcpServer() {
|
|
|
742
822
|
case "search_class":
|
|
743
823
|
result = await searchMinecraftClass(v, query ?? className);
|
|
744
824
|
break;
|
|
825
|
+
case "source_info":
|
|
826
|
+
result = await getMinecraftSourceInfo(v, className);
|
|
827
|
+
break;
|
|
828
|
+
case "index_status":
|
|
829
|
+
result = await hostedMinecraftVersionIndexStatus(v);
|
|
830
|
+
break;
|
|
745
831
|
case "get_source":
|
|
746
|
-
result =
|
|
832
|
+
result = principal !== undefined && !allowMinecraftSource
|
|
833
|
+
? await prepareMinecraftSource(v, className)
|
|
834
|
+
: await getMinecraftSource(v, className, startLine, endLine, maxLines);
|
|
747
835
|
break;
|
|
748
836
|
case "bytecode":
|
|
749
837
|
result = await getMcClassBytecode(v, className);
|
|
@@ -808,7 +896,7 @@ function createMcpServer() {
|
|
|
808
896
|
return out(result);
|
|
809
897
|
}));
|
|
810
898
|
// ── 7. mappings ───────────────────────────────────────────────────────────────
|
|
811
|
-
|
|
899
|
+
registerTool("mappings", "Minecraft name mappings and Parchment parameter docs. action=find translates one symbol between namespaces; action=remap rewrites a mod JAR and requires inputJar/outputJar/toMapping; action=parchment|list_parchment|parchment_summary reads Parchment docs.", {
|
|
812
900
|
action: z.enum(["find", "remap", "parchment", "list_parchment", "parchment_summary"]),
|
|
813
901
|
symbol: z.string().optional().describe("Symbol to translate; use with action=find, not action=remap"),
|
|
814
902
|
version: z.string().optional(),
|
|
@@ -854,7 +942,7 @@ function createMcpServer() {
|
|
|
854
942
|
return out(result);
|
|
855
943
|
}));
|
|
856
944
|
// ── 8. docs ───────────────────────────────────────────────────────────────────
|
|
857
|
-
|
|
945
|
+
registerTool("docs", "Minecraft modding documentation database. action=ingest|seed|get|search|list|delete|semantic_search|backfill_embeddings. semantic_search requires Ollama running.", {
|
|
858
946
|
action: z.enum(["ingest", "seed", "get", "search", "list", "delete", "semantic_search", "backfill_embeddings"]),
|
|
859
947
|
entries: z.array(z.object({
|
|
860
948
|
className: z.string().optional(),
|
|
@@ -903,7 +991,7 @@ function createMcpServer() {
|
|
|
903
991
|
return out(result);
|
|
904
992
|
}));
|
|
905
993
|
// ── 9. primers ────────────────────────────────────────────────────────────────
|
|
906
|
-
|
|
994
|
+
registerTool("primers", "Minecraft migration primers. by_version finds each migration step, including vanilla changes for the selected loader; includeContent=true bundles the Markdown with a shared maxChars budget. Continue with nextCursor until null, keeping the same range and loader. get reads one guide (follow nextStartLine). Missing content is fetched and cached unless fetchContent=false. seed populates the catalogue. Bundles report per-guide failures; retry failed IDs with get. Published guides may leave coverage gaps. semantic_search requires Ollama.", {
|
|
907
995
|
action: z.enum(["ingest", "seed", "get", "by_version", "search", "list", "delete", "semantic_search", "backfill_embeddings"]),
|
|
908
996
|
entries: z.array(z.object({
|
|
909
997
|
fromVersion: z.string(),
|
|
@@ -964,7 +1052,7 @@ function createMcpServer() {
|
|
|
964
1052
|
return { ...out(result), ...(result && typeof result === "object" && "failed" in result && Number(result.failed) > 0 ? { isError: true } : {}) };
|
|
965
1053
|
}));
|
|
966
1054
|
// ── 10. mc_registry ───────────────────────────────────────────────────────────
|
|
967
|
-
|
|
1055
|
+
registerTool("mc_registry", "Vanilla MC registry and meta data (blocks, commands, registries, sounds, item components). action=blocks|commands|registries|sounds|item_components|registry_entries|mcmeta_versions.", {
|
|
968
1056
|
action: z.enum(["blocks", "commands", "registries", "sounds", "item_components", "registry_entries", "mcmeta_versions"]),
|
|
969
1057
|
version: z.string().optional(),
|
|
970
1058
|
registry: z.string().optional().describe("registry key e.g. 'block','item','entity_type'"),
|
|
@@ -997,7 +1085,7 @@ function createMcpServer() {
|
|
|
997
1085
|
return out(result);
|
|
998
1086
|
}));
|
|
999
1087
|
// ── 11. mc_data ───────────────────────────────────────────────────────────────
|
|
1000
|
-
|
|
1088
|
+
registerTool("mc_data", "Vanilla MC data browser — tags, recipes, loot tables, lang, blockstates, models, biomes, enchantments, advancements, structures, particles, entity attributes. action=tags|find_tags_for|recipes|get_recipe|find_recipes_for|loot_tables|get_loot_table|lang|blockstate|model|model_tree|biomes|get_biome|damage_types|enchantments|get_enchantment|advancements|get_advancement|structures|get_structure|particles|get_particle|entity_attributes.", {
|
|
1001
1089
|
action: z.enum([
|
|
1002
1090
|
"tags", "find_tags_for", "recipes", "get_recipe", "find_recipes_for",
|
|
1003
1091
|
"loot_tables", "get_loot_table", "lang", "blockstate", "model", "model_tree",
|
|
@@ -1101,7 +1189,7 @@ function createMcpServer() {
|
|
|
1101
1189
|
return out(result);
|
|
1102
1190
|
}));
|
|
1103
1191
|
// ── 12. mc_files ─────────────────────────────────────────────────────────────
|
|
1104
|
-
|
|
1192
|
+
registerTool("mc_files", "Vanilla MC file access via misode/mcmeta (data packs, assets, atlases, diffs, changelogs). action=get_data|get_asset|list_files|diff|atlas|raw|compare|changelog. list_files requires dirPath+version+branch. diff requires filePath+versionA+versionB. get_data/get_asset require filePath.", {
|
|
1105
1193
|
action: z.enum(["get_data", "get_asset", "list_files", "diff", "atlas", "raw", "compare", "changelog"]),
|
|
1106
1194
|
filePath: z.string().optional().describe("relative path in branch"),
|
|
1107
1195
|
version: z.string().optional(),
|
|
@@ -1143,7 +1231,7 @@ function createMcpServer() {
|
|
|
1143
1231
|
return out(result);
|
|
1144
1232
|
}));
|
|
1145
1233
|
// ── 13. mod_jar ───────────────────────────────────────────────────────────────
|
|
1146
|
-
|
|
1234
|
+
registerTool("mod_jar", "Mod JAR file access and registry discovery (lang, sounds, atlases, configs, manifests). action=list_files|get_file|lang|sounds|atlas|registry_entries|manifest|list_configs|get_config.", {
|
|
1147
1235
|
action: z.enum(["list_files", "get_file", "lang", "sounds", "atlas", "registry_entries", "manifest", "list_configs", "get_config"]),
|
|
1148
1236
|
modId: z.union([z.string(), z.number()]).describe("mod ID or DB id"),
|
|
1149
1237
|
prefix: z.string().optional(),
|
|
@@ -1187,7 +1275,7 @@ function createMcpServer() {
|
|
|
1187
1275
|
return out(result);
|
|
1188
1276
|
}));
|
|
1189
1277
|
// ── 14. mod_data ─────────────────────────────────────────────────────────────
|
|
1190
|
-
|
|
1278
|
+
registerTool("mod_data", "Mod JAR structured data — list/get/diff JSON (recipes, loot tables, advancements, blockstates, models, biomes, tags, etc.) or trace a crafting dependency tree. action=list|get|diff|trace_item.", {
|
|
1191
1279
|
action: z.enum(["list", "get", "diff", "trace_item"]).describe("list/get: JAR data operations; diff: compare two mod versions; trace_item: recipe chain"),
|
|
1192
1280
|
type: z.enum(["recipe", "loot_table", "advancement", "blockstate", "model", "biome", "structure", "data_tag", "particle", "damage_type", "enchantment", "configured_feature", "placed_feature", "structure_set", "noise", "density_function", "processor_list", "template_pool", "dimension_type", "dimension", "trim_material", "trim_pattern", "painting_variant", "wolf_variant", "cat_variant", "chat_type"]).describe("Data type to query"),
|
|
1193
1281
|
modId: z.union([z.string(), z.number()]).optional().describe("mod ID or DB id"),
|
|
@@ -1235,7 +1323,7 @@ function createMcpServer() {
|
|
|
1235
1323
|
return out(result);
|
|
1236
1324
|
}));
|
|
1237
1325
|
// ── 15. mod_tags ──────────────────────────────────────────────────────────────
|
|
1238
|
-
|
|
1326
|
+
registerTool("mod_tags", "Cross-mod data-pack tag indexing — index, browse, expand, find contributors, and detect replace:true conflicts. action=index|index_all|namespaces|contributors|expand|mod_list|find_conflicts|search.", {
|
|
1239
1327
|
action: z.enum(["index", "index_all", "namespaces", "contributors", "expand", "mod_list", "find_conflicts", "search"]),
|
|
1240
1328
|
modId: z.union([z.string(), z.number()]).optional().describe("Mod ID string or DB id (index, mod_list)"),
|
|
1241
1329
|
tagPath: z.string().optional().describe("Tag path to look up contributors or expand (contributors, expand), e.g. 'c:ores/iron', '#forge:storage_blocks'"),
|
|
@@ -1274,7 +1362,7 @@ function createMcpServer() {
|
|
|
1274
1362
|
return out(result);
|
|
1275
1363
|
}));
|
|
1276
1364
|
// ── 16. mixin_scan ────────────────────────────────────────────────────────────
|
|
1277
|
-
|
|
1365
|
+
registerTool("mixin_scan", "Cross-mod mixin conflict analysis. action=list_mods|conflict_matrix|class_detail|hotspots|batch_resolve.", {
|
|
1278
1366
|
action: z.enum(["list_mods", "conflict_matrix", "class_detail", "hotspots", "batch_resolve"]),
|
|
1279
1367
|
loader: z.string().optional().describe("fabric|neoforge|forge|quilt"),
|
|
1280
1368
|
mcVersion: z.string().optional(),
|
|
@@ -1303,7 +1391,7 @@ function createMcpServer() {
|
|
|
1303
1391
|
return out(result);
|
|
1304
1392
|
}));
|
|
1305
1393
|
// ── 17. gradle ────────────────────────────────────────────────────────────────
|
|
1306
|
-
|
|
1394
|
+
registerTool("gradle", "Gradle build file analysis — extract deps/plugins, search across mods, compare dependency versions. action=get_files|search|compare_deps.", {
|
|
1307
1395
|
action: z.enum(["get_files", "search", "compare_deps"]),
|
|
1308
1396
|
modId: z.union([z.string(), z.number()]).optional().describe("Mod ID string or DB id (get_files)"),
|
|
1309
1397
|
query: z.string().optional(),
|
|
@@ -1326,13 +1414,13 @@ function createMcpServer() {
|
|
|
1326
1414
|
return out(result);
|
|
1327
1415
|
}));
|
|
1328
1416
|
// ── 18. reports ───────────────────────────────────────────────────────────────
|
|
1329
|
-
|
|
1417
|
+
registerTool("project", "Import and query a private Gradle compile environment, including prepared ATs, mappings and loader patches. " +
|
|
1330
1418
|
"Use this instead of mc_source/mod source or member tools when answering about an imported project. " +
|
|
1331
1419
|
"Every query requires projectKey and immutable environmentId; no shared vanilla caches are modified. " +
|
|
1332
1420
|
"Export with scripts/gradle/modlens.init.gradle; use scripts/project-upload.mjs for remote imports. " +
|
|
1333
1421
|
"action=import_local|upload_begin|upload_chunk|upload_finish|upload_abort|list|info|classes|source|search|members|bytecode. " +
|
|
1334
|
-
"list is restricted to the supplied private key. Search covers provided sources and cached decompiles, not all runtime transformations.", projectToolSchema, safe(async (params) => out(await projectAction(params))));
|
|
1335
|
-
|
|
1422
|
+
"list is restricted to the supplied private key. Search covers provided sources and cached decompiles, not all runtime transformations.", projectToolSchema, safe(async (params) => out(await projectAction(params, undefined, principal !== undefined))));
|
|
1423
|
+
registerTool("reports", "Generate Markdown reports. report=mixin_conflicts|tag_conflicts|version_conflicts|mod_overview|gradle_deps|pack_compat|dep_graph|sidedness|mod_complexity|pack_changelog. savePath to write to disk.", {
|
|
1336
1424
|
report: z.enum(["mixin_conflicts", "tag_conflicts", "version_conflicts", "mod_overview", "gradle_deps", "pack_compat", "dep_graph", "sidedness", "mod_complexity", "pack_changelog"]),
|
|
1337
1425
|
savePath: z.string().optional().describe("Absolute path to save the .md file, e.g. 'C:/reports/mixin_conflicts.md'"),
|
|
1338
1426
|
modId: z.union([z.string(), z.number()]).optional(),
|
|
@@ -1350,7 +1438,7 @@ function createMcpServer() {
|
|
|
1350
1438
|
return out(result.savedTo ? `Saved to: ${result.savedTo}\n\n${result.markdown}` : result.markdown);
|
|
1351
1439
|
}));
|
|
1352
1440
|
// ── 20. pack_tools ────────────────────────────────────────────────────────────
|
|
1353
|
-
|
|
1441
|
+
registerTool("pack_tools", "Modpack-specific analysis tools. " +
|
|
1354
1442
|
"action=asset_conflicts: find assets/ paths shipped by 2+ mods \u2014 last-loaded mod silently wins, causing visual/audio corruption (assetType, mcVersion, loader, limit). " +
|
|
1355
1443
|
"action=vanilla_overrides: find mods overriding data/minecraft/ or assets/minecraft/ \u2014 affects vanilla recipes, loot tables, textures (overrideType, dataSubtype, mcVersion, loader). " +
|
|
1356
1444
|
"action=sidedness: determine whether a single mod is client_only / client_optional / common / server_only (modId). " +
|
|
@@ -1411,7 +1499,7 @@ function createMcpServer() {
|
|
|
1411
1499
|
return out(result);
|
|
1412
1500
|
}));
|
|
1413
1501
|
// ── 21. kubejs ────────────────────────────────────────────────────────────────
|
|
1414
|
-
|
|
1502
|
+
registerTool("kubejs", "KubeJS script analysis \u2014 index a kubejs/ directory or text-search scripts. action=index|search|semantic_search. semantic_search requires Ollama running.", {
|
|
1415
1503
|
action: z.enum(["index", "search", "semantic_search"]),
|
|
1416
1504
|
scriptsDir: z.string().describe("Absolute path to the kubejs/ folder or a subfolder like kubejs/server_scripts/"),
|
|
1417
1505
|
query: z.string().optional(),
|
|
@@ -1432,14 +1520,14 @@ function createMcpServer() {
|
|
|
1432
1520
|
return out(result);
|
|
1433
1521
|
}));
|
|
1434
1522
|
// ── 22. analyze_crash_log ────────────────────────────────────────────────────
|
|
1435
|
-
|
|
1523
|
+
registerTool("analyze_crash_log", "Parse a NeoForge/Forge/Fabric crash log and return suspect mods ranked by stack-frame hits against the indexed class DB.", {
|
|
1436
1524
|
logText: z.string().describe("full crash log text"),
|
|
1437
1525
|
}, safe(async ({ logText }) => {
|
|
1438
1526
|
const result = await analyzeCrashLog(logText);
|
|
1439
1527
|
return out(result);
|
|
1440
1528
|
}));
|
|
1441
1529
|
// ── 23. find_missing_deps ────────────────────────────────────────────────────
|
|
1442
|
-
|
|
1530
|
+
registerTool("find_missing_deps", "Find declared mod dependencies not satisfied by any ingested mod in the DB. Skips loader pseudo-deps (minecraft, neoforge, java, etc.).", {
|
|
1443
1531
|
mcVersion: z.string().optional(),
|
|
1444
1532
|
loader: z.string().optional().describe("neoforge|forge|fabric|quilt"),
|
|
1445
1533
|
}, safe(async ({ mcVersion, loader }) => {
|
|
@@ -1447,7 +1535,7 @@ function createMcpServer() {
|
|
|
1447
1535
|
return out(result);
|
|
1448
1536
|
}));
|
|
1449
1537
|
// ── 24. check_mod_compat ──────────────────────────────────────────────────────
|
|
1450
|
-
|
|
1538
|
+
registerTool("check_mod_compat", "Pre-flight check a candidate JAR against the DB — mixin conflicts, AT/AW overlaps, asset conflicts, missing deps, sidedness. Does not require prior ingestion.", {
|
|
1451
1539
|
jarPath: z.string().describe("Absolute path to the candidate mod JAR"),
|
|
1452
1540
|
mcVersion: z.string().optional(),
|
|
1453
1541
|
loader: z.string().optional().describe("neoforge|forge|fabric|quilt"),
|
|
@@ -1458,8 +1546,11 @@ function createMcpServer() {
|
|
|
1458
1546
|
return server;
|
|
1459
1547
|
}
|
|
1460
1548
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
1461
|
-
process.on("SIGINT", async () => { await disconnect(); process.exit(0); });
|
|
1462
|
-
process.on("SIGTERM", async () => { await disconnect(); process.exit(0); });
|
|
1549
|
+
process.on("SIGINT", async () => { await runtimeHub.close(); await disconnect(); process.exit(0); });
|
|
1550
|
+
process.on("SIGTERM", async () => { await runtimeHub.close(); await disconnect(); process.exit(0); });
|
|
1551
|
+
// No listener or JVM is started until a project has opted in through setup.
|
|
1552
|
+
if (!process.env.MCP_PORT)
|
|
1553
|
+
await runtimeHub.initialize().catch(e => console.error("[modlens] runtime:", String(e)));
|
|
1463
1554
|
// Transport selection:
|
|
1464
1555
|
// • MCP_PORT set → Streamable HTTP transport (for k8s / remote deployments).
|
|
1465
1556
|
// • otherwise → stdio transport (default, local CLI use).
|
|
@@ -1492,8 +1583,29 @@ if (process.env.MODLENS_AUTO_EMBED !== "0") {
|
|
|
1492
1583
|
async function startHttpServer(port) {
|
|
1493
1584
|
const mcpPath = process.env.MCP_PATH ?? "/mcp";
|
|
1494
1585
|
const host = process.env.MCP_HOST ?? "0.0.0.0";
|
|
1586
|
+
const restricted = process.env.MODLENS_HOSTED_LIMITS !== "0";
|
|
1587
|
+
const authMode = process.env.MODLENS_HOSTED_AUTH ?? "gateway";
|
|
1588
|
+
if (authMode !== "gateway" && authMode !== "oauth")
|
|
1589
|
+
throw new Error("MODLENS_HOSTED_AUTH must be gateway or oauth");
|
|
1590
|
+
if (authMode === "oauth" && !restricted)
|
|
1591
|
+
throw new Error("OAuth requires hosted limits");
|
|
1592
|
+
if (restricted)
|
|
1593
|
+
hostedLimits(); // Reject invalid operator configuration at startup.
|
|
1594
|
+
const proxySecret = process.env.MODLENS_HOSTED_PROXY_SECRET;
|
|
1595
|
+
if (proxySecret && proxySecret.length < 32)
|
|
1596
|
+
throw new Error("MODLENS_HOSTED_PROXY_SECRET must have at least 32 characters");
|
|
1597
|
+
if (authMode === "oauth" && proxySecret)
|
|
1598
|
+
throw new Error("OAuth and gateway credentials cannot be combined");
|
|
1599
|
+
if (authMode === "oauth" && process.env.MODLENS_HOSTED_MC_SOURCE === "1")
|
|
1600
|
+
throw new Error("OAuth mode cannot enable hosted Minecraft source");
|
|
1601
|
+
const oauth = authMode === "oauth" ? await HostedOAuth.create() : undefined;
|
|
1602
|
+
if (oauth && new URL(process.env.MODLENS_OAUTH_PUBLIC_URL).pathname !== mcpPath)
|
|
1603
|
+
throw new Error("MODLENS_OAUTH_PUBLIC_URL path must match MCP_PATH");
|
|
1604
|
+
const sourceTeams = restricted && !oauth ? hostedMinecraftSourceTeams(process.env, proxySecret) : new Set();
|
|
1495
1605
|
// Active sessions keyed by Mcp-Session-Id header.
|
|
1496
1606
|
const transports = new Map();
|
|
1607
|
+
const sessionOwners = new Map();
|
|
1608
|
+
const sessionSourceAccess = new Map();
|
|
1497
1609
|
const readBody = (req) => new Promise((resolve, reject) => {
|
|
1498
1610
|
const chunks = [];
|
|
1499
1611
|
let size = 0;
|
|
@@ -1544,15 +1656,32 @@ async function startHttpServer(port) {
|
|
|
1544
1656
|
if (url.pathname === "/healthz") {
|
|
1545
1657
|
return sendJson(res, 200, { status: "ok" });
|
|
1546
1658
|
}
|
|
1659
|
+
if (oauth) {
|
|
1660
|
+
try {
|
|
1661
|
+
if (await oauth.handle(req, res, url))
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
catch (err) {
|
|
1665
|
+
if (err instanceof HostedOAuthError) {
|
|
1666
|
+
return sendJson(res, err.status, { error: err.code, error_description: err.message });
|
|
1667
|
+
}
|
|
1668
|
+
logServerError(`oauth ${req.method} ${url.pathname}`, err);
|
|
1669
|
+
return sendJson(res, 503, { error: "temporarily_unavailable" });
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1547
1672
|
if (url.pathname !== mcpPath) {
|
|
1548
1673
|
return sendJson(res, 404, { error: "not found" });
|
|
1549
1674
|
}
|
|
1550
1675
|
const sessionId = getHeader(req, "mcp-session-id");
|
|
1551
1676
|
try {
|
|
1677
|
+
const principal = oauth ? await oauth.authenticate(req)
|
|
1678
|
+
: restricted ? hostedPrincipal(req.headers, proxySecret) : undefined;
|
|
1679
|
+
const allowMinecraftSource = !oauth && restricted && hostedMinecraftSourceAccess(req.headers, sourceTeams);
|
|
1552
1680
|
// Existing session — route to its transport.
|
|
1553
1681
|
if (sessionId) {
|
|
1554
1682
|
const transport = transports.get(sessionId);
|
|
1555
|
-
if (!transport)
|
|
1683
|
+
if (!transport || (restricted && (sessionOwners.get(sessionId) !== principal
|
|
1684
|
+
|| sessionSourceAccess.get(sessionId) !== allowMinecraftSource))) {
|
|
1556
1685
|
return sendJson(res, 404, {
|
|
1557
1686
|
jsonrpc: "2.0",
|
|
1558
1687
|
error: { code: -32001, message: "Session not found" },
|
|
@@ -1575,13 +1704,22 @@ async function startHttpServer(port) {
|
|
|
1575
1704
|
}
|
|
1576
1705
|
const transport = new StreamableHTTPServerTransport({
|
|
1577
1706
|
sessionIdGenerator: () => randomUUID(),
|
|
1578
|
-
onsessioninitialized: (id) => {
|
|
1707
|
+
onsessioninitialized: (id) => {
|
|
1708
|
+
transports.set(id, transport);
|
|
1709
|
+
if (principal !== undefined)
|
|
1710
|
+
sessionOwners.set(id, principal);
|
|
1711
|
+
if (principal !== undefined)
|
|
1712
|
+
sessionSourceAccess.set(id, allowMinecraftSource);
|
|
1713
|
+
},
|
|
1579
1714
|
});
|
|
1580
1715
|
transport.onclose = () => {
|
|
1581
|
-
if (transport.sessionId)
|
|
1716
|
+
if (transport.sessionId) {
|
|
1582
1717
|
transports.delete(transport.sessionId);
|
|
1718
|
+
sessionOwners.delete(transport.sessionId);
|
|
1719
|
+
sessionSourceAccess.delete(transport.sessionId);
|
|
1720
|
+
}
|
|
1583
1721
|
};
|
|
1584
|
-
const server = createMcpServer();
|
|
1722
|
+
const server = createMcpServer(principal, allowMinecraftSource);
|
|
1585
1723
|
await server.connect(transport);
|
|
1586
1724
|
await transport.handleRequest(req, res, body);
|
|
1587
1725
|
return;
|
|
@@ -1594,6 +1732,15 @@ async function startHttpServer(port) {
|
|
|
1594
1732
|
}
|
|
1595
1733
|
catch (err) {
|
|
1596
1734
|
if (!res.headersSent) {
|
|
1735
|
+
if (err instanceof HostedOAuthError && oauth) {
|
|
1736
|
+
if (err.status === 401) {
|
|
1737
|
+
oauth.challenge(res);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
return sendJson(res, err.status, { error: err.code });
|
|
1741
|
+
}
|
|
1742
|
+
if (err instanceof HostedPolicyError)
|
|
1743
|
+
return sendJson(res, 401, { error: err.message });
|
|
1597
1744
|
if (err instanceof HttpBodyTooLargeError)
|
|
1598
1745
|
return sendJson(res, 413, { error: err.message });
|
|
1599
1746
|
if (err instanceof JsonBodyParseError) {
|