@levnikolaevich/hex-line-mcp 1.0.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 +293 -0
- package/benchmark.mjs +1180 -0
- package/hook.mjs +299 -0
- package/lib/bulk-replace.mjs +55 -0
- package/lib/changes.mjs +174 -0
- package/lib/coerce.mjs +43 -0
- package/lib/edit.mjs +420 -0
- package/lib/graph-enrich.mjs +208 -0
- package/lib/hash.mjs +109 -0
- package/lib/info.mjs +109 -0
- package/lib/normalize.mjs +106 -0
- package/lib/outline.mjs +200 -0
- package/lib/read.mjs +129 -0
- package/lib/search.mjs +132 -0
- package/lib/security.mjs +114 -0
- package/lib/setup.mjs +132 -0
- package/lib/tree.mjs +162 -0
- package/lib/update-check.mjs +56 -0
- package/lib/verify.mjs +54 -0
- package/package.json +57 -0
- package/server.mjs +368 -0
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# hex-line-mcp
|
|
2
|
+
|
|
3
|
+
Hash-verified file editing MCP + token efficiency hook for AI coding agents.
|
|
4
|
+
|
|
5
|
+
Every line carries an FNV-1a content hash. Every edit must present those hashes back -- proving the agent is editing what it thinks it's editing. No stale context, no silent corruption.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
### 11 MCP Tools
|
|
10
|
+
|
|
11
|
+
| Tool | Description | Key Feature |
|
|
12
|
+
|------|-------------|-------------|
|
|
13
|
+
| `read_file` | Read file with hash-annotated lines and range checksums | Partial reads via `offset`/`limit` |
|
|
14
|
+
| `edit_file` | Hash-verified edits with anchor or text replacement | Returns compact diff via `diff` package |
|
|
15
|
+
| `write_file` | Create new file or overwrite, auto-creates parent dirs | Path validation, no hash overhead |
|
|
16
|
+
| `grep_search` | Search with ripgrep, returns hash-annotated matches | Edit-ready results -- search then edit directly |
|
|
17
|
+
| `outline` | AST-based structural overview via tree-sitter WASM | 95% token reduction (10 lines instead of 500) |
|
|
18
|
+
| `verify` | Check if held range checksums are still valid | Single-line response avoids full re-read |
|
|
19
|
+
| `directory_tree` | Compact directory tree with .gitignore support | Skips node_modules/.git, shows file sizes |
|
|
20
|
+
| `get_file_info` | File metadata without reading content | Size, lines, mtime, type, binary detection |
|
|
21
|
+
| `setup_hooks` | Configure PreToolUse + PostToolUse hooks for Claude/Gemini/Codex | One call sets up everything, idempotent |
|
|
22
|
+
| `changes` | Compare file against git ref, shows added/removed/modified symbols | AST-level semantic diff |
|
|
23
|
+
| `bulk_replace` | Search-and-replace across multiple files by glob | Per-file diffs, dry_run, max_files safety |
|
|
24
|
+
|
|
25
|
+
### Hooks (PreToolUse + PostToolUse)
|
|
26
|
+
|
|
27
|
+
| Event | Trigger | Action |
|
|
28
|
+
|-------|---------|--------|
|
|
29
|
+
| **PreToolUse** | Read/Edit/Write/Grep on text files | Blocks built-in, forces hex-line tools |
|
|
30
|
+
| **PreToolUse** | Bash with dangerous commands | Blocks `rm -rf /`, `git push --force`, etc. Agent must confirm with user |
|
|
31
|
+
| **PostToolUse** | Bash with 50+ lines output | RTK: deduplicates, truncates, summarizes |
|
|
32
|
+
| **SessionStart** | Session begins | Injects full tool preference list into agent context |
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Bash Redirects
|
|
36
|
+
|
|
37
|
+
PreToolUse also intercepts simple Bash commands: cat, head, tail, ls, tree, find, stat, wc -l, grep, rg, sed -i, diff — redirects to hex-line equivalents. Compound commands with pipes are allowed.
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
### MCP Server
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
claude mcp add -s user hex-line -- node path/to/mcp/hex-line-mcp/server.mjs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then install dependencies:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd mcp/hex-line-mcp && npm install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Hooks
|
|
53
|
+
|
|
54
|
+
Automatic setup (run once after MCP install):
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
mcp__hex-line__setup_hooks(agent="claude")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or manual — add to `.claude/settings.local.json`:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"hooks": {
|
|
65
|
+
"PreToolUse": [{"matcher": "Read|Edit|Write|Grep|Bash", "hooks": [{"type": "command", "command": "node mcp/hex-line-mcp/hook.mjs", "timeout": 5}]}],
|
|
66
|
+
"PostToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "node mcp/hex-line-mcp/hook.mjs", "timeout": 10}]}]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Token Efficiency
|
|
72
|
+
|
|
73
|
+
Benchmark v3 (46 code files, 9,134 lines, 18 scenarios):
|
|
74
|
+
|
|
75
|
+
| Scenario | Without | With Hex-line | Savings |
|
|
76
|
+
|----------|---------|---------------|--------|
|
|
77
|
+
| Read full (any size) | raw | hash-annotated | 6-8% |
|
|
78
|
+
| Outline+read (200-500L) | 10,723 ch | 3,347 ch | **69%** |
|
|
79
|
+
| Outline+read (500L+) | 39,617 ch | 9,531 ch | **76%** |
|
|
80
|
+
| Edit x5 sequential | 2,581 ch | 1,529 ch | **41%** |
|
|
81
|
+
| Verify checksums | 8,295 ch | 93 ch | **99%** |
|
|
82
|
+
| Directory tree | 80,853 ch | 22,120 ch | **73%** |
|
|
83
|
+
| File info | 368 ch | 195 ch | **47%** |
|
|
84
|
+
| bulk_replace (5 files) | 2,795 ch | 1,706 ch | **39%** |
|
|
85
|
+
| Changes (semantic diff) | 830 ch | 133 ch | **84%** |
|
|
86
|
+
| FILE_NOT_FOUND recovery | 2,020 ch | 283 ch | **86%** |
|
|
87
|
+
| Hash mismatch recovery | 8,918 ch | 423 ch | **95%** |
|
|
88
|
+
| Bash redirects (cat+ls+stat) | 54,658 ch | 25,153 ch | **54%** |
|
|
89
|
+
| Grep search | 3,938 ch | 4,091 ch | -4% |
|
|
90
|
+
|
|
91
|
+
**Average savings: 46%.** Break-even: ~50 lines. Hash overhead: negligible.
|
|
92
|
+
|
|
93
|
+
Reproduce: `node benchmark.mjs` or `node benchmark.mjs --repo /path/to/repo`
|
|
94
|
+
|
|
95
|
+
## Tools Reference
|
|
96
|
+
|
|
97
|
+
### read_file
|
|
98
|
+
|
|
99
|
+
Read a file with FNV-1a hash-annotated lines and range checksums. Supports directory listing.
|
|
100
|
+
|
|
101
|
+
| Parameter | Type | Required | Description |
|
|
102
|
+
|-----------|------|----------|-------------|
|
|
103
|
+
| `path` | string | yes | File or directory path |
|
|
104
|
+
| `offset` | number | no | Start line, 1-indexed (default: 1) |
|
|
105
|
+
| `limit` | number | no | Max lines to return (default: 2000, 0 = all) |
|
|
106
|
+
| `plain` | boolean | no | Omit hashes, output `lineNum\|content` instead |
|
|
107
|
+
|
|
108
|
+
Output format:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
ab.1 import { resolve } from "node:path";
|
|
112
|
+
cd.2 import { readFileSync } from "node:fs";
|
|
113
|
+
...
|
|
114
|
+
checksum: 1-50:f7e2a1b0
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### edit_file
|
|
118
|
+
|
|
119
|
+
Edit using hash-verified anchors or text replacement. Returns a unified diff.
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Required | Description |
|
|
122
|
+
|-----------|------|----------|-------------|
|
|
123
|
+
| `path` | string | yes | File to edit |
|
|
124
|
+
| `edits` | string | yes | JSON array of edit operations (see below) |
|
|
125
|
+
| `dry_run` | boolean | no | Preview changes without writing |
|
|
126
|
+
|
|
127
|
+
Edit operations (JSON array):
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
[
|
|
131
|
+
{"set_line": {"anchor": "ab.12", "new_text": "replacement line"}},
|
|
132
|
+
{"replace_lines": {"start_anchor": "ab.10", "end_anchor": "cd.15", "new_text": "..."}},
|
|
133
|
+
{"insert_after": {"anchor": "ab.20", "text": "inserted line"}},
|
|
134
|
+
{"replace": {"old_text": "find this", "new_text": "replace with", "all": false}}
|
|
135
|
+
]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### write_file
|
|
139
|
+
|
|
140
|
+
Create a new file or overwrite an existing one. Creates parent directories automatically.
|
|
141
|
+
|
|
142
|
+
| Parameter | Type | Required | Description |
|
|
143
|
+
|-----------|------|----------|-------------|
|
|
144
|
+
| `path` | string | yes | File path |
|
|
145
|
+
| `content` | string | yes | File content |
|
|
146
|
+
|
|
147
|
+
### grep_search
|
|
148
|
+
|
|
149
|
+
Search file contents using ripgrep with hash-annotated results.
|
|
150
|
+
|
|
151
|
+
| Parameter | Type | Required | Description |
|
|
152
|
+
|-----------|------|----------|-------------|
|
|
153
|
+
| `pattern` | string | yes | Regex search pattern |
|
|
154
|
+
| `path` | string | no | Directory or file to search (default: cwd) |
|
|
155
|
+
| `glob` | string | no | Glob filter, e.g. `"*.ts"` |
|
|
156
|
+
| `type` | string | no | File type filter, e.g. `"js"`, `"py"` |
|
|
157
|
+
| `case_insensitive` | boolean | no | Ignore case |
|
|
158
|
+
| `context` | number | no | Context lines around matches |
|
|
159
|
+
| `limit` | number | no | Max matches per file (default: 100) |
|
|
160
|
+
|
|
161
|
+
### outline
|
|
162
|
+
|
|
163
|
+
AST-based structural outline: functions, classes, interfaces with line ranges.
|
|
164
|
+
|
|
165
|
+
| Parameter | Type | Required | Description |
|
|
166
|
+
|-----------|------|----------|-------------|
|
|
167
|
+
| `path` | string | yes | Source file path |
|
|
168
|
+
|
|
169
|
+
Supported languages: JavaScript, TypeScript (JSX/TSX), Python, Go, Rust, Java, C, C++, C#, Ruby, PHP, Kotlin, Swift, Bash -- 15+ via tree-sitter WASM.
|
|
170
|
+
|
|
171
|
+
Not for `.md`, `.json`, `.yaml`, `.txt` -- use `read_file` directly for those.
|
|
172
|
+
|
|
173
|
+
### verify
|
|
174
|
+
|
|
175
|
+
Check if range checksums from a prior read are still valid.
|
|
176
|
+
|
|
177
|
+
| Parameter | Type | Required | Description |
|
|
178
|
+
|-----------|------|----------|-------------|
|
|
179
|
+
| `path` | string | yes | File path |
|
|
180
|
+
| `checksums` | string | yes | JSON array of checksum strings, e.g. `["1-50:f7e2a1b0"]` |
|
|
181
|
+
|
|
182
|
+
Returns a single-line confirmation or lists changed ranges.
|
|
183
|
+
|
|
184
|
+
### directory_tree
|
|
185
|
+
|
|
186
|
+
Compact directory tree with .gitignore support and file sizes.
|
|
187
|
+
|
|
188
|
+
| Parameter | Type | Required | Description |
|
|
189
|
+
|-----------|------|----------|-------------|
|
|
190
|
+
| `path` | string | yes | Directory path |
|
|
191
|
+
| `max_depth` | number | no | Max recursion depth (default: 3) |
|
|
192
|
+
| `gitignore` | boolean | no | Respect .gitignore patterns (default: true) |
|
|
193
|
+
|
|
194
|
+
Skips `node_modules`, `.git`, `dist`, `build`, `__pycache__`, `.next`, `coverage` by default.
|
|
195
|
+
|
|
196
|
+
### get_file_info
|
|
197
|
+
|
|
198
|
+
File metadata without reading content.
|
|
199
|
+
|
|
200
|
+
| Parameter | Type | Required | Description |
|
|
201
|
+
|-----------|------|----------|-------------|
|
|
202
|
+
| `path` | string | yes | File path |
|
|
203
|
+
|
|
204
|
+
Returns: size, line count, modification time (absolute + relative), file type, binary detection.
|
|
205
|
+
|
|
206
|
+
## Hook
|
|
207
|
+
|
|
208
|
+
The unified PostToolUse hook (`hook.mjs`) handles two concerns:
|
|
209
|
+
|
|
210
|
+
### Hex-line Reminder
|
|
211
|
+
|
|
212
|
+
Triggers on built-in `Read`, `Edit`, `Write`, `Grep` tool usage for text files. Outputs a short reminder to stderr (exit code 2) nudging the agent to use the corresponding hex-line tool instead.
|
|
213
|
+
|
|
214
|
+
Binary files (images, PDFs, notebooks, archives, executables, fonts, media) are excluded -- those should use built-in tools.
|
|
215
|
+
|
|
216
|
+
### RTK Output Filter
|
|
217
|
+
|
|
218
|
+
Triggers on `Bash` tool output exceeding 50 lines. Pipeline:
|
|
219
|
+
|
|
220
|
+
1. **Detect command type** -- npm install, test, build, pip install, git verbose, or generic
|
|
221
|
+
2. **Type-specific summary** -- extracts key metrics (e.g., `npm install: 42 added, 3 warnings`)
|
|
222
|
+
3. **Normalize** -- replaces UUIDs, timestamps, IPs, hex values, large numbers with placeholders
|
|
223
|
+
4. **Deduplicate** -- collapses identical normalized lines with `(xN)` counts
|
|
224
|
+
5. **Truncate** -- keeps first 12 + last 12 lines, omits the middle
|
|
225
|
+
|
|
226
|
+
Configuration constants in `hook.mjs`:
|
|
227
|
+
|
|
228
|
+
| Constant | Default | Purpose |
|
|
229
|
+
|----------|---------|---------|
|
|
230
|
+
| `LINE_THRESHOLD` | 50 | Minimum lines to trigger filtering |
|
|
231
|
+
| `TRUNCATE_LIMIT` | 30 | Lines below this are kept as-is after dedup |
|
|
232
|
+
| `HEAD_LINES` | 12 | Lines to keep from start |
|
|
233
|
+
| `TAIL_LINES` | 12 | Lines to keep from end |
|
|
234
|
+
|
|
235
|
+
## Architecture
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
hex-line-mcp/
|
|
239
|
+
server.mjs MCP server (stdio transport, 6 tools)
|
|
240
|
+
hook.mjs PostToolUse hook (reminder + RTK filter)
|
|
241
|
+
package.json
|
|
242
|
+
lib/
|
|
243
|
+
hash.mjs FNV-1a hashing, 2-char tags, range checksums
|
|
244
|
+
read.mjs File reading with hash annotation
|
|
245
|
+
edit.mjs Anchor-based and text-based edits, diff output
|
|
246
|
+
search.mjs ripgrep wrapper with hash-annotated results
|
|
247
|
+
outline.mjs tree-sitter WASM AST outline
|
|
248
|
+
verify.mjs Range checksum verification
|
|
249
|
+
security.mjs Path validation, binary detection, size limits
|
|
250
|
+
normalize.mjs Output normalization, deduplication, truncation
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Hash Format
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
ab.42 const x = calculateTotal(items);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
- `ab` -- 2-char FNV-1a tag derived from content (whitespace-normalized)
|
|
260
|
+
- `42` -- line number (1-indexed)
|
|
261
|
+
- Tab separator, then original content
|
|
262
|
+
- Tag alphabet: `abcdefghijklmnopqrstuvwxyz234567` (32 symbols, bitwise selection)
|
|
263
|
+
|
|
264
|
+
### Range Checksums
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
checksum: 1-50:f7e2a1b0
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
FNV-1a accumulator over all line hashes in the range (little-endian byte feed). Detects changes to any line, even ones not being edited.
|
|
271
|
+
|
|
272
|
+
### Security
|
|
273
|
+
|
|
274
|
+
- Path canonicalization via `realpathSync` (resolves symlinks)
|
|
275
|
+
- Binary file detection (null byte scan in first 8KB)
|
|
276
|
+
- 10 MB file size limit
|
|
277
|
+
- Write path validation (ancestor directory must exist)
|
|
278
|
+
- Directory restrictions delegated to Claude Code sandbox
|
|
279
|
+
|
|
280
|
+
## Differences from trueline-mcp
|
|
281
|
+
|
|
282
|
+
| Aspect | hex-line-mcp | trueline-mcp |
|
|
283
|
+
|--------|---------------|--------------|
|
|
284
|
+
| Hash algorithm | FNV-1a (pure JS, zero dependencies) | xxHash (native addon) |
|
|
285
|
+
| Diff output | Compact unified diff via `diff` npm package | Custom diff implementation |
|
|
286
|
+
| Hook | Unified `hook.mjs` (reminder + RTK filter) | Separate hook scripts |
|
|
287
|
+
| Path security | Canonicalization + binary detection, no ALLOWED_DIRS | Explicit ALLOWED_DIRS allowlist |
|
|
288
|
+
| Transport | stdio only | stdio |
|
|
289
|
+
| Outline | tree-sitter WASM (15+ languages) | tree-sitter WASM |
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT
|