@adhisang/minecraft-modding-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/dist/access-widener-parser.d.ts +24 -0
  5. package/dist/access-widener-parser.js +77 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +4 -0
  8. package/dist/config.d.ts +27 -0
  9. package/dist/config.js +178 -0
  10. package/dist/decompiler/vineflower.d.ts +15 -0
  11. package/dist/decompiler/vineflower.js +185 -0
  12. package/dist/errors.d.ts +50 -0
  13. package/dist/errors.js +49 -0
  14. package/dist/hash.d.ts +1 -0
  15. package/dist/hash.js +12 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.js +1447 -0
  18. package/dist/java-process.d.ts +16 -0
  19. package/dist/java-process.js +120 -0
  20. package/dist/logger.d.ts +3 -0
  21. package/dist/logger.js +21 -0
  22. package/dist/mapping-pipeline-service.d.ts +18 -0
  23. package/dist/mapping-pipeline-service.js +60 -0
  24. package/dist/mapping-service.d.ts +161 -0
  25. package/dist/mapping-service.js +1706 -0
  26. package/dist/maven-resolver.d.ts +22 -0
  27. package/dist/maven-resolver.js +122 -0
  28. package/dist/minecraft-explorer-service.d.ts +43 -0
  29. package/dist/minecraft-explorer-service.js +562 -0
  30. package/dist/mixin-parser.d.ts +34 -0
  31. package/dist/mixin-parser.js +194 -0
  32. package/dist/mixin-validator.d.ts +59 -0
  33. package/dist/mixin-validator.js +274 -0
  34. package/dist/mod-analyzer.d.ts +23 -0
  35. package/dist/mod-analyzer.js +346 -0
  36. package/dist/mod-decompile-service.d.ts +39 -0
  37. package/dist/mod-decompile-service.js +136 -0
  38. package/dist/mod-remap-service.d.ts +17 -0
  39. package/dist/mod-remap-service.js +186 -0
  40. package/dist/mod-search-service.d.ts +28 -0
  41. package/dist/mod-search-service.js +174 -0
  42. package/dist/mojang-tiny-mapping-service.d.ts +13 -0
  43. package/dist/mojang-tiny-mapping-service.js +351 -0
  44. package/dist/nbt/java-nbt-codec.d.ts +3 -0
  45. package/dist/nbt/java-nbt-codec.js +385 -0
  46. package/dist/nbt/json-patch.d.ts +3 -0
  47. package/dist/nbt/json-patch.js +352 -0
  48. package/dist/nbt/pipeline.d.ts +39 -0
  49. package/dist/nbt/pipeline.js +173 -0
  50. package/dist/nbt/typed-json.d.ts +10 -0
  51. package/dist/nbt/typed-json.js +205 -0
  52. package/dist/nbt/types.d.ts +66 -0
  53. package/dist/nbt/types.js +2 -0
  54. package/dist/observability.d.ts +88 -0
  55. package/dist/observability.js +165 -0
  56. package/dist/path-converter.d.ts +12 -0
  57. package/dist/path-converter.js +161 -0
  58. package/dist/path-resolver.d.ts +19 -0
  59. package/dist/path-resolver.js +78 -0
  60. package/dist/registry-service.d.ts +29 -0
  61. package/dist/registry-service.js +214 -0
  62. package/dist/repo-downloader.d.ts +15 -0
  63. package/dist/repo-downloader.js +111 -0
  64. package/dist/resources.d.ts +3 -0
  65. package/dist/resources.js +154 -0
  66. package/dist/search-hit-accumulator.d.ts +38 -0
  67. package/dist/search-hit-accumulator.js +153 -0
  68. package/dist/source-jar-reader.d.ts +13 -0
  69. package/dist/source-jar-reader.js +216 -0
  70. package/dist/source-resolver.d.ts +14 -0
  71. package/dist/source-resolver.js +274 -0
  72. package/dist/source-service.d.ts +404 -0
  73. package/dist/source-service.js +2881 -0
  74. package/dist/storage/artifacts-repo.d.ts +45 -0
  75. package/dist/storage/artifacts-repo.js +209 -0
  76. package/dist/storage/db.d.ts +14 -0
  77. package/dist/storage/db.js +132 -0
  78. package/dist/storage/files-repo.d.ts +78 -0
  79. package/dist/storage/files-repo.js +437 -0
  80. package/dist/storage/index-meta-repo.d.ts +35 -0
  81. package/dist/storage/index-meta-repo.js +97 -0
  82. package/dist/storage/migrations.d.ts +11 -0
  83. package/dist/storage/migrations.js +71 -0
  84. package/dist/storage/schema.d.ts +1 -0
  85. package/dist/storage/schema.js +160 -0
  86. package/dist/storage/sqlite.d.ts +20 -0
  87. package/dist/storage/sqlite.js +111 -0
  88. package/dist/storage/symbols-repo.d.ts +63 -0
  89. package/dist/storage/symbols-repo.js +401 -0
  90. package/dist/symbols/symbol-extractor.d.ts +7 -0
  91. package/dist/symbols/symbol-extractor.js +64 -0
  92. package/dist/tiny-remapper-resolver.d.ts +1 -0
  93. package/dist/tiny-remapper-resolver.js +62 -0
  94. package/dist/tiny-remapper-service.d.ts +16 -0
  95. package/dist/tiny-remapper-service.js +73 -0
  96. package/dist/types.d.ts +120 -0
  97. package/dist/types.js +2 -0
  98. package/dist/version-diff-service.d.ts +41 -0
  99. package/dist/version-diff-service.js +222 -0
  100. package/dist/version-service.d.ts +70 -0
  101. package/dist/version-service.js +411 -0
  102. package/dist/vineflower-resolver.d.ts +1 -0
  103. package/dist/vineflower-resolver.js +62 -0
  104. package/dist/workspace-mapping-service.d.ts +18 -0
  105. package/dist/workspace-mapping-service.js +89 -0
  106. package/package.json +61 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Parser for Fabric Access Widener files (.accesswidener).
