@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 +1 -1
- package/dist/server.mjs +241 -31
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
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
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
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
|
|
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
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(),
|
|
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
|
-
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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.
|
|
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.",
|