@adhisang/minecraft-modding-mcp 1.0.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.1] - 2026-03-02
9
+
10
+ ### Fixed
11
+ - `search-class-source` now safely supports recursive `fileGlob` patterns such as `**` without regex construction failures.
12
+ - `get-class-source` now rejects package-incompatible fallback matches and preserves canonical inner-class (`Outer.Inner`) lookup support.
13
+
14
+ ## [1.1.0] - 2026-03-01
15
+
16
+ ### Changed
17
+ - Migrate stdio transport from mcp-use to @modelcontextprotocol/sdk.
18
+
19
+ ### Fixed
20
+ - Restore Codex startup handshake compatibility by accepting both newline-delimited and `Content-Length` stdio framing.
21
+
22
+ ### Documentation
23
+ - Add Quick Start setup for Claude Code, OpenAI Codex CLI, and Gemini CLI.
24
+
8
25
  ## [1.0.0] - 2026-03-01
9
26
 
10
27
  ### Added
package/README.md CHANGED
@@ -39,6 +39,45 @@ It lets you explore decompiled Minecraft source, convert symbol names across fou
39
39
  npx @adhisang/minecraft-modding-mcp
40
40
  ```
41
41
 
42
+ ### CLI Agent Tools
43
+
44
+ #### Claude Code
45
+
46
+ ```bash
47
+ claude mcp add minecraft-modding -- npx -y @adhisang/minecraft-modding-mcp
48
+ claude mcp list
49
+ ```
50
+
51
+ #### OpenAI Codex CLI
52
+
53
+ ```bash
54
+ codex mcp add minecraft-modding -- npx -y @adhisang/minecraft-modding-mcp
55
+ codex mcp list
56
+ ```
57
+
58
+ The stdio transport auto-detects both newline-delimited and `Content-Length` framing, so Codex and newline-based MCP clients can use the same server command.
59
+
60
+ #### Gemini CLI
61
+
62
+ Add the following to `~/.gemini/settings.json`:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "minecraft-modding": {
68
+ "command": "npx",
69
+ "args": ["-y", "@adhisang/minecraft-modding-mcp"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ Then run this command in Gemini CLI:
76
+
77
+ ```text
78
+ /mcp list
79
+ ```
80
+
42
81
  ### For Developers (Repository)
43
82
  ```bash
44
83
  pnpm install
@@ -213,6 +252,8 @@ Tools for querying generated registry data and inspecting server runtime state.
213
252
  `get-class-members` requires either `artifactId` or `targetKind`+`targetValue`, and needs a binary jar (`binaryJarPath`) to read `.class` entries.
214
253
  `search-class-source` uses `limit: 20` by default; `snippetLines` defaults to `8` and is clamped to `1..80`; `includeDefinition` and `includeOneHop` default to `false`.
215
254
  `search-class-source` with `match=regex` enforces `query.length <= 200` and a strict result cap of `100`.
255
+ `search-class-source` `fileGlob` supports `*`, `**`, and `?`; recursive patterns such as `net/minecraft/**/*.java` are supported.
256
+ `get-class-source` fallback matching enforces package compatibility and returns `ERR_CLASS_NOT_FOUND` when only name-colliding classes from other packages exist.
216
257
  `resolve-artifact` with `targetKind=jar` only auto-adopts the exact sibling `"<jar-basename>-sources.jar"`. Other adjacent `*-sources.jar` files are returned as `adjacentSourceCandidates` info only and are never auto-selected.
217
258
  Mod tool `jarPath` inputs are normalized to a canonical local `.jar` file path before existence checks, cache keying, and processing.
218
259
  `search-mod-source` enforces `query.length <= 200` and `limit <= 200`.
@@ -745,7 +786,7 @@ Use `resolve-workspace-symbol` when you need compile-visible names from actual G
745
786
  | Component | Technology |
746
787
  | --- | --- |
747
788
  | Runtime | Node.js 22+ (native `node:sqlite`) |
748
- | Transport | stdio (MCP standard) |
789
+ | Transport | stdio (MCP standard, auto-detects newline + `Content-Length` framing) |
749
790
  | Storage | SQLite — artifact metadata, source index, mapping cache |
