@levnikolaevich/hex-line-mcp 1.28.0 → 1.30.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.
package/README.md CHANGED
@@ -462,7 +462,7 @@ FNV-1a accumulator over all line hashes in the range (little-endian byte feed).
462
462
  <details>
463
463
  <summary><b>Does it work without Claude Code?</b></summary>
464
464
 
465
- Yes. hex-line-mcp is a standard MCP server (stdio transport). It works with any MCP-compatible client -- Claude Code, Gemini CLI, Codex CLI, or custom integrations. Hook installation is Claude-specific; Gemini/Codex use MCP Tool Preferences guidance instead.
465
+ Yes. hex-line-mcp is a standard MCP server (stdio transport). It works with any MCP-compatible client -- Claude Code, Codex CLI, or custom integrations. Hook installation is Claude-specific; Codex uses MCP Tool Preferences guidance instead.
466
466
 
467
467
  </details>
468
468
 
package/dist/server.mjs CHANGED
@@ -202,11 +202,12 @@ function readText(filePath) {
202
202
  // lib/security.mjs
203
203
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
204
204
  var EXTERNAL_SAFE_FOLDERS = [
205
- ".hex-skills/",
206
- ".claude/"
205
+ ".hex-skills",
206
+ ".claude"
207
207
  ];
208
208
  function isInExternalSafeFolder(absPath) {
209
- return EXTERNAL_SAFE_FOLDERS.some((folder) => absPath.includes(folder));
209
+ const parts = normalizeScopeValue(absPath).split("/").filter(Boolean);
210
+ return EXTERNAL_SAFE_FOLDERS.some((folder) => parts.includes(folder));
210
211
  }
211
212
  function normalizePath(p) {
212
213
  if (process.platform === "win32") {
@@ -1658,17 +1659,78 @@ import { statSync as statSync6, writeFileSync } from "node:fs";
1658
1659
  import { diffLines as diffLines2 } from "diff";
1659
1660
 
1660
1661
  // lib/edit-resolution.mjs
1661
- function normalizeAnchoredEdits(edits) {
1662
- const anchored = [];
1663
- for (const edit of edits) {
1664
- if (edit.set_line || edit.replace_lines || edit.insert_after || edit.replace_between) {
1665
- anchored.push(edit);
1666
- continue;
1667
- }
1662
+ function badInput(message) {
1663
+ const err = new Error(`BAD_INPUT: ${message}`);
1664
+ err.code = "BAD_INPUT";
1665
+ err.recovery = "Use canonical edit shapes with required string fields: set_line.anchor/new_text, insert_after.anchor/text, replace_lines.start_anchor/end_anchor/range_checksum/new_text, or replace_between.start_anchor/end_anchor/new_text.";
1666
+ return err;
1667
+ }
1668
+ function requirePlainObject(value, path) {
1669
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1670
+ throw badInput(`${path} must be an object`);
1671
+ }
1672
+ }
1673
+ function requireString(value, path) {
1674
+ if (typeof value !== "string") {
1675
+ throw badInput(`${path} must be a string`);
1676
+ }
1677
+ }
1678
+ function requireOptionalString(value, path) {
1679
+ if (value !== void 0 && typeof value !== "string") {
1680
+ throw badInput(`${path} must be a string when provided`);
1681
+ }
1682
+ }
1683
+ function validateEditShape(edit) {
1684
+ requirePlainObject(edit, "edit");
1685
+ const kinds = ["set_line", "replace_lines", "insert_after", "replace_between"].filter((kind2) => edit[kind2] !== void 0);
1686
+ if (kinds.length !== 1) {
1668
1687
  if (edit.replace) {
1669
1688
  throw new Error("REPLACE_REMOVED: replace is no longer supported in edit_file. Use set_line/replace_lines for single edits, bulk_replace tool for rename/refactor.");
1670
1689
  }
1671
- throw new Error(`BAD_INPUT: unknown edit type: ${JSON.stringify(edit)}`);
1690
+ if (kinds.length === 0) throw badInput(`unknown edit type: ${JSON.stringify(edit)}`);
1691
+ throw badInput(`exactly one edit type is allowed, got ${kinds.join(", ")}`);
1692
+ }
1693
+ const kind = kinds[0];
1694
+ if (kind === "set_line") {
1695
+ requirePlainObject(edit.set_line, "set_line");
1696
+ requireString(edit.set_line.anchor, "set_line.anchor");
1697
+ requireString(edit.set_line.new_text, "set_line.new_text");
1698
+ return;
1699
+ }
1700
+ if (kind === "insert_after") {
1701
+ requirePlainObject(edit.insert_after, "insert_after");
1702
+ requireString(edit.insert_after.anchor, "insert_after.anchor");
1703
+ requireString(edit.insert_after.text, "insert_after.text");
1704
+ return;
1705
+ }
1706
+ if (kind === "replace_lines") {
1707
+ requirePlainObject(edit.replace_lines, "replace_lines");
1708
+ requireString(edit.replace_lines.start_anchor, "replace_lines.start_anchor");
1709
+ requireString(edit.replace_lines.end_anchor, "replace_lines.end_anchor");
1710
+ requireString(edit.replace_lines.range_checksum, "replace_lines.range_checksum");
1711
+ requireString(edit.replace_lines.new_text, "replace_lines.new_text");
1712
+ return;
1713
+ }
1714
+ requirePlainObject(edit.replace_between, "replace_between");
1715
+ requireString(edit.replace_between.start_anchor, "replace_between.start_anchor");
1716
+ requireString(edit.replace_between.end_anchor, "replace_between.end_anchor");
1717
+ requireString(edit.replace_between.new_text, "replace_between.new_text");
1718
+ requireOptionalString(edit.replace_between.range_checksum, "replace_between.range_checksum");
1719
+ if (edit.replace_between.boundary_mode !== void 0 && !["inclusive", "exclusive"].includes(edit.replace_between.boundary_mode)) {
1720
+ throw badInput('replace_between.boundary_mode must be "inclusive" or "exclusive"');
1721
+ }
1722
+ }
1723
+ function normalizeAnchoredEdits(edits) {
1724
+ if (!Array.isArray(edits)) {
1725
+ throw badInput("edits must be a non-empty array");
1726
+ }
1727
+ if (edits.length === 0) {
1728
+ throw badInput("edits must be a non-empty array");
1729
+ }
1730
+ const anchored = [];
1731
+ for (const edit of edits) {
1732
+ validateEditShape(edit);
1733
+ anchored.push(edit);
1672
1734
  }
1673
1735
  return anchored;
1674
1736
  }
@@ -1832,12 +1894,16 @@ var REASON = {
1832
1894
  };
1833
1895
 
1834
1896
  // lib/edit.mjs
1897
+ function asLine(value) {
1898
+ return typeof value === "string" ? value : String(value ?? "");
1899
+ }
1835
1900
  function restoreIndent(origLines, newLines) {
1836
1901
  if (!origLines.length || !newLines.length) return newLines;
1837
- const origIndent = origLines[0].match(/^\s*/)[0];
1838
- const newIndent = newLines[0].match(/^\s*/)[0];
1839
- if (origIndent === newIndent) return newLines;
1840
- return newLines.map((line) => {
1902
+ const safeNewLines = newLines.map(asLine);
1903
+ const origIndent = asLine(origLines[0]).match(/^\s*/)[0];
1904
+ const newIndent = safeNewLines[0].match(/^\s*/)[0];
1905
+ if (origIndent === newIndent) return safeNewLines;
1906
+ return safeNewLines.map((line) => {
1841
1907
  if (!line.trim()) return line;
1842
1908
  if (line.startsWith(newIndent)) return origIndent + line.slice(newIndent.length);
1843
1909
  return line;
@@ -2815,8 +2881,8 @@ Recovery: read_file path ranges=["${csStart}-${csEnd}"], then retry edit with fr
2815
2881
  let boundaryEchoSkipped = false;
2816
2882
  const insertEnd = sliceStart + newLines.length;
2817
2883
  if (newLines.length > 0 && insertEnd < lines.length) {
2818
- const lastNew = newLines[newLines.length - 1].trim();
2819
- const firstAfter = lines[insertEnd].trim();
2884
+ const lastNew = asLine(newLines[newLines.length - 1]).trim();
2885
+ const firstAfter = asLine(lines[insertEnd]).trim();
2820
2886
  if (lastNew && lastNew === firstAfter) {
2821
2887
  if (isAmbiguousDelimiter(firstAfter)) {
2822
2888
  boundaryEchoSkipped = true;
@@ -2834,8 +2900,8 @@ Recovery: read_file path ranges=["${csStart}-${csEnd}"], then retry edit with fr
2834
2900
  }
2835
2901
  }
2836
2902
  if (newLines.length > 0 && sliceStart > 0) {
2837
- const firstNew = newLines[0].trim();
2838
- const lineBefore = lines[sliceStart - 1].trim();
2903
+ const firstNew = asLine(newLines[0]).trim();
2904
+ const lineBefore = asLine(lines[sliceStart - 1]).trim();
2839
2905
  if (firstNew && firstNew === lineBefore) {
2840
2906
  if (isAmbiguousDelimiter(lineBefore)) {
2841
2907
  boundaryEchoSkipped = true;
@@ -4943,6 +5009,85 @@ OUTPUT_CAPPED: Output exceeded ${MAX_BULK_OUTPUT_CHARS} chars.`;
4943
5009
  return output;
4944
5010
  }
4945
5011
 
5012
+ // ../hex-common/src/runtime/error-classifier.mjs
5013
+ var FAILURE_CLASS = Object.freeze({
5014
+ NONE: "none",
5015
+ TIMEOUT_IDLE: "timeout_idle",
5016
+ TIMEOUT_PRODUCTIVE: "timeout_productive",
5017
+ PERMISSION_DENIAL: "permission_denial",
5018
+ TOOL_MISSING: "tool_missing",
5019
+ AUTH_MISSING: "auth_missing",
5020
+ RATE_LIMITED: "rate_limited",
5021
+ ASKED_QUESTION: "asked_question",
5022
+ AGENT_ERROR: "agent_error",
5023
+ UNKNOWN: "unknown"
5024
+ });
5025
+ var INPUT_ERROR_CODES = /* @__PURE__ */ new Set([
5026
+ "BAD_INPUT",
5027
+ "BAD_PATH",
5028
+ "BAD_REMOTE_PLATFORM",
5029
+ "INVALID_INPUT",
5030
+ "INVALID_EDIT_PAYLOAD",
5031
+ "INVALID_JSON",
5032
+ "PATH_OUTSIDE_ROOT",
5033
+ "PATH_NOT_FOUND",
5034
+ "FILE_NOT_FOUND",
5035
+ "FILE_OUTSIDE_PROJECT",
5036
+ "OUT_OF_RANGE",
5037
+ "UNSUPPORTED_REMOTE_PLATFORM"
5038
+ ]);
5039
+ var PERMISSION_ERROR_CODES = /* @__PURE__ */ new Set([
5040
+ "SSH_HOST_NOT_ALLOWED",
5041
+ "BLOCKED_COMMAND",
5042
+ "REMOTE_SSH_DISABLED"
5043
+ ]);
5044
+ var AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
5045
+ "SSH_AUTH_FAILED",
5046
+ "SSH_AUTH_MISSING",
5047
+ "SSH_KEY_UNREADABLE"
5048
+ ]);
5049
+ var TIMEOUT_ERROR_CODES = /* @__PURE__ */ new Set([
5050
+ "SSH_EXEC_TIMEOUT",
5051
+ "SSH_CONNECT_TIMEOUT",
5052
+ "EXEC_TIMEOUT",
5053
+ "TRANSFER_TIMEOUT"
5054
+ ]);
5055
+ function textFor(input) {
5056
+ return [
5057
+ input?.code,
5058
+ input?.message,
5059
+ input?.recovery,
5060
+ input?.stderr,
5061
+ input?.error
5062
+ ].filter(Boolean).map(String).join("\n");
5063
+ }
5064
+ function classifyMcpFailure(input = {}) {
5065
+ const code = String(input.code || "").toUpperCase();
5066
+ const text = textFor(input).toLowerCase();
5067
+ if (/\b(429|rate limit|too many requests|quota exceeded|throttl)/i.test(text) || code.includes("RATE_LIMIT")) {
5068
+ return { failure_class: FAILURE_CLASS.RATE_LIMITED, next_action: "defer_retry" };
5069
+ }
5070
+ if (/\b(auth|authentication|unauthorized|not authorized|login required|credential|token missing|no user for host|permission denied \(publickey\)|publickey)\b/i.test(text) || code.includes("AUTH") || AUTH_ERROR_CODES.has(code)) {
5071
+ return { failure_class: FAILURE_CLASS.AUTH_MISSING, next_action: "authenticate" };
5072
+ }
5073
+ if (/\b(eacces|eperm|permission denied|access denied|operation not permitted|forbidden)\b/i.test(text) || code === "GRAPH_DB_UNREADABLE" || PERMISSION_ERROR_CODES.has(code)) {
5074
+ return { failure_class: FAILURE_CLASS.PERMISSION_DENIAL, next_action: "fix_permissions" };
5075
+ }
5076
+ if (/\b(enoent|command not found|not recognized as|not found in path|required tool|provider setup failed|missing provider|tool missing)\b/i.test(text) || code === "GRAPH_PROVIDER_SETUP_FAILED") {
5077
+ return { failure_class: FAILURE_CLASS.TOOL_MISSING, next_action: "install_tool" };
5078
+ }
5079
+ if (/\b(etimedout|timed out|timeout|transfer_timeout|connection timed out|database is locked|busy or locked)\b/i.test(text) || code.includes("TIMEOUT") || code === "GRAPH_DB_BUSY" || TIMEOUT_ERROR_CODES.has(code)) {
5080
+ return { failure_class: FAILURE_CLASS.TIMEOUT_IDLE, next_action: "retry_after_wait" };
5081
+ }
5082
+ if (/\?\s*$|\b(please confirm|confirm\?|choose one|which option)\b/i.test(text)) {
5083
+ return { failure_class: FAILURE_CLASS.ASKED_QUESTION, next_action: "fix_inputs" };
5084
+ }
5085
+ if (INPUT_ERROR_CODES.has(code) || code.startsWith("INVALID_") || code.endsWith("_REQUIRED")) {
5086
+ return { failure_class: FAILURE_CLASS.UNKNOWN, next_action: "fix_inputs" };
5087
+ }
5088
+ return { failure_class: FAILURE_CLASS.UNKNOWN, next_action: "fix_inputs" };
5089
+ }
5090
+
4946
5091
  // ../hex-common/src/runtime/results.mjs
4947
5092
  var LARGE_RESULT_META = { "anthropic/maxResultSizeChars": 5e5 };
4948
5093
  function result(structured, { large = false } = {}) {
@@ -4956,9 +5101,26 @@ function result(structured, { large = false } = {}) {
4956
5101
  return response;
4957
5102
  }
4958
5103
  function errorResult(code, message, recovery, { large = false, extra = null } = {}) {
5104
+ const normalizedCode = String(code || "ERROR");
5105
+ const normalizedMessage = String(message || "Unknown MCP tool error");
5106
+ const normalizedRecovery = String(recovery || "Review the error and retry with corrected inputs");
5107
+ const classification = classifyMcpFailure({
5108
+ code: normalizedCode,
5109
+ message: normalizedMessage,
5110
+ recovery: normalizedRecovery
5111
+ });
4959
5112
  const payload = {
4960
5113
  status: "ERROR",
4961
- error: { code, message, recovery }
5114
+ code: normalizedCode,
5115
+ summary: normalizedMessage,
5116
+ next_action: classification.next_action,
5117
+ recovery: normalizedRecovery,
5118
+ failure_class: classification.failure_class,
5119
+ error: {
5120
+ code: normalizedCode,
5121
+ message: normalizedMessage,
5122
+ recovery: normalizedRecovery
5123
+ }
4962
5124
  };
4963
5125
  if (extra && typeof extra === "object") {
4964
5126
  Object.assign(payload, extra);
@@ -4967,9 +5129,17 @@ function errorResult(code, message, recovery, { large = false, extra = null } =
4967
5129
  }
4968
5130
 
4969
5131
  // server.mjs
4970
- var version = true ? "1.28.0" : (await null).createRequire(import.meta.url)("./package.json").version;
5132
+ var version = true ? "1.30.0" : (await null).createRequire(import.meta.url)("./package.json").version;
4971
5133
  var STATUS_ENUM = z2.enum(STATUS_VALUES);
4972
5134
  var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
5135
+ var ERROR_RESULT_FIELDS = {
5136
+ code: z2.string().optional(),
5137
+ summary: z2.string().optional(),
5138
+ next_action: z2.string().optional(),
5139
+ recovery: z2.string().optional(),
5140
+ failure_class: z2.string().optional(),
5141
+ error: ERROR_SHAPE
5142
+ };
4973
5143
  var LINE_REPORT_KEYS = /* @__PURE__ */ new Set([
4974
5144
  "status",
4975
5145
  "reason",
@@ -4990,6 +5160,46 @@ var LINE_REPORT_KEYS = /* @__PURE__ */ new Set([
4990
5160
  "remapped_refs",
4991
5161
  "warnings"
4992
5162
  ]);
5163
+ var EDIT_PAYLOAD_TYPES = ["set_line", "insert_after", "replace_lines", "replace_between"];
5164
+ var EDIT_REQUIRED_FIELDS = {
5165
+ set_line: ["anchor", "new_text"],
5166
+ insert_after: ["anchor", "text"],
5167
+ replace_lines: ["start_anchor", "end_anchor", "new_text"],
5168
+ replace_between: ["start_anchor", "end_anchor", "new_text"]
5169
+ };
5170
+ function inputError(code, message, recovery) {
5171
+ const error = new Error(message);
5172
+ error.code = code;
5173
+ error.recovery = recovery;
5174
+ return error;
5175
+ }
5176
+ function validateEditPayload(edits) {
5177
+ if (!Array.isArray(edits) || edits.length === 0) {
5178
+ throw inputError("INVALID_EDIT_PAYLOAD", "BAD_INPUT: edits must be a non-empty JSON array", 'Pass canonical edit objects such as {"set_line":{"anchor":"ab.12","new_text":"..."}}');
5179
+ }
5180
+ edits.forEach((edit, index) => {
5181
+ if (!edit || typeof edit !== "object" || Array.isArray(edit)) {
5182
+ throw inputError("INVALID_EDIT_PAYLOAD", `BAD_INPUT: edit at index ${index} must be an object`, "Use one canonical edit object per array item");
5183
+ }
5184
+ const keys = EDIT_PAYLOAD_TYPES.filter((type2) => Object.prototype.hasOwnProperty.call(edit, type2));
5185
+ if (keys.length === 0) {
5186
+ throw inputError("INVALID_EDIT_PAYLOAD", `BAD_INPUT: unknown edit type at index ${index}`, "Use set_line, insert_after, replace_lines, or replace_between");
5187
+ }
5188
+ if (keys.length > 1) {
5189
+ throw inputError("INVALID_EDIT_PAYLOAD", `BAD_INPUT: edit at index ${index} has multiple edit types`, "Use exactly one edit type per object");
5190
+ }
5191
+ const [type] = keys;
5192
+ const payload = edit[type];
5193
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
5194
+ throw inputError("INVALID_EDIT_PAYLOAD", `BAD_INPUT: ${type} payload at index ${index} must be an object`, "Nest fields under the canonical edit type");
5195
+ }
5196
+ for (const field of EDIT_REQUIRED_FIELDS[type]) {
5197
+ if (typeof payload[field] !== "string") {
5198
+ throw inputError("INVALID_EDIT_PAYLOAD", `BAD_INPUT: ${type}.${field} must be a string at index ${index}`, "Provide all required canonical edit fields before retrying");
5199
+ }
5200
+ }
5201
+ });
5202
+ }
4993
5203
  var { server, StdioServerTransport } = await createServerRuntime({
4994
5204
  name: "hex-line-mcp",
4995
5205
  version
@@ -5061,7 +5271,7 @@ server.registerTool("read_file", {
5061
5271
  content: z2.string().optional(),
5062
5272
  edit_ready: z2.boolean().optional(),
5063
5273
  next_action: z2.string().optional(),
5064
- error: ERROR_SHAPE
5274
+ ...ERROR_RESULT_FIELDS
5065
5275
  }),
5066
5276
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
5067
5277
  }, async (rawParams) => {
@@ -5125,7 +5335,7 @@ server.registerTool("edit_file", {
5125
5335
  conflict_policy: z2.enum(["strict", "conservative"]).optional().describe('Conflict handling (default: "conservative"). "conservative" returns structured CONFLICT output with recovery_ranges, retry_edit/retry_edits, suggested_read_call, and retry_plan when available.'),
5126
5336
  allow_external: flexBool().describe("Allow editing a path outside the current project root. Use only when you intentionally target a temp or external file.")
5127
5337
  }),
5128
- outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), summary: z2.string().optional(), next_action: z2.string().optional(), warnings: z2.array(z2.object({ code: z2.string() }).passthrough()).optional(), error: ERROR_SHAPE }),
5338
+ outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), warnings: z2.array(z2.object({ code: z2.string() }).passthrough()).optional(), ...ERROR_RESULT_FIELDS }),
5129
5339
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
5130
5340
  }, async (rawParams) => {
5131
5341
  const { file_path: p, edits: json, dry_run, restore_indent, base_revision, conflict_policy, allow_external } = rawParams ?? {};
@@ -5137,7 +5347,7 @@ server.registerTool("edit_file", {
5137
5347
  } catch {
5138
5348
  throw new Error('edits: invalid JSON. Expected: [{"set_line":{"anchor":"xx.N","new_text":"..."}}]');
5139
5349
  }
5140
- if (!Array.isArray(parsed) || !parsed.length) throw new Error("Edits: non-empty JSON array required");
5350
+ validateEditPayload(parsed);
5141
5351
  const content = editFile(p, parsed, {
5142
5352
  dryRun: dry_run,
5143
5353
  restoreIndent: restore_indent,
@@ -5157,7 +5367,7 @@ server.registerTool("write_file", {
5157
5367
  content: z2.string().describe("File content"),
5158
5368
  allow_external: flexBool().describe("Allow writing a path outside the current project root. Use only when you intentionally target a temp or external file.")
5159
5369
  }),
5160
- outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), lines: z2.number().optional(), error: ERROR_SHAPE }),
5370
+ outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), lines: z2.number().optional(), ...ERROR_RESULT_FIELDS }),
5161
5371
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
5162
5372
  }, async (rawParams) => {
5163
5373
  const { file_path: p, content, allow_external } = rawParams ?? {};
@@ -5194,7 +5404,7 @@ server.registerTool("grep_search", {
5194
5404
  edit_ready: flexBool().describe("Preserve hash/checksum search hunks in `content` mode. Default: false."),
5195
5405
  allow_large_output: flexBool().describe("Bypass the default content-mode block/char caps when you intentionally need a larger payload.")
5196
5406
  }),
5197
- outputSchema: z2.object({ status: STATUS_ENUM, pattern: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
5407
+ outputSchema: z2.object({ status: STATUS_ENUM, pattern: z2.string().optional(), content: z2.string().optional(), ...ERROR_RESULT_FIELDS }),
5198
5408
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
5199
5409
  }, async (rawParams) => {
5200
5410
  const {
@@ -5249,7 +5459,7 @@ server.registerTool("outline", {
5249
5459
  inputSchema: z2.object({
5250
5460
  file_path: z2.string().describe("Source file path")
5251
5461
  }),
5252
- outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), summary: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
5462
+ outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), ...ERROR_RESULT_FIELDS }),
5253
5463
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
5254
5464
  }, async (rawParams) => {
5255
5465
  const { file_path: p } = rawParams ?? {};
@@ -5268,7 +5478,7 @@ server.registerTool("verify", {
5268
5478
  checksums: z2.array(z2.string()).describe('Checksum strings, e.g. ["1-50:f7e2a1b0", "51-100:abcd1234"]'),
5269
5479
  base_revision: z2.string().optional().describe("Optional prior revision to compare against latest state.")
5270
5480
  }),
5271
- outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), summary: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
5481
+ outputSchema: z2.object({ status: STATUS_ENUM, file_path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), ...ERROR_RESULT_FIELDS }),
5272
5482
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
5273
5483
  }, async (rawParams) => {
5274
5484
  const { file_path: p, checksums, base_revision } = rawParams ?? {};
@@ -5295,7 +5505,7 @@ server.registerTool("inspect_path", {
5295
5505
  format: z2.enum(["compact", "full"]).optional().describe('"compact" = shorter path view, "full" = include sizes/metadata where available'),
5296
5506
  verbosity: z2.enum(["minimal", "compact", "full"]).optional().describe("Response budget. `minimal` returns the shortest tree summary.")
5297
5507
  }),
5298
- outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), summary: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
5508
+ outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), ...ERROR_RESULT_FIELDS }),
5299
5509
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
5300
5510
  }, async (rawParams) => {
5301
5511
  const { path: p, max_depth, max_entries, gitignore, format, pattern, type: entryType, verbosity } = rawParams ?? {};
@@ -5321,7 +5531,7 @@ server.registerTool("changes", {
5321
5531
  path: z2.string().describe("File or directory path"),
5322
5532
  compare_against: z2.string().optional().describe('Git ref to compare against (default: "HEAD")')
5323
5533
  }),
5324
- outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
5534
+ outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), ...ERROR_RESULT_FIELDS }),
5325
5535
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
5326
5536
  }, async (rawParams) => {
5327
5537
  const { path: p, compare_against } = rawParams ?? {};
@@ -5344,7 +5554,7 @@ server.registerTool("bulk_replace", {
5344
5554
  format: z2.enum(["compact", "full"]).optional().describe('"compact" (default) = summary only, "full" = include capped diffs'),
5345
5555
  allow_external: flexBool().describe("Allow a replacement root outside the current project root. Use only when you intentionally target a temp or external directory.")
5346
5556
  }),
5347
- outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), summary: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
5557
+ outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), reason: z2.string().optional(), ...ERROR_RESULT_FIELDS }),
5348
5558
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }
5349
5559
  }, async (rawParams) => {
5350
5560
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levnikolaevich/hex-line-mcp",
3
- "version": "1.28.0",
3
+ "version": "1.30.0",
4
4
  "mcpName": "io.github.levnikolaevich/hex-line-mcp",
5
5
  "type": "module",
6
6
  "description": "Hash-verified file editing MCP + token efficiency hook for AI coding agents. 9 tools: inspect_path, read, edit, write, grep, outline, verify, changes, bulk_replace.",