@levnikolaevich/hex-line-mcp 1.17.0 → 1.18.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/dist/hook.mjs +36 -2
- package/dist/server.mjs +93 -48
- package/package.json +1 -1
package/dist/hook.mjs
CHANGED
|
@@ -191,6 +191,11 @@ function isWithinDir(candidatePath, dirPath) {
|
|
|
191
191
|
|
|
192
192
|
// hook.mjs
|
|
193
193
|
import { fileURLToPath } from "node:url";
|
|
194
|
+
var HEX_LINE_MUTATING = /* @__PURE__ */ new Set([
|
|
195
|
+
"mcp__hex-line__edit_file",
|
|
196
|
+
"mcp__hex-line__write_file",
|
|
197
|
+
"mcp__hex-line__bulk_replace"
|
|
198
|
+
]);
|
|
194
199
|
function extOf(filePath) {
|
|
195
200
|
const dot = filePath.lastIndexOf(".");
|
|
196
201
|
return dot !== -1 ? filePath.slice(dot).toLowerCase() : "";
|
|
@@ -403,7 +408,9 @@ function block(reason, context) {
|
|
|
403
408
|
${context}` : reason;
|
|
404
409
|
const output = {
|
|
405
410
|
hookSpecificOutput: {
|
|
406
|
-
|
|
411
|
+
hookEventName: "PreToolUse",
|
|
412
|
+
permissionDecision: "deny",
|
|
413
|
+
permissionDecisionReason: reason
|
|
407
414
|
},
|
|
408
415
|
systemMessage: msg
|
|
409
416
|
};
|
|
@@ -413,7 +420,8 @@ ${context}` : reason;
|
|
|
413
420
|
function advise(reason, context) {
|
|
414
421
|
const output = {
|
|
415
422
|
hookSpecificOutput: {
|
|
416
|
-
|
|
423
|
+
hookEventName: "PreToolUse",
|
|
424
|
+
additionalContext: reason
|
|
417
425
|
},
|
|
418
426
|
systemMessage: context ? `${reason}
|
|
419
427
|
${context}` : reason
|
|
@@ -430,6 +438,12 @@ function redirect(reason, context) {
|
|
|
430
438
|
function handlePreToolUse(data) {
|
|
431
439
|
const toolName = data.tool_name || "";
|
|
432
440
|
const toolInput = data.tool_input || {};
|
|
441
|
+
if (data.permission_mode === "plan" && HEX_LINE_MUTATING.has(toolName)) {
|
|
442
|
+
block(
|
|
443
|
+
"PLAN_MODE: hex-line write tools are blocked during planning.",
|
|
444
|
+
"Use read-only tools: read_file, grep_search, outline, verify, inspect_path, changes."
|
|
445
|
+
);
|
|
446
|
+
}
|
|
433
447
|
if (toolName.startsWith("mcp__hex-line__")) {
|
|
434
448
|
process.exit(0);
|
|
435
449
|
}
|
|
@@ -576,6 +590,22 @@ function handleSessionStart() {
|
|
|
576
590
|
const msg = styleActive ? "Hex-line MCP available. Output style active.\n<hex-line_instructions>\n <deferred_loading>If hex-line schemas not loaded, run: ToolSearch('+hex-line read edit')</deferred_loading>\n <note>Follow the active hex-line output style for primary tool choices.</note>\n <exceptions>Built-in tools stay OK for images, PDFs, notebooks, and text paths outside the current project root.</exceptions>\n</hex-line_instructions>" : "Hex-line MCP available.\n<hex-line_instructions>\n <deferred_loading>If hex-line schemas not loaded, run: ToolSearch('+hex-line read edit')</deferred_loading>\n <exploration>\n <rule>Use outline for structure (code + markdown), not Read. ~10-20 lines vs hundreds.</rule>\n <rule>Use read_file with offset/limit or ranges for targeted reads.</rule>\n <rule>Use grep_search before editing to get hash anchors.</rule>\n </exploration>\n <editing>\n <path name='surgical'>grep_search \u2192 edit_file (fastest: hash-verified, no full read needed)</path>\n <path name='exploratory'>outline \u2192 read_file (ranges) \u2192 edit_file with base_revision</path>\n <path name='multi-file'>bulk_replace(path="<project root>") for text rename/refactor across files</path>\n </editing>\n <tips>\n <tip>Auto-fill path from the active file or project root. Read-only tools may inspect explicit temp-file paths outside the repo. Mutating tools stay project-scoped unless you intentionally pass allow_external=true.</tip>\n <tip>Never invent range_checksum. Copy it from fresh read_file or grep_search blocks.</tip>\n <tip>Prefer set_line or insert_after for small local changes and replace_between for larger bounded rewrites.</tip>\n <tip>Carry revision from read_file into base_revision on edit_file.</tip>\n <tip>If edit returns CONFLICT, call verify \u2014 only reread when STALE.</tip>\n <tip>Avoid large first-pass edit batches. Start with 1-2 hunks, then continue from the returned revision.</tip>\n <tip>Use write_file for new files (no prior Read needed).</tip>\n <tip>Use inspect_path(pattern=...) for project file discovery and name/path globbing.</tip>\n </tips>\n <exceptions>Built-in tools stay OK for images, PDFs, notebooks, and text paths outside the current project root.</exceptions>\n</hex-line_instructions>";
|
|
577
591
|
safeExit(1, JSON.stringify({ systemMessage: msg }), 0);
|
|
578
592
|
}
|
|
593
|
+
function handleConfigChange(data) {
|
|
594
|
+
const source = data.source || "";
|
|
595
|
+
if (["project_settings", "local_settings", "user_settings", "policy_settings"].includes(source)) {
|
|
596
|
+
_hookMode = void 0;
|
|
597
|
+
_hexLineDisabled = null;
|
|
598
|
+
}
|
|
599
|
+
process.exit(0);
|
|
600
|
+
}
|
|
601
|
+
function handlePermissionDenied(data) {
|
|
602
|
+
const reason = data.reason || "";
|
|
603
|
+
const toolName = data.tool_name || "";
|
|
604
|
+
if (reason.includes("mcp__hex-line__") || toolName === "Bash") {
|
|
605
|
+
debugLog("PERMISSION_DENIED_AFTER_HEX_HINT", `${toolName}: ${reason.slice(0, 120)}`);
|
|
606
|
+
}
|
|
607
|
+
process.exit(0);
|
|
608
|
+
}
|
|
579
609
|
var _norm = (p) => p.replace(/\\/g, "/");
|
|
580
610
|
if (_norm(process.argv[1]) === _norm(fileURLToPath(import.meta.url))) {
|
|
581
611
|
let input = "";
|
|
@@ -586,6 +616,9 @@ if (_norm(process.argv[1]) === _norm(fileURLToPath(import.meta.url))) {
|
|
|
586
616
|
try {
|
|
587
617
|
const data = JSON.parse(input);
|
|
588
618
|
const event = data.hook_event_name || "";
|
|
619
|
+
if (event === "ConfigChange") {
|
|
620
|
+
handleConfigChange(data);
|
|
621
|
+
}
|
|
589
622
|
if (isHexLineDisabled()) {
|
|
590
623
|
if (event === "PreToolUse") handlePreToolUseReverse(data);
|
|
591
624
|
process.exit(0);
|
|
@@ -593,6 +626,7 @@ if (_norm(process.argv[1]) === _norm(fileURLToPath(import.meta.url))) {
|
|
|
593
626
|
if (event === "SessionStart") handleSessionStart();
|
|
594
627
|
else if (event === "PreToolUse") handlePreToolUse(data);
|
|
595
628
|
else if (event === "PostToolUse") handlePostToolUse(data);
|
|
629
|
+
else if (event === "PermissionDenied") handlePermissionDenied(data);
|
|
596
630
|
else process.exit(0);
|
|
597
631
|
} catch {
|
|
598
632
|
process.exit(0);
|
package/dist/server.mjs
CHANGED
|
@@ -3504,13 +3504,13 @@ async function fileOutline(filePath) {
|
|
|
3504
3504
|
}
|
|
3505
3505
|
const snapshot = readSnapshot(real);
|
|
3506
3506
|
const isMarkdown = MARKDOWN_EXTS.has(ext);
|
|
3507
|
-
const
|
|
3507
|
+
const result2 = isMarkdown ? null : await outlineFromContent(snapshot.content, ext);
|
|
3508
3508
|
let entries;
|
|
3509
3509
|
let skippedRanges = [];
|
|
3510
3510
|
let note = "";
|
|
3511
|
-
if (
|
|
3512
|
-
entries =
|
|
3513
|
-
skippedRanges =
|
|
3511
|
+
if (result2 && result2.entries.length > 0) {
|
|
3512
|
+
entries = result2.entries;
|
|
3513
|
+
skippedRanges = result2.skippedRanges;
|
|
3514
3514
|
} else if (isMarkdown) {
|
|
3515
3515
|
entries = markdownOutline(snapshot.lines);
|
|
3516
3516
|
} else {
|
|
@@ -4722,8 +4722,34 @@ OUTPUT_CAPPED: Output exceeded ${MAX_BULK_OUTPUT_CHARS} chars.`;
|
|
|
4722
4722
|
return output;
|
|
4723
4723
|
}
|
|
4724
4724
|
|
|
4725
|
+
// ../hex-common/src/runtime/results.mjs
|
|
4726
|
+
var LARGE_RESULT_META = { "anthropic/maxResultSizeChars": 5e5 };
|
|
4727
|
+
function result(structured, { large = false } = {}) {
|
|
4728
|
+
const text = JSON.stringify(structured);
|
|
4729
|
+
const response = {
|
|
4730
|
+
content: [{ type: "text", text }],
|
|
4731
|
+
structuredContent: structured
|
|
4732
|
+
};
|
|
4733
|
+
if (large) response._meta = LARGE_RESULT_META;
|
|
4734
|
+
if (structured.status === "ERROR") response.isError = true;
|
|
4735
|
+
return response;
|
|
4736
|
+
}
|
|
4737
|
+
function errorResult(code, message, recovery, { large = false, extra = null } = {}) {
|
|
4738
|
+
const payload = {
|
|
4739
|
+
status: "ERROR",
|
|
4740
|
+
error: { code, message, recovery },
|
|
4741
|
+
summary: message
|
|
4742
|
+
};
|
|
4743
|
+
if (extra && typeof extra === "object") {
|
|
4744
|
+
Object.assign(payload, extra);
|
|
4745
|
+
}
|
|
4746
|
+
return result(payload, { large });
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4725
4749
|
// server.mjs
|
|
4726
|
-
var version = true ? "1.
|
|
4750
|
+
var version = true ? "1.18.0" : (await null).createRequire(import.meta.url)("./package.json").version;
|
|
4751
|
+
var STATUS_ENUM = z2.enum(["OK", "ERROR", "AUTO_REBASED", "CONFLICT", "STALE", "INVALID", "NO_CHANGES", "CHANGED", "UNSUPPORTED"]);
|
|
4752
|
+
var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
|
|
4727
4753
|
var { server, StdioServerTransport } = await createServerRuntime({
|
|
4728
4754
|
name: "hex-line-mcp",
|
|
4729
4755
|
version
|
|
@@ -4759,7 +4785,16 @@ server.registerTool("read_file", {
|
|
|
4759
4785
|
verbosity: z2.enum(["minimal", "compact", "full"]).optional().describe("Response budget. `minimal` is discovery-first, `compact` adds revision context, `full` preserves the richest payload."),
|
|
4760
4786
|
edit_ready: flexBool().describe("Include hash/checksum edit protocol blocks explicitly. Default: false for discovery reads.")
|
|
4761
4787
|
}),
|
|
4762
|
-
|
|
4788
|
+
outputSchema: z2.object({
|
|
4789
|
+
status: STATUS_ENUM,
|
|
4790
|
+
path: z2.string().optional(),
|
|
4791
|
+
paths: z2.array(z2.string()).optional(),
|
|
4792
|
+
content: z2.string().optional(),
|
|
4793
|
+
edit_ready: z2.boolean().optional(),
|
|
4794
|
+
next_action: z2.string().optional(),
|
|
4795
|
+
error: ERROR_SHAPE
|
|
4796
|
+
}),
|
|
4797
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
4763
4798
|
}, async (rawParams) => {
|
|
4764
4799
|
const { path: p, paths: multi, offset, limit, ranges: rawRanges, plain, verbosity, edit_ready } = rawParams ?? {};
|
|
4765
4800
|
try {
|
|
@@ -4790,19 +4825,21 @@ next_hint: read_file path="${fp}" verbosity="compact"`);
|
|
|
4790
4825
|
ERROR: ${e.message}`);
|
|
4791
4826
|
}
|
|
4792
4827
|
}
|
|
4793
|
-
|
|
4828
|
+
const content2 = results.join("\n\n---\n\n");
|
|
4829
|
+
return result({ status: "OK", paths: multi, content: content2, edit_ready: !!edit_ready }, { large: !!edit_ready || readVerbosity === "full" || content2.length > 5e4 });
|
|
4794
4830
|
}
|
|
4795
4831
|
if (!p) throw new Error("Either 'path' or 'paths' is required");
|
|
4796
|
-
|
|
4832
|
+
const content = readFile2(p, {
|
|
4797
4833
|
offset,
|
|
4798
4834
|
limit: readLimit,
|
|
4799
4835
|
ranges,
|
|
4800
4836
|
plain: readPlain,
|
|
4801
4837
|
verbosity: readVerbosity,
|
|
4802
4838
|
editReady: !!edit_ready
|
|
4803
|
-
})
|
|
4839
|
+
});
|
|
4840
|
+
return result({ status: "OK", path: p, content, edit_ready: !!edit_ready }, { large: !!edit_ready || readVerbosity === "full" || content.length > 5e4 });
|
|
4804
4841
|
} catch (e) {
|
|
4805
|
-
return
|
|
4842
|
+
return errorResult(e.code || "READ_ERROR", e.message, e.recovery || "Check path and permissions");
|
|
4806
4843
|
}
|
|
4807
4844
|
});
|
|
4808
4845
|
server.registerTool("edit_file", {
|
|
@@ -4819,7 +4856,8 @@ server.registerTool("edit_file", {
|
|
|
4819
4856
|
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.'),
|
|
4820
4857
|
allow_external: flexBool().describe("Allow editing a path outside the current project root. Use only when you intentionally target a temp or external file.")
|
|
4821
4858
|
}),
|
|
4822
|
-
|
|
4859
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
|
|
4860
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
4823
4861
|
}, async (rawParams) => {
|
|
4824
4862
|
const { path: p, edits: json, dry_run, restore_indent, base_revision, conflict_policy, allow_external } = rawParams ?? {};
|
|
4825
4863
|
try {
|
|
@@ -4831,19 +4869,15 @@ server.registerTool("edit_file", {
|
|
|
4831
4869
|
throw new Error('edits: invalid JSON. Expected: [{"set_line":{"anchor":"xx.N","new_text":"..."}}]');
|
|
4832
4870
|
}
|
|
4833
4871
|
if (!Array.isArray(parsed) || !parsed.length) throw new Error("Edits: non-empty JSON array required");
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
conflictPolicy: conflict_policy
|
|
4842
|
-
})
|
|
4843
|
-
}]
|
|
4844
|
-
};
|
|
4872
|
+
const content = editFile(p, parsed, {
|
|
4873
|
+
dryRun: dry_run,
|
|
4874
|
+
restoreIndent: restore_indent,
|
|
4875
|
+
baseRevision: base_revision,
|
|
4876
|
+
conflictPolicy: conflict_policy
|
|
4877
|
+
});
|
|
4878
|
+
return result({ status: "OK", path: p, content }, { large: content.length > 5e4 });
|
|
4845
4879
|
} catch (e) {
|
|
4846
|
-
return
|
|
4880
|
+
return errorResult(e.code || "EDIT_ERROR", e.message, e.recovery || "Check anchors and checksums");
|
|
4847
4881
|
}
|
|
4848
4882
|
});
|
|
4849
4883
|
server.registerTool("write_file", {
|
|
@@ -4854,7 +4888,8 @@ server.registerTool("write_file", {
|
|
|
4854
4888
|
content: z2.string().describe("File content"),
|
|
4855
4889
|
allow_external: flexBool().describe("Allow writing a path outside the current project root. Use only when you intentionally target a temp or external file.")
|
|
4856
4890
|
}),
|
|
4857
|
-
|
|
4891
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), lines: z2.number().optional(), error: ERROR_SHAPE }),
|
|
4892
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
4858
4893
|
}, async (rawParams) => {
|
|
4859
4894
|
const { path: p, content, allow_external } = rawParams ?? {};
|
|
4860
4895
|
try {
|
|
@@ -4862,9 +4897,10 @@ server.registerTool("write_file", {
|
|
|
4862
4897
|
const abs = validateWritePath(p);
|
|
4863
4898
|
mkdirSync2(dirname6(abs), { recursive: true });
|
|
4864
4899
|
writeFileSync4(abs, content, "utf-8");
|
|
4865
|
-
|
|
4900
|
+
const lines = content.split("\n").length;
|
|
4901
|
+
return result({ status: "OK", path: p, lines });
|
|
4866
4902
|
} catch (e) {
|
|
4867
|
-
return
|
|
4903
|
+
return errorResult(e.code || "WRITE_ERROR", e.message, e.recovery || "Check path and permissions");
|
|
4868
4904
|
}
|
|
4869
4905
|
});
|
|
4870
4906
|
server.registerTool("grep_search", {
|
|
@@ -4889,7 +4925,8 @@ server.registerTool("grep_search", {
|
|
|
4889
4925
|
edit_ready: flexBool().describe("Preserve hash/checksum search hunks in `content` mode. Default: false."),
|
|
4890
4926
|
allow_large_output: flexBool().describe("Bypass the default content-mode block/char caps when you intentionally need a larger payload.")
|
|
4891
4927
|
}),
|
|
4892
|
-
|
|
4928
|
+
outputSchema: z2.object({ status: STATUS_ENUM, pattern: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
|
|
4929
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
4893
4930
|
}, async (rawParams) => {
|
|
4894
4931
|
const {
|
|
4895
4932
|
pattern,
|
|
@@ -4914,7 +4951,7 @@ server.registerTool("grep_search", {
|
|
|
4914
4951
|
const resolvedOutput = output ?? "summary";
|
|
4915
4952
|
const resolvedLimit = limit ?? (resolvedOutput === "summary" ? 20 : 100);
|
|
4916
4953
|
const resolvedTotalLimit = total_limit ?? (resolvedOutput === "summary" ? 50 : void 0);
|
|
4917
|
-
const
|
|
4954
|
+
const searchResult = await grepSearch(pattern, {
|
|
4918
4955
|
path: p,
|
|
4919
4956
|
glob,
|
|
4920
4957
|
type,
|
|
@@ -4932,9 +4969,9 @@ server.registerTool("grep_search", {
|
|
|
4932
4969
|
editReady: !!edit_ready,
|
|
4933
4970
|
allowLargeOutput: !!allow_large_output
|
|
4934
4971
|
});
|
|
4935
|
-
return {
|
|
4972
|
+
return result({ status: "OK", pattern, content: searchResult }, { large: !!allow_large_output || resolvedOutput === "content" });
|
|
4936
4973
|
} catch (e) {
|
|
4937
|
-
return
|
|
4974
|
+
return errorResult(e.code || "GREP_ERROR", e.message, e.recovery || "Check pattern syntax");
|
|
4938
4975
|
}
|
|
4939
4976
|
});
|
|
4940
4977
|
server.registerTool("outline", {
|
|
@@ -4943,14 +4980,15 @@ server.registerTool("outline", {
|
|
|
4943
4980
|
inputSchema: z2.object({
|
|
4944
4981
|
path: z2.string().describe("Source file path")
|
|
4945
4982
|
}),
|
|
4946
|
-
|
|
4983
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), error: ERROR_SHAPE }),
|
|
4984
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
4947
4985
|
}, async (rawParams) => {
|
|
4948
4986
|
const { path: p } = rawParams ?? {};
|
|
4949
4987
|
try {
|
|
4950
|
-
const
|
|
4951
|
-
return {
|
|
4988
|
+
const content = await fileOutline(p);
|
|
4989
|
+
return result({ status: "OK", path: p, content });
|
|
4952
4990
|
} catch (e) {
|
|
4953
|
-
return
|
|
4991
|
+
return errorResult(e.code || "OUTLINE_ERROR", e.message, e.recovery || "Check file path and language support");
|
|
4954
4992
|
}
|
|
4955
4993
|
});
|
|
4956
4994
|
server.registerTool("verify", {
|
|
@@ -4961,16 +4999,18 @@ server.registerTool("verify", {
|
|
|
4961
4999
|
checksums: z2.array(z2.string()).describe('Checksum strings, e.g. ["1-50:f7e2a1b0", "51-100:abcd1234"]'),
|
|
4962
5000
|
base_revision: z2.string().optional().describe("Optional prior revision to compare against latest state.")
|
|
4963
5001
|
}),
|
|
4964
|
-
|
|
5002
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), error: ERROR_SHAPE }),
|
|
5003
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
4965
5004
|
}, async (rawParams) => {
|
|
4966
5005
|
const { path: p, checksums, base_revision } = rawParams ?? {};
|
|
4967
5006
|
try {
|
|
4968
5007
|
if (!Array.isArray(checksums) || checksums.length === 0) {
|
|
4969
5008
|
throw new Error("checksums must be a non-empty array of strings");
|
|
4970
5009
|
}
|
|
4971
|
-
|
|
5010
|
+
const content = verifyChecksums(p, checksums, { baseRevision: base_revision });
|
|
5011
|
+
return result({ status: "OK", path: p, content });
|
|
4972
5012
|
} catch (e) {
|
|
4973
|
-
return
|
|
5013
|
+
return errorResult(e.code || "VERIFY_ERROR", e.message, e.recovery || "Check checksums format");
|
|
4974
5014
|
}
|
|
4975
5015
|
});
|
|
4976
5016
|
server.registerTool("inspect_path", {
|
|
@@ -4986,21 +5026,23 @@ server.registerTool("inspect_path", {
|
|
|
4986
5026
|
format: z2.enum(["compact", "full"]).optional().describe('"compact" = shorter path view, "full" = include sizes/metadata where available'),
|
|
4987
5027
|
verbosity: z2.enum(["minimal", "compact", "full"]).optional().describe("Response budget. `minimal` returns the shortest tree summary.")
|
|
4988
5028
|
}),
|
|
4989
|
-
|
|
5029
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
|
|
5030
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
4990
5031
|
}, async (rawParams) => {
|
|
4991
5032
|
const { path: p, max_depth, max_entries, gitignore, format, pattern, type: entryType, verbosity } = rawParams ?? {};
|
|
4992
5033
|
try {
|
|
4993
5034
|
const resolvedVerbosity = verbosity ?? "minimal";
|
|
4994
|
-
|
|
5035
|
+
const content = inspectPath(p, {
|
|
4995
5036
|
max_depth: max_depth ?? (pattern ? 20 : resolvedVerbosity === "full" ? 3 : 2),
|
|
4996
5037
|
gitignore,
|
|
4997
5038
|
format: format ?? (resolvedVerbosity === "full" ? "full" : "compact"),
|
|
4998
5039
|
pattern,
|
|
4999
5040
|
type: entryType,
|
|
5000
5041
|
max_entries
|
|
5001
|
-
})
|
|
5042
|
+
});
|
|
5043
|
+
return result({ status: "OK", path: p, content });
|
|
5002
5044
|
} catch (e) {
|
|
5003
|
-
return
|
|
5045
|
+
return errorResult(e.code || "INSPECT_ERROR", e.message, e.recovery || "Check path exists");
|
|
5004
5046
|
}
|
|
5005
5047
|
});
|
|
5006
5048
|
server.registerTool("changes", {
|
|
@@ -5010,13 +5052,15 @@ server.registerTool("changes", {
|
|
|
5010
5052
|
path: z2.string().describe("File or directory path"),
|
|
5011
5053
|
compare_against: z2.string().optional().describe('Git ref to compare against (default: "HEAD")')
|
|
5012
5054
|
}),
|
|
5013
|
-
|
|
5055
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
|
|
5056
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
5014
5057
|
}, async (rawParams) => {
|
|
5015
5058
|
const { path: p, compare_against } = rawParams ?? {};
|
|
5016
5059
|
try {
|
|
5017
|
-
|
|
5060
|
+
const content = await fileChanges(p, compare_against);
|
|
5061
|
+
return result({ status: "OK", path: p, content }, { large: content.length > 5e4 });
|
|
5018
5062
|
} catch (e) {
|
|
5019
|
-
return
|
|
5063
|
+
return errorResult(e.code || "CHANGES_ERROR", e.message, e.recovery || "Check git ref and path");
|
|
5020
5064
|
}
|
|
5021
5065
|
});
|
|
5022
5066
|
server.registerTool("bulk_replace", {
|
|
@@ -5031,7 +5075,8 @@ server.registerTool("bulk_replace", {
|
|
|
5031
5075
|
format: z2.enum(["compact", "full"]).optional().describe('"compact" (default) = summary only, "full" = include capped diffs'),
|
|
5032
5076
|
allow_external: flexBool().describe("Allow a replacement root outside the current project root. Use only when you intentionally target a temp or external directory.")
|
|
5033
5077
|
}),
|
|
5034
|
-
|
|
5078
|
+
outputSchema: z2.object({ status: STATUS_ENUM, path: z2.string().optional(), content: z2.string().optional(), next_action: z2.string().optional(), error: ERROR_SHAPE }),
|
|
5079
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }
|
|
5035
5080
|
}, async (rawParams) => {
|
|
5036
5081
|
try {
|
|
5037
5082
|
const params = rawParams ?? {};
|
|
@@ -5044,15 +5089,15 @@ server.registerTool("bulk_replace", {
|
|
|
5044
5089
|
throw new Error('replacements: invalid JSON. Expected: [{"old":"text","new":"replacement"}]');
|
|
5045
5090
|
}
|
|
5046
5091
|
const replacements = replacementPairsSchema.parse(replacementsInput);
|
|
5047
|
-
const
|
|
5092
|
+
const content = bulkReplace(
|
|
5048
5093
|
params.path,
|
|
5049
5094
|
params.glob || "**/*.{md,mjs,json,yml,ts,js}",
|
|
5050
5095
|
replacements,
|
|
5051
5096
|
{ dryRun: params.dry_run || false, maxFiles: params.max_files || 100, format: params.format }
|
|
5052
5097
|
);
|
|
5053
|
-
return {
|
|
5098
|
+
return result({ status: "OK", path: params.path, content }, { large: params.format === "full" });
|
|
5054
5099
|
} catch (e) {
|
|
5055
|
-
return
|
|
5100
|
+
return errorResult(e.code || "REPLACE_ERROR", e.message, e.recovery || "Check replacement pairs and scope");
|
|
5056
5101
|
}
|
|
5057
5102
|
});
|
|
5058
5103
|
var transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@levnikolaevich/hex-line-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.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.",
|