750
791
  | Decompilation | [Vineflower](https://github.com/Vineflower/vineflower) (auto-downloaded) |
751
792
  | Remapping | [tiny-remapper](https://github.com/FabricMC/tiny-remapper) (requires Java) |
package/dist/cli.js CHANGED
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { startServer } from "./index.js";
3
- startServer();
3
+ startServer()
4
+ .then(() => undefined)
5
+ .catch((err) => {
6
+ console.error("Fatal: server failed to start", err);
7
+ process.exit(1);
8
+ });
4
9
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,27 @@
1
+ import { type JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
2
+ type StdioReadable = NodeJS.ReadStream;
3
+ type StdioWritable = NodeJS.WriteStream;
4
+ export declare class CompatStdioServerTransport {
5
+ private readonly stdin;
6
+ private readonly stdout;
7
+ private started;
8
+ private closed;
9
+ private mode;
10
+ private buffer;
11
+ onclose?: () => void;
12
+ onerror?: (error: Error) => void;
13
+ onmessage?: (message: JSONRPCMessage) => void;
14
+ constructor(stdin?: StdioReadable, stdout?: StdioWritable);
15
+ start(): Promise<void>;
16
+ send(message: JSONRPCMessage): Promise<void>;
17
+ close(): Promise<void>;
18
+ private readonly handleData;
19
+ private readonly handleStreamError;
20
+ private readonly handleStreamClosed;
21
+ private emitCloseOnce;
22
+ private processReadBuffer;
23
+ private detectMode;
24
+ private readLineDelimitedMessage;
25
+ private readContentLengthMessage;
26
+ }
27
+ export {};
@@ -0,0 +1,217 @@
1
+ import process from "node:process";
2
+ import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
3
+ function findHeaderBoundary(buffer) {
4
+ const crlfBoundary = buffer.indexOf("\r\n\r\n");
5
+ if (crlfBoundary !== -1) {
6
+ return { index: crlfBoundary, delimiterBytes: 4 };
7
+ }
8
+ const lfBoundary = buffer.indexOf("\n\n");
9
+ if (lfBoundary !== -1) {
10
+ return { index: lfBoundary, delimiterBytes: 2 };
11
+ }
12
+ return undefined;
13
+ }
14
+ function parseJsonRpcMessage(json) {
15
+ return JSONRPCMessageSchema.parse(JSON.parse(json));
16
+ }
17
+ function asError(value) {
18
+ return value instanceof Error ? value : new Error(String(value));
19
+ }
20
+ export class CompatStdioServerTransport {
21
+ stdin;
22
+ stdout;
23
+ started = false;
24
+ closed = false;
25
+ mode = "unknown";
26
+ buffer = Buffer.alloc(0);
27
+ onclose;
28
+ onerror;
29
+ onmessage;
30
+ constructor(stdin = process.stdin, stdout = process.stdout) {
31
+ this.stdin = stdin;
32
+ this.stdout = stdout;
33
+ }
34
+ async start() {
35
+ if (this.started) {
36
+ throw new Error("CompatStdioServerTransport already started. connect() should be called only once.");
37
+ }
38
+ this.started = true;
39
+ this.stdin.on("data", this.handleData);
40
+ this.stdin.on("error", this.handleStreamError);
41
+ this.stdin.on("end", this.handleStreamClosed);
42
+ this.stdin.on("close", this.handleStreamClosed);
43
+ this.stdin.resume();
44
+ }
45
+ async send(message) {
46
+ const json = JSON.stringify(message);
47
+ const frame = this.mode === "content-length"
48
+ ? `Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`
49
+ : `${json}\n`;
50
+ await new Promise((resolve) => {
51
+ if (this.stdout.write(frame)) {
52
+ resolve();
53
+ return;
54
+ }
55
+ this.stdout.once("drain", resolve);
56
+ });
57
+ }
58
+ async close() {
59
+ this.stdin.off("data", this.handleData);
60
+ this.stdin.off("error", this.handleStreamError);
61
+ this.stdin.off("end", this.handleStreamClosed);
62
+ this.stdin.off("close", this.handleStreamClosed);
63
+ if (this.stdin.listenerCount("data") === 0) {
64
+ this.stdin.pause();
65
+ }
66
+ this.buffer = Buffer.alloc(0);
67
+ this.emitCloseOnce();
68
+ }
69
+ handleData = (chunk) => {
70
+ if (chunk.length === 0) {
71
+ return;
72
+ }
73
+ this.buffer = Buffer.concat([this.buffer, chunk]);
74
+ this.processReadBuffer();
75
+ };
76
+ handleStreamError = (error) => {
77
+ this.onerror?.(error);
78
+ };
79
+ handleStreamClosed = () => {
80
+ this.emitCloseOnce();
81
+ };
82
+ emitCloseOnce() {
83
+ if (this.closed) {
84
+ return;
85
+ }
86
+ this.closed = true;
87
+ this.onclose?.();
88
+ }
89
+ processReadBuffer() {
90
+ while (true) {
91
+ try {
92
+ if (this.mode === "unknown") {
93
+ const detected = this.detectMode();
94
+ if (!detected) {
95
+ return;
96
+ }
97
+ this.mode = detected;
98
+ continue;
99
+ }
100
+ const modeBefore = this.mode;
101
+ const message = this.mode === "content-length"
102
+ ? this.readContentLengthMessage()
103
+ : this.readLineDelimitedMessage();
104
+ if (!message) {
105
+ // readLineDelimitedMessage may switch mode to "content-length"
106
+ // mid-stream; retry with the new parser instead of stopping.
107
+ if (this.mode !== modeBefore) {
108
+ continue;
109
+ }
110
+ return;
111
+ }
112
+ this.onmessage?.(message);
113
+ }
114
+ catch (caughtError) {
115
+ this.onerror?.(asError(caughtError));
116
+ this.mode = "unknown";
117
+ }
118
+ }
119
+ }
120
+ detectMode() {
121
+ // Skip blank leading lines that some clients may emit.
122
+ while (this.buffer.length > 0) {
123
+ if (this.buffer[0] === 0x0a) {
124
+ this.buffer = this.buffer.subarray(1);
125
+ continue;
126
+ }
127
+ if (this.buffer.length >= 2 && this.buffer[0] === 0x0d && this.buffer[1] === 0x0a) {
128
+ this.buffer = this.buffer.subarray(2);
129
+ continue;
130
+ }
131
+ break;
132
+ }
133
+ if (this.buffer.length === 0) {
134
+ return undefined;
135
+ }
136
+ const prefix = this.buffer
137
+ .subarray(0, Math.min(this.buffer.length, 32))
138
+ .toString("utf8")
139
+ .toLowerCase();
140
+ if (prefix.startsWith("content-length")) {
141
+ return "content-length";
142
+ }
143
+ const firstNewline = this.buffer.indexOf(0x0a);
144
+ if (firstNewline === -1) {
145
+ return undefined;
146
+ }
147
+ const firstLine = this.buffer.subarray(0, firstNewline).toString("utf8").replace(/\r$/, "");
148
+ if (/^\s*content-length\s*:/i.test(firstLine)) {
149
+ return "content-length";
150
+ }
151
+ return "line";
152
+ }
153
+ readLineDelimitedMessage() {
154
+ while (true) {
155
+ const newlineIndex = this.buffer.indexOf(0x0a);
156
+ if (newlineIndex === -1) {
157
+ return undefined;
158
+ }
159
+ const line = this.buffer.subarray(0, newlineIndex).toString("utf8").replace(/\r$/, "");
160
+ this.buffer = this.buffer.subarray(newlineIndex + 1);
161
+ if (line.trim().length === 0) {
162
+ continue;
163
+ }
164
+ if (/^\s*content-length\s*:/i.test(line)) {
165
+ // Reconstruct the header with the correct line ending so that
166
+ // findHeaderBoundary can locate \r\n\r\n or \n\n reliably.
167
+ const sep = this.buffer.length > 0 && this.buffer[0] === 0x0d ? "\r\n" : "\n";
168
+ this.buffer = Buffer.concat([Buffer.from(`${line}${sep}`, "utf8"), this.buffer]);
169
+ this.mode = "content-length";
170
+ return undefined;
171
+ }
172
+ return parseJsonRpcMessage(line);
173
+ }
174
+ }
175
+ readContentLengthMessage() {
176
+ const headerBoundary = findHeaderBoundary(this.buffer);
177
+ if (!headerBoundary) {
178
+ return undefined;
179
+ }
180
+ const headersRaw = this.buffer.subarray(0, headerBoundary.index).toString("utf8");
181
+ const headerLines = headersRaw
182
+ .split(/\r?\n/)
183
+ .map((line) => line.trim())
184
+ .filter((line) => line.length > 0);
185
+ let contentLength;
186
+ for (const headerLine of headerLines) {
187
+ const separatorIndex = headerLine.indexOf(":");
188
+ if (separatorIndex === -1) {
189
+ this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
190
+ throw new Error(`Malformed header line: ${headerLine}`);
191
+ }
192
+ const headerName = headerLine.slice(0, separatorIndex).trim().toLowerCase();
193
+ const headerValue = headerLine.slice(separatorIndex + 1).trim();
194
+ if (headerName === "content-length") {
195
+ const parsed = Number.parseInt(headerValue, 10);
196
+ if (!Number.isFinite(parsed) || parsed < 0) {
197
+ this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
198
+ throw new Error(`Invalid Content-Length header value: ${headerValue}`);
199
+ }
200
+ contentLength = parsed;
201
+ }
202
+ }
203
+ if (contentLength === undefined) {
204
+ this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
205
+ throw new Error("Missing Content-Length header.");
206
+ }
207
+ const messageStart = headerBoundary.index + headerBoundary.delimiterBytes;
208
+ const frameEnd = messageStart + contentLength;
209
+ if (this.buffer.length < frameEnd) {
210
+ return undefined;
211
+ }
212
+ const body = this.buffer.subarray(messageStart, frameEnd).toString("utf8");
213
+ this.buffer = this.buffer.subarray(frameEnd);
214
+ return parseJsonRpcMessage(body);
215
+ }
216
+ }
217
+ //# sourceMappingURL=compat-stdio-transport.js.map
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
2
  import { SourceService } from "./source-service.js";
2
3
  declare const SERVER_VERSION: string;
3
- declare const server: import("mcp-use/server").McpServerInstance<false>;
4
+ declare const server: McpServer;
4
5
  declare const config: import("./types.js").Config;
5
6
  declare const sourceService: SourceService;
6
- export declare function startServer(): void;
7
+ export declare function startServer(): Promise<void>;
7
8
  export { server, sourceService, config, SERVER_VERSION };