3
+ * Format: line-based, tab/space separated.
4
+ * Header: `accessWidener v2 intermediary`
5
+ * Entry: `accessible class net/minecraft/foo/Bar`
6
+ * `accessible method net/minecraft/foo/Bar baz (I)V`
7
+ * `mutable field net/minecraft/foo/Bar qux I`
8
+ */
9
+ export type AccessWidenerEntry = {
10
+ line: number;
11
+ kind: "accessible" | "extendable" | "mutable";
12
+ targetKind: "class" | "method" | "field";
13
+ target: string;
14
+ owner?: string;
15
+ name?: string;
16
+ descriptor?: string;
17
+ };
18
+ export type ParsedAccessWidener = {
19
+ headerVersion: string;
20
+ namespace: string;
21
+ entries: AccessWidenerEntry[];
22
+ parseWarnings: string[];
23
+ };
24
+ export declare function parseAccessWidener(content: string): ParsedAccessWidener;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Parser for Fabric Access Widener files (.accesswidener).
3
+ * Format: line-based, tab/space separated.
4
+ * Header: `accessWidener v2 intermediary`
5
+ * Entry: `accessible class net/minecraft/foo/Bar`
6
+ * `accessible method net/minecraft/foo/Bar baz (I)V`
7
+ * `mutable field net/minecraft/foo/Bar qux I`
8
+ */
9
+ const VALID_KINDS = new Set(["accessible", "extendable", "mutable"]);
10
+ const VALID_TARGET_KINDS = new Set(["class", "method", "field"]);
11
+ export function parseAccessWidener(content) {
12
+ const lines = content.split(/\r?\n/);
13
+ const parseWarnings = [];
14
+ const entries = [];
15
+ let headerVersion = "";
16
+ let namespace = "";
17
+ for (let i = 0; i < lines.length; i++) {
18
+ const raw = lines[i].trim();
19
+ const lineNum = i + 1;
20
+ // Skip blank lines and comments
21
+ if (!raw || raw.startsWith("#"))
22
+ continue;
23
+ // Header line
24
+ if (!headerVersion) {
25
+ const parts = raw.split(/\s+/);
26
+ if (parts[0] === "accessWidener" && parts.length >= 3) {
27
+ headerVersion = parts[1];
28
+ namespace = parts[2];
29
+ }
30
+ else {
31
+ parseWarnings.push(`Line ${lineNum}: Expected accessWidener header, got: "${raw}"`);
32
+ }
33
+ continue;
34
+ }
35
+ // Entry lines
36
+ const parts = raw.split(/\s+/);
37
+ if (parts.length < 3) {
38
+ parseWarnings.push(`Line ${lineNum}: Incomplete entry: "${raw}"`);
39
+ continue;
40
+ }
41
+ const kind = parts[0];
42
+ const targetKind = parts[1];
43
+ if (!VALID_KINDS.has(kind)) {
44
+ parseWarnings.push(`Line ${lineNum}: Unknown access kind "${kind}".`);
45
+ continue;
46
+ }
47
+ if (!VALID_TARGET_KINDS.has(targetKind)) {
48
+ parseWarnings.push(`Line ${lineNum}: Unknown target kind "${targetKind}".`);
49
+ continue;
50
+ }
51
+ const validKind = kind;
52
+ const validTargetKind = targetKind;
53
+ if (validTargetKind === "class") {
54
+ entries.push({ line: lineNum, kind: validKind, targetKind: validTargetKind, target: parts[2] });
55
+ }
56
+ else if (parts.length >= 5) {
57
+ // method/field: <kind> <targetKind> <owner> <name> <descriptor>
58
+ entries.push({
59
+ line: lineNum,
60
+ kind: validKind,
61
+ targetKind: validTargetKind,
62
+ target: parts[2],
63
+ owner: parts[2],
64
+ name: parts[3],
65
+ descriptor: parts[4]
66
+ });
67
+ }
68
+ else {
69
+ parseWarnings.push(`Line ${lineNum}: ${validTargetKind} entry requires owner, name, and descriptor.`);
70
+ }
71
+ }
72
+ if (!headerVersion) {
73
+ parseWarnings.push("Missing accessWidener header.");
74
+ }
75
+ return { headerVersion, namespace, entries, parseWarnings };
76
+ }
77
+ //# sourceMappingURL=access-widener-parser.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from "./index.js";
3
+ startServer();
4
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,27 @@
1
+ import type { Config } from "./types.js";
2
+ declare const DEFAULTS: {
3
+ readonly cacheDir: ".cache/minecraft-modding-mcp";
4
+ readonly sourceRepos: readonly ["https://repo1.maven.org/maven2", "https://maven.fabricmc.net", "https://maven.minecraftforge.net", "https://maven.neoforged.net/releases"];
5
+ readonly localM2Path: "~/.m2/repository";
6
+ readonly indexedSearchEnabled: true;
7
+ readonly mappingSourcePriority: "loom-first";
8
+ readonly maxContentBytes: 1000000;
9
+ readonly maxSearchHits: 200;
10
+ readonly maxArtifacts: 200;
11
+ readonly maxCacheBytes: 2147483648;
12
+ readonly fetchTimeoutMs: 15000;
13
+ readonly fetchRetries: 2;
14
+ readonly searchScanPageSize: 250;
15
+ readonly indexInsertChunkSize: 200;
16
+ readonly maxMappingGraphCache: 16;
17
+ readonly maxSignatureCache: 2000;
18
+ readonly maxVersionDetailCache: 256;
19
+ readonly maxNbtInputBytes: number;
20
+ readonly maxNbtInflatedBytes: number;
21
+ readonly maxNbtResponseBytes: number;
22
+ readonly remapTimeoutMs: 600000;
23
+ readonly remapMaxMemoryMb: 4096;
24
+ };
25
+ export declare function loadConfig(): Config;
26
+ export declare function stableArtifactId(parts: string[]): string;
27
+ export { DEFAULTS };
package/dist/config.js ADDED
@@ -0,0 +1,178 @@
1
+ import { createHash } from "node:crypto";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, resolve } from "node:path";
4
+ import { normalizePathForHost } from "./path-converter.js";
5
+ const DEFAULTS = {
6
+ cacheDir: ".cache/minecraft-modding-mcp",
7
+ sourceRepos: [
8
+ "https://repo1.maven.org/maven2",
9
+ "https://maven.fabricmc.net",
10
+ "https://maven.minecraftforge.net",
11
+ "https://maven.neoforged.net/releases"
12
+ ],
13
+ localM2Path: "~/.m2/repository",
14
+ indexedSearchEnabled: true,
15
+ mappingSourcePriority: "loom-first",
16
+ maxContentBytes: 1_000_000,
17
+ maxSearchHits: 200,
18
+ maxArtifacts: 200,
19
+ maxCacheBytes: 2_147_483_648,
20
+ fetchTimeoutMs: 15000,
21
+ fetchRetries: 2,
22
+ searchScanPageSize: 250,
23
+ indexInsertChunkSize: 200,
24
+ maxMappingGraphCache: 16,
25
+ maxSignatureCache: 2_000,
26
+ maxVersionDetailCache: 256,
27
+ maxNbtInputBytes: 4 * 1024 * 1024,
28
+ maxNbtInflatedBytes: 16 * 1024 * 1024,
29
+ maxNbtResponseBytes: 8 * 1024 * 1024,
30
+ remapTimeoutMs: 600_000,
31
+ remapMaxMemoryMb: 4096
32
+ };
33
+ const MAX_RETRIES_LOWER_BOUND = 0;
34
+ const MAX_RETRIES_UPPER_BOUND = 20;
35
+ const MAX_BYTES_LOWER_BOUND = 1;
36
+ const MAX_SEARCH_HITS_LOWER_BOUND = 1;
37
+ const MAX_SEARCH_HITS_UPPER_BOUND = 10_000;
38
+ const MAX_ARTIFACTS_LOWER_BOUND = 1;
39
+ const MAX_ARTIFACTS_UPPER_BOUND = 100_000;
40
+ const MAX_CACHE_BYTES_LOWER_BOUND = 1_024;
41
+ const TIMEOUT_LOWER_BOUND_MS = 500;
42
+ const SEARCH_SCAN_PAGE_SIZE_LOWER_BOUND = 1;
43
+ const SEARCH_SCAN_PAGE_SIZE_UPPER_BOUND = 10_000;
44
+ const INDEX_INSERT_CHUNK_SIZE_LOWER_BOUND = 1;
45
+ const INDEX_INSERT_CHUNK_SIZE_UPPER_BOUND = 20_000;
46
+ const CACHE_ENTRIES_LOWER_BOUND = 1;
47
+ const CACHE_ENTRIES_UPPER_BOUND = 100_000;
48
+ function expandHome(pathValue) {
49
+ if (!pathValue.startsWith("~")) {
50
+ return pathValue;
51
+ }
52
+ const withoutTilde = pathValue.startsWith("~/") ? pathValue.slice(2) : pathValue.slice(1);
53
+ return resolve(homedir(), withoutTilde);
54
+ }
55
+ function normalizePath(pathValue, field) {
56
+ const expanded = expandHome(pathValue.trim());
57
+ const normalizedForHost = normalizePathForHost(expanded, undefined, field);
58
+ if (isAbsolute(normalizedForHost)) {
59
+ return normalizedForHost;
60
+ }
61
+ return resolve(process.cwd(), normalizedForHost);
62
+ }
63
+ function parseNumber(envValue, fallback, min, max) {
64
+ if (!envValue) {
65
+ return fallback;
66
+ }
67
+ const parsed = Number.parseInt(envValue, 10);
68
+ if (!Number.isFinite(parsed) || parsed < min || parsed > max) {
69
+ return fallback;
70
+ }
71
+ return parsed;
72
+ }
73
+ function parseRepos(envValue) {
74
+ const entries = (envValue ?? "").split(",").map((value) => value.trim()).filter(Boolean);
75
+ if (entries.length === 0) {
76
+ return [...DEFAULTS.sourceRepos];
77
+ }
78
+ const validated = [];
79
+ for (const entry of entries) {
80
+ const parsed = parseRepoUrl(entry);
81
+ if (parsed) {
82
+ validated.push(parsed);
83
+ }
84
+ }
85
+ return validated.length > 0 ? validated : [...DEFAULTS.sourceRepos];
86
+ }
87
+ function parseBoolean(envValue, fallback) {
88
+ if (!envValue) {
89
+ return fallback;
90
+ }
91
+ const normalized = envValue.trim().toLowerCase();
92
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
93
+ return true;
94
+ }
95
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
96
+ return false;
97
+ }
98
+ return fallback;
99
+ }
100
+ function parseRepoUrl(raw) {
101
+ try {
102
+ const parsed = new URL(raw);
103
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
104
+ return undefined;
105
+ }
106
+ return raw;
107
+ }
108
+ catch {
109
+ return undefined;
110
+ }
111
+ }
112
+ function parseMappingSourcePriority(envValue) {
113
+ const normalized = envValue?.trim().toLowerCase();
114
+ if (normalized === "maven-first") {
115
+ return "maven-first";
116
+ }
117
+ if (normalized === "loom-first") {
118
+ return "loom-first";
119
+ }
120
+ return DEFAULTS.mappingSourcePriority;
121
+ }
122
+ function parseOptionalJarPath(envValue, field) {
123
+ if (!envValue) {
124
+ return undefined;
125
+ }
126
+ const trimmed = envValue.trim();
127
+ if (!trimmed) {
128
+ return undefined;
129
+ }
130
+ return normalizePath(trimmed, field);
131
+ }
132
+ function parseVineflowerPath(envValue) {
133
+ return parseOptionalJarPath(envValue, "MCP_VINEFLOWER_JAR_PATH");
134
+ }
135
+ function buildArtifactsDirectory(cacheDir) {
136
+ const input = `${cacheDir}/source-cache.db`;
137
+ return normalizePath(input, "MCP_SQLITE_PATH");
138
+ }
139
+ export function loadConfig() {
140
+ const cacheDir = normalizePath(process.env.MCP_CACHE_DIR ?? DEFAULTS.cacheDir, "MCP_CACHE_DIR");
141
+ const localM2Path = normalizePath(process.env.MCP_LOCAL_M2 ?? DEFAULTS.localM2Path, "MCP_LOCAL_M2");
142
+ const sourceRepos = parseRepos(process.env.MCP_SOURCE_REPOS);
143
+ return {
144
+ cacheDir,
145
+ sqlitePath: normalizePath(process.env.MCP_SQLITE_PATH ?? buildArtifactsDirectory(cacheDir), "MCP_SQLITE_PATH"),
146
+ sourceRepos,
147
+ localM2Path,
148
+ vineflowerJarPath: parseVineflowerPath(process.env.MCP_VINEFLOWER_JAR_PATH),
149
+ indexedSearchEnabled: parseBoolean(process.env.MCP_ENABLE_INDEXED_SEARCH, DEFAULTS.indexedSearchEnabled),
150
+ mappingSourcePriority: parseMappingSourcePriority(process.env.MCP_MAPPING_SOURCE_PRIORITY),
151
+ maxContentBytes: parseNumber(process.env.MCP_MAX_CONTENT_BYTES, DEFAULTS.maxContentBytes, MAX_BYTES_LOWER_BOUND, Number.MAX_SAFE_INTEGER),
152
+ maxSearchHits: parseNumber(process.env.MCP_MAX_SEARCH_HITS, DEFAULTS.maxSearchHits, MAX_SEARCH_HITS_LOWER_BOUND, MAX_SEARCH_HITS_UPPER_BOUND),
153
+ maxArtifacts: parseNumber(process.env.MCP_MAX_ARTIFACTS, DEFAULTS.maxArtifacts, MAX_ARTIFACTS_LOWER_BOUND, MAX_ARTIFACTS_UPPER_BOUND),
154
+ maxCacheBytes: parseNumber(process.env.MCP_MAX_CACHE_BYTES, DEFAULTS.maxCacheBytes, MAX_CACHE_BYTES_LOWER_BOUND, Number.MAX_SAFE_INTEGER),
155
+ fetchTimeoutMs: parseNumber(process.env.MCP_FETCH_TIMEOUT_MS, DEFAULTS.fetchTimeoutMs, TIMEOUT_LOWER_BOUND_MS, Number.MAX_SAFE_INTEGER),
156
+ fetchRetries: parseNumber(process.env.MCP_FETCH_RETRIES, DEFAULTS.fetchRetries, MAX_RETRIES_LOWER_BOUND, MAX_RETRIES_UPPER_BOUND),
157
+ searchScanPageSize: parseNumber(process.env.MCP_SEARCH_SCAN_PAGE_SIZE, DEFAULTS.searchScanPageSize, SEARCH_SCAN_PAGE_SIZE_LOWER_BOUND, SEARCH_SCAN_PAGE_SIZE_UPPER_BOUND),
158
+ indexInsertChunkSize: parseNumber(process.env.MCP_INDEX_INSERT_CHUNK_SIZE, DEFAULTS.indexInsertChunkSize, INDEX_INSERT_CHUNK_SIZE_LOWER_BOUND, INDEX_INSERT_CHUNK_SIZE_UPPER_BOUND),
159
+ maxMappingGraphCache: parseNumber(process.env.MCP_CACHE_GRAPH_MAX, DEFAULTS.maxMappingGraphCache, CACHE_ENTRIES_LOWER_BOUND, CACHE_ENTRIES_UPPER_BOUND),
160
+ maxSignatureCache: parseNumber(process.env.MCP_CACHE_SIGNATURE_MAX, DEFAULTS.maxSignatureCache, CACHE_ENTRIES_LOWER_BOUND, CACHE_ENTRIES_UPPER_BOUND),
161
+ maxVersionDetailCache: parseNumber(process.env.MCP_CACHE_VERSION_DETAIL_MAX, DEFAULTS.maxVersionDetailCache, CACHE_ENTRIES_LOWER_BOUND, CACHE_ENTRIES_UPPER_BOUND),
162
+ maxNbtInputBytes: parseNumber(process.env.MCP_MAX_NBT_INPUT_BYTES, DEFAULTS.maxNbtInputBytes, MAX_BYTES_LOWER_BOUND, Number.MAX_SAFE_INTEGER),
163
+ maxNbtInflatedBytes: parseNumber(process.env.MCP_MAX_NBT_INFLATED_BYTES, DEFAULTS.maxNbtInflatedBytes, MAX_BYTES_LOWER_BOUND, Number.MAX_SAFE_INTEGER),
164
+ maxNbtResponseBytes: parseNumber(process.env.MCP_MAX_NBT_RESPONSE_BYTES, DEFAULTS.maxNbtResponseBytes, MAX_BYTES_LOWER_BOUND, Number.MAX_SAFE_INTEGER),
165
+ tinyRemapperJarPath: parseOptionalJarPath(process.env.MCP_TINY_REMAPPER_JAR_PATH, "MCP_TINY_REMAPPER_JAR_PATH"),
166
+ remapTimeoutMs: parseNumber(process.env.MCP_REMAP_TIMEOUT_MS, DEFAULTS.remapTimeoutMs, TIMEOUT_LOWER_BOUND_MS, Number.MAX_SAFE_INTEGER),
167
+ remapMaxMemoryMb: parseNumber(process.env.MCP_REMAP_MAX_MEMORY_MB, DEFAULTS.remapMaxMemoryMb, 64, Number.MAX_SAFE_INTEGER)
168
+ };
169
+ }
170
+ export function stableArtifactId(parts) {
171
+ const normalizer = parts
172
+ .map((part) => part.trim())
173
+ .filter(Boolean)
174
+ .join("|");
175
+ return createHash("sha256").update(normalizer).digest("hex");
176
+ }
177
+ export { DEFAULTS };
178
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,15 @@
1
+ export interface DecompileResult {
2
+ outputDir: string;
3
+ javaFiles: Array<{
4
+ filePath: string;
5
+ content: string;
6
+ }>;
7
+ }
8
+ interface DecompileBinaryOptions {
9
+ vineflowerJarPath?: string;
10
+ timeoutMs?: number;
11
+ signature?: string;
12
+ artifactIdCandidate?: string;
13
+ }
14
+ export declare function decompileBinaryJar(binaryJarPath: string, cacheDir: string, options?: DecompileBinaryOptions): Promise<DecompileResult>;
15
+ export {};
@@ -0,0 +1,185 @@
1
+ import { access, constants } from "node:fs/promises";
2
+ import { mkdirSync, readdirSync, statSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { basename, join, relative, sep } from "node:path";
5
+ import { createError, ERROR_CODES, isAppError } from "../errors.js";
6
+ import { assertJavaAvailable, runJavaProcess } from "../java-process.js";
7
+ import { log } from "../logger.js";
8
+ const DEFAULT_TIMEOUT_MS = 120_000;
9
+ function emitDecompileLog(event, details) {
10
+ log(event === "decompile.error" ? "error" : "info", event, details);
11
+ }
12
+ function extractStderrTail(error) {
13
+ if (!isAppError(error)) {
14
+ return undefined;
15
+ }
16
+ const tail = error.details?.stderrTail;
17
+ return typeof tail === "string" ? tail : undefined;
18
+ }
19
+ function normalizeBinaryJarPath(binaryJarPath) {
20
+ const normalized = binaryJarPath.trim();
21
+ if (!normalized.toLowerCase().endsWith(".jar")) {
22
+ throw createError({
23
+ code: ERROR_CODES.DECOMPILER_UNAVAILABLE,
24
+ message: "binaryJarPath must point to a .jar file.",
25
+ details: { binaryJarPath }
26
+ });
27
+ }
28
+ return normalized;
29
+ }
30
+ function normalizeOutputPath(root, childPath) {
31
+ return relative(root, childPath).split(sep).join("/");
32
+ }
33
+ async function assertVineflowerAvailable(vineflowerJarPath) {
34
+ try {
35
+ await access(vineflowerJarPath, constants.F_OK | constants.R_OK);
36
+ }
37
+ catch {
38
+ throw createError({
39
+ code: ERROR_CODES.DECOMPILER_UNAVAILABLE,
40
+ message: "Vineflower jar is not available.",
41
+ details: { vineflowerJarPath }
42
+ });
43
+ }
44
+ }
45
+ function collectJavaFilesSync(baseDir, currentDir = "") {
46
+ const absoluteBase = currentDir ? join(baseDir, currentDir) : baseDir;
47
+ const entries = readdirSync(absoluteBase, { withFileTypes: true });
48
+ const result = [];
49
+ for (const entry of entries) {
50
+ const next = currentDir ? join(currentDir, entry.name) : entry.name;
51
+ if (entry.isDirectory()) {
52
+ result.push(...collectJavaFilesSync(baseDir, next));
53
+ continue;
54
+ }
55
+ if (entry.isFile() && entry.name.endsWith(".java")) {
56
+ result.push(next);
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ async function collectJavaFiles(baseDir) {
62
+ try {
63
+ const fastGlobModule = (await import("fast-glob"));
64
+ const sync = fastGlobModule.default?.sync;
65
+ if (typeof sync === "function") {
66
+ return sync("**/*.java", { cwd: baseDir, onlyFiles: true });
67
+ }
68
+ }
69
+ catch {
70
+ // optional dependency: fallback to recursive traversal
71
+ }
72
+ return collectJavaFilesSync(baseDir).map((candidate) => candidate.split(sep).join("/"));
73
+ }
74
+ function readFileTreeText(filePath) {
75
+ return new Promise((resolve, reject) => {
76
+ import("node:fs/promises")
77
+ .then((fs) => fs.readFile(filePath, "utf8"))
78
+ .then(resolve)
79
+ .catch(reject);
80
+ });
81
+ }
82
+ function decompileOutputDir(cacheDir, binaryJarPath, signature) {
83
+ const digest = createHash("sha256").update(binaryJarPath).update(signature).digest("hex");
84
+ return join(cacheDir, "decompiled", digest);
85
+ }
86
+ async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeoutMs) {
87
+ const result = await runJavaProcess({
88
+ jarPath: vineflowerJarPath,
89
+ args: [binaryJarPath, outputDir, "-din=1", "-rbr=1", "-dgs=1"],
90
+ timeoutMs,
91
+ normalizePathArgs: true
92
+ });
93
+ if (result.exitCode !== 0) {
94
+ throw createError({
95
+ code: ERROR_CODES.DECOMPILER_FAILED,
96
+ message: `Vineflower exited with code ${result.exitCode}.`,
97
+ details: {
98
+ binaryJarPath,
99
+ outputDir,
100
+ code: result.exitCode,
101
+ stdout: result.stdoutTail,
102
+ stderrTail: result.stderrTail
103
+ }
104
+ });
105
+ }
106
+ }
107
+ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
108
+ const normalizedBinaryJarPath = normalizeBinaryJarPath(binaryJarPath);
109
+ if (!options?.vineflowerJarPath) {
110
+ throw createError({
111
+ code: ERROR_CODES.DECOMPILER_UNAVAILABLE,
112
+ message: "Vineflower JAR path was not resolved. Set MCP_VINEFLOWER_JAR_PATH or ensure auto-download can reach GitHub."
113
+ });
114
+ }
115
+ const startedAt = Date.now();
116
+ emitDecompileLog("decompile.start", {
117
+ binaryJarPath: normalizedBinaryJarPath,
118
+ artifactIdCandidate: options.artifactIdCandidate
119
+ });
120
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
121
+ const signature = options.signature ?? basename(normalizedBinaryJarPath);
122
+ const outputDir = decompileOutputDir(cacheDir, normalizedBinaryJarPath, signature).replace(/[/\\]$/, "");
123
+ try {
124
+ mkdirSync(outputDir, { recursive: true });
125
+ await assertVineflowerAvailable(options.vineflowerJarPath);
126
+ await assertJavaAvailable();
127
+ if (statSync(outputDir, { throwIfNoEntry: false })) {
128
+ const existingJavaFiles = await collectJavaFiles(outputDir);
129
+ if (existingJavaFiles.length > 0) {
130
+ const results = await Promise.all(existingJavaFiles.map(async (candidate) => {
131
+ const abs = join(outputDir, candidate);
132
+ return {
133
+ filePath: normalizeOutputPath(outputDir, abs),
134
+ content: await readFileTreeText(abs)
135
+ };
136
+ }));
137
+ emitDecompileLog("decompile.done", {
138
+ durationMs: Date.now() - startedAt,
139
+ artifactIdCandidate: options.artifactIdCandidate,
140
+ javaFileCount: results.length
141
+ });
142
+ return {
143
+ outputDir,
144
+ javaFiles: results
145
+ };
146
+ }
147
+ }
148
+ await runVineflower(options.vineflowerJarPath, normalizedBinaryJarPath, outputDir, timeoutMs);
149
+ const javaFileNames = await collectJavaFiles(outputDir);
150
+ if (javaFileNames.length === 0) {
151
+ throw createError({
152
+ code: ERROR_CODES.DECOMPILER_FAILED,
153
+ message: "No Java files were produced by decompilation.",
154
+ details: { binaryJarPath: normalizedBinaryJarPath, outputDir }
155
+ });
156
+ }
157
+ const javaFiles = await Promise.all(javaFileNames.map(async (candidate) => {
158
+ const abs = join(outputDir, candidate);
159
+ return {
160
+ filePath: normalizeOutputPath(outputDir, abs),
161
+ content: await readFileTreeText(abs)
162
+ };
163
+ }));
164
+ emitDecompileLog("decompile.done", {
165
+ durationMs: Date.now() - startedAt,
166
+ artifactIdCandidate: options.artifactIdCandidate,
167
+ javaFileCount: javaFiles.length
168
+ });
169
+ return {
170
+ outputDir,
171
+ javaFiles
172
+ };
173
+ }
174
+ catch (error) {
175
+ emitDecompileLog("decompile.error", {
176
+ durationMs: Date.now() - startedAt,
177
+ binaryJarPath: normalizedBinaryJarPath,
178
+ artifactIdCandidate: options.artifactIdCandidate,
179
+ code: isAppError(error) ? error.code : ERROR_CODES.DECOMPILER_FAILED,
180
+ stderrTail: extractStderrTail(error)
181
+ });
182
+ throw error;
183
+ }
184
+ }
185
+ //# sourceMappingURL=vineflower.js.map
@@ -0,0 +1,50 @@
1
+ export declare const ERROR_CODES: {
2
+ readonly INVALID_INPUT: "ERR_INVALID_INPUT";
3
+ readonly SOURCE_NOT_FOUND: "ERR_SOURCE_NOT_FOUND";
4
+ readonly REPO_FETCH_FAILED: "ERR_REPO_FETCH_FAILED";
5
+ readonly COORDINATE_PARSE_FAILED: "ERR_COORDINATE_PARSE_FAILED";
6
+ readonly DB_FAILURE: "ERR_DB_FAILURE";
7
+ readonly DECOMPILER_UNAVAILABLE: "ERR_DECOMPILER_UNAVAILABLE";
8
+ readonly DECOMPILER_FAILED: "ERR_DECOMPILER_FAILED";
9
+ readonly FILE_NOT_FOUND: "ERR_FILE_NOT_FOUND";
10
+ readonly LIMIT_EXCEEDED: "ERR_LIMIT_EXCEEDED";
11
+ readonly INVALID_LINE_RANGE: "ERR_INVALID_LINE_RANGE";
12
+ readonly ARTIFACT_RESOLUTION_FAILED: "ERR_ARTIFACT_RESOLUTION_FAILED";
13
+ readonly MAPPING_NOT_APPLIED: "ERR_MAPPING_NOT_APPLIED";
14
+ readonly PROVENANCE_INCOMPLETE: "ERR_PROVENANCE_INCOMPLETE";
15
+ readonly CONTEXT_UNRESOLVED: "ERR_CONTEXT_UNRESOLVED";
16
+ readonly JAR_NOT_FOUND: "ERR_JAR_NOT_FOUND";
17
+ readonly VERSION_NOT_FOUND: "ERR_VERSION_NOT_FOUND";
18
+ readonly CLASS_NOT_FOUND: "ERR_CLASS_NOT_FOUND";
19
+ readonly NAMESPACE_MISMATCH: "ERR_NAMESPACE_MISMATCH";
20
+ readonly MAPPING_UNAVAILABLE: "ERR_MAPPING_UNAVAILABLE";
21
+ readonly DECOMPILE_DISABLED: "ERR_DECOMPILE_DISABLED";
22
+ readonly NBT_PARSE_FAILED: "ERR_NBT_PARSE_FAILED";
23
+ readonly NBT_INVALID_TYPED_JSON: "ERR_NBT_INVALID_TYPED_JSON";
24
+ readonly JSON_PATCH_INVALID: "ERR_JSON_PATCH_INVALID";
25
+ readonly JSON_PATCH_CONFLICT: "ERR_JSON_PATCH_CONFLICT";
26
+ readonly NBT_ENCODE_FAILED: "ERR_NBT_ENCODE_FAILED";
27
+ readonly NBT_UNSUPPORTED_FEATURE: "ERR_NBT_UNSUPPORTED_FEATURE";
28
+ readonly REGISTRY_GENERATION_FAILED: "ERR_REGISTRY_GENERATION_FAILED";
29
+ readonly JAVA_UNAVAILABLE: "ERR_JAVA_UNAVAILABLE";
30
+ readonly JAVA_PROCESS_FAILED: "ERR_JAVA_PROCESS_FAILED";
31
+ readonly REMAP_FAILED: "ERR_REMAP_FAILED";
32
+ readonly REMAPPER_UNAVAILABLE: "ERR_REMAPPER_UNAVAILABLE";
33
+ readonly INTERNAL: "ERR_INTERNAL";
34
+ };
35
+ export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
36
+ export interface ErrorDetails {
37
+ [key: string]: unknown;
38
+ }
39
+ export interface ErrorPayload {
40
+ code: ErrorCode;
41
+ message: string;
42
+ details?: ErrorDetails;
43
+ }
44
+ export declare class AppError extends Error {
45
+ readonly code: ErrorCode;
46
+ readonly details?: ErrorDetails;
47
+ constructor(payload: ErrorPayload);
48
+ }
49
+ export declare const createError: (payload: ErrorPayload) => AppError;
50
+ export declare const isAppError: (value: unknown) => value is AppError;
package/dist/errors.js ADDED
@@ -0,0 +1,49 @@
1
+ export const ERROR_CODES = {
2
+ INVALID_INPUT: "ERR_INVALID_INPUT",
3
+ SOURCE_NOT_FOUND: "ERR_SOURCE_NOT_FOUND",
4
+ REPO_FETCH_FAILED: "ERR_REPO_FETCH_FAILED",
5
+ COORDINATE_PARSE_FAILED: "ERR_COORDINATE_PARSE_FAILED",
6
+ DB_FAILURE: "ERR_DB_FAILURE",
7
+ DECOMPILER_UNAVAILABLE: "ERR_DECOMPILER_UNAVAILABLE",
8
+ DECOMPILER_FAILED: "ERR_DECOMPILER_FAILED",
9
+ FILE_NOT_FOUND: "ERR_FILE_NOT_FOUND",
10
+ LIMIT_EXCEEDED: "ERR_LIMIT_EXCEEDED",
11
+ INVALID_LINE_RANGE: "ERR_INVALID_LINE_RANGE",
12
+ ARTIFACT_RESOLUTION_FAILED: "ERR_ARTIFACT_RESOLUTION_FAILED",
13
+ MAPPING_NOT_APPLIED: "ERR_MAPPING_NOT_APPLIED",
14
+ PROVENANCE_INCOMPLETE: "ERR_PROVENANCE_INCOMPLETE",
15
+ CONTEXT_UNRESOLVED: "ERR_CONTEXT_UNRESOLVED",
16
+ JAR_NOT_FOUND: "ERR_JAR_NOT_FOUND",
17
+ VERSION_NOT_FOUND: "ERR_VERSION_NOT_FOUND",
18
+ CLASS_NOT_FOUND: "ERR_CLASS_NOT_FOUND",
19
+ NAMESPACE_MISMATCH: "ERR_NAMESPACE_MISMATCH",
20
+ MAPPING_UNAVAILABLE: "ERR_MAPPING_UNAVAILABLE",
21
+ DECOMPILE_DISABLED: "ERR_DECOMPILE_DISABLED",
22
+ NBT_PARSE_FAILED: "ERR_NBT_PARSE_FAILED",
23
+ NBT_INVALID_TYPED_JSON: "ERR_NBT_INVALID_TYPED_JSON",
24
+ JSON_PATCH_INVALID: "ERR_JSON_PATCH_INVALID",
25
+ JSON_PATCH_CONFLICT: "ERR_JSON_PATCH_CONFLICT",
26
+ NBT_ENCODE_FAILED: "ERR_NBT_ENCODE_FAILED",
27
+ NBT_UNSUPPORTED_FEATURE: "ERR_NBT_UNSUPPORTED_FEATURE",
28
+ REGISTRY_GENERATION_FAILED: "ERR_REGISTRY_GENERATION_FAILED",
29
+ JAVA_UNAVAILABLE: "ERR_JAVA_UNAVAILABLE",
30
+ JAVA_PROCESS_FAILED: "ERR_JAVA_PROCESS_FAILED",
31
+ REMAP_FAILED: "ERR_REMAP_FAILED",
32
+ REMAPPER_UNAVAILABLE: "ERR_REMAPPER_UNAVAILABLE",
33
+ INTERNAL: "ERR_INTERNAL"
34
+ };
35
+ export class AppError extends Error {
36
+ code;
37
+ details;
38
+ constructor(payload) {
39
+ super(payload.message);
40
+ this.name = "AppError";
41
+ this.code = payload.code;
42
+ this.details = payload.details;
43
+ }
44
+ }
45
+ export const createError = (payload) => new AppError(payload);
46
+ export const isAppError = (value) => {
47
+ return value instanceof Error && value.code !== undefined;
48
+ };
49
+ //# sourceMappingURL=errors.js.map
package/dist/hash.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function computeFileSha1(filePath: string): Promise<string>;
package/dist/hash.js ADDED
@@ -0,0 +1,12 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ export async function computeFileSha1(filePath) {
4
+ return new Promise((resolve, reject) => {
5
+ const hash = createHash("sha1");
6
+ const stream = createReadStream(filePath);
7
+ stream.on("data", (chunk) => hash.update(chunk));
8
+ stream.on("end", () => resolve(hash.digest("hex")));
9
+ stream.on("error", reject);
10
+ });
11
+ }
12
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1,7 @@
1
+ import { SourceService } from "./source-service.js";
2
+ declare const SERVER_VERSION: string;
3
+ declare const server: import("mcp-use/server").McpServerInstance<false>;
4
+ declare const config: import("./types.js").Config;
5
+ declare const sourceService: SourceService;
6
+ export declare function startServer(): void;
7
+ export { server, sourceService, config, SERVER_VERSION };