@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,209 @@
|
|
|
1
|
+
// HTTP MCP -> local CLI -> persistent companion -> real instrumented SDL JVM.
|
|
2
|
+
// Set JAVA_HOME (25+) and MODLENS_SDL_CLASSPATH, after building server and agent.
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
5
|
+
import { mkdtemp, mkdir, writeFile, readFile, access } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join, delimiter } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createServer } from "node:net";
|
|
10
|
+
import { once } from "node:events";
|
|
11
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
13
|
+
|
|
14
|
+
if (!process.env.JAVA_HOME || !process.env.MODLENS_SDL_CLASSPATH)
|
|
15
|
+
throw new Error("Set JAVA_HOME and MODLENS_SDL_CLASSPATH");
|
|
16
|
+
const repo = fileURLToPath(new URL("..", import.meta.url));
|
|
17
|
+
const root = await mkdtemp(join(tmpdir(), "modlens-runtime-remote-"));
|
|
18
|
+
console.log("Evidence: " + root);
|
|
19
|
+
const listener = createServer();
|
|
20
|
+
listener.listen(0, "127.0.0.1");
|
|
21
|
+
await once(listener, "listening");
|
|
22
|
+
const port = listener.address().port;
|
|
23
|
+
await new Promise((resolve) => listener.close(resolve));
|
|
24
|
+
const remoteEnv = {
|
|
25
|
+
...process.env,
|
|
26
|
+
MCP_PORT: String(port),
|
|
27
|
+
MCP_HOST: "127.0.0.1",
|
|
28
|
+
MODLENS_AUTO_EMBED: "0",
|
|
29
|
+
MODLENS_CACHE_ROOT: join(root, "remote-cache"),
|
|
30
|
+
MODLENS_HOME: join(root, "remote-home"),
|
|
31
|
+
DATABASE_URL: `file:${join(root, "unused.db")}`,
|
|
32
|
+
};
|
|
33
|
+
const localEnv = {
|
|
34
|
+
...process.env,
|
|
35
|
+
MCP_PORT: String(port),
|
|
36
|
+
MODLENS_CACHE_ROOT: join(root, "local-cache"),
|
|
37
|
+
MODLENS_HOME: join(root, "local-home"),
|
|
38
|
+
};
|
|
39
|
+
const server = spawn(process.execPath, [join(repo, "dist/server.js")], {
|
|
40
|
+
cwd: root,
|
|
41
|
+
env: remoteEnv,
|
|
42
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
});
|
|
45
|
+
let serverLog = "";
|
|
46
|
+
server.stderr.on("data", (b) => (serverLog += b));
|
|
47
|
+
const serverDone = once(server, "exit");
|
|
48
|
+
const client = new Client({ name: "runtime-remote-smoke", version: "1.0.0" });
|
|
49
|
+
const cli = (args) =>
|
|
50
|
+
JSON.parse(
|
|
51
|
+
execFileSync(process.execPath, [join(repo, "dist/launcher.js"), "--runtime", ...args], {
|
|
52
|
+
cwd: root,
|
|
53
|
+
env: localEnv,
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
timeout: 45000,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
async function local(request) {
|
|
59
|
+
const file = join(root, "request.json");
|
|
60
|
+
await writeFile(file, JSON.stringify(request));
|
|
61
|
+
return cli(["--request-file", file]);
|
|
62
|
+
}
|
|
63
|
+
async function plan(request) {
|
|
64
|
+
const response = await client.callTool({ name: "runtime", arguments: request });
|
|
65
|
+
assert.ok(!response.isError, JSON.stringify(response));
|
|
66
|
+
const result = JSON.parse(response.content[0].text);
|
|
67
|
+
assert.equal(result.executed, false);
|
|
68
|
+
assert.equal(result.execution, "local_helper_required");
|
|
69
|
+
assert.ok(result.invocation.arguments.includes("--runtime"));
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
async function throughRemote(request) {
|
|
73
|
+
return local((await plan(request)).request);
|
|
74
|
+
}
|
|
75
|
+
async function wait(fn, ms = 30000) {
|
|
76
|
+
const end = Date.now() + ms;
|
|
77
|
+
while (Date.now() < end) {
|
|
78
|
+
const value = await fn();
|
|
79
|
+
if (value) return value;
|
|
80
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
81
|
+
}
|
|
82
|
+
throw new Error("Timed out\n" + serverLog);
|
|
83
|
+
}
|
|
84
|
+
let game,
|
|
85
|
+
gameDone,
|
|
86
|
+
gameLog = "";
|
|
87
|
+
try {
|
|
88
|
+
await wait(() => serverLog.includes("listening on"));
|
|
89
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${port}/mcp`)));
|
|
90
|
+
assert.match(client.getInstructions(), /local.*helper/);
|
|
91
|
+
const project = join(root, "local project with spaces");
|
|
92
|
+
await mkdir(join(project, "gradle/wrapper"), { recursive: true });
|
|
93
|
+
await writeFile(join(project, "gradle/wrapper/gradle-wrapper.jar"), "fixture");
|
|
94
|
+
const setupPlan = await plan({ action: "setup", projectDir: project, mode: "hidden" });
|
|
95
|
+
await assert.rejects(access(join(project, ".modlens")), "Remote planning must not change local files");
|
|
96
|
+
const setup = await local(setupPlan.request);
|
|
97
|
+
assert.equal(setup.state, "configured");
|
|
98
|
+
await assert.rejects(
|
|
99
|
+
access(join(root, "local-home", "data/modlens.db")),
|
|
100
|
+
"Helper must not bootstrap a source database",
|
|
101
|
+
);
|
|
102
|
+
await assert.rejects(
|
|
103
|
+
access(join(root, "remote-cache/runtime")),
|
|
104
|
+
"Remote server must not start a runtime bridge",
|
|
105
|
+
);
|
|
106
|
+
const agent = join(repo, "dist/runtime/modlens-agent.jar");
|
|
107
|
+
const java = (name) =>
|
|
108
|
+
join(process.env.JAVA_HOME, "bin", name + (process.platform === "win32" ? ".exe" : ""));
|
|
109
|
+
execFileSync(
|
|
110
|
+
java("javac"),
|
|
111
|
+
["-cp", agent, "-d", root, join(repo, "runtime-agent/test/io/modlens/runtime/NativeSmoke.java")],
|
|
112
|
+
{ stdio: "inherit" },
|
|
113
|
+
);
|
|
114
|
+
game = spawn(
|
|
115
|
+
java("java"),
|
|
116
|
+
[
|
|
117
|
+
"--enable-native-access=ALL-UNNAMED",
|
|
118
|
+
setup.vmOption,
|
|
119
|
+
"-cp",
|
|
120
|
+
[root, agent, process.env.MODLENS_SDL_CLASSPATH].join(delimiter),
|
|
121
|
+
"io.modlens.runtime.NativeSmoke",
|
|
122
|
+
root,
|
|
123
|
+
],
|
|
124
|
+
{ windowsHide: true },
|
|
125
|
+
);
|
|
126
|
+
game.stdout.on("data", (b) => (gameLog += b));
|
|
127
|
+
game.stderr.on("data", (b) => (gameLog += b));
|
|
128
|
+
gameDone = once(game, "exit");
|
|
129
|
+
const session = await wait(async () =>
|
|
130
|
+
(await local({ action: "sessions" })).find((s) => s.capabilities.inputBackend === "lwjgl-sdl3"),
|
|
131
|
+
);
|
|
132
|
+
const key = await throughRemote({
|
|
133
|
+
action: "command",
|
|
134
|
+
sessionId: session.sessionId,
|
|
135
|
+
command: { type: "key", key: "W", down: true, holdMs: 150 },
|
|
136
|
+
});
|
|
137
|
+
assert.equal(key.state, "event_delivered");
|
|
138
|
+
const allocation = await throughRemote({
|
|
139
|
+
action: "command",
|
|
140
|
+
sessionId: session.sessionId,
|
|
141
|
+
command: { type: "allocations", packagePrefix: "io.modlens.runtime.NativeSmoke" },
|
|
142
|
+
});
|
|
143
|
+
const report = await throughRemote({
|
|
144
|
+
action: "artifact",
|
|
145
|
+
sessionId: session.sessionId,
|
|
146
|
+
artifactName: allocation.artifact,
|
|
147
|
+
});
|
|
148
|
+
assert.ok(JSON.parse(report.text).hotspots.some((h) => h.site.includes("allocateFixture")));
|
|
149
|
+
assert.ok(
|
|
150
|
+
cli(["sessions"]).some((s) => s.sessionId === session.sessionId),
|
|
151
|
+
"CLI invocations must share the same live session",
|
|
152
|
+
);
|
|
153
|
+
cli(["stop"]);
|
|
154
|
+
await local({ action: "status" });
|
|
155
|
+
await wait(async () =>
|
|
156
|
+
(await local({ action: "sessions" })).find((s) => s.sessionId === session.sessionId),
|
|
157
|
+
);
|
|
158
|
+
const threads = await throughRemote({
|
|
159
|
+
action: "command",
|
|
160
|
+
sessionId: session.sessionId,
|
|
161
|
+
command: { type: "threads" },
|
|
162
|
+
});
|
|
163
|
+
assert.match(
|
|
164
|
+
(
|
|
165
|
+
await throughRemote({
|
|
166
|
+
action: "artifact",
|
|
167
|
+
sessionId: session.sessionId,
|
|
168
|
+
artifactName: threads.artifact,
|
|
169
|
+
})
|
|
170
|
+
).text,
|
|
171
|
+
/ModLens telemetry/,
|
|
172
|
+
);
|
|
173
|
+
await writeFile(join(root, "finish"), "done");
|
|
174
|
+
assert.equal((await gameDone)[0], 0, gameLog);
|
|
175
|
+
const events = await wait(async () => {
|
|
176
|
+
const result = await local({ action: "events" });
|
|
177
|
+
return result.events.some((e) => e.type === "uncaught_exception") && result;
|
|
178
|
+
});
|
|
179
|
+
assert.match(gameLog, /KEY 26/);
|
|
180
|
+
await writeFile(
|
|
181
|
+
join(root, "evidence.json"),
|
|
182
|
+
JSON.stringify({ session, report: JSON.parse(report.text), events, gameLog }, null, 2),
|
|
183
|
+
);
|
|
184
|
+
cli(["stop"]);
|
|
185
|
+
await wait(async () => {
|
|
186
|
+
try {
|
|
187
|
+
await access(join(root, "local-cache/runtime/bridge.lock"));
|
|
188
|
+
return false;
|
|
189
|
+
} catch {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
assert.equal(cli(["stop"]).state, "not_running");
|
|
194
|
+
console.log(
|
|
195
|
+
"PASS: remote MCP guidance, local helper setup, persistent session, real JVM input, allocation report, crash events, and clean stop",
|
|
196
|
+
);
|
|
197
|
+
} finally {
|
|
198
|
+
if (game && game.exitCode === null) {
|
|
199
|
+
game.kill();
|
|
200
|
+
await gameDone;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
cli(["stop"]);
|
|
204
|
+
} catch {}
|
|
205
|
+
await client.close();
|
|
206
|
+
server.kill();
|
|
207
|
+
await serverDone;
|
|
208
|
+
await writeFile(join(root, "server.log"), serverLog);
|
|
209
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Refresh reviewed standard texts from SPDX. Review and commit changes before deploying.
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
const ids = ["0BSD", "Unlicense", "MIT", "ISC", "BSD-2-Clause", "BSD-3-Clause", "Apache-2.0", "LGPL-3.0-only", "GPL-3.0-only", "CC-BY-NC-SA-4.0"];
|
|
4
|
+
const entries = await Promise.all(ids.map(async id => {
|
|
5
|
+
const url = `https://raw.githubusercontent.com/spdx/license-list-data/v3.27.0/json/details/${id}.json`;
|
|
6
|
+
const response = await fetch(url);
|
|
7
|
+
if (!response.ok) throw new Error(`${id}: ${response.status}`);
|
|
8
|
+
const data = await response.json();
|
|
9
|
+
return [id, { name: data.name, text: data.licenseText, source: url }];
|
|
10
|
+
}));
|
|
11
|
+
await writeFile(new URL("../src/license-templates.ts", import.meta.url),
|
|
12
|
+
"// SPDX License List 3.27.0 standard texts; see scripts/update-license-templates.mjs.\n" +
|
|
13
|
+
"// SPDX data: https://github.com/spdx/license-list-data (CC0-1.0).\n" +
|
|
14
|
+
`export const licenseTemplates: Record<string, { name: string; text: string; source: string }> = ${JSON.stringify(Object.fromEntries(entries), null, 2)};\n`);
|