@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.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 (174) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +36 -23
  3. package/dist/build-suggested-call.d.ts +29 -0
  4. package/dist/build-suggested-call.js +58 -0
  5. package/dist/cache-registry.d.ts +3 -1
  6. package/dist/cache-registry.js +50 -6
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
  8. package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
  9. package/dist/entry-tools/batch-class-members-service.js +97 -0
  10. package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
  11. package/dist/entry-tools/batch-class-source-service.js +100 -0
  12. package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
  13. package/dist/entry-tools/batch-mappings-service.js +66 -0
  14. package/dist/entry-tools/batch-runner.d.ts +72 -0
  15. package/dist/entry-tools/batch-runner.js +90 -0
  16. package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
  17. package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
  18. package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
  19. package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
  20. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
  21. package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
  22. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
  23. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
  27. package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
  28. package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
  29. package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
  31. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
  32. package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
  33. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
  34. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
  35. package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
  36. package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
  37. package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
  38. package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
  39. package/dist/entry-tools/manage-cache-service.d.ts +16 -16
  40. package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
  41. package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
  42. package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
  43. package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
  44. package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
  45. package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
  46. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +97 -0
  47. package/dist/entry-tools/validate-project/cases/project-summary.js +346 -0
  48. package/dist/entry-tools/validate-project/internal.d.ts +135 -0
  49. package/dist/entry-tools/validate-project/internal.js +287 -0
  50. package/dist/entry-tools/validate-project-service.d.ts +63 -47
  51. package/dist/entry-tools/validate-project-service.js +12 -562
  52. package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
  53. package/dist/entry-tools/verify-mixin-target-service.js +323 -0
  54. package/dist/error-mapping.d.ts +40 -0
  55. package/dist/error-mapping.js +139 -0
  56. package/dist/errors.d.ts +6 -0
  57. package/dist/errors.js +6 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +142 -1352
  60. package/dist/mapping/internal-types.d.ts +54 -0
  61. package/dist/mapping/internal-types.js +14 -0
  62. package/dist/mapping/loaders/mojang.d.ts +2 -0
  63. package/dist/mapping/loaders/mojang.js +64 -0
  64. package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
  65. package/dist/mapping/loaders/tiny-loom.js +73 -0
  66. package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
  67. package/dist/mapping/loaders/tiny-maven.js +104 -0
  68. package/dist/mapping/loaders/types.d.ts +14 -0
  69. package/dist/mapping/loaders/types.js +2 -0
  70. package/dist/mapping/lookup.d.ts +52 -0
  71. package/dist/mapping/lookup.js +496 -0
  72. package/dist/mapping/parsers/normalize.d.ts +10 -0
  73. package/dist/mapping/parsers/normalize.js +52 -0
  74. package/dist/mapping/parsers/proguard.d.ts +20 -0
  75. package/dist/mapping/parsers/proguard.js +138 -0
  76. package/dist/mapping/parsers/symbol-records.d.ts +27 -0
  77. package/dist/mapping/parsers/symbol-records.js +216 -0
  78. package/dist/mapping/parsers/tiny.d.ts +9 -0
  79. package/dist/mapping/parsers/tiny.js +96 -0
  80. package/dist/mapping/types.d.ts +147 -0
  81. package/dist/mapping/types.js +2 -0
  82. package/dist/mapping-pipeline-service.js +3 -2
  83. package/dist/mapping-service.d.ts +3 -144
  84. package/dist/mapping-service.js +19 -1201
  85. package/dist/mixin/access-validators.d.ts +9 -0
  86. package/dist/mixin/access-validators.js +257 -0
  87. package/dist/mixin/annotation-validators.d.ts +5 -0
  88. package/dist/mixin/annotation-validators.js +162 -0
  89. package/dist/mixin/helpers.d.ts +28 -0
  90. package/dist/mixin/helpers.js +315 -0
  91. package/dist/mixin/parsed-validator.d.ts +8 -0
  92. package/dist/mixin/parsed-validator.js +337 -0
  93. package/dist/mixin/types.d.ts +208 -0
  94. package/dist/mixin/types.js +28 -0
  95. package/dist/mixin-validator.d.ts +9 -201
  96. package/dist/mixin-validator.js +8 -1020
  97. package/dist/source/access-validate.d.ts +4 -0
  98. package/dist/source/access-validate.js +254 -0
  99. package/dist/source/artifact-resolver.d.ts +110 -0
  100. package/dist/source/artifact-resolver.js +1174 -0
  101. package/dist/source/cache-metrics.d.ts +26 -0
  102. package/dist/source/cache-metrics.js +172 -0
  103. package/dist/source/class-source/members-builder.d.ts +34 -0
  104. package/dist/source/class-source/members-builder.js +46 -0
  105. package/dist/source/class-source/snippet-builder.d.ts +19 -0
  106. package/dist/source/class-source/snippet-builder.js +46 -0
  107. package/dist/source/class-source-helpers.d.ts +34 -0
  108. package/dist/source/class-source-helpers.js +140 -0
  109. package/dist/source/class-source.d.ts +42 -0
  110. package/dist/source/class-source.js +883 -0
  111. package/dist/source/descriptor-utils.d.ts +6 -0
  112. package/dist/source/descriptor-utils.js +37 -0
  113. package/dist/source/file-access.d.ts +4 -0
  114. package/dist/source/file-access.js +102 -0
  115. package/dist/source/indexer.d.ts +82 -0
  116. package/dist/source/indexer.js +505 -0
  117. package/dist/source/lifecycle/diff-utils.d.ts +9 -0
  118. package/dist/source/lifecycle/diff-utils.js +107 -0
  119. package/dist/source/lifecycle/diff.d.ts +2 -0
  120. package/dist/source/lifecycle/diff.js +265 -0
  121. package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
  122. package/dist/source/lifecycle/mapping-helpers.js +327 -0
  123. package/dist/source/lifecycle/runtime-check.d.ts +2 -0
  124. package/dist/source/lifecycle/runtime-check.js +142 -0
  125. package/dist/source/lifecycle/trace.d.ts +2 -0
  126. package/dist/source/lifecycle/trace.js +231 -0
  127. package/dist/source/lifecycle.d.ts +4 -0
  128. package/dist/source/lifecycle.js +5 -0
  129. package/dist/source/search.d.ts +51 -0
  130. package/dist/source/search.js +676 -0
  131. package/dist/source/shared-utils.d.ts +6 -0
  132. package/dist/source/shared-utils.js +55 -0
  133. package/dist/source/state.d.ts +21 -0
  134. package/dist/source/state.js +19 -0
  135. package/dist/source/symbol-resolver.d.ts +3 -0
  136. package/dist/source/symbol-resolver.js +212 -0
  137. package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
  138. package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
  139. package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
  140. package/dist/source/validate-mixin/pipeline/parse.js +10 -0
  141. package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
  142. package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
  143. package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
  144. package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
  145. package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
  146. package/dist/source/validate-mixin/pipeline-context.js +93 -0
  147. package/dist/source/validate-mixin.d.ts +22 -0
  148. package/dist/source/validate-mixin.js +799 -0
  149. package/dist/source/workspace-target.d.ts +18 -0
  150. package/dist/source/workspace-target.js +305 -0
  151. package/dist/source-service.d.ts +147 -170
  152. package/dist/source-service.js +67 -6116
  153. package/dist/stage-emitter.d.ts +13 -0
  154. package/dist/stage-emitter.js +30 -0
  155. package/dist/stdio-supervisor.d.ts +61 -0
  156. package/dist/stdio-supervisor.js +326 -9
  157. package/dist/tool-contract-manifest.d.ts +1 -1
  158. package/dist/tool-contract-manifest.js +23 -6
  159. package/dist/tool-guidance.d.ts +82 -0
  160. package/dist/tool-guidance.js +734 -0
  161. package/dist/tool-schema-registry.d.ts +16 -0
  162. package/dist/tool-schema-registry.js +37 -0
  163. package/dist/tool-schemas.d.ts +3518 -0
  164. package/dist/tool-schemas.js +813 -0
  165. package/dist/types.d.ts +36 -0
  166. package/dist/version-service.js +7 -6
  167. package/dist/workspace-context-cache.d.ts +32 -0
  168. package/dist/workspace-context-cache.js +66 -0
  169. package/dist/workspace-mapping-service.d.ts +16 -0
  170. package/dist/workspace-mapping-service.js +173 -1
  171. package/docs/README-ja.md +414 -0
  172. package/docs/examples.md +483 -0
  173. package/docs/tool-reference.md +459 -0
  174. package/package.json +3 -2
@@ -0,0 +1,13 @@
1
+ export type StageEmitter = (stage: string, meta?: Record<string, unknown>) => Promise<void>;
2
+ export declare const NOOP_STAGE_EMITTER: StageEmitter;
3
+ export type StageEmitterExtra = {
4
+ requestId?: string | number;
5
+ sendNotification?: (notification: {
6
+ method: string;
7
+ params?: unknown;
8
+ }) => Promise<void>;
9
+ };
10
+ export type StageEmitterOptions = {
11
+ disabled?: boolean;
12
+ };
13
+ export declare function makeStageEmitter(extra: StageEmitterExtra | undefined, options?: StageEmitterOptions): StageEmitter;
@@ -0,0 +1,30 @@
1
+ import { performance } from "node:perf_hooks";
2
+ const STAGE_PROGRESS_DISABLED = process.env.MIXIN_STAGE_PROGRESS_OFF === "1";
3
+ export const NOOP_STAGE_EMITTER = async () => {
4
+ /* noop */
5
+ };
6
+ export function makeStageEmitter(extra, options = {}) {
7
+ if (options.disabled ?? STAGE_PROGRESS_DISABLED) {
8
+ return NOOP_STAGE_EMITTER;
9
+ }
10
+ if (!extra) {
11
+ return NOOP_STAGE_EMITTER;
12
+ }
13
+ const requestId = extra.requestId;
14
+ const sendNotification = extra.sendNotification;
15
+ if (requestId === undefined || typeof sendNotification !== "function") {
16
+ return NOOP_STAGE_EMITTER;
17
+ }
18
+ return async (stage, meta) => {
19
+ await sendNotification({
20
+ method: "$/stageUpdate",
21
+ params: {
22
+ stage,
23
+ meta: meta ?? null,
24
+ t: performance.now(),
25
+ requestId
26
+ }
27
+ });
28
+ };
29
+ }
30
+ //# sourceMappingURL=stage-emitter.js.map
@@ -1,12 +1,72 @@
1
+ import type { JSONRPCResponse } from "@modelcontextprotocol/sdk/types.js";
1
2
  type SupervisorOptions = {
2
3
  entryFile: string;
3
4
  };
5
+ type RequestId = string | number;
6
+ export type ExitInfo = {
7
+ code: number | null;
8
+ signal: NodeJS.Signals | null;
9
+ };
10
+ export type RetryRecommendation = "narrow-query" | "clear-cache" | "report-bug" | "same-request";
11
+ export type RestartContext = {
12
+ toolName: string | undefined;
13
+ durationMs: number;
14
+ lastStage: string | undefined;
15
+ lastStageElapsedMs: number | undefined;
16
+ lastStageMeta: unknown;
17
+ exit: ExitInfo;
18
+ toolArgsRedacted: Record<string, unknown> | undefined;
19
+ /** `true` when redaction replaced any value with a sentinel placeholder; the synthetic envelope omits `suggestedCall` in that case. */
20
+ toolArgsRedactedModified: boolean;
21
+ retryRecommendation: RetryRecommendation;
22
+ };
23
+ export type PendingRequestSnapshot = {
24
+ id: RequestId;
25
+ method?: string;
26
+ toolName?: string;
27
+ toolArgsRedacted?: Record<string, unknown>;
28
+ /** `true` when toolArgsRedacted contains sentinel placeholders (truncate/redact/overflow). */
29
+ toolArgsRedactedModified?: boolean;
30
+ startedAt: number;
31
+ lastStage?: string;
32
+ lastStageStartedAt?: number;
33
+ lastStageMeta?: unknown;
34
+ };
35
+ export declare function buildLegacyJsonRpcError(id: RequestId): JSONRPCResponse;
36
+ export type RedactedToolArgs = {
37
+ args: Record<string, unknown>;
38
+ /** `true` when redaction replaced any value with a sentinel; supervisor uses this to decide whether to surface `suggestedCall`. */
39
+ modified: boolean;
40
+ };
41
+ export declare function redactToolArgs(args: unknown): RedactedToolArgs;
42
+ export declare function decideRetryRecommendation(ctx: Pick<RestartContext, "toolName" | "lastStage" | "lastStageMeta" | "exit">, recentRestartTimestamps: number[]): RetryRecommendation;
43
+ export declare function buildSyntheticCallToolResult(id: RequestId, ctx: RestartContext): JSONRPCResponse;
44
+ export declare function pruneRestartTimestamps(timestamps: number[], now: number, windowMs?: number): number[];
45
+ /**
46
+ * Group a worker-exit batch by toolName and produce the per-tool pruned
47
+ * timestamp set plus the post-exit snapshot. Grouping ensures one exit
48
+ * counts as one death per tool regardless of how many concurrent pending
49
+ * requests it killed. `prunedByTool` feeds `buildWorkerRestartReply`;
50
+ * `updatedByTool` is what the caller writes back to `recentRestarts`.
51
+ */
52
+ export declare function buildExitTimestampGroups(pendingToolNames: Iterable<string | undefined>, recentRestarts: Map<string, number[]>, now: number): {
53
+ prunedByTool: Map<string, number[]>;
54
+ updatedByTool: Map<string, number[]>;
55
+ };
56
+ export type BuildWorkerRestartReplyOptions = {
57
+ structuredRestartDisabled?: boolean;
58
+ };
59
+ export declare function buildWorkerRestartReply(req: PendingRequestSnapshot, exit: ExitInfo, now: number, recentRestartTimestamps: number[], options?: BuildWorkerRestartReplyOptions): {
60
+ reply: JSONRPCResponse;
61
+ updatedTimestamps: number[];
62
+ };
4
63
  export declare class StdioSupervisor {
5
64
  private readonly entryFile;
6
65
  private readonly clientReader;
7
66
  private readonly workerReader;
8
67
  private readonly queuedMessages;
9
68
  private readonly pendingRequests;
69
+ private readonly recentRestarts;
10
70
  private child;
11
71
  private childReady;
12
72
  private shuttingDown;
@@ -33,6 +93,7 @@ export declare class StdioSupervisor {
33
93
  private readonly handleWorkerProcessError;
34
94
  private readonly handleWorkerExit;
35
95
  private handleWorkerMessage;
96
+ private applyStageUpdate;
36
97
  private handleWorkerReady;
37
98
  private isInitializationResponse;
38
99
  private flushQueue;
@@ -2,10 +2,20 @@ import { spawn } from "node:child_process";
2
2
  import process from "node:process";
3
3
  import { encodeJsonRpcMessage, JsonRpcFrameReader } from "./json-rpc-framing.js";
4
4
  import { log } from "./logger.js";
5
+ import { buildSuggestedCall } from "./build-suggested-call.js";
6
+ import { getToolSchema } from "./tool-schema-registry.js";
5
7
  const DEFAULT_CLIENT_MODE = "line";
6
8
  const WORKER_MODE_ENV = "MCP_STDIO_WORKER_MODE";
7
9
  const WORKER_READY_MARKER = "__MCP_STDIO_WORKER_READY__";
8
10
  const SUPERVISOR_DEBUG_ENABLED = process.env.MCP_SUPERVISOR_DEBUG === "1";
11
+ const STRUCTURED_RESTART_DISABLED = process.env.SUPERVISOR_STRUCTURED_RESTART_OFF === "1";
12
+ const RESTART_WINDOW_MS = 60_000;
13
+ const RESTART_REPEAT_THRESHOLD = 3;
14
+ const REDACT_KEY_PATTERNS = [/secret/i, /token/i, /apikey/i, /password/i];
15
+ const PRESERVED_PATH_KEYS = new Set(["projectPath", "sourcePath", "mixinConfigPath"]);
16
+ const MAX_STRING_BYTES = 256;
17
+ const MAX_ARRAY_LENGTH = 8;
18
+ const MAX_OBJECT_KEYS = 16;
9
19
  function isRequest(message) {
10
20
  return "method" in message && "id" in message;
11
21
  }
@@ -23,7 +33,7 @@ function getTrackedRequestId(message) {
23
33
  function requestKey(id) {
24
34
  return `${typeof id}:${String(id)}`;
25
35
  }
26
- function buildWorkerRestartError(id) {
36
+ export function buildLegacyJsonRpcError(id) {
27
37
  return {
28
38
  jsonrpc: "2.0",
29
39
  id,
@@ -33,6 +43,252 @@ function buildWorkerRestartError(id) {
33
43
  }
34
44
  };
35
45
  }
46
+ function byteLengthUtf8(value) {
47
+ return Buffer.byteLength(value, "utf8");
48
+ }
49
+ function isRedactKey(key) {
50
+ return REDACT_KEY_PATTERNS.some((re) => re.test(key));
51
+ }
52
+ function redactValue(value, counter, keyName) {
53
+ if (keyName !== undefined && !PRESERVED_PATH_KEYS.has(keyName) && isRedactKey(keyName)) {
54
+ counter.modified = true;
55
+ return "<redacted>";
56
+ }
57
+ if (value === null || value === undefined) {
58
+ return value;
59
+ }
60
+ const valueType = typeof value;
61
+ if (valueType === "string") {
62
+ const str = value;
63
+ if (keyName !== undefined && PRESERVED_PATH_KEYS.has(keyName)) {
64
+ return str;
65
+ }
66
+ const byteLen = byteLengthUtf8(str);
67
+ if (byteLen > MAX_STRING_BYTES) {
68
+ counter.modified = true;
69
+ return `<truncated:${byteLen} bytes>`;
70
+ }
71
+ return str;
72
+ }
73
+ if (valueType === "number" || valueType === "boolean") {
74
+ return value;
75
+ }
76
+ if (Array.isArray(value)) {
77
+ if (value.length > MAX_ARRAY_LENGTH) {
78
+ counter.modified = true;
79
+ const head = value
80
+ .slice(0, MAX_ARRAY_LENGTH)
81
+ .map((item) => redactValue(item, counter));
82
+ head.push(`<+${value.length - MAX_ARRAY_LENGTH} more>`);
83
+ return head;
84
+ }
85
+ return value.map((item) => redactValue(item, counter));
86
+ }
87
+ if (valueType === "object") {
88
+ const entries = Object.entries(value);
89
+ const limited = entries.slice(0, MAX_OBJECT_KEYS);
90
+ const result = {};
91
+ for (const [key, child] of limited) {
92
+ result[key] = redactValue(child, counter, key);
93
+ }
94
+ if (entries.length > MAX_OBJECT_KEYS) {
95
+ counter.modified = true;
96
+ result["<+more>"] = `<+${entries.length - MAX_OBJECT_KEYS} more keys>`;
97
+ }
98
+ return result;
99
+ }
100
+ return undefined;
101
+ }
102
+ export function redactToolArgs(args) {
103
+ if (args === null || args === undefined || typeof args !== "object" || Array.isArray(args)) {
104
+ return { args: {}, modified: false };
105
+ }
106
+ const counter = { modified: false };
107
+ const redacted = redactValue(args, counter);
108
+ if (redacted && typeof redacted === "object" && !Array.isArray(redacted)) {
109
+ return { args: redacted, modified: counter.modified };
110
+ }
111
+ return { args: {}, modified: counter.modified };
112
+ }
113
+ export function decideRetryRecommendation(ctx, recentRestartTimestamps) {
114
+ if (ctx.lastStage === "target-lookup") {
115
+ const meta = ctx.lastStageMeta;
116
+ const targetTotal = typeof meta?.targetTotal === "number" ? meta.targetTotal : undefined;
117
+ if (targetTotal !== undefined && targetTotal > 5) {
118
+ return "narrow-query";
119
+ }
120
+ }
121
+ if (ctx.exit.signal === "SIGABRT" || ctx.exit.signal === "SIGSEGV" || ctx.exit.signal === "SIGKILL") {
122
+ return "clear-cache";
123
+ }
124
+ if (recentRestartTimestamps.length >= RESTART_REPEAT_THRESHOLD) {
125
+ return "report-bug";
126
+ }
127
+ return "same-request";
128
+ }
129
+ export function buildSyntheticCallToolResult(id, ctx) {
130
+ const tool = ctx.toolName ?? "unknown";
131
+ const hints = [];
132
+ if (ctx.lastStage) {
133
+ const indexNote = (() => {
134
+ const meta = ctx.lastStageMeta;
135
+ if (meta &&
136
+ typeof meta.targetIndex === "number" &&
137
+ typeof meta.targetTotal === "number") {
138
+ return `at index ${meta.targetIndex}/${meta.targetTotal}`;
139
+ }
140
+ return "";
141
+ })();
142
+ const recommendation = (() => {
143
+ switch (ctx.retryRecommendation) {
144
+ case "narrow-query":
145
+ return "narrow mixinConfigPath to one file and retry";
146
+ case "clear-cache":
147
+ return "clear cache and retry";
148
+ case "report-bug":
149
+ return "report a bug — repeated worker exits";
150
+ case "same-request":
151
+ default:
152
+ return "retry the same request";
153
+ }
154
+ })();
155
+ hints.push(`lastStage=${ctx.lastStage}${indexNote ? ` ${indexNote}` : ""} — ${recommendation}`);
156
+ }
157
+ else {
158
+ hints.push("worker exited before stage tracking began");
159
+ }
160
+ const error = {
161
+ type: "about:blank/mcp/worker-restart",
162
+ title: "MCP worker restarted",
163
+ detail: `The worker process exited while handling tools/call (${tool}).`,
164
+ status: 503,
165
+ code: "ERR_WORKER_RESTART",
166
+ instance: `urn:mcp:request:${String(id)}`,
167
+ hints
168
+ };
169
+ // Mutated args carry sentinel placeholders (`<truncated:…>`, `<redacted>`)
170
+ // that are not safe to retry. Surface them on `meta.restart.redactedToolArgs`
171
+ // for diagnostics instead.
172
+ if (ctx.toolArgsRedacted !== undefined && !ctx.toolArgsRedactedModified) {
173
+ // Only emit `suggestedCall` when the resolved tool name is a
174
+ // currently-registered public tool. The "unknown" fallback above and
175
+ // unregistered names (typo, disabled tool, version-skewed) cannot
176
+ // produce a re-callable payload; buildSuggestedCall fails open for
177
+ // unregistered names so the registration check belongs here. The
178
+ // diagnostic `restart.redactedToolArgs` field below still surfaces the
179
+ // args.
180
+ if (ctx.toolName !== undefined &&
181
+ ctx.toolName.length > 0 &&
182
+ getToolSchema(ctx.toolName) !== undefined) {
183
+ const gated = buildSuggestedCall({
184
+ tool: ctx.toolName,
185
+ params: ctx.toolArgsRedacted
186
+ });
187
+ if (gated.suggestedCall) {
188
+ error.suggestedCall = gated.suggestedCall;
189
+ }
190
+ }
191
+ }
192
+ if (ctx.lastStage !== undefined) {
193
+ error.failedStage = ctx.lastStage;
194
+ }
195
+ const restart = {
196
+ tool,
197
+ durationMs: ctx.durationMs,
198
+ lastStage: ctx.lastStage ?? null,
199
+ lastStageElapsedMs: ctx.lastStageElapsedMs ?? null,
200
+ lastStageMeta: ctx.lastStageMeta ?? null,
201
+ exit: {
202
+ code: ctx.exit.code,
203
+ signal: ctx.exit.signal
204
+ },
205
+ retryRecommendation: ctx.retryRecommendation
206
+ };
207
+ // Diagnostic echo (paired with the modified flag); not retryable.
208
+ if (ctx.toolArgsRedacted !== undefined) {
209
+ restart.redactedToolArgs = ctx.toolArgsRedacted;
210
+ restart.redactedToolArgsModified = ctx.toolArgsRedactedModified;
211
+ }
212
+ const meta = {
213
+ synthetic: true,
214
+ syntheticSource: "supervisor",
215
+ restart
216
+ };
217
+ const structuredContent = { error, meta };
218
+ return {
219
+ jsonrpc: "2.0",
220
+ id,
221
+ result: {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: JSON.stringify(structuredContent)
226
+ }
227
+ ],
228
+ isError: true,
229
+ structuredContent
230
+ }
231
+ };
232
+ }
233
+ export function pruneRestartTimestamps(timestamps, now, windowMs = RESTART_WINDOW_MS) {
234
+ const cutoff = now - windowMs;
235
+ return timestamps.filter((t) => t >= cutoff);
236
+ }
237
+ /**
238
+ * Group a worker-exit batch by toolName and produce the per-tool pruned
239
+ * timestamp set plus the post-exit snapshot. Grouping ensures one exit
240
+ * counts as one death per tool regardless of how many concurrent pending
241
+ * requests it killed. `prunedByTool` feeds `buildWorkerRestartReply`;
242
+ * `updatedByTool` is what the caller writes back to `recentRestarts`.
243
+ */
244
+ export function buildExitTimestampGroups(pendingToolNames, recentRestarts, now) {
245
+ const prunedByTool = new Map();
246
+ const updatedByTool = new Map();
247
+ for (const raw of pendingToolNames) {
248
+ const toolName = raw ?? "unknown";
249
+ if (prunedByTool.has(toolName))
250
+ continue;
251
+ const pruned = pruneRestartTimestamps(recentRestarts.get(toolName) ?? [], now);
252
+ prunedByTool.set(toolName, pruned);
253
+ updatedByTool.set(toolName, [...pruned, now]);
254
+ }
255
+ return { prunedByTool, updatedByTool };
256
+ }
257
+ export function buildWorkerRestartReply(req, exit, now, recentRestartTimestamps, options = {}) {
258
+ if (options.structuredRestartDisabled || req.method !== "tools/call") {
259
+ return {
260
+ reply: buildLegacyJsonRpcError(req.id),
261
+ updatedTimestamps: recentRestartTimestamps
262
+ };
263
+ }
264
+ const pruned = pruneRestartTimestamps(recentRestartTimestamps, now);
265
+ // Include the current restart in the count so "3 deaths in 60s →
266
+ // report-bug" fires on the third event, not the fourth.
267
+ const updatedTimestamps = [...pruned, now];
268
+ const recommendation = decideRetryRecommendation({
269
+ toolName: req.toolName,
270
+ lastStage: req.lastStage,
271
+ lastStageMeta: req.lastStageMeta,
272
+ exit
273
+ }, updatedTimestamps);
274
+ const ctx = {
275
+ toolName: req.toolName,
276
+ durationMs: Math.max(0, now - req.startedAt),
277
+ lastStage: req.lastStage,
278
+ lastStageElapsedMs: req.lastStageStartedAt !== undefined
279
+ ? Math.max(0, now - req.lastStageStartedAt)
280
+ : undefined,
281
+ lastStageMeta: req.lastStageMeta,
282
+ exit,
283
+ toolArgsRedacted: req.toolArgsRedacted,
284
+ toolArgsRedactedModified: req.toolArgsRedactedModified ?? false,
285
+ retryRecommendation: recommendation
286
+ };
287
+ return {
288
+ reply: buildSyntheticCallToolResult(req.id, ctx),
289
+ updatedTimestamps
290
+ };
291
+ }
36
292
  function debugSupervisor(event, details) {
37
293
  if (!SUPERVISOR_DEBUG_ENABLED) {
38
294
  return;
@@ -45,6 +301,7 @@ export class StdioSupervisor {
45
301
  workerReader = new JsonRpcFrameReader();
46
302
  queuedMessages = [];
47
303
  pendingRequests = new Map();
304
+ recentRestarts = new Map();
48
305
  child;
49
306
  childReady = false;
50
307
  shuttingDown = false;
@@ -121,10 +378,21 @@ export class StdioSupervisor {
121
378
  if (isRequest(message)) {
122
379
  const id = getTrackedRequestId(message);
123
380
  if (id !== undefined) {
124
- this.pendingRequests.set(requestKey(id), {
381
+ const pending = {
125
382
  id,
126
- method: message.method
127
- });
383
+ method: message.method,
384
+ startedAt: performance.now()
385
+ };
386
+ if (message.method === "tools/call") {
387
+ const params = (message.params ?? {});
388
+ if (typeof params.name === "string") {
389
+ pending.toolName = params.name;
390
+ }
391
+ const redacted = redactToolArgs(params.arguments);
392
+ pending.toolArgsRedacted = redacted.args;
393
+ pending.toolArgsRedactedModified = redacted.modified;
394
+ }
395
+ this.pendingRequests.set(requestKey(id), pending);
128
396
  }
129
397
  if (message.method === "initialize") {
130
398
  this.initializeSentToWorker = true;
@@ -202,7 +470,7 @@ export class StdioSupervisor {
202
470
  signal,
203
471
  pendingRequests: this.pendingRequests.size
204
472
  });
205
- this.failPendingRequestsOnWorkerExit();
473
+ this.failPendingRequestsOnWorkerExit({ code, signal });
206
474
  this.scheduleRestart();
207
475
  };
208
476
  handleWorkerMessage(message) {
@@ -212,6 +480,12 @@ export class StdioSupervisor {
212
480
  id: "id" in message ? message.id : undefined,
213
481
  replayingInitialization: this.replayingInitialization
214
482
  });
483
+ if (isNotification(message) && typeof message.method === "string" && message.method.startsWith("$/")) {
484
+ if (message.method === "$/stageUpdate") {
485
+ this.applyStageUpdate(message.params);
486
+ }
487
+ return;
488
+ }
215
489
  if (this.isInitializationResponse(message)) {
216
490
  const id = getTrackedRequestId(message);
217
491
  if (id !== undefined) {
@@ -240,6 +514,33 @@ export class StdioSupervisor {
240
514
  }
241
515
  this.writeToClient(message);
242
516
  }
517
+ applyStageUpdate(params) {
518
+ if (params === null || params === undefined || typeof params !== "object") {
519
+ return;
520
+ }
521
+ const p = params;
522
+ const requestId = typeof p.requestId === "string" || typeof p.requestId === "number"
523
+ ? p.requestId
524
+ : undefined;
525
+ if (requestId === undefined)
526
+ return;
527
+ const pending = this.pendingRequests.get(requestKey(requestId));
528
+ if (!pending)
529
+ return;
530
+ // Refresh `lastStageStartedAt` only on stage transitions so
531
+ // `lastStageElapsedMs` measures from stage entry, not from the latest
532
+ // per-target emit. `lastStageMeta` still updates on every emit so
533
+ // restart envelopes carry the latest progress payload.
534
+ if (typeof p.stage === "string" && p.stage !== pending.lastStage) {
535
+ pending.lastStage = p.stage;
536
+ pending.lastStageStartedAt = performance.now();
537
+ }
538
+ else if (pending.lastStageStartedAt === undefined) {
539
+ // First emit for this request; bootstrap the stage timer.
540
+ pending.lastStageStartedAt = performance.now();
541
+ }
542
+ pending.lastStageMeta = p.meta;
543
+ }
243
544
  handleWorkerReady() {
244
545
  debugSupervisor("worker_ready", {
245
546
  hasInitializeRequest: this.initializeRequest !== undefined,
@@ -278,16 +579,32 @@ export class StdioSupervisor {
278
579
  this.forwardToWorker(message);
279
580
  }
280
581
  }
281
- failPendingRequestsOnWorkerExit() {
582
+ failPendingRequestsOnWorkerExit(exit) {
282
583
  const preservedInitializeKey = this.initializeRequest && !this.clientInitialized
283
584
  ? requestKey(this.initializeRequest.id)
284
585
  : undefined;
586
+ const now = performance.now();
587
+ // Record this exit once per toolName regardless of how many concurrent
588
+ // pending requests it killed; per-request recording would inflate the
589
+ // count to N and falsely escalate retryRecommendation to "report-bug".
590
+ const pendingToolNames = [];
591
+ for (const [key, pending] of this.pendingRequests.entries()) {
592
+ if (key === preservedInitializeKey)
593
+ continue;
594
+ pendingToolNames.push(pending.toolName);
595
+ }
596
+ const { prunedByTool, updatedByTool } = buildExitTimestampGroups(pendingToolNames, this.recentRestarts, now);
597
+ for (const [toolName, updated] of updatedByTool) {
598
+ this.recentRestarts.set(toolName, updated);
599
+ }
285
600
  for (const [key, pending] of [...this.pendingRequests.entries()]) {
286
- if (key === preservedInitializeKey) {
601
+ if (key === preservedInitializeKey)
287
602
  continue;
288
- }
289
603
  this.pendingRequests.delete(key);
290
- this.writeToClient(buildWorkerRestartError(pending.id));
604
+ const toolName = pending.toolName ?? "unknown";
605
+ const pruned = prunedByTool.get(toolName) ?? [];
606
+ const { reply } = buildWorkerRestartReply(pending, exit, now, pruned, { structuredRestartDisabled: STRUCTURED_RESTART_DISABLED });
607
+ this.writeToClient(reply);
291
608
  }
292
609
  }
293
610
  writeToClient(message) {
@@ -1,4 +1,4 @@
1
- export declare const TOOL_SURFACE_SECTION_IDS: readonly ["v3-entry-tools", "source-exploration", "version-comparison-symbol-tracking", "mapping-symbols", "nbt-utilities", "mod-analysis", "validation", "registry-diagnostics"];
1
+ export declare const TOOL_SURFACE_SECTION_IDS: readonly ["v3-entry-tools", "source-exploration", "version-comparison-symbol-tracking", "mapping-symbols", "nbt-utilities", "mod-analysis", "validation", "registry-diagnostics", "batch-lookup"];
2
2
  export type ToolSurfaceSectionId = (typeof TOOL_SURFACE_SECTION_IDS)[number];
3
3
  export type ToolSurfaceLocale = "en" | "ja";
4
4
  export declare function renderToolSurfaceSection(locale: ToolSurfaceLocale, sectionId: ToolSurfaceSectionId): string;
@@ -6,7 +6,8 @@ export const TOOL_SURFACE_SECTION_IDS = [
6
6
  "nbt-utilities",
7
7
  "mod-analysis",
8
8
  "validation",
9
- "registry-diagnostics"
9
+ "registry-diagnostics",
10
+ "batch-lookup"
10
11
  ];
11
12
  const SECTION_ROWS = {
12
13
  "v3-entry-tools": {
@@ -109,14 +110,16 @@ const SECTION_ROWS = {
109
110
  },
110
111
  "validation": {
111
112
  en: [
112
- "| `validate-mixin` | Validate Mixin source against a target Minecraft version |",
113
+ "| `validate-mixin` | Validate Mixin source against a target Minecraft version (returns `validationStatus: \"partial\"` with `targetOutcomes` when a stage budget defers work) |",
113
114
  "| `validate-access-widener` | Validate Access Widener content against a target Minecraft version, optionally using runtime-aware Loom artifacts |",
114
- "| `validate-access-transformer` | Validate Access Transformer content against a target Minecraft version, optionally using Forge/NeoForge runtime artifacts |"
115
+ "| `validate-access-transformer` | Validate Access Transformer content against a target Minecraft version, optionally using Forge/NeoForge runtime artifacts |",
116
+ "| `verify-mixin-target` | Single-call probe for owner / member existence with `@Shadow` / `@Accessor` / `@Invoker` advice |"
115
117
  ],
116
118
  ja: [
117
- "| `validate-mixin` | 対象 Minecraft バージョンに対して Mixin ソースを検証する |",
118
- "| `validate-access-widener` | 対象 Minecraft バージョンに対して Access Widener の内容を検証し、必要に応じて Loom runtime artifact も使う |",
119
- "| `validate-access-transformer` | 対象 Minecraft バージョンに対して Access Transformer の内容を検証し、必要に応じて Forge / NeoForge runtime artifact も使う |"
119
+ "| `validate-mixin` | 対象 Minecraft バージョンに対して Mixin ソースを検証する (段階別予算で一部作業を後回しにした場合は `validationStatus: \"partial\"` と `targetOutcomes` を含む) |",
120
+ "| `validate-access-widener` | 対象 Minecraft バージョンに対して Access Widener の内容を検証し、必要に応じて Loom ランタイムアーティファクトも使う |",
121
+ "| `validate-access-transformer` | 対象 Minecraft バージョンに対して Access Transformer の内容を検証し、必要に応じて Forge / NeoForge ランタイムアーティファクトも使う |",
122
+ "| `verify-mixin-target` | owner / member の存在確認と `@Shadow` / `@Accessor` / `@Invoker` 助言を 1 回の確認で返す |"
120
123
  ]
121
124
  },
122
125
  "registry-diagnostics": {
@@ -128,6 +131,20 @@ const SECTION_ROWS = {
128
131
  "| `get-registry-data` | 生成済みレジストリスナップショットを読み取り、必要に応じてエントリデータも含める |",
129
132
  "| `get-runtime-metrics` | ランタイムメトリクスとレイテンシスナップショットを確認する |"
130
133
  ]
134
+ },
135
+ "batch-lookup": {
136
+ en: [
137
+ "| `batch-class-source` | Read source for many classes against one shared resolved artifact (1..50 entries per call) |",
138
+ "| `batch-class-members` | List members for many classes against one shared resolved artifact (1..50 entries per call) |",
139
+ "| `batch-symbol-exists` | Probe symbol existence for many entries against one shared Minecraft-version artifact (workspace / version targets only) |",
140
+ "| `batch-mappings` | Translate many symbols across mapping namespaces with one shared Minecraft version (no shared artifact) |"
141
+ ],
142
+ ja: [
143
+ "| `batch-class-source` | 共有解決した 1 つのアーティファクトに対して最大 50 件のクラスソースを読み取る |",
144
+ "| `batch-class-members` | 共有解決した 1 つのアーティファクトに対して最大 50 件のクラスメンバーを列挙する |",
145
+ "| `batch-symbol-exists` | 共有解決した 1 つの Minecraft バージョンアーティファクトに対して最大 50 件のシンボル存在確認を行う (ワークスペースまたはバージョン対象のみ) |",
146
+ "| `batch-mappings` | 1 つの Minecraft バージョンで最大 50 件のシンボルを名前空間横断で変換する (共有アーティファクトなし) |"
147
+ ]
131
148
  }
132
149
  };
133
150
  export function renderToolSurfaceSection(locale, sectionId) {
@@ -0,0 +1,82 @@
1
+ import { ZodError } from "zod";
2
+ import { type ExampleCall, type ProblemDetails, type ProblemFieldError, type SuggestedCall } from "./error-mapping.js";
3
+ import type { SourceTargetInput } from "./types.js";
4
+ export type ToolMeta = {
5
+ requestId: string;
6
+ tool: string;
7
+ durationMs: number;
8
+ warnings: string[];
9
+ detailApplied?: "summary" | "standard" | "full";
10
+ includeApplied?: string[];
11
+ truncated?: Record<string, unknown>;
12
+ pagination?: Record<string, unknown>;
13
+ /** Set to `true` on ERR_STAGE_BUDGET_PRE_PARSE error envelopes to signal a budget-driven failure. */
14
+ stageBudgetExhausted?: boolean;
15
+ /** Stage budget (ms) that was exceeded; populated alongside `stageBudgetExhausted`. */
16
+ budgetMs?: number;
17
+ /** Actual stage elapsed time (ms); populated alongside `stageBudgetExhausted`. */
18
+ elapsedMs?: number;
19
+ };
20
+ export declare const SUGGESTED_CALL_DEFAULTS: {
21
+ readonly allowDecompile: true;
22
+ readonly preferProjectVersion: false;
23
+ readonly strictVersion: false;
24
+ readonly mode: "metadata";
25
+ readonly access: "public";
26
+ readonly includeSynthetic: false;
27
+ readonly includeInherited: false;
28
+ readonly hideUncertain: false;
29
+ readonly explain: false;
30
+ readonly preferProjectMapping: false;
31
+ readonly minSeverity: "all";
32
+ readonly reportMode: "full";
33
+ readonly treatInfoAsWarning: true;
34
+ readonly includeIssues: true;
35
+ };
36
+ export declare function isSuggestedCallDefault(field: keyof typeof SUGGESTED_CALL_DEFAULTS, value: unknown): boolean;
37
+ export declare const ANALYZE_MOD_INCLUDE_GROUPS: readonly ["warnings", "files", "source", "samples", "timings"];
38
+ export declare const ANALYZE_MOD_LEGACY_METADATA_INCLUDES: readonly ["metadata", "entrypoints", "mixins", "dependencies"];
39
+ export declare const VALIDATE_PROJECT_INCLUDE_GROUPS: readonly ["warnings", "issues", "workspace", "recovery"];
40
+ export declare const VALIDATE_PROJECT_LEGACY_WORKSPACE_INCLUDES: readonly ["detectedConfig", "mixins", "accessWideners"];
41
+ export declare const VALIDATION_FALLBACK_HINT = "suggested call payload failed schema validation; using fallback examples";
42
+ export declare function toFieldErrorsFromZod(error: ZodError): ProblemFieldError[];
43
+ export declare function toHints(details: unknown): string[] | undefined;
44
+ export declare function extractValidatedSuggestionAndExamples(details: unknown): {
45
+ suggestedCall?: SuggestedCall;
46
+ exampleCalls?: ExampleCall[];
47
+ primaryDropped: boolean;
48
+ };
49
+ export declare function extractFailedStageFromDetails(details: unknown): string | undefined;
50
+ export declare function extractFieldErrorsFromDetails(details: unknown): ProblemFieldError[] | undefined;
51
+ export declare function asObjectRecord(value: unknown): Record<string, unknown> | undefined;
52
+ export declare function asNonEmptyString(value: unknown): string | undefined;
53
+ export declare function asStringArray(value: unknown): string[] | undefined;
54
+ export declare function truncateSuggestionText(value: string, maxLength?: number): string;
55
+ export declare function parseJsonObjectString(value: string): Record<string, unknown> | undefined;
56
+ export declare function inferTargetKindFromString(value: string): SourceTargetInput["kind"];
57
+ export declare function copySourceLookupSuggestionFields(tool: "get-class-source" | "get-class-members", source: Record<string, unknown>): Record<string, unknown>;
58
+ export declare function copyValidateMixinSharedParams(source: Record<string, unknown>): Record<string, unknown>;
59
+ export declare function buildValidateMixinSuggestedParams(normalizedInput: unknown): Record<string, unknown>;
60
+ export declare function buildResolveArtifactSuggestedParams(normalizedInput: unknown): Record<string, unknown>;
61
+ export declare function buildSourceLookupSuggestedParams(tool: "get-class-source" | "get-class-members", normalizedInput: unknown): Record<string, unknown>;
62
+ export declare function filterAllowedIncludeValues(values: string[] | undefined, allowed: readonly string[]): string[];
63
+ export declare function buildAnalyzeModSuggestedParams(normalizedInput: unknown): Record<string, unknown>;
64
+ export declare function buildValidateProjectSuggestedParams(normalizedInput: unknown): Record<string, unknown>;
65
+ type InvalidInputGuidance = {
66
+ hints?: string[];
67
+ suggestedCall?: SuggestedCall;
68
+ exampleCalls?: ExampleCall[];
69
+ primaryDropped?: boolean;
70
+ };
71
+ export declare function buildInvalidInputGuidance(tool: string, normalizedInput: unknown): InvalidInputGuidance | undefined;
72
+ export declare function mapErrorToProblem(caughtError: unknown, requestId: string, context?: {
73
+ tool?: string;
74
+ normalizedInput?: unknown;
75
+ }): ProblemDetails;
76
+ /**
77
+ * Copy documented error-only meta fields from AppError.details into the
78
+ * public envelope. Scoped to `ERR_STAGE_BUDGET_PRE_PARSE` per
79
+ * docs/tool-reference.md §Meta fields.
80
+ */
81
+ export declare function applyErrorMetaExtensions(meta: ToolMeta, error: unknown): void;
82
+ export {};