@levnikolaevich/hex-line-mcp 1.0.0 → 1.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.
package/server.mjs CHANGED
@@ -54,7 +54,7 @@ try {
54
54
  process.exit(1);
55
55
  }
56
56
 
57
- const server = new McpServer({ name: "hex-line-mcp", version: "1.0.0" });
57
+ const server = new McpServer({ name: "hex-line-mcp", version: "1.1.0" });
58
58
 
59
59
 
60
60
  // ==================== read_file ====================
@@ -62,10 +62,10 @@ const server = new McpServer({ name: "hex-line-mcp", version: "1.0.0" });
62
62
  server.registerTool("read_file", {
63
63
  title: "Read File",
64
64
  description:
65
- "Read a file with FNV-1a hash-annotated lines (tag.lineNum\\tcontent) and range checksums. " +
66
- "All file types: code, markdown, config, text. Directory listing if path is a directory. " +
67
- "For large code files: use outline first, then read_file with offset/limit. " +
68
- "For markdown/config: read_file directly.",
65
+ "Read a file with FNV-1a hash-annotated lines and range checksums. " +
66
+ "Directory listing if path is a directory. " +
67
+ "For files >100 lines: ALWAYS use outline first, then read_file with offset/limit for specific sections. " +
68
+ "Reading a 500+ line file in full wastes 75% of context tokens.",
69
69
  inputSchema: z.object({
70
70
  path: z.string().optional().describe("File or directory path"),
71
71
  paths: z.array(z.string()).optional().describe("Array of file paths to read (batch mode)"),
@@ -102,9 +102,8 @@ server.registerTool("edit_file", {
102
102
  title: "Edit File",
103
103
  description:
104
104
  "Edit a file using hash-verified anchors or text replacement. Returns diff. " +
105
- "Anchors: set_line {anchor:'ab.12',new_text:'...'}, replace_lines, insert_after. " +
106
- "Text: replace {old_text,new_text,all}. " +
107
- "For anchor-based edits, use read_file first to get hashes. For text replace, read_file is optional.",
105
+ "new_text replaces anchor range exactly — include boundary lines if you want to keep them. " +
106
+ "Preserve indentation from read_file. For anchor edits, read_file first to get hashes.",
108
107
  inputSchema: z.object({
109
108
  path: z.string().describe("File to edit"),
110
109
  edits: z.string().describe(
@@ -115,14 +114,15 @@ server.registerTool("edit_file", {
115
114
  '{"replace":{"old_text":"find","new_text":"replace","all":false}} — text match',
116
115
  ),
117
116
  dry_run: flexBool().describe("Preview changes without writing"),
117
+ restore_indent: flexBool().describe("Auto-fix indentation to match anchor (default: false)"),
118
118
  }),
119
119
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
120
120
  }, async (rawParams) => {
121
- const { path: p, edits: json, dry_run } = coerceParams(rawParams);
121
+ const { path: p, edits: json, dry_run, restore_indent } = coerceParams(rawParams);
122
122
  try {
123
123
  const parsed = JSON.parse(json);
124
124
  if (!Array.isArray(parsed) || !parsed.length) throw new Error("Edits: non-empty JSON array required");
125
- return { content: [{ type: "text", text: editFile(p, parsed, { dryRun: dry_run }) }] };
125
+ return { content: [{ type: "text", text: editFile(p, parsed, { dryRun: dry_run, restoreIndent: restore_indent }) }] };
126
126
  } catch (e) {
127
127
  return { content: [{ type: "text", text: e.message }], isError: true };
128
128
  }
@@ -160,9 +160,7 @@ server.registerTool("grep_search", {
160
160
  title: "Search Files",
161
161
  description:
162
162
  "Search file contents with ripgrep. Returns hash-annotated matches for direct editing. " +
163
- "ALWAYS prefer over shell grep/rg/findstr instant and returns edit-ready hashes. " +
164
- "Use to find code locations before read_file or edit_file. " +
165
- "When codegraph DB available (.codegraph/index.db), matches annotated with symbol type and call counts [fn N↓ M↑].",
163
+ "ALWAYS prefer over shell grep/rg/findstr. Use to find code before read_file or edit_file.",
166
164
  inputSchema: z.object({
167
165
  pattern: z.string().describe("Regex search pattern"),
168
166
  path: z.string().optional().describe("Search dir/file (default: cwd)"),
@@ -193,10 +191,8 @@ server.registerTool("outline", {
193
191
  title: "File Outline",
194
192
  description:
195
193
  "AST-based structural outline: functions, classes, interfaces with line ranges. " +
196
- "Code files only (.js/.ts/.py/.go/.rs/.java/.c/.cpp/.cs/.rb/.php/.kt/.swift/.sh). " +
197
- "NOT for .md/.json/.yaml/.txt — use read_file directly for those. " +
198
194
  "10-20 lines instead of 500 — 95% token reduction. " +
199
- "Output maps directly to read_file ranges. Use before reading large code files.",
195
+ "Use before reading large code files. NOT for .md/.json/.yaml use read_file.",
200
196
  inputSchema: z.object({
201
197
  path: z.string().describe("Source file path"),
202
198
  }),
@@ -218,8 +214,7 @@ server.registerTool("verify", {
218
214
  title: "Verify Checksums",
219
215
  description:
220
216
  "Check if range checksums from prior reads are still valid. " +
221
- "Single-line response when nothing changed. Avoids full re-read for staleness check. " +
222
- "Use to check if file changed since last read, without re-reading.",
217
+ "Single-line response when nothing changed. Use to check file staleness without re-reading.",
223
218
  inputSchema: z.object({
224
219
  path: z.string().describe("File path"),
225
220
  checksums: z.string().describe('JSON array of checksum strings, e.g. ["1-50:f7e2a1b0", "51-100:abcd1234"]'),
@@ -242,20 +237,23 @@ server.registerTool("verify", {
242
237
  server.registerTool("directory_tree", {
243
238
  title: "Directory Tree",
244
239
  description:
245
- "Compact directory tree with file sizes and .gitignore support. " +
246
- "Use to understand repo structure before reading files. " +
240
+ "Compact directory tree with .gitignore support. " +
241
+ "Supports pattern glob to find files/dirs by name (like find -name). " +
242
+ "Use to understand repo structure or find specific files/dirs. " +
247
243
  "Skips node_modules, .git, dist by default.",
248
244
  inputSchema: z.object({
249
245
  path: z.string().describe("Directory path"),
250
- max_depth: flexNum().describe("Max recursion depth (default: 3)"),
246
+ pattern: z.string().optional().describe('Glob filter on names (e.g. "*-mcp", "*.mjs"). Returns flat match list instead of tree'),
247
+ type: z.enum(["file", "dir", "all"]).optional().describe('"file", "dir", or "all" (default). Like find -type f/d'),
248
+ max_depth: flexNum().describe("Max recursion depth (default: 3, or 20 in pattern mode)"),
251
249
  gitignore: flexBool().describe("Respect .gitignore patterns (default: true)"),
252
250
  format: z.enum(["compact", "full"]).optional().describe('"compact" = names only, no sizes, depth 1. "full" = default with sizes'),
253
251
  }),
254
252
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
255
253
  }, async (rawParams) => {
256
- const { path: p, max_depth, gitignore, format } = coerceParams(rawParams);
254
+ const { path: p, max_depth, gitignore, format, pattern, type: entryType } = coerceParams(rawParams);
257
255
  try {
258
- return { content: [{ type: "text", text: directoryTree(p, { max_depth, gitignore, format }) }] };
256
+ return { content: [{ type: "text", text: directoryTree(p, { max_depth, gitignore, format, pattern, type: entryType }) }] };
259
257
  } catch (e) {
260
258
  return { content: [{ type: "text", text: e.message }], isError: true };
261
259
  }
@@ -289,7 +287,8 @@ server.registerTool("setup_hooks", {
289
287
  title: "Setup Hooks",
290
288
  description:
291
289
  "Configure hex-line hooks in CLI agent settings. " +
292
- "Claude: writes PreToolUse + PostToolUse to .claude/settings.local.json. " +
290
+ "Claude: writes hooks to ~/.claude/settings.json (global) with absolute path, " +
291
+ "removes old hooks from per-project settings.local.json. " +
293
292
  "Gemini/Codex: returns guidance (no hook support). " +
294
293
  "Idempotent: re-running produces no changes if already configured.",
295
294
  inputSchema: z.object({