@creeperhost/modlens-mcp 1.6.19 → 1.6.21
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 +82 -5
- package/RUNTIME.md +262 -0
- package/TESTING.md +8 -0
- package/dist/cli.js +28 -8
- package/dist/cli.js.map +1 -1
- 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-policy.d.ts +32 -0
- package/dist/hosted-policy.d.ts.map +1 -0
- package/dist/hosted-policy.js +257 -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/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/primer-catalog.d.ts +15 -0
- package/dist/primer-catalog.d.ts.map +1 -0
- package/dist/primer-catalog.js +43 -0
- package/dist/primer-catalog.js.map +1 -0
- package/dist/primer-content.d.ts +7 -0
- package/dist/primer-content.d.ts.map +1 -0
- package/dist/primer-content.js +185 -0
- package/dist/primer-content.js.map +1 -0
- 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 +141 -45
- package/dist/server.js.map +1 -1
- package/dist/setup.js +9 -4
- package/dist/setup.js.map +1 -1
- package/dist/tools/primers.d.ts +133 -22
- package/dist/tools/primers.d.ts.map +1 -1
- package/dist/tools/primers.js +391 -386
- package/dist/tools/primers.js.map +1 -1
- package/dist/tools/project.d.ts +11 -7
- 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/package.json +8 -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-policy.mjs +123 -0
- package/scripts/test-mod-licenses-live.mjs +86 -0
- package/scripts/test-package.mjs +141 -5
- package/scripts/test-primers-live.mjs +93 -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
|
@@ -39,6 +39,10 @@ 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 { runtimeAction, runtimeToolSchema, runtimeHub } from "./tools/runtime.js";
|
|
43
|
+
import { reportIssue, reportIssueSchema } from "./tools/report-issue.js";
|
|
44
|
+
import { HOSTED_ACTIONS, HostedBudget, HostedPolicyError, hostedLimits, hostedPrincipal, runHostedTool } from "./hosted-policy.js";
|
|
45
|
+
import { reviewHostedMod, localModPlan } from "./hosted-mod-license.js";
|
|
42
46
|
// Load .env — try ~/.modlens/.env first (npx/installed users), then local .env (git-clone users)
|
|
43
47
|
import { readFileSync, existsSync } from "fs";
|
|
44
48
|
import { join, dirname } from "path";
|
|
@@ -158,10 +162,74 @@ class JsonBodyParseError extends Error {
|
|
|
158
162
|
}
|
|
159
163
|
class HttpBodyTooLargeError extends Error {
|
|
160
164
|
}
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
const hostedBudget = new HostedBudget();
|
|
166
|
+
function createMcpServer(principal) {
|
|
167
|
+
const server = new McpServer({ name: "modlens", version: pkg.version }, {
|
|
168
|
+
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.",
|
|
169
|
+
});
|
|
170
|
+
const limits = principal === undefined ? undefined : hostedLimits();
|
|
171
|
+
function registerTool(name, description, schema, handler) {
|
|
172
|
+
if (!limits || principal === undefined) {
|
|
173
|
+
server.tool(name, description, schema, handler);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const actions = HOSTED_ACTIONS[name];
|
|
177
|
+
if (!actions)
|
|
178
|
+
return;
|
|
179
|
+
const actionSchema = schema.action;
|
|
180
|
+
let publicSchema = actionSchema instanceof z.ZodEnum
|
|
181
|
+
? { ...schema, action: z.enum(actions.filter(a => actionSchema.options.includes(a))) }
|
|
182
|
+
: schema;
|
|
183
|
+
if (name === "mod_bytecode")
|
|
184
|
+
publicSchema = { ...publicSchema,
|
|
185
|
+
startLine: z.number().int().positive().optional().describe("First bytecode line, 1-based"),
|
|
186
|
+
maxLines: z.number().int().positive().optional().describe("Bytecode lines to return (hosted cap applies)"),
|
|
187
|
+
};
|
|
188
|
+
// Do not advertise host paths, cache jobs, registry exports or unavailable operations.
|
|
189
|
+
const publicDescription = `${description.split(". ")[0]}. Hosted access: ${actions.filter(Boolean).join(", ") || "analysis"}. Responses and cumulative usage are bounded; use focused queries and source ranges.`;
|
|
190
|
+
server.tool(name, publicDescription, publicSchema, (async (args, extra) => runHostedTool(name, args, principal, limits, hostedBudget, async (bounded) => handler(bounded, extra))));
|
|
191
|
+
}
|
|
192
|
+
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. " +
|
|
193
|
+
"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))));
|
|
194
|
+
registerTool("mod_license", "Inspect a mod's license evidence before hosted source access, or request consent for local decompilation. " +
|
|
195
|
+
"Precedence: applicable GitHub version, mod JAR, CurseForge. action=check|local_plan. " +
|
|
196
|
+
"Provide modId/dbId or projectKey+environmentId+className. Local plans must run on the user's computer; never claim a plan has executed.", {
|
|
197
|
+
action: z.enum(["check", "local_plan"]),
|
|
198
|
+
modId: z.union([z.string(), z.number()]).optional(), dbId: z.number().int().positive().optional(),
|
|
199
|
+
projectKey: z.string().optional(), environmentId: z.string().optional(), className: z.string().optional(),
|
|
200
|
+
operation: z.enum(["source", "bytecode"]).optional(),
|
|
201
|
+
startLine: z.number().int().positive().optional(), maxLines: z.number().int().positive().max(10000).optional(),
|
|
202
|
+
}, safe(async (args) => {
|
|
203
|
+
const decision = await reviewHostedMod(args);
|
|
204
|
+
if (args.action === "check")
|
|
205
|
+
return out(decision);
|
|
206
|
+
let accepted = false;
|
|
207
|
+
if (server.server.getClientCapabilities()?.elicitation?.form !== undefined) {
|
|
208
|
+
const consent = await server.server.elicitInput({
|
|
209
|
+
mode: "form",
|
|
210
|
+
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.`,
|
|
211
|
+
requestedSchema: { type: "object", properties: {
|
|
212
|
+
acceptLocal: { type: "boolean", title: "I accept local decompilation of this artifact", default: false },
|
|
213
|
+
}, required: ["acceptLocal"] },
|
|
214
|
+
});
|
|
215
|
+
if (consent.action !== "accept" || consent.content?.acceptLocal !== true)
|
|
216
|
+
return out({ executed: false, declined: true });
|
|
217
|
+
accepted = true;
|
|
218
|
+
}
|
|
219
|
+
return out({ licenseReview: decision, local: localModPlan(decision, { ...args, action: args.operation ?? "source" }, accepted) });
|
|
220
|
+
}));
|
|
221
|
+
registerTool("runtime", "Optional Minecraft 26.3 live dev runtime: help/setup/status/sessions/launch/events/command/artifact. " +
|
|
222
|
+
"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. " +
|
|
223
|
+
"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. " +
|
|
224
|
+
"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) => {
|
|
225
|
+
const result = await runtimeAction(args);
|
|
226
|
+
if (args.action === "artifact" && result && typeof result === "object" && "mimeType" in result && "data" in result) {
|
|
227
|
+
return { content: [{ type: "image", mimeType: "image/png", data: result.data }] };
|
|
228
|
+
}
|
|
229
|
+
return out(result);
|
|
230
|
+
}));
|
|
163
231
|
// ── 1. mod ────────────────────────────────────────────────────────────────────
|
|
164
|
-
|
|
232
|
+
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
233
|
action: z.enum([
|
|
166
234
|
"ingest", "list", "get", "search", "stats", "dependencies", "dep_graph",
|
|
167
235
|
"version_conflicts", "source_urls", "decompile", "decompile_status",
|
|
@@ -176,6 +244,8 @@ function createMcpServer() {
|
|
|
176
244
|
dbId: z.number().optional().describe("DB id"),
|
|
177
245
|
query: z.string().optional(),
|
|
178
246
|
path: z.string().optional(),
|
|
247
|
+
startLine: z.number().int().positive().optional().describe("First source line, 1-based"),
|
|
248
|
+
maxLines: z.number().int().positive().optional().describe("Source lines to return"),
|
|
179
249
|
className: z.string().optional(),
|
|
180
250
|
loader: z.string().optional().describe("fabric|neoforge|forge|quilt"),
|
|
181
251
|
mcVersion: z.string().optional(),
|
|
@@ -208,7 +278,7 @@ function createMcpServer() {
|
|
|
208
278
|
autoEmbed: z.boolean().optional().describe("Override auto-embed on decompile completion (default: env MODLENS_AUTO_EMBED, true)"),
|
|
209
279
|
autoGraph: z.boolean().optional().describe("Override auto-graph-build on decompile/source-download (default: env MODLENS_AUTO_GRAPH, true)"),
|
|
210
280
|
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 }) => {
|
|
281
|
+
}, 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
282
|
const dbId = await resolveDbIdAsync(rawDbId, modId);
|
|
213
283
|
const resolvedTargetType = targetType ?? "mod";
|
|
214
284
|
const resolvedTargetId = targetId ?? (typeof modId === "string" ? modId : undefined) ?? (resolvedTargetType === "vanilla" ? "minecraft" : undefined);
|
|
@@ -260,7 +330,7 @@ function createMcpServer() {
|
|
|
260
330
|
case "source":
|
|
261
331
|
if (dbId == null)
|
|
262
332
|
throw new Error(`Mod not found: ${String(modId ?? rawDbId ?? "")}`);
|
|
263
|
-
result = await getModSource(dbId, path);
|
|
333
|
+
result = await getModSource(dbId, path, startLine, maxLines);
|
|
264
334
|
break;
|
|
265
335
|
case "search_source":
|
|
266
336
|
result = await searchSource(query, dbId, isRegex ?? false, limit ?? 50);
|
|
@@ -381,7 +451,7 @@ function createMcpServer() {
|
|
|
381
451
|
return out(result);
|
|
382
452
|
}));
|
|
383
453
|
// ── 2. mod_bytecode ───────────────────────────────────────────────────────────
|
|
384
|
-
|
|
454
|
+
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
455
|
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
456
|
dbId: z.number().optional().describe("DB id"),
|
|
387
457
|
dbIdA: z.number().optional().describe("older DB id"),
|
|
@@ -457,7 +527,7 @@ function createMcpServer() {
|
|
|
457
527
|
return out(result);
|
|
458
528
|
}));
|
|
459
529
|
// ── 3. mod_mixins ─────────────────────────────────────────────────────────────
|
|
460
|
-
|
|
530
|
+
registerTool("mod_mixins", "Mod mixin and access transformer analysis. action=targets|resolve|conflicts|targets_in_package|at_conflicts|at_entries|aw_entries.", {
|
|
461
531
|
action: z.enum(["targets", "resolve", "conflicts", "targets_in_package", "at_conflicts", "at_entries", "aw_entries"]),
|
|
462
532
|
modId: z.union([z.string(), z.number()]).optional().describe("mod ID or DB id"),
|
|
463
533
|
dbId: z.number().optional().describe("DB id"),
|
|
@@ -494,7 +564,7 @@ function createMcpServer() {
|
|
|
494
564
|
return out(result);
|
|
495
565
|
}));
|
|
496
566
|
// ── 4. platform ───────────────────────────────────────────────────────────────
|
|
497
|
-
|
|
567
|
+
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
568
|
"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
569
|
"action=batch_check_updates: check ALL linked mods for newer versions on Modrinth/CurseForge in one pass (mcVersion, loader, modIdFilter, limit). " +
|
|
500
570
|
"download_source downloads GitHub source without decompilation — auto-queues graph build (override with autoGraph). Env: MODLENS_AUTO_GRAPH (default: 1).", {
|
|
@@ -540,7 +610,7 @@ function createMcpServer() {
|
|
|
540
610
|
return out(result);
|
|
541
611
|
}));
|
|
542
612
|
// ── 5. modpacks_ch ───────────────────────────────────────────────────────────
|
|
543
|
-
|
|
613
|
+
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
614
|
"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
615
|
"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
616
|
"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 +718,7 @@ function createMcpServer() {
|
|
|
648
718
|
return out(result);
|
|
649
719
|
}));
|
|
650
720
|
// ── 6. mc_versions ────────────────────────────────────────────────────────────
|
|
651
|
-
|
|
721
|
+
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
722
|
action: z.enum(["list_mc", "list_neoforge", "list_forge", "list_fabric", "ingest_neoforge", "ingest_forge", "ingest_fabric"]),
|
|
653
723
|
type: z.enum(["release", "snapshot", "all"]).optional(),
|
|
654
724
|
mcVersion: z.string().optional(),
|
|
@@ -712,7 +782,7 @@ function createMcpServer() {
|
|
|
712
782
|
return out(result);
|
|
713
783
|
}));
|
|
714
784
|
// ── 6. mc_source ─────────────────────────────────────────────────────────────
|
|
715
|
-
|
|
785
|
+
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|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. 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.", {
|
|
716
786
|
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"]),
|
|
717
787
|
version: z.string().optional(),
|
|
718
788
|
versionA: z.string().optional(),
|
|
@@ -808,7 +878,7 @@ function createMcpServer() {
|
|
|
808
878
|
return out(result);
|
|
809
879
|
}));
|
|
810
880
|
// ── 7. mappings ───────────────────────────────────────────────────────────────
|
|
811
|
-
|
|
881
|
+
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
882
|
action: z.enum(["find", "remap", "parchment", "list_parchment", "parchment_summary"]),
|
|
813
883
|
symbol: z.string().optional().describe("Symbol to translate; use with action=find, not action=remap"),
|
|
814
884
|
version: z.string().optional(),
|
|
@@ -854,7 +924,7 @@ function createMcpServer() {
|
|
|
854
924
|
return out(result);
|
|
855
925
|
}));
|
|
856
926
|
// ── 8. docs ───────────────────────────────────────────────────────────────────
|
|
857
|
-
|
|
927
|
+
registerTool("docs", "Minecraft modding documentation database. action=ingest|seed|get|search|list|delete|semantic_search|backfill_embeddings. semantic_search requires Ollama running.", {
|
|
858
928
|
action: z.enum(["ingest", "seed", "get", "search", "list", "delete", "semantic_search", "backfill_embeddings"]),
|
|
859
929
|
entries: z.array(z.object({
|
|
860
930
|
className: z.string().optional(),
|
|
@@ -903,7 +973,7 @@ function createMcpServer() {
|
|
|
903
973
|
return out(result);
|
|
904
974
|
}));
|
|
905
975
|
// ── 9. primers ────────────────────────────────────────────────────────────────
|
|
906
|
-
|
|
976
|
+
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
977
|
action: z.enum(["ingest", "seed", "get", "by_version", "search", "list", "delete", "semantic_search", "backfill_embeddings"]),
|
|
908
978
|
entries: z.array(z.object({
|
|
909
979
|
fromVersion: z.string(),
|
|
@@ -916,27 +986,34 @@ function createMcpServer() {
|
|
|
916
986
|
tags: z.array(z.string()).optional(),
|
|
917
987
|
source: z.string().optional(),
|
|
918
988
|
fetchContent: z.boolean().optional(),
|
|
919
|
-
})).optional().describe("Primer entries to ingest
|
|
920
|
-
id: z.number().optional(),
|
|
989
|
+
})).min(1).optional().describe("Primer entries to ingest; set entries[].fetchContent=true to fetch before saving"),
|
|
990
|
+
id: z.number().int().positive().optional(),
|
|
921
991
|
fromVersion: z.string().optional(),
|
|
922
992
|
toVersion: z.string().optional(),
|
|
923
|
-
modloader: z.string().optional().describe("neoforge|forge|fabric|quilt"),
|
|
993
|
+
modloader: z.string().optional().describe("neoforge|forge|fabric|quilt|vanilla; discovery includes vanilla guides"),
|
|
924
994
|
query: z.string().optional(),
|
|
925
|
-
limit: z.number().optional(),
|
|
926
|
-
|
|
995
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
996
|
+
fetchContent: z.boolean().optional().describe("get/seed/by_version: fetch missing content (default true); false permits cached/offline reads"),
|
|
997
|
+
includeContent: z.boolean().optional().describe("by_version: bundle guide bodies (default false); includes vanilla guides for the selected loader"),
|
|
998
|
+
maxChars: z.number().int().min(1000).max(200000).optional().describe("by_version with includeContent: combined text budget (default 60000); up to 20 guides per page"),
|
|
999
|
+
cursor: z.string().min(1).max(2048).optional().describe("by_version with includeContent: nextCursor from the previous page; retain the same range and loader"),
|
|
1000
|
+
refresh: z.boolean().optional().describe("get: re-fetch even if cached; failed refresh preserves the cached guide"),
|
|
1001
|
+
startLine: z.number().int().positive().optional().describe("get: first line, 1-based (default 1)"),
|
|
1002
|
+
maxLines: z.number().int().min(1).max(2000).optional().describe("get: page size (default 400); follow nextStartLine until null"),
|
|
1003
|
+
}, safe(async ({ action, entries, id, fromVersion, toVersion, modloader, query, limit, fetchContent, includeContent, maxChars, cursor, refresh, startLine, maxLines }) => {
|
|
927
1004
|
let result;
|
|
928
1005
|
switch (action) {
|
|
929
1006
|
case "ingest":
|
|
930
1007
|
result = await ingestPrimer(entries);
|
|
931
1008
|
break;
|
|
932
1009
|
case "seed":
|
|
933
|
-
result = await seedDefaultPrimers();
|
|
1010
|
+
result = await seedDefaultPrimers(fetchContent);
|
|
934
1011
|
break;
|
|
935
1012
|
case "get":
|
|
936
|
-
result = await getPrimer(id);
|
|
1013
|
+
result = await getPrimer(id, { fetchContent, refresh, startLine, maxLines });
|
|
937
1014
|
break;
|
|
938
1015
|
case "by_version":
|
|
939
|
-
result = await getPrimersByVersionRange(fromVersion, toVersion, modloader);
|
|
1016
|
+
result = await getPrimersByVersionRange(fromVersion, toVersion, modloader, { includeContent, fetchContent, maxChars, cursor });
|
|
940
1017
|
break;
|
|
941
1018
|
case "search":
|
|
942
1019
|
result = await searchPrimers(query, modloader, fromVersion, toVersion, limit);
|
|
@@ -954,10 +1031,10 @@ function createMcpServer() {
|
|
|
954
1031
|
result = await backfillPrimerEmbeddings();
|
|
955
1032
|
break;
|
|
956
1033
|
}
|
|
957
|
-
return out(result);
|
|
1034
|
+
return { ...out(result), ...(result && typeof result === "object" && "failed" in result && Number(result.failed) > 0 ? { isError: true } : {}) };
|
|
958
1035
|
}));
|
|
959
1036
|
// ── 10. mc_registry ───────────────────────────────────────────────────────────
|
|
960
|
-
|
|
1037
|
+
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.", {
|
|
961
1038
|
action: z.enum(["blocks", "commands", "registries", "sounds", "item_components", "registry_entries", "mcmeta_versions"]),
|
|
962
1039
|
version: z.string().optional(),
|
|
963
1040
|
registry: z.string().optional().describe("registry key e.g. 'block','item','entity_type'"),
|
|
@@ -990,7 +1067,7 @@ function createMcpServer() {
|
|
|
990
1067
|
return out(result);
|
|
991
1068
|
}));
|
|
992
1069
|
// ── 11. mc_data ───────────────────────────────────────────────────────────────
|
|
993
|
-
|
|
1070
|
+
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.", {
|
|
994
1071
|
action: z.enum([
|
|
995
1072
|
"tags", "find_tags_for", "recipes", "get_recipe", "find_recipes_for",
|
|
996
1073
|
"loot_tables", "get_loot_table", "lang", "blockstate", "model", "model_tree",
|
|
@@ -1094,7 +1171,7 @@ function createMcpServer() {
|
|
|
1094
1171
|
return out(result);
|
|
1095
1172
|
}));
|
|
1096
1173
|
// ── 12. mc_files ─────────────────────────────────────────────────────────────
|
|
1097
|
-
|
|
1174
|
+
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.", {
|
|
1098
1175
|
action: z.enum(["get_data", "get_asset", "list_files", "diff", "atlas", "raw", "compare", "changelog"]),
|
|
1099
1176
|
filePath: z.string().optional().describe("relative path in branch"),
|
|
1100
1177
|
version: z.string().optional(),
|
|
@@ -1136,7 +1213,7 @@ function createMcpServer() {
|
|
|
1136
1213
|
return out(result);
|
|
1137
1214
|
}));
|
|
1138
1215
|
// ── 13. mod_jar ───────────────────────────────────────────────────────────────
|
|
1139
|
-
|
|
1216
|
+
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.", {
|
|
1140
1217
|
action: z.enum(["list_files", "get_file", "lang", "sounds", "atlas", "registry_entries", "manifest", "list_configs", "get_config"]),
|
|
1141
1218
|
modId: z.union([z.string(), z.number()]).describe("mod ID or DB id"),
|
|
1142
1219
|
prefix: z.string().optional(),
|
|
@@ -1180,7 +1257,7 @@ function createMcpServer() {
|
|
|
1180
1257
|
return out(result);
|
|
1181
1258
|
}));
|
|
1182
1259
|
// ── 14. mod_data ─────────────────────────────────────────────────────────────
|
|
1183
|
-
|
|
1260
|
+
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.", {
|
|
1184
1261
|
action: z.enum(["list", "get", "diff", "trace_item"]).describe("list/get: JAR data operations; diff: compare two mod versions; trace_item: recipe chain"),
|
|
1185
1262
|
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"),
|
|
1186
1263
|
modId: z.union([z.string(), z.number()]).optional().describe("mod ID or DB id"),
|
|
@@ -1228,7 +1305,7 @@ function createMcpServer() {
|
|
|
1228
1305
|
return out(result);
|
|
1229
1306
|
}));
|
|
1230
1307
|
// ── 15. mod_tags ──────────────────────────────────────────────────────────────
|
|
1231
|
-
|
|
1308
|
+
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.", {
|
|
1232
1309
|
action: z.enum(["index", "index_all", "namespaces", "contributors", "expand", "mod_list", "find_conflicts", "search"]),
|
|
1233
1310
|
modId: z.union([z.string(), z.number()]).optional().describe("Mod ID string or DB id (index, mod_list)"),
|
|
1234
1311
|
tagPath: z.string().optional().describe("Tag path to look up contributors or expand (contributors, expand), e.g. 'c:ores/iron', '#forge:storage_blocks'"),
|
|
@@ -1267,7 +1344,7 @@ function createMcpServer() {
|
|
|
1267
1344
|
return out(result);
|
|
1268
1345
|
}));
|
|
1269
1346
|
// ── 16. mixin_scan ────────────────────────────────────────────────────────────
|
|
1270
|
-
|
|
1347
|
+
registerTool("mixin_scan", "Cross-mod mixin conflict analysis. action=list_mods|conflict_matrix|class_detail|hotspots|batch_resolve.", {
|
|
1271
1348
|
action: z.enum(["list_mods", "conflict_matrix", "class_detail", "hotspots", "batch_resolve"]),
|
|
1272
1349
|
loader: z.string().optional().describe("fabric|neoforge|forge|quilt"),
|
|
1273
1350
|
mcVersion: z.string().optional(),
|
|
@@ -1296,7 +1373,7 @@ function createMcpServer() {
|
|
|
1296
1373
|
return out(result);
|
|
1297
1374
|
}));
|
|
1298
1375
|
// ── 17. gradle ────────────────────────────────────────────────────────────────
|
|
1299
|
-
|
|
1376
|
+
registerTool("gradle", "Gradle build file analysis — extract deps/plugins, search across mods, compare dependency versions. action=get_files|search|compare_deps.", {
|
|
1300
1377
|
action: z.enum(["get_files", "search", "compare_deps"]),
|
|
1301
1378
|
modId: z.union([z.string(), z.number()]).optional().describe("Mod ID string or DB id (get_files)"),
|
|
1302
1379
|
query: z.string().optional(),
|
|
@@ -1319,13 +1396,13 @@ function createMcpServer() {
|
|
|
1319
1396
|
return out(result);
|
|
1320
1397
|
}));
|
|
1321
1398
|
// ── 18. reports ───────────────────────────────────────────────────────────────
|
|
1322
|
-
|
|
1399
|
+
registerTool("project", "Import and query a private Gradle compile environment, including prepared ATs, mappings and loader patches. " +
|
|
1323
1400
|
"Use this instead of mc_source/mod source or member tools when answering about an imported project. " +
|
|
1324
1401
|
"Every query requires projectKey and immutable environmentId; no shared vanilla caches are modified. " +
|
|
1325
1402
|
"Export with scripts/gradle/modlens.init.gradle; use scripts/project-upload.mjs for remote imports. " +
|
|
1326
1403
|
"action=import_local|upload_begin|upload_chunk|upload_finish|upload_abort|list|info|classes|source|search|members|bytecode. " +
|
|
1327
|
-
"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))));
|
|
1328
|
-
|
|
1404
|
+
"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))));
|
|
1405
|
+
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.", {
|
|
1329
1406
|
report: z.enum(["mixin_conflicts", "tag_conflicts", "version_conflicts", "mod_overview", "gradle_deps", "pack_compat", "dep_graph", "sidedness", "mod_complexity", "pack_changelog"]),
|
|
1330
1407
|
savePath: z.string().optional().describe("Absolute path to save the .md file, e.g. 'C:/reports/mixin_conflicts.md'"),
|
|
1331
1408
|
modId: z.union([z.string(), z.number()]).optional(),
|
|
@@ -1343,7 +1420,7 @@ function createMcpServer() {
|
|
|
1343
1420
|
return out(result.savedTo ? `Saved to: ${result.savedTo}\n\n${result.markdown}` : result.markdown);
|
|
1344
1421
|
}));
|
|
1345
1422
|
// ── 20. pack_tools ────────────────────────────────────────────────────────────
|
|
1346
|
-
|
|
1423
|
+
registerTool("pack_tools", "Modpack-specific analysis tools. " +
|
|
1347
1424
|
"action=asset_conflicts: find assets/ paths shipped by 2+ mods \u2014 last-loaded mod silently wins, causing visual/audio corruption (assetType, mcVersion, loader, limit). " +
|
|
1348
1425
|
"action=vanilla_overrides: find mods overriding data/minecraft/ or assets/minecraft/ \u2014 affects vanilla recipes, loot tables, textures (overrideType, dataSubtype, mcVersion, loader). " +
|
|
1349
1426
|
"action=sidedness: determine whether a single mod is client_only / client_optional / common / server_only (modId). " +
|
|
@@ -1404,7 +1481,7 @@ function createMcpServer() {
|
|
|
1404
1481
|
return out(result);
|
|
1405
1482
|
}));
|
|
1406
1483
|
// ── 21. kubejs ────────────────────────────────────────────────────────────────
|
|
1407
|
-
|
|
1484
|
+
registerTool("kubejs", "KubeJS script analysis \u2014 index a kubejs/ directory or text-search scripts. action=index|search|semantic_search. semantic_search requires Ollama running.", {
|
|
1408
1485
|
action: z.enum(["index", "search", "semantic_search"]),
|
|
1409
1486
|
scriptsDir: z.string().describe("Absolute path to the kubejs/ folder or a subfolder like kubejs/server_scripts/"),
|
|
1410
1487
|
query: z.string().optional(),
|
|
@@ -1425,14 +1502,14 @@ function createMcpServer() {
|
|
|
1425
1502
|
return out(result);
|
|
1426
1503
|
}));
|
|
1427
1504
|
// ── 22. analyze_crash_log ────────────────────────────────────────────────────
|
|
1428
|
-
|
|
1505
|
+
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.", {
|
|
1429
1506
|
logText: z.string().describe("full crash log text"),
|
|
1430
1507
|
}, safe(async ({ logText }) => {
|
|
1431
1508
|
const result = await analyzeCrashLog(logText);
|
|
1432
1509
|
return out(result);
|
|
1433
1510
|
}));
|
|
1434
1511
|
// ── 23. find_missing_deps ────────────────────────────────────────────────────
|
|
1435
|
-
|
|
1512
|
+
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.).", {
|
|
1436
1513
|
mcVersion: z.string().optional(),
|
|
1437
1514
|
loader: z.string().optional().describe("neoforge|forge|fabric|quilt"),
|
|
1438
1515
|
}, safe(async ({ mcVersion, loader }) => {
|
|
@@ -1440,7 +1517,7 @@ function createMcpServer() {
|
|
|
1440
1517
|
return out(result);
|
|
1441
1518
|
}));
|
|
1442
1519
|
// ── 24. check_mod_compat ──────────────────────────────────────────────────────
|
|
1443
|
-
|
|
1520
|
+
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.", {
|
|
1444
1521
|
jarPath: z.string().describe("Absolute path to the candidate mod JAR"),
|
|
1445
1522
|
mcVersion: z.string().optional(),
|
|
1446
1523
|
loader: z.string().optional().describe("neoforge|forge|fabric|quilt"),
|
|
@@ -1451,8 +1528,11 @@ function createMcpServer() {
|
|
|
1451
1528
|
return server;
|
|
1452
1529
|
}
|
|
1453
1530
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
1454
|
-
process.on("SIGINT", async () => { await disconnect(); process.exit(0); });
|
|
1455
|
-
process.on("SIGTERM", async () => { await disconnect(); process.exit(0); });
|
|
1531
|
+
process.on("SIGINT", async () => { await runtimeHub.close(); await disconnect(); process.exit(0); });
|
|
1532
|
+
process.on("SIGTERM", async () => { await runtimeHub.close(); await disconnect(); process.exit(0); });
|
|
1533
|
+
// No listener or JVM is started until a project has opted in through setup.
|
|
1534
|
+
if (!process.env.MCP_PORT)
|
|
1535
|
+
await runtimeHub.initialize().catch(e => console.error("[modlens] runtime:", String(e)));
|
|
1456
1536
|
// Transport selection:
|
|
1457
1537
|
// • MCP_PORT set → Streamable HTTP transport (for k8s / remote deployments).
|
|
1458
1538
|
// • otherwise → stdio transport (default, local CLI use).
|
|
@@ -1485,8 +1565,15 @@ if (process.env.MODLENS_AUTO_EMBED !== "0") {
|
|
|
1485
1565
|
async function startHttpServer(port) {
|
|
1486
1566
|
const mcpPath = process.env.MCP_PATH ?? "/mcp";
|
|
1487
1567
|
const host = process.env.MCP_HOST ?? "0.0.0.0";
|
|
1568
|
+
const restricted = process.env.MODLENS_HOSTED_LIMITS !== "0";
|
|
1569
|
+
if (restricted)
|
|
1570
|
+
hostedLimits(); // Reject invalid operator configuration at startup.
|
|
1571
|
+
const proxySecret = process.env.MODLENS_HOSTED_PROXY_SECRET;
|
|
1572
|
+
if (proxySecret && proxySecret.length < 32)
|
|
1573
|
+
throw new Error("MODLENS_HOSTED_PROXY_SECRET must have at least 32 characters");
|
|
1488
1574
|
// Active sessions keyed by Mcp-Session-Id header.
|
|
1489
1575
|
const transports = new Map();
|
|
1576
|
+
const sessionOwners = new Map();
|
|
1490
1577
|
const readBody = (req) => new Promise((resolve, reject) => {
|
|
1491
1578
|
const chunks = [];
|
|
1492
1579
|
let size = 0;
|
|
@@ -1542,10 +1629,11 @@ async function startHttpServer(port) {
|
|
|
1542
1629
|
}
|
|
1543
1630
|
const sessionId = getHeader(req, "mcp-session-id");
|
|
1544
1631
|
try {
|
|
1632
|
+
const principal = restricted ? hostedPrincipal(req.headers, proxySecret) : undefined;
|
|
1545
1633
|
// Existing session — route to its transport.
|
|
1546
1634
|
if (sessionId) {
|
|
1547
1635
|
const transport = transports.get(sessionId);
|
|
1548
|
-
if (!transport) {
|
|
1636
|
+
if (!transport || (restricted && sessionOwners.get(sessionId) !== principal)) {
|
|
1549
1637
|
return sendJson(res, 404, {
|
|
1550
1638
|
jsonrpc: "2.0",
|
|
1551
1639
|
error: { code: -32001, message: "Session not found" },
|
|
@@ -1568,13 +1656,19 @@ async function startHttpServer(port) {
|
|
|
1568
1656
|
}
|
|
1569
1657
|
const transport = new StreamableHTTPServerTransport({
|
|
1570
1658
|
sessionIdGenerator: () => randomUUID(),
|
|
1571
|
-
onsessioninitialized: (id) => {
|
|
1659
|
+
onsessioninitialized: (id) => {
|
|
1660
|
+
transports.set(id, transport);
|
|
1661
|
+
if (principal !== undefined)
|
|
1662
|
+
sessionOwners.set(id, principal);
|
|
1663
|
+
},
|
|
1572
1664
|
});
|
|
1573
1665
|
transport.onclose = () => {
|
|
1574
|
-
if (transport.sessionId)
|
|
1666
|
+
if (transport.sessionId) {
|
|
1575
1667
|
transports.delete(transport.sessionId);
|
|
1668
|
+
sessionOwners.delete(transport.sessionId);
|
|
1669
|
+
}
|
|
1576
1670
|
};
|
|
1577
|
-
const server = createMcpServer();
|
|
1671
|
+
const server = createMcpServer(principal);
|
|
1578
1672
|
await server.connect(transport);
|
|
1579
1673
|
await transport.handleRequest(req, res, body);
|
|
1580
1674
|
return;
|
|
@@ -1587,6 +1681,8 @@ async function startHttpServer(port) {
|
|
|
1587
1681
|
}
|
|
1588
1682
|
catch (err) {
|
|
1589
1683
|
if (!res.headersSent) {
|
|
1684
|
+
if (err instanceof HostedPolicyError)
|
|
1685
|
+
return sendJson(res, 401, { error: err.message });
|
|
1590
1686
|
if (err instanceof HttpBodyTooLargeError)
|
|
1591
1687
|
return sendJson(res, 413, { error: err.message });
|
|
1592
1688
|
if (err instanceof JsonBodyParseError) {
|