svn-agent-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/.editorconfig +12 -0
- package/.gitattributes +7 -0
- package/.github/workflows/ci.yml +39 -0
- package/.svn-mcp-policy.json +9 -0
- package/CHANGELOG.md +248 -0
- package/CONTRIBUTING.md +49 -0
- package/LICENSE +201 -0
- package/README.md +143 -0
- package/SECURITY.md +21 -0
- package/THIRD_PARTY_CHECKSUMS.txt +52 -0
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/bin/SlikSvn-DB44-20-x64.dll +0 -0
- package/bin/SlikSvn-libapr-1.dll +0 -0
- package/bin/SlikSvn-libaprutil-1.dll +0 -0
- package/bin/SlikSvn-libcrypto-3-x64.dll +0 -0
- package/bin/SlikSvn-libintl.dll +0 -0
- package/bin/SlikSvn-libsasl21.dll +0 -0
- package/bin/SlikSvn-libssl-3-x64.dll +0 -0
- package/bin/SlikSvn-libsvn_client-1.dll +0 -0
- package/bin/SlikSvn-libsvn_delta-1.dll +0 -0
- package/bin/SlikSvn-libsvn_diff-1.dll +0 -0
- package/bin/SlikSvn-libsvn_fs-1.dll +0 -0
- package/bin/SlikSvn-libsvn_fs_base-1.dll +0 -0
- package/bin/SlikSvn-libsvn_fs_fs-1.dll +0 -0
- package/bin/SlikSvn-libsvn_fs_util-1.dll +0 -0
- package/bin/SlikSvn-libsvn_fs_x-1.dll +0 -0
- package/bin/SlikSvn-libsvn_ra-1.dll +0 -0
- package/bin/SlikSvn-libsvn_repos-1.dll +0 -0
- package/bin/SlikSvn-libsvn_subr-1.dll +0 -0
- package/bin/SlikSvn-libsvn_wc-1.dll +0 -0
- package/bin/System64/concrt140.dll +0 -0
- package/bin/System64/msvcp140.dll +0 -0
- package/bin/System64/msvcp140_1.dll +0 -0
- package/bin/System64/msvcp140_2.dll +0 -0
- package/bin/System64/msvcp140_atomic_wait.dll +0 -0
- package/bin/System64/msvcp140_codecvt_ids.dll +0 -0
- package/bin/System64/vccorlib140.dll +0 -0
- package/bin/System64/vcruntime140.dll +0 -0
- package/bin/System64/vcruntime140_1.dll +0 -0
- package/bin/dos2unix.exe +0 -0
- package/bin/engines/capi.dll +0 -0
- package/bin/libsvnjavahl-1.dll +0 -0
- package/bin/mac2unix.exe +0 -0
- package/bin/svn-populate-node-origins-index.exe +0 -0
- package/bin/svn.exe +0 -0
- package/bin/svnadmin.exe +0 -0
- package/bin/svnauthz-validate.exe +0 -0
- package/bin/svnauthz.exe +0 -0
- package/bin/svnbench.exe +0 -0
- package/bin/svndumpfilter.exe +0 -0
- package/bin/svnfsfs.exe +0 -0
- package/bin/svnlook.exe +0 -0
- package/bin/svnmucc.exe +0 -0
- package/bin/svnrdump.exe +0 -0
- package/bin/svnserve.exe +0 -0
- package/bin/svnsync.exe +0 -0
- package/bin/svnversion.exe +0 -0
- package/bin/unix2dos.exe +0 -0
- package/bin/unix2mac.exe +0 -0
- package/dist/envelope.d.ts +39 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +137 -0
- package/dist/envelope.js.map +1 -0
- package/dist/eol.d.ts +15 -0
- package/dist/eol.d.ts.map +1 -0
- package/dist/eol.js +119 -0
- package/dist/eol.js.map +1 -0
- package/dist/guards.d.ts +26 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +306 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +221 -0
- package/dist/index.js.map +1 -0
- package/dist/parse/commitText.d.ts +2 -0
- package/dist/parse/commitText.d.ts.map +1 -0
- package/dist/parse/commitText.js +9 -0
- package/dist/parse/commitText.js.map +1 -0
- package/dist/parse/diffText.d.ts +7 -0
- package/dist/parse/diffText.d.ts.map +1 -0
- package/dist/parse/diffText.js +55 -0
- package/dist/parse/diffText.js.map +1 -0
- package/dist/parse/infoXml.d.ts +3 -0
- package/dist/parse/infoXml.d.ts.map +1 -0
- package/dist/parse/infoXml.js +35 -0
- package/dist/parse/infoXml.js.map +1 -0
- package/dist/parse/logXml.d.ts +10 -0
- package/dist/parse/logXml.d.ts.map +1 -0
- package/dist/parse/logXml.js +39 -0
- package/dist/parse/logXml.js.map +1 -0
- package/dist/parse/statusXml.d.ts +6 -0
- package/dist/parse/statusXml.d.ts.map +1 -0
- package/dist/parse/statusXml.js +64 -0
- package/dist/parse/statusXml.js.map +1 -0
- package/dist/parse/updateText.d.ts +6 -0
- package/dist/parse/updateText.d.ts.map +1 -0
- package/dist/parse/updateText.js +67 -0
- package/dist/parse/updateText.js.map +1 -0
- package/dist/runner.d.ts +41 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +270 -0
- package/dist/runner.js.map +1 -0
- package/dist/tools/composite.d.ts +15 -0
- package/dist/tools/composite.d.ts.map +1 -0
- package/dist/tools/composite.js +238 -0
- package/dist/tools/composite.js.map +1 -0
- package/dist/tools/diagnose.d.ts +6 -0
- package/dist/tools/diagnose.d.ts.map +1 -0
- package/dist/tools/diagnose.js +131 -0
- package/dist/tools/diagnose.js.map +1 -0
- package/dist/tools/mutating.d.ts +80 -0
- package/dist/tools/mutating.d.ts.map +1 -0
- package/dist/tools/mutating.js +558 -0
- package/dist/tools/mutating.js.map +1 -0
- package/dist/tools/readonly.d.ts +52 -0
- package/dist/tools/readonly.d.ts.map +1 -0
- package/dist/tools/readonly.js +638 -0
- package/dist/tools/readonly.js.map +1 -0
- package/dist/tools/selfcheck.d.ts +5 -0
- package/dist/tools/selfcheck.d.ts.map +1 -0
- package/dist/tools/selfcheck.js +91 -0
- package/dist/tools/selfcheck.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/README.md +11 -0
- package/docs/SPEC.md +964 -0
- package/docs/decisions/ADR-001-use-spec-v1-architecture.md +25 -0
- package/docs/decisions/ADR-002-use-capability-based-generic-svn-policy.md +31 -0
- package/docs/decisions/ADR-003-use-plug-and-play-global-registration.md +39 -0
- package/docs/decisions/ADR-004-borrow-diagnostics-not-external-mcp-surface.md +39 -0
- package/docs/svnrules.md +84 -0
- package/docs/toolrules.md +34 -0
- package/jest.config.cjs +14 -0
- package/package.json +51 -0
- package/scripts/clean.mjs +27 -0
- package/scripts/prepare-release.mjs +84 -0
- package/src/envelope.ts +182 -0
- package/src/eol.ts +137 -0
- package/src/guards.ts +362 -0
- package/src/index.ts +364 -0
- package/src/parse/commitText.ts +9 -0
- package/src/parse/diffText.ts +66 -0
- package/src/parse/infoXml.ts +51 -0
- package/src/parse/logXml.ts +64 -0
- package/src/parse/statusXml.ts +87 -0
- package/src/parse/updateText.ts +76 -0
- package/src/runner.ts +344 -0
- package/src/tools/composite.ts +289 -0
- package/src/tools/diagnose.ts +150 -0
- package/src/tools/mutating.ts +694 -0
- package/src/tools/readonly.ts +786 -0
- package/src/tools/selfcheck.ts +97 -0
- package/src/types.ts +76 -0
- package/tests/README.md +9 -0
- package/tests/diff-redaction.test.ts +146 -0
- package/tests/entrypoint.test.ts +49 -0
- package/tests/guards-eol.test.ts +170 -0
- package/tests/integration-svn.test.ts +709 -0
- package/tests/parsers.test.ts +119 -0
- package/tests/runner.test.ts +159 -0
- package/tests/selfcheck.test.ts +22 -0
- package/tsconfig.json +30 -0
package/docs/SPEC.md
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
# svn-agent — Generic Implementation Spec
|
|
2
|
+
|
|
3
|
+
**Spec version 1.17 — public implementation contract. Single source of truth.**
|
|
4
|
+
This document describes the current generic SVN MCP design without deployment-specific paths,
|
|
5
|
+
hostnames, or product-specific role assignments. Date: 2026-07-08.
|
|
6
|
+
|
|
7
|
+
**What this is:** one document containing the pain points, the resolution strategy, the full
|
|
8
|
+
architecture and tool contracts for a strict SVN MCP server, companion operational guidance, and
|
|
9
|
+
the historical development plan with verification gates. A maintainer should be able to implement
|
|
10
|
+
or review the server from this document and the source tree.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Pain points (why this exists)
|
|
15
|
+
|
|
16
|
+
Automated clients working SVN checkouts across multiple projects waste large amounts of time on SVN
|
|
17
|
+
housekeeping. The workflow usually has write-capable clients that may mutate SVN state and
|
|
18
|
+
read-only clients that may inspect but must not mutate. Deployments choose which client fills
|
|
19
|
+
each capability. The MCP must enforce permissions by configuration, not by product name.
|
|
20
|
+
|
|
21
|
+
Common SVN automation friction splits into three buckets:
|
|
22
|
+
|
|
23
|
+
**P1 — The client loop (dominant, ~70–80%).** A single commit-prep today costs 5–8 separate
|
|
24
|
+
shell calls (status → diff → EOL check → EOL fix → re-diff → commit → post-status). Every call
|
|
25
|
+
is a full model round trip; every shell call can hit a permission prompt, and in an unattended
|
|
26
|
+
session one prompt stalls the run until a human returns. Clients also re-derive the SVN policy
|
|
27
|
+
(exact diff flags, `-F` rule, never-commit list) from `docs/svnrules.md` every session —
|
|
28
|
+
repeated reasoning overhead, and each hand-composed command is a chance to get policy wrong.
|
|
29
|
+
|
|
30
|
+
**P2 — EOL damage that shouldn't exist.** Many Windows SVN source trees require CRLF +
|
|
31
|
+
`svn:eol-style=native`, no BOM. Automated file-creation tools can create new files with bare LF (`\n` in the
|
|
32
|
+
content string goes to disk as-is); shell heredocs/redirects do the same. The repo property only
|
|
33
|
+
normalizes at commit, so the working file stays LF, `svn diff` shows whole-file churn, and the
|
|
34
|
+
client burns a detect → `unix2dos` → re-diff loop. All of it is remediation for damage that is
|
|
35
|
+
preventable at write time. (Patch-based edits often preserve EOL; the damage paths are new-file writes and
|
|
36
|
+
shell output.)
|
|
37
|
+
|
|
38
|
+
**P3 — Raw `svn.exe` speed on Windows.** `svn status/diff` stat thousands of files plus the
|
|
39
|
+
`.svn` pristine store; Microsoft Defender real-time scanning taxes each of those file
|
|
40
|
+
operations (typically 2–10× on file-heavy svn work). Unscoped status/diff over the whole
|
|
41
|
+
working copy multiplies the cost.
|
|
42
|
+
|
|
43
|
+
## 2. Resolution strategy (pain → fix mapping)
|
|
44
|
+
|
|
45
|
+
| Pain | Fix | Where in this doc |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| P1: 5–8 round trips per commit | Composite MCP tools: `svn_precommit` + `svn_commit` = 2 calls total | §8.3, §8.4 |
|
|
48
|
+
| P1: permission-prompt stalls | Allowlist read-only tools; only mutations prompt | §10.4 |
|
|
49
|
+
| P1: policy re-derivation + drift | Policy baked into the MCP as defaults & guards; read-only clients hard-READONLY | §7 |
|
|
50
|
+
| P1: raw-diff dumping into context | Structured JSON envelope, per-file ± counts, line-capped excerpts | §6.4, §8.3 |
|
|
51
|
+
| P2: LF files born from file-creation tools | Client write hook or MCP repair normalizes CRLF/no-BOM | §10.1 |
|
|
52
|
+
| P2: new files missing eol-style prop | Repo-root inherited `svn:auto-props` (SVN ≥1.8) | §10.2 |
|
|
53
|
+
| P2: remediation loop when it does happen | `eol_fix_verified`: fix + proof re-diff in one call | §8.3 |
|
|
54
|
+
| P3: Defender tax | Optional path exclusion for a chosen working-copy root (operator decision) | §10.3 |
|
|
55
|
+
| P3: unscoped commands | Tools require/target explicit paths structurally | §7 |
|
|
56
|
+
|
|
57
|
+
Rejected options (so maintainers do not re-litigate them): **forking an existing permissive SVN
|
|
58
|
+
MCP** (optional commit paths, dangerous update accept-modes, PATH/env setup, credentials in
|
|
59
|
+
environment variables; the safety layer is most of the code, so write from scratch). Safe ideas
|
|
60
|
+
from external projects may be borrowed only when they preserve this MCP's guard model; the current
|
|
61
|
+
baseline borrows the read-only diagnose/error-taxonomy pattern, not permissive mutating or
|
|
62
|
+
configuration semantics.
|
|
63
|
+
**`exclusive-locking=true`** in the svn runtime config
|
|
64
|
+
(write-capable clients, read-only clients, and GUI SVN tools may share the WC; exclusive SQLite locking
|
|
65
|
+
makes them error on each other); **pristine-less checkout `--store-pristine=no`** (needs SVN ≥1.15, and makes `diff` —
|
|
66
|
+
the hottest path — hit the network); **svn client upgrade** (no measurable win for this
|
|
67
|
+
workflow).
|
|
68
|
+
|
|
69
|
+
## 3. Reference environment assumptions
|
|
70
|
+
|
|
71
|
+
- The full Windows SVN `bin` payload and full EOL converter `bin` payload are bundled under
|
|
72
|
+
`<MCP_HOME>\svn-agent\bin` and copied into every release under `releases\v<version>\bin`.
|
|
73
|
+
The reference implementation targets SVN 1.14+ behavior and should probe the exact client at
|
|
74
|
+
startup.
|
|
75
|
+
- Normal end-user configuration needs no environment variables and no project-specific `cwd`.
|
|
76
|
+
`SVN_AGENT_BIN_DIR`, `SVN_AGENT_SVN_PATH`, and `SVN_AGENT_DOS2UNIX_DIR` are development/test
|
|
77
|
+
overrides; PATH lookup is only a final fallback when the bundled tools are missing.
|
|
78
|
+
- Each tool call operates against a caller-provided `cwd` or an inferred working copy from absolute
|
|
79
|
+
paths. Relative paths require explicit per-call `cwd`. A single MCP registration may service many
|
|
80
|
+
SVN working copies on the same machine.
|
|
81
|
+
- Project text policy is discovered from SVN properties and local bytes. The default Windows
|
|
82
|
+
remediation target is CRLF + `svn:eol-style=native`, no BOM, but this must not hard-code one
|
|
83
|
+
repository's language or folder names.
|
|
84
|
+
- MCP installation home is chosen by the deployer, for example `<MCP_HOME>\svn-agent`.
|
|
85
|
+
- Node ≥ 20 is required.
|
|
86
|
+
|
|
87
|
+
## 4. Locked decisions (no open questions)
|
|
88
|
+
|
|
89
|
+
| # | Decision | Rationale |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| D1 | Write from scratch in TypeScript/Node; do **not** fork an existing SVN MCP | Safety layer is the product; forking inherits a permissive surface |
|
|
92
|
+
| D2 | Read-only safety = launch with `--readonly` (legacy/dev env `SVN_AGENT_READONLY=1` also works); every mutating tool refuses | Simple, unbypassable, matches "read-only clients never change SVN state" |
|
|
93
|
+
| D3 | Mixed-revision WC on commit → **warn in `note`, proceed** | Caller decides; refusing blocks legitimate scoped commits |
|
|
94
|
+
| D4 | `riskAck:true` required for mechanically detectable risky slices (§7 G6); undetectable risk categories stay the calling client's approval-gate duty | Encodes the risky-slice gate without pretending to detect the undetectable |
|
|
95
|
+
| D5 | Branch/switch/merge/relocate/delete: **out of v0.1** | Not needed for daily flow; each is high-risk |
|
|
96
|
+
| D6 | Versioning: **semver**, first release `v0.1.0`; `current` junction → `releases\v0.1.0` | One pin, easy rollback |
|
|
97
|
+
| D7 | Prefer bundled `bin` tools; env overrides win; PATH is final fallback | Self-contained runtime, no client time spent locating tools |
|
|
98
|
+
| D8 | Commit message format checked, **warn not refuse** | Format is policy but judgment; a hard block would fight legitimate cases |
|
|
99
|
+
| D9 | Commit message via temp **`-F` file outside the WC**, never `-m` | Encodes the shared SVN policy |
|
|
100
|
+
| D10 | `svn_update` needs explicit `paths[]` or `updateAll:true`; always `--accept postpone` | Update is operator-gated; conflicts must surface, never auto-resolve |
|
|
101
|
+
| D11 | XML output (`--xml`) for status/info/log parsing; regex only where svn has no XML (diff, update, commit) | Locale-proof, stable parsing |
|
|
102
|
+
| D12 | ESM TypeScript, strict mode; deps only `@modelcontextprotocol/sdk`, `zod`, `fast-xml-parser` | Small, auditable |
|
|
103
|
+
| D13 | Server registered under the name **`svn`**; tools named `svn_*` / `eol_*` | Short, unambiguous |
|
|
104
|
+
| D14 | External SVN MCPs are reference material, not the base implementation | Borrow diagnostics/docs lessons; reject force flags, optional broad commits, shell execution, credential env vars, and repo-specific registration |
|
|
105
|
+
|
|
106
|
+
## 5. Generic SVN policy inlined
|
|
107
|
+
|
|
108
|
+
These are restated here so the implementer does not need deployment-specific rule files:
|
|
109
|
+
|
|
110
|
+
1. Scoped commands only; commit prep = scoped MCP `svn_status` + scoped MCP `svn_diff` on
|
|
111
|
+
intended paths. `svn_diff` owns the internal ignored-EOL diff command by default.
|
|
112
|
+
2. `svn update` only on explicit operator request; **never** as a default preflight.
|
|
113
|
+
3. Commit: message file + explicit file list — `svn commit -F <msgfile> <path1> <path2>`;
|
|
114
|
+
never bare inline `-m`. After commit, report the revision and clean scoped status.
|
|
115
|
+
4. Risky slices (large, destructive, schema-changing, version-bumping, build-system-changing,
|
|
116
|
+
delete-heavy, security-sensitive, scope-unclear) stop for operator approval before commit.
|
|
117
|
+
5. Read-only instances never commit, stage, revert, update, or change SVN state. They
|
|
118
|
+
may report the intended fix or commit plan for a write-capable client.
|
|
119
|
+
6. EOL: preserve existing encoding/EOL; never normalize whole trees; EOL-only churn is not a
|
|
120
|
+
code change (check = empty MCP `svn_diff`); fix a single failing file with MCP
|
|
121
|
+
`eol_fix_verified`, which invokes `unix2dos`/`dos2unix` directly; never rewrite bytes via
|
|
122
|
+
PowerShell/in-process.
|
|
123
|
+
7. For managed project working copies, never commit: `bin/`, `obj/`, `.vs/`, generated output,
|
|
124
|
+
`*.db`, `scratch/**`, secrets, keys, certificates, tool caches, or unrelated drive-by changes.
|
|
125
|
+
These guards are segment-aware so nested build output such as `src/App/bin/Debug/**` is also
|
|
126
|
+
blocked. A repository may version an optional `.svn-mcp-policy.json` to allow intentional
|
|
127
|
+
payloads; the MCP repository uses that to allow its root `bin/` runtime toolchain and
|
|
128
|
+
versioned release payloads without weakening normal project defaults.
|
|
129
|
+
8. Commit message format:
|
|
130
|
+
```
|
|
131
|
+
<short summary>
|
|
132
|
+
|
|
133
|
+
- <logical change group>
|
|
134
|
+
- <verification performed>
|
|
135
|
+
- <behavior impact, or "No behavior changes">
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 6. Architecture
|
|
139
|
+
|
|
140
|
+
### 6.1 Process model & layout
|
|
141
|
+
|
|
142
|
+
Stdio MCP server, one process per client instance. Write-capable clients launch normally;
|
|
143
|
+
read-only clients launch with `--readonly`. Thin wrapper: every tool call launches `svn.exe` (or
|
|
144
|
+
`unix2dos`/`dos2unix`) through `execFile` or streaming `spawn` with `shell:false` — **no shell**,
|
|
145
|
+
no quoting pitfalls, and never an in-process rewrite of tracked file bytes.
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
<MCP_HOME>\svn-agent\
|
|
149
|
+
src\
|
|
150
|
+
index.ts # server bootstrap, tool registration, READONLY gate
|
|
151
|
+
runner.ts # no-shell process wrappers: timeout, bundled-bin lookup, streaming, redaction
|
|
152
|
+
envelope.ts # Envelope type + builders (ok/fail)
|
|
153
|
+
guards.ts # G1–G7 guard framework (§7)
|
|
154
|
+
parse\
|
|
155
|
+
statusXml.ts, infoXml.ts, logXml.ts # --xml parsers (fast-xml-parser)
|
|
156
|
+
diffText.ts # unified-diff → per-file {added, removed}; lineLimit excerpting
|
|
157
|
+
updateText.ts # U/G/C/E line parser + "Summary of conflicts"
|
|
158
|
+
commitText.ts # /Committed revision (\d+)\./
|
|
159
|
+
eol.ts # byte sniffing (EOL kind, BOM, binary), dos2unix/unix2dos invocation
|
|
160
|
+
tools\
|
|
161
|
+
readonly.ts # svn_status, svn_info, svn_diff, svn_log, eol_check, svn_propget
|
|
162
|
+
composite.ts # svn_precommit, eol_fix_verified
|
|
163
|
+
mutating.ts # svn_add, svn_commit, svn_move, svn_rename, svn_copy,
|
|
164
|
+
# svn_update, svn_revert, svn_resolved, svn_cleanup,
|
|
165
|
+
# svn_propset_eol_style, svn_propset, svn_export, svn_import
|
|
166
|
+
tests\ # jest: unit + integration (temp file:// repo)
|
|
167
|
+
bin\ # versioned full Windows SVN and dos2unix runtime payloads
|
|
168
|
+
dist\ # tsc output (committed into releases\, not into src tree)
|
|
169
|
+
releases\v<version>\dist\index.js
|
|
170
|
+
releases\v<version>\bin\...
|
|
171
|
+
current -> releases\v<version> (directory junction)
|
|
172
|
+
docs\SPEC.md (this file)
|
|
173
|
+
package.json, tsconfig.json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Launch write-capable clients: `node <MCP_HOME>\svn-agent\current\dist\index.js`.
|
|
177
|
+
Launch read-only clients: `node <MCP_HOME>\svn-agent\current\dist\index.js --readonly`.
|
|
178
|
+
|
|
179
|
+
### 6.2 Environment variables
|
|
180
|
+
|
|
181
|
+
No environment variable is required for normal end-user operation. These variables are retained
|
|
182
|
+
only as development/test escape hatches:
|
|
183
|
+
|
|
184
|
+
| Var | Normal user? | Meaning |
|
|
185
|
+
|---|---:|---|
|
|
186
|
+
| `SVN_AGENT_READONLY` | No | Legacy/dev equivalent of `--readonly`; mutating tools return `ok:false`, `note:"READONLY instance"` |
|
|
187
|
+
| `SVN_AGENT_BIN_DIR` | No | Dev/test override for directory containing bundled svn/EOL tools |
|
|
188
|
+
| `SVN_AGENT_SVN_PATH` | No | Dev/test full path override for svn.exe |
|
|
189
|
+
| `SVN_AGENT_DOS2UNIX_DIR` | No | Dev/test directory override containing dos2unix.exe/unix2dos.exe |
|
|
190
|
+
| `SVN_AGENT_MAX_DIFF_LINES` | No | Dev/test default diff excerpt cap; tools also accept `lineLimit` |
|
|
191
|
+
| `SVN_AGENT_TIMEOUT_MS` | No | Dev/test per-process timeout |
|
|
192
|
+
|
|
193
|
+
### 6.3 Path & cwd rules
|
|
194
|
+
|
|
195
|
+
Every tool accepts optional `cwd` (absolute). For plug-and-play global registration, clients do
|
|
196
|
+
not set a launch `cwd`; when a tool call supplies absolute paths and omits `cwd`, the MCP locates
|
|
197
|
+
the nearest SVN working copy for those paths. Relative paths still resolve against explicit `cwd`
|
|
198
|
+
when provided; without `cwd` or absolute path hints, the call is refused. Resolved paths **must stay inside one working
|
|
199
|
+
copy root** (found via `.svn` ancestor discovery + `svn info --xml`) — anything outside → guard
|
|
200
|
+
refusal. Empty `paths: []` where paths are required → refusal (`note:"explicit paths required"`),
|
|
201
|
+
never silently `.`.
|
|
202
|
+
|
|
203
|
+
### 6.4 Response envelope (every tool, success and failure)
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
interface Envelope {
|
|
207
|
+
ok: boolean;
|
|
208
|
+
command: string; // argv joined for display; credentials redacted (flags, URL userinfo/query secrets)
|
|
209
|
+
cwd: string;
|
|
210
|
+
revision: number | null; // resulting/queried revision when meaningful
|
|
211
|
+
changed_paths: { status: string; path: string }[];
|
|
212
|
+
conflicts: { path: string; type: "text" | "tree" | "prop" }[];
|
|
213
|
+
stdout_summary: string; // capped (200 lines default); "truncated" set if capped
|
|
214
|
+
stderr_summary: string;
|
|
215
|
+
truncated: boolean;
|
|
216
|
+
note: string; // one-liner: guard fired / warning / verdict
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Tools add tool-specific fields beside these (documented per tool). Errors are always envelopes
|
|
221
|
+
(`ok:false`), never thrown raw stacks. Redaction applies to `command`, `stdout_summary`, and
|
|
222
|
+
`stderr_summary`: `--password`/`--username` values, inline `--password=...`/`--username=...`
|
|
223
|
+
values, URL userinfo (including malformed userinfo with raw `@`), and sensitive URL query
|
|
224
|
+
parameters are replaced with `***` (the server itself never passes credentials; svn uses its
|
|
225
|
+
cached auth).
|
|
226
|
+
|
|
227
|
+
### 6.5 Startup probe
|
|
228
|
+
|
|
229
|
+
All SVN child processes run without a shell, with `--non-interactive`, a stable `C` locale, bounded
|
|
230
|
+
stderr capture, timeout settlement, and latin1 fallback for non-UTF8 bytes. On boot: resolve bundled-or-overridden svn (`--version --quiet`), resolve bundled-or-overridden
|
|
231
|
+
dos2unix/unix2dos (`--version`), detect READONLY. Failures don't kill the server — the affected
|
|
232
|
+
tools return `ok:false` with an explanatory `note` (e.g. `eol_*` unavailable when dos2unix
|
|
233
|
+
missing).
|
|
234
|
+
|
|
235
|
+
### 6.6 Error taxonomy (svn stderr → structured notes)
|
|
236
|
+
|
|
237
|
+
| svn error | Mapping |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `E155004`/`E155036` (WC locked) | `note:"working copy locked - run svn_cleanup"` |
|
|
240
|
+
| `E170001`/`E215004`/auth | `note:"authentication failed - fix svn cached auth outside the MCP"` |
|
|
241
|
+
| `E175002`/connection failures | `note:"network or repository connection failed"` |
|
|
242
|
+
| `E155007` (not a WC) | `note:"path is not inside a working copy"` |
|
|
243
|
+
| `E135000` / inconsistent EOL | `note:"inconsistent line endings - run eol_fix_verified on affected files"` plus EOL diagnostics where available |
|
|
244
|
+
| `E200009` / unversioned target | `note:"target not versioned"` |
|
|
245
|
+
| `E200030`/SQLite database failures | `note:"working copy database problem - run svn_cleanup"` |
|
|
246
|
+
| timeout | `ok:false`, `note:"svn timed out after <ms>"`, process killed |
|
|
247
|
+
| non-UTF8 output bytes | decode lossy (`latin1` fallback), never crash |
|
|
248
|
+
|
|
249
|
+
## 7. Guard framework (applies across tools)
|
|
250
|
+
|
|
251
|
+
- **G1 explicit targets:** mutating path-list tools require non-empty `paths[]`; source/destination tools require explicit `src` and `dest` (exceptions: `svn_update` with `updateAll:true`; `svn_cleanup` takes one `path`). No implicit `.`, no recursive default anywhere.
|
|
252
|
+
- **G2 READONLY:** `--readonly` (or legacy/dev `SVN_AGENT_READONLY=1`) → all §8.4 tools + `eol_fix_verified` refuse.
|
|
253
|
+
- **G3 WC containment:** resolved paths must be inside the working copy (§6.3).
|
|
254
|
+
- **G4 never-commit globs** (block in `svn_add`, `svn_move`, `svn_rename`, `svn_copy`, and `svn_commit`; case-insensitive, match on repo-relative path): `**/bin/**`, `**/dist/**`, `**/node_modules/**`, `**/coverage/**`, `**/obj/**`, `**/.vs/**`, `**/.cache/**`, `**/*.db`, `**/*.tsbuildinfo`, `scratch/**`, `packages/**`, `tags/**`, `.graphify/**`, `graphify-out/**`, `**/*.pfx`, `**/*.key`, `**/*.pem`, `**/*.p12`, `**/*.snk`, `**/.env*`. Optional repo-local `.svn-mcp-policy.json` may add strict allow/deny exceptions, for example to version a toolchain payload in the MCP repository itself. ("Unrelated drive-by changes" cannot be a glob — mitigated by G1 + G5; stays agent judgment.)
|
|
255
|
+
Policy shape:
|
|
256
|
+
```json
|
|
257
|
+
{ "neverCommit": { "allow": ["bin/**"], "deny": ["custom-generated/**"] } }
|
|
258
|
+
```
|
|
259
|
+
Defaults stay strict when no policy file is present; policy is read from the working-copy root and never from an environment variable. Policy files are cached by working-copy root and mtime/size, and malformed or pathological policy globs fail with a `policy-error:` guard note.
|
|
260
|
+
Repository-local `deny` rules are evaluated before repository-local `allow` rules, so a broad
|
|
261
|
+
allow exception cannot bypass a stricter project-specific deny.
|
|
262
|
+
- **G5 must-be-changed:** `svn_commit` verifies every listed path is actually modified/added/deleted per scoped status; unknown/clean path → refusal naming the path.
|
|
263
|
+
- **G6 risky-slice ack:** `svn_commit` requires `riskAck:true` when any mechanical signal is present: a delete-scheduled path (status `D`), **more than 8 paths**, `version.ver` among the paths, or a build-system file among the paths (`*.sln`, `*.csproj`, `Directory.Build.props`, `Directory.Build.targets`, `*.props`, `*.targets`, `packages.config`). Refusal lists the triggered signals. Schema-changing / security-sensitive / scope-unclear risk is **not detectable** — the calling client's responsibility (§5.4).
|
|
264
|
+
- **G7 no dangerous flags:** `--force` is never emitted. `svn_update` always gets `--accept postpone`. `svn_cleanup` never gets `--remove-unversioned`/`--remove-ignored`/`--vacuum-pristines`. `svn_resolved` requires an explicit `accept` value from the caller.
|
|
265
|
+
|
|
266
|
+
## 8. Tool contracts
|
|
267
|
+
|
|
268
|
+
All inputs validated with zod; all outputs = Envelope + the extra fields listed. "argv" shows
|
|
269
|
+
the exact svn invocation (before path resolution).
|
|
270
|
+
|
|
271
|
+
### 8.1 Read-only tools (allowed under READONLY)
|
|
272
|
+
|
|
273
|
+
**`svn_status`** — `{ cwd?, paths?: string[] }`
|
|
274
|
+
argv: `svn status --xml [--no-ignore] [paths…]` (default target: `.` of explicit `cwd`;
|
|
275
|
+
when both `cwd` and absolute path hints are absent, the MCP refuses instead of falling back to
|
|
276
|
+
its launch directory). Inputs also accept
|
|
277
|
+
`includeIgnored?: boolean` for explicit ignored-path audits and `hideNoise?: boolean` to remove
|
|
278
|
+
common local runtime clutter (`node_modules`, `dist`, `current`, `.cache`, `coverage`)
|
|
279
|
+
from `changed_paths` while reporting filtered paths in `filtered_paths`. Parses `wc-status` into
|
|
280
|
+
`changed_paths` (status letters `M A D R C ? ! ~ I`, plus `_M` for property-only changes),
|
|
281
|
+
property conflicts into `{type:"prop"}`, and tree/text conflicts into `conflicts`.
|
|
282
|
+
|
|
283
|
+
**`svn_info`** — `{ cwd?, paths?: string[] }`
|
|
284
|
+
argv: `svn info --xml [paths…]`. Extra fields: `url`, `repo_root`, `wc_root`.
|
|
285
|
+
Mixed-revision detection: additionally run `svnversion <wc-root>` once; output containing
|
|
286
|
+
`:` → `mixed_revision:true` (+ `note`), suffix `M`→modified, `S`→switched, `P`→partial reported
|
|
287
|
+
in `note`. The MCP also returns `svnversion`, `revision_range:{min,max}`, `local_modifications`,
|
|
288
|
+
`switched`, `partial`, `remote_head_revision`, and `stale_base` so clients can distinguish a
|
|
289
|
+
mixed-revision working copy from dirty local edits.
|
|
290
|
+
|
|
291
|
+
**`svn_diff`** — `{ cwd?, paths: string[], ignoreEol?: boolean = true, lineLimit?: number = 800 }`
|
|
292
|
+
argv (default): `svn diff --internal-diff -x --ignore-eol-style <paths…>` — the generic
|
|
293
|
+
commit-prep standard. `ignoreEol:false` → `svn diff --internal-diff <paths…>` (raw, for EOL
|
|
294
|
+
diagnosis). Extra fields: `per_file: [{path, added, removed, binary}]` (parsed from unified
|
|
295
|
+
diff; `binary:true` when svn prints "Cannot display"), `diff_excerpt` (first `lineLimit`
|
|
296
|
+
lines), `truncated`, `ignore_eol:boolean`.
|
|
297
|
+
|
|
298
|
+
**`svn_log`** — `{ cwd?, paths?: string[], limit?: number = 10, verbose?: boolean = true }`
|
|
299
|
+
argv: `svn log --xml -l <limit> [-v] [targets…]`. For working-copy targets, the MCP
|
|
300
|
+
resolves target URLs and queries repository URLs at HEAD when possible. This avoids the common
|
|
301
|
+
mixed-revision working-copy peg problem where `svn log <wc-root>` only shows history through the
|
|
302
|
+
root directory's older BASE revision. If URL resolution fails, the MCP falls back to the original
|
|
303
|
+
working-copy paths. Extra: `entries: [{rev, author, date, msg, changed_paths}]`,
|
|
304
|
+
`target_mode: "repository-url"|"working-copy-path"`.
|
|
305
|
+
|
|
306
|
+
**`eol_check`** — `{ cwd?, paths: string[] }`
|
|
307
|
+
Pure read: batched `svn propget svn:eol-style --xml` + async byte sniff (cap 5 MB, larger →
|
|
308
|
+
`sniff:"skipped-too-large"`; NUL byte in first 8 KB → `kind:"binary"`; directory or other
|
|
309
|
+
non-file target → `kind:"not-a-file"`, never a thrown error). Extra per file:
|
|
310
|
+
`{ path, kind: "crlf"|"lf"|"mixed"|"none"|"binary"|"not-a-file", eol_style: string|null,
|
|
311
|
+
has_bom: boolean, mismatch: boolean }`. `mismatch` = text file whose working EOL does not match `svn:eol-style`
|
|
312
|
+
(`native` resolves to platform-native CRLF on Windows and LF elsewhere; explicit `LF`/`CRLF`
|
|
313
|
+
resolve literally). Files with no line breaks (`kind:"none"`) are not mismatches.
|
|
314
|
+
|
|
315
|
+
**`svn_propget`** — `{ cwd?, paths: string[], name: string }`
|
|
316
|
+
Pure read: `svn propget <name> --xml <paths…>`. Property names are bounded to ordinary SVN
|
|
317
|
+
property-name characters (`A-Za-z0-9_.:-`, starting with a letter/underscore). Extra:
|
|
318
|
+
`properties:[{path,name,value}]` and `missing_paths:string[]`; absent properties are
|
|
319
|
+
successful reads with missing targets listed in `missing_paths`, not fatal SVN failures.
|
|
320
|
+
When a batch has mixed presence, `properties` contains the found values and `missing_paths`
|
|
321
|
+
contains only the absent targets. Purpose:
|
|
322
|
+
let clients inspect property-only slices without dropping to raw SVN or special-casing only
|
|
323
|
+
`svn:eol-style`.
|
|
324
|
+
|
|
325
|
+
**`svn_self_check`** — `{ cwd? }`
|
|
326
|
+
Pure read/self-diagnostic tool. Reports MCP package/runtime version, `current` junction target,
|
|
327
|
+
whether `current` matches the package version, release `bin` and `dist` payload counts, startup
|
|
328
|
+
probe results for bundled tools, whether the bundled SVN/EOL toolchain is healthy, and whether
|
|
329
|
+
release/clean scripts use the Node-based paths. Purpose: avoid manual checks for ignored `current`
|
|
330
|
+
drift and noisy release payload adds.
|
|
331
|
+
|
|
332
|
+
**`svn_diagnose`** — `{ cwd?, paths?: string[] }`
|
|
333
|
+
Pure read working-copy diagnostic tool. Runs startup SVN availability, then local status, remote
|
|
334
|
+
status with `--show-updates`, `svn info -r HEAD`, and latest-log reachability checks against the
|
|
335
|
+
resolved working copy/targets in parallel. Extra fields: `health:"healthy"|"warning"|"error"`,
|
|
336
|
+
`svn_available`, `working_copy_valid`, `wc_root`, `remote_accessible`,
|
|
337
|
+
`checks:[{name, ok, command, note}]`, and `suggestions:string[]`. Purpose: collapse common
|
|
338
|
+
SVN failure triage into one structured call without changing the working copy or clearing
|
|
339
|
+
credentials.
|
|
340
|
+
|
|
341
|
+
### 8.2 (reserved)
|
|
342
|
+
|
|
343
|
+
### 8.3 Composite tools (the P1 killers)
|
|
344
|
+
|
|
345
|
+
**`svn_precommit`** — `{ cwd?, paths: string[], lineLimit?: number = 800 }` *(read-only; allowed under READONLY)*
|
|
346
|
+
One call = scoped status + scoped ignore-EOL diff + `eol_check` + G4/G5/G6 dry evaluation +
|
|
347
|
+
mixed-revision check. Extra fields:
|
|
348
|
+
|
|
349
|
+
```jsonc
|
|
350
|
+
{
|
|
351
|
+
"verdict": "READY" | "EOL_FIX_NEEDED" | "GUARD_BLOCKED" | "NOTHING_TO_COMMIT" | "DIFF_FAILED",
|
|
352
|
+
"per_file": [{ "path": "...", "status": "M", "added": 12, "removed": 3,
|
|
353
|
+
"eol": "crlf", "eol_style": "native", "bom": false,
|
|
354
|
+
"pure_eol_churn": false, "guard": null }],
|
|
355
|
+
"risk_signals": ["build-system file touched"], // what svn_commit will demand riskAck for
|
|
356
|
+
"diff_excerpt": "...", "truncated": true
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Verdict rules (first match): any G3/G4 hit, ignored path, or listed-but-clean path → `GUARD_BLOCKED` (offender
|
|
361
|
+
named in `guard`); `svn_diff` failure with `recovery_tool:"eol_fix_verified"` →
|
|
362
|
+
`EOL_FIX_NEEDED`; other `svn_diff` failure → `DIFF_FAILED`; any text file with `mismatch` or
|
|
363
|
+
`pure_eol_churn` → `EOL_FIX_NEEDED`; no path with a real change → `NOTHING_TO_COMMIT`; else
|
|
364
|
+
`READY`. `pure_eol_churn` = file shows as modified in status but its ignore-EOL diff is empty.
|
|
365
|
+
Intended flow: **precommit → (review summary; fetch full per-file diff only if a count looks
|
|
366
|
+
wrong) → commit.** Two round trips.
|
|
367
|
+
|
|
368
|
+
**`eol_fix_verified`** — `{ cwd?, path: string, target?: "crlf"|"lf", removeBom?: boolean = true, dryRun?: boolean = false, allowLarge?: boolean = false }` *(mutating; refused under READONLY)*
|
|
369
|
+
One call = read `svn:eol-style`, infer the target (`native` → platform native, `LF` → lf,
|
|
370
|
+
`CRLF` → crlf, no property → platform native), execute the real converter via `execFile`
|
|
371
|
+
(`unix2dos` for crlf / `dos2unix` for lf; `--remove-bom` when `removeBom`) on **one file**,
|
|
372
|
+
then automatically re-run the ignore-EOL diff on it. Clients normally pass only `{path}` when
|
|
373
|
+
using absolute paths; `cwd` is optional and mainly for relative paths.
|
|
374
|
+
Extra: `{ before: {kind, has_bom}, after: {kind, has_bom}, target, eol_style, converter,
|
|
375
|
+
verification_command, diff_ignored_eol:true, pure_eol_churn: boolean }` —
|
|
376
|
+
`pure_eol_churn:true` is the proof the fix changed nothing but line endings. `dryRun:true`
|
|
377
|
+
reports `before` + inferred converter/target, touches nothing. Never invoked implicitly by any
|
|
378
|
+
other tool (fixing is always an explicit caller decision). Missing paths, non-files, binary files,
|
|
379
|
+
and `sniff:"skipped-too-large"` files return structured refusals; oversized files require
|
|
380
|
+
explicit `allowLarge:true`. No PowerShell scripts, byte rewrites, pipes, redirects, or shell
|
|
381
|
+
quoting are involved.
|
|
382
|
+
|
|
383
|
+
### 8.4 Mutating tools (all refused under READONLY)
|
|
384
|
+
|
|
385
|
+
**`svn_add`** — `{ cwd?, paths: string[], allowRecursive?: boolean = false }`
|
|
386
|
+
argv: `svn add --parents --depth empty <paths…>` (files); intermediate parent directories are
|
|
387
|
+
scheduled as needed without recursively adding siblings. A directory path requires
|
|
388
|
+
`allowRecursive:true` (then `--parents --depth infinity`). G4 enforced — can't add what may never be committed
|
|
389
|
+
(`scratch/**` is reserved for local scratch files; never add).
|
|
390
|
+
|
|
391
|
+
**`svn_commit`** — `{ cwd?, paths: string[], message: string, riskAck?: boolean = false }`
|
|
392
|
+
Sequence: G1→G6 checks → message format check against §5.8 template (summary line + blank +
|
|
393
|
+
≥1 `- ` bullet; deviation → warning appended to `note`, not refusal) → write message to temp
|
|
394
|
+
file **outside the WC** (secure temp dir, UTF-8 **no BOM**, leading BOM stripped) → argv:
|
|
395
|
+
`svn commit -F <tmpfile> --depth empty <paths…>` → delete tmpfile (always, incl. on failure) →
|
|
396
|
+
parse `Committed revision N.` → run scoped `svn status --xml <paths…>`.
|
|
397
|
+
If an explicit file path is under newly-added parent directories, the commit argv includes only
|
|
398
|
+
those scheduled-added ancestors plus the explicit path, so the caller does not need to name parent
|
|
399
|
+
directories manually.
|
|
400
|
+
Extra: `{ revision, post_status_clean: boolean, risk_signals: string[] }`. Mixed-revision WC →
|
|
401
|
+
warning in `note`, commit proceeds (D3).
|
|
402
|
+
|
|
403
|
+
**`svn_move`** — `{ cwd?, src: string, dest: string }`
|
|
404
|
+
argv: `svn move --parents <src> <dest>`. Working-copy path → working-copy path only; repository
|
|
405
|
+
URL forms are refused because URL moves create revisions immediately and require message-file
|
|
406
|
+
handling. `src` must exist and both `src` and `dest` must resolve inside the working copy.
|
|
407
|
+
Intermediate destination directories are created/scheduled by SVN. G4 enforced on both `src` and
|
|
408
|
+
`dest`. Extra: `{ operation:"move", src, dest }` plus scoped `changed_paths` for review/commit.
|
|
409
|
+
Committing a move normally requires both old and new paths; because the old path is scheduled
|
|
410
|
+
delete, `svn_commit` requires `riskAck:true` by G6.
|
|
411
|
+
|
|
412
|
+
**`svn_rename`** — `{ cwd?, src: string, dest: string }`
|
|
413
|
+
Alias for `svn_move`, registered separately so clients can use the natural verb when doing a
|
|
414
|
+
rename. Same argv, guards, and response shape as `svn_move`.
|
|
415
|
+
|
|
416
|
+
**`svn_copy`** — `{ cwd?, src: string, dest: string }`
|
|
417
|
+
argv: `svn copy --parents <src> <dest>`. Working-copy path → working-copy path only; repository
|
|
418
|
+
URL forms are refused. `src` must exist and both paths must be inside the working copy.
|
|
419
|
+
Intermediate destination directories are created/scheduled by SVN. G4 enforced on both `src` and
|
|
420
|
+
`dest`. Extra: `{ operation:"copy", src, dest }` plus scoped `changed_paths`.
|
|
421
|
+
|
|
422
|
+
**`svn_update`** — `{ cwd?, paths?: string[], updateAll?: boolean = false }`
|
|
423
|
+
Refuses unless `paths` non-empty or `updateAll:true` (deliberate friction; the operator-request
|
|
424
|
+
requirement in §5.2 remains the caller's responsibility). argv:
|
|
425
|
+
`svn update --accept postpone [paths…]`. Parses multi-column update output + "Summary of conflicts" →
|
|
426
|
+
`changed_paths` + `conflicts`; any conflict ⇒ prominent `note`. Never auto-resolves.
|
|
427
|
+
|
|
428
|
+
**`svn_revert`** — `{ cwd?, paths: string[], allowRecursive?: boolean = false, dryRun?: boolean = true }`
|
|
429
|
+
`dryRun:true` (default) = preview: returns scoped status + per-file ± counts of what would be
|
|
430
|
+
**lost**, changes nothing. `dryRun:false` → argv `svn revert <file paths…>` for files and a
|
|
431
|
+
separate `svn revert --depth infinity <directory paths…>` for directories; a directory or `.`
|
|
432
|
+
requires `allowRecursive:true`. Reverting the WC root path is refused unconditionally.
|
|
433
|
+
|
|
434
|
+
**`svn_resolved`** — `{ cwd?, path: string, accept: "working"|"mine-full"|"theirs-full"|"base" }`
|
|
435
|
+
argv: `svn resolve --accept <accept> <path>`. Single path; `accept` has **no default** — the
|
|
436
|
+
caller must state the resolution. Intended only after an operator asked for conflict resolution.
|
|
437
|
+
|
|
438
|
+
**`svn_cleanup`** — `{ cwd?, path?: string }`
|
|
439
|
+
argv: `svn cleanup [path]` — releases stale WC locks (the `E155004` remedy). **Never** passes
|
|
440
|
+
`--remove-unversioned`, `--remove-ignored`, or `--vacuum-pristines`. Mutating classification
|
|
441
|
+
(refused under READONLY) because it rewrites WC metadata.
|
|
442
|
+
|
|
443
|
+
**`svn_propset_eol_style`** — `{ cwd?, paths: string[], style?: "native"|"LF"|"CRLF" = "native" }`
|
|
444
|
+
argv: `svn propset svn:eol-style <style> <paths…>`. Guard: each target must currently be
|
|
445
|
+
**missing or mismatched** on the prop (checked via propget first) — mass re-propset of
|
|
446
|
+
already-correct files is refused (preserve-existing rule, §5.6). Rarely needed once §10.2 lands.
|
|
447
|
+
|
|
448
|
+
**`svn_propset`** — `{ cwd?, paths: string[], name: string, value: string, riskAck?: boolean = false }`
|
|
449
|
+
argv: `svn propset <name> <value> <paths…>`. Guard: explicit existing paths inside one working
|
|
450
|
+
copy, READONLY refusal, never-commit target checks, bounded property names/values. `riskAck:true`
|
|
451
|
+
is required for high-risk properties that can hide or redirect repository behavior:
|
|
452
|
+
`svn:ignore`, `svn:global-ignores`, `svn:externals`, and `svn:auto-props`.
|
|
453
|
+
|
|
454
|
+
**`svn_export`** — `{ cwd?, src: string, dest: string, revision?: string }` /
|
|
455
|
+
**`svn_import`** — `{ cwd?, src: string, url: string, message: string }`
|
|
456
|
+
argv: `svn export [-r rev] <src> <dest>` / `svn import -F <tmpfile> <src> <url>`. Explicit
|
|
457
|
+
src+dest/url; `svn_export` validates revision strings before invoking SVN, and `svn_import`
|
|
458
|
+
scans the source tree for never-commit descendants before invoking SVN. `svn_import` uses the
|
|
459
|
+
same secure `-F` tempfile mechanics as commit. Purpose: MCP release packaging.
|
|
460
|
+
|
|
461
|
+
## 9. Edge cases (defined so no doubts remain)
|
|
462
|
+
|
|
463
|
+
- `paths: []` where required → `ok:false`, `note:"explicit paths required"`.
|
|
464
|
+
- Nonexistent path → `ok:false`, naming the path (fail before spawning svn).
|
|
465
|
+
- Path outside WC root → G3 refusal.
|
|
466
|
+
- WC locked (`E155004`) on any tool → mapped note pointing at `svn_cleanup` (§6.6).
|
|
467
|
+
- Binary file in `svn_diff`/`eol_check` → flagged `binary`, never sniffed/converted; `eol_fix_verified` on a binary → refusal.
|
|
468
|
+
- File > 5 MB in `eol_check` → prop still reported, byte sniff skipped with note.
|
|
469
|
+
- Diff larger than `lineLimit` → excerpt + `truncated:true`; per-file counts always complete (counted while streaming, not from the excerpt).
|
|
470
|
+
- Two svn-agent instances on one WC → fine: svn handles concurrent readers; the only writer is the non-READONLY instance, and svn's own wc locking covers overlap.
|
|
471
|
+
- Message containing `"""`, backticks, non-ASCII → irrelevant: message goes through a file (`-F`), never a shell string. Process launches never use a shell, so there is no shell interpolation.
|
|
472
|
+
- Commit succeeds but post-status shows residue → `post_status_clean:false` + note (caller decides).
|
|
473
|
+
- svn prints warnings on stderr with exit 0 → `ok:true`, stderr preserved in `stderr_summary`.
|
|
474
|
+
|
|
475
|
+
## 10. Companion fixes outside the MCP (part of the plan, not the server)
|
|
476
|
+
|
|
477
|
+
### 10.1 EOL handling
|
|
478
|
+
|
|
479
|
+
EOL remediation belongs inside this MCP. Callers should call `eol_fix_verified` with a file path;
|
|
480
|
+
the MCP infers the target from `svn:eol-style`, runs bundled `unix2dos`/`dos2unix` directly via
|
|
481
|
+
`execFile`, and rechecks the ignored-EOL diff. Do not install or generate PowerShell EOL
|
|
482
|
+
hooks/scripts for this workflow.
|
|
483
|
+
|
|
484
|
+
### 10.2 Repo-dictated auto-props — *one commit at the repository root, maintainer approval*
|
|
485
|
+
|
|
486
|
+
SVN ≥1.8 inherited property; every 1.8+ client then auto-applies on `svn add`, no client config.
|
|
487
|
+
This is an example policy; repositories should adapt patterns to their own text files:
|
|
488
|
+
|
|
489
|
+
```
|
|
490
|
+
svn propset svn:auto-props "*.cs = svn:eol-style=native
|
|
491
|
+
*.xaml = svn:eol-style=native
|
|
492
|
+
*.csproj = svn:eol-style=native
|
|
493
|
+
*.config = svn:eol-style=native
|
|
494
|
+
*.props = svn:eol-style=native
|
|
495
|
+
*.targets = svn:eol-style=native
|
|
496
|
+
*.md = svn:eol-style=native
|
|
497
|
+
*.resx = svn:eol-style=native
|
|
498
|
+
*.ts = svn:eol-style=native
|
|
499
|
+
*.js = svn:eol-style=native
|
|
500
|
+
*.json = svn:eol-style=native" <REPOSITORY_ROOT>
|
|
501
|
+
svn commit -F <msgfile> <REPOSITORY_ROOT> # prop-only commit
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### 10.3 Defender exclusion — *operator decision (security trade-off), admin shell*
|
|
505
|
+
|
|
506
|
+
```powershell
|
|
507
|
+
Add-MpPreference -ExclusionPath '<WORKING_COPY_ROOT>'
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Measure before/after: `Measure-Command { svn status <PROJECT_ROOT>\src }`. Expected 2–10× on
|
|
511
|
+
file-heavy svn ops. Trade-off: files under the path are not scanned on access. No process-level
|
|
512
|
+
exclusions.
|
|
513
|
+
|
|
514
|
+
### 10.4 Permission allowlist
|
|
515
|
+
|
|
516
|
+
Interim (before MCP): allow read-only `svn status`, `svn diff`, `svn info`, and `svn log`
|
|
517
|
+
commands in the client permission system.
|
|
518
|
+
Final (after Phase 4): allow `mcp__svn__svn_self_check`, `mcp__svn__svn_diagnose`,
|
|
519
|
+
`mcp__svn__svn_status`, `mcp__svn__svn_info`, `mcp__svn__svn_diff`, `mcp__svn__svn_log`,
|
|
520
|
+
`mcp__svn__eol_check`, `mcp__svn__svn_propget`, and `mcp__svn__svn_precommit`; leave every
|
|
521
|
+
mutating tool prompt-gated.
|
|
522
|
+
|
|
523
|
+
## 11. Historical development phases
|
|
524
|
+
|
|
525
|
+
Phases 1-4 are complete in the shipped v1.0.0 baseline. This section remains as traceability
|
|
526
|
+
for why the implementation was built in this order and how future release phases should be
|
|
527
|
+
gated.
|
|
528
|
+
|
|
529
|
+
**Phase 0 — no-code quick wins** *(operator executes/approves; independent of the MCP)*
|
|
530
|
+
0a Defender exclusion (§10.3) · 0b repo auto-props (§10.2) · 0c interim allowlist (§10.4).
|
|
531
|
+
Gate: Defender win measured with before/after `Measure-Command`; EOL repair is verified through
|
|
532
|
+
`eol_fix_verified`, not an external hook.
|
|
533
|
+
|
|
534
|
+
**Phase 1 — scaffold + read-only tools**
|
|
535
|
+
`package.json` (ESM, `"engines": {"node": ">=20"}`), `tsconfig` (strict, ES2022), deps per D12;
|
|
536
|
+
`runner`, `envelope`, `guards`, XML parsers; tools `svn_status`, `svn_info`, `svn_diff`,
|
|
537
|
+
`svn_log`, `eol_check`; startup probe.
|
|
538
|
+
Gate: jest unit tests green (guard matrix, envelope shape, parser fixtures incl. locale-odd and
|
|
539
|
+
truncated outputs); manual smoke of all five tools against a sample working copy (read-only).
|
|
540
|
+
|
|
541
|
+
**Phase 2 — composite tools**
|
|
542
|
+
`svn_precommit`, `eol_fix_verified`.
|
|
543
|
+
Gate: integration tests on a **throwaway temp repo** (`svnadmin create` + `file:///` checkout in
|
|
544
|
+
a temporary directory — never a production working copy): LF-damaged file → precommit `EOL_FIX_NEEDED` → fix →
|
|
545
|
+
`pure_eol_churn:true` → precommit `READY`. Unit tests for verdict precedence and per-file
|
|
546
|
+
counting.
|
|
547
|
+
|
|
548
|
+
**Phase 3 — mutating tools**
|
|
549
|
+
All §8.4 tools. Gate: temp-repo integration matrix — commit happy path (`-F` file used and
|
|
550
|
+
cleaned up; revision parsed; post-status clean), every guard refusal (G1–G7, incl. commit
|
|
551
|
+
without riskAck on a 9-file slice, revert of WC root refused, update without paths/updateAll
|
|
552
|
+
refused), READONLY instance refuses every mutating tool + `eol_fix_verified`. **No mutating
|
|
553
|
+
test ever touches a production working copy.**
|
|
554
|
+
|
|
555
|
+
**Phase 4 — release + registration**
|
|
556
|
+
`tsc` build → `npm run release:prepare` copies `dist` and source-tree `bin` to
|
|
557
|
+
`releases\v<version>\`, validates payload counts, and repoints the `current` junction without
|
|
558
|
+
PowerShell wildcard/copy commands → register:
|
|
559
|
+
write-capable client: `<client mcp add svn> node <MCP_HOME>\svn-agent\current\dist\index.js`;
|
|
560
|
+
read-only client config:
|
|
561
|
+
```toml
|
|
562
|
+
[mcp_servers.svn]
|
|
563
|
+
command = "node"
|
|
564
|
+
args = ["<MCP_HOME>\\svn-agent\\current\\dist\\index.js", "--readonly"]
|
|
565
|
+
```
|
|
566
|
+
Also: final allowlist (§10.4) and optional auto-props commit (§10.2, maintainer approved).
|
|
567
|
+
Gate: from a sample write-capable client session, `svn_precommit` on a touched path returns a
|
|
568
|
+
correct verdict; from a read-only client session, `svn_status` works and `svn_commit` refuses
|
|
569
|
+
with the READONLY note.
|
|
570
|
+
|
|
571
|
+
**Phase 5 — retire the manual workflow**
|
|
572
|
+
Slim `docs/svnrules.md` to "use the `svn` MCP tools; raw svn only where the MCP has no tool",
|
|
573
|
+
keeping the policy prose as the reference the MCP encodes. Maintainer-approved docs edit.
|
|
574
|
+
Gate: one full sample commit slice executed end-to-end through the MCP (precommit → commit),
|
|
575
|
+
2 round trips, zero prompts on the read path.
|
|
576
|
+
|
|
577
|
+
## 12. Historical definition of done (v0.1.0)
|
|
578
|
+
|
|
579
|
+
1. All Phase 1–4 gates green; jest suite green; zero mutating-tool tests against production working copies.
|
|
580
|
+
2. A sample working-copy slice committed via `svn_precommit` + `svn_commit` in 2 calls, with correct
|
|
581
|
+
revision + clean post-status in the envelope.
|
|
582
|
+
3. Read-only instance demonstrably refuses mutating tools.
|
|
583
|
+
4. EOL hook + (if approved) auto-props live → a week of normal work produces zero EOL
|
|
584
|
+
remediation loops.
|
|
585
|
+
5. This SPEC.md updated only via a new version header (spec changes are deliberate, not drift).
|
|
586
|
+
|
|
587
|
+
## 13. Out of scope / future
|
|
588
|
+
|
|
589
|
+
Branch, switch, merge, relocate, delete (v0.2+ candidates, each with its own guard
|
|
590
|
+
design); blame/annotate; lock/unlock; changelist support; any Git interop; any mass
|
|
591
|
+
reformatting, ever. Project build/test time can dominate total slice time, but it is not SVN
|
|
592
|
+
housekeeping — separate initiative.
|
|
593
|
+
|
|
594
|
+
## 14. Change Log
|
|
595
|
+
|
|
596
|
+
The complete release history lives in `../CHANGELOG.md`. Spec-affecting changes:
|
|
597
|
+
|
|
598
|
+
### Spec 1.17 / v1.0.0 — 2026-07-08
|
|
599
|
+
|
|
600
|
+
- Declares the first public open-source release as `1.0.0`.
|
|
601
|
+
- Documents the GitHub clone -> `npm install` -> `npm run prepare:local` setup path for automated clients.
|
|
602
|
+
- Keeps generated `releases/`, `current`, and root `dist/` ignored; only the source tree and root
|
|
603
|
+
bundled Windows runtime payload are versioned.
|
|
604
|
+
|
|
605
|
+
### Spec 1.16 / v0.1.15 — 2026-07-08
|
|
606
|
+
|
|
607
|
+
- Adds generic working-copy property tools: read-only `svn_propget` and guarded `svn_propset`.
|
|
608
|
+
- Defines property guard boundaries: explicit paths, one working copy, READONLY refusal for
|
|
609
|
+
writes, never-commit target checks, bounded property names/values, and `riskAck` for high-risk
|
|
610
|
+
ignore/externals/auto-props properties.
|
|
611
|
+
- Keeps `svn_propset_eol_style` as the stricter EOL-specific shortcut.
|
|
612
|
+
|
|
613
|
+
### Spec 1.15 / v0.1.14 — 2026-07-07
|
|
614
|
+
|
|
615
|
+
- Adds §15.7 CLI failsafe mode: on mechanical MCP failure (server down, runtime broken), callers
|
|
616
|
+
fall back to scoped raw svn CLI for the session under the same §5 policy; guard refusals are
|
|
617
|
+
explicitly not failures and must never be bypassed via CLI.
|
|
618
|
+
- `noteFromRun` flags executable-launch failures (`ENOENT`/`EACCES`/`EPERM`) as
|
|
619
|
+
"MCP svn runtime unavailable" with the failsafe hint; `svn_diagnose` adds the failsafe
|
|
620
|
+
suggestion when the bundled SVN toolchain is unavailable.
|
|
621
|
+
|
|
622
|
+
### Spec 1.14 / v0.1.13 — 2026-07-07
|
|
623
|
+
|
|
624
|
+
- Fixes the critical junction-launch defect: the ESM launched-directly check now compares real
|
|
625
|
+
paths, so `node <MCP_HOME>\svn-agent\current\dist\index.js` (the documented registration)
|
|
626
|
+
actually starts the server instead of exiting silently.
|
|
627
|
+
- Update-output parsing accepts only structurally valid status lines, so informational trailers
|
|
628
|
+
("Updated to revision N.", "At revision N.", "Updating '.':", "Restored ...") can no longer
|
|
629
|
+
appear as phantom changed paths.
|
|
630
|
+
- `eol_check`/`svn_precommit` return a structured `kind:"not-a-file"` for directory targets
|
|
631
|
+
instead of failing with a thrown filesystem error.
|
|
632
|
+
- Streamed stdout/stderr (the `svn_diff` hot path) now decode with the same latin1 fallback as
|
|
633
|
+
buffered output, honoring §6.5 for non-UTF8 bytes.
|
|
634
|
+
- Working-copy containment (G3) verifies physical paths, so junctions/symlinks under a working
|
|
635
|
+
copy cannot redirect tools to files outside it.
|
|
636
|
+
- Policy globs additionally cap total wildcard count to bound regex backtracking.
|
|
637
|
+
- The read-only working-copy probe no longer falls back to the MCP launch directory under any
|
|
638
|
+
input combination.
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
- Defines the full hardening pass: non-interactive stable-locale SVN execution, bounded
|
|
643
|
+
streaming stderr, timeout settlement, latin1 output fallback, stronger redaction, parser
|
|
644
|
+
correctness, ignored-path guards, secure message files, split recursive reverts, guarded import
|
|
645
|
+
source scanning, export revision validation, policy validation/caching, nullable working-copy
|
|
646
|
+
roots, and no ambiguous process-cwd fallback.
|
|
647
|
+
- Updates read-only performance contracts: batched EOL propget, async EOL sniffing, parallel
|
|
648
|
+
diagnostics, and reduced repeated `svn_info` process spawns.
|
|
649
|
+
- Records coverage for the hardening items.
|
|
650
|
+
|
|
651
|
+
### Spec 1.12 / v0.1.11 — 2026-07-07
|
|
652
|
+
|
|
653
|
+
- Defines repository-local never-commit policy precedence: `deny` rules override broad `allow`
|
|
654
|
+
exceptions, while `allow` rules may still override the default generated-artifact guard set.
|
|
655
|
+
- Defines envelope summary redaction for `stdout_summary` and `stderr_summary`, matching command
|
|
656
|
+
redaction for URL userinfo and sensitive query parameters.
|
|
657
|
+
- Updates release references to v0.1.11 after the hardening fixes.
|
|
658
|
+
|
|
659
|
+
### Spec 1.11 — 2026-07-07
|
|
660
|
+
|
|
661
|
+
- Updates project documentation guidance for the v0.1.10 shipped baseline.
|
|
662
|
+
- Adds `svn_self_check` and `svn_diagnose` to the read-only MCP allowlist guidance.
|
|
663
|
+
- Marks the implementation phase plan and v0.1.0 definition of done as historical traceability.
|
|
664
|
+
- Adds ADR-004 as the formal decision record for borrowing diagnostic ideas from external SVN MCPs
|
|
665
|
+
without adopting their mutating/configuration semantics.
|
|
666
|
+
|
|
667
|
+
### Spec 1.10 / v0.1.10 — 2026-07-07
|
|
668
|
+
|
|
669
|
+
- Defines `svn_diagnose` as a read-only working-copy diagnostic tool for local status, remote
|
|
670
|
+
status, HEAD info, and latest-log reachability.
|
|
671
|
+
- Expands the SVN error taxonomy for `E215004` auth exhaustion, `E175002` network/repository
|
|
672
|
+
failures, `E155036` working-copy locks, and `E200030` SQLite working-copy database failures.
|
|
673
|
+
- Records the external SVN MCP comparison decision: borrow safe diagnostic patterns
|
|
674
|
+
and reject permissive commit/update/force/auth/shell/plain-text semantics.
|
|
675
|
+
|
|
676
|
+
### Spec 1.9 / v0.1.9 — 2026-07-07
|
|
677
|
+
|
|
678
|
+
- Defines segment-aware never-commit guards plus optional repo-local `.svn-mcp-policy.json`
|
|
679
|
+
allow/deny exceptions, including recursive-add descendant scanning.
|
|
680
|
+
- Defines streamed `svn_diff` counting so per-file summaries remain complete when excerpts are
|
|
681
|
+
truncated.
|
|
682
|
+
- Defines `DIFF_FAILED` precommit verdict behavior and EOL-recoverable diff failure handling.
|
|
683
|
+
- Defines structured `eol_fix_verified` refusals for missing/non-file/binary/too-large targets
|
|
684
|
+
and the explicit `allowLarge` escape hatch.
|
|
685
|
+
- Defines URL userinfo and sensitive query-parameter redaction for command display.
|
|
686
|
+
|
|
687
|
+
### Spec 1.8 / v0.1.8 — 2026-07-07
|
|
688
|
+
|
|
689
|
+
- Defines richer `svn_info` mixed-revision interpretation and remote HEAD/stale-base fields.
|
|
690
|
+
- Defines `svn_status` `hideNoise` and `includeIgnored` controls for daily noise reduction and
|
|
691
|
+
explicit review passes.
|
|
692
|
+
- Defines `svn_self_check` for release pointer, payload count, startup probe, and packaging
|
|
693
|
+
script health.
|
|
694
|
+
- Replaces remaining PowerShell clean behavior with Node-based cleanup.
|
|
695
|
+
|
|
696
|
+
### Spec 1.7 — 2026-07-07
|
|
697
|
+
|
|
698
|
+
- Adds the SVN/Subversion pain-point matrix in §16, including which issues the MCP
|
|
699
|
+
already handles, which v0.1.x changes addressed, and which items remain workflow or future
|
|
700
|
+
tooling concerns.
|
|
701
|
+
|
|
702
|
+
### Spec 1.6 / v0.1.7 — 2026-07-07
|
|
703
|
+
|
|
704
|
+
- Defines `svn_log` repository-URL-at-HEAD targeting for working-copy paths to avoid
|
|
705
|
+
mixed-revision root log gaps.
|
|
706
|
+
- Defines inconsistent-EOL diff recovery diagnostics that point callers to `eol_fix_verified`
|
|
707
|
+
instead of surfacing a generic SVN failure.
|
|
708
|
+
- Defines the Node-based `npm run release:prepare` release packaging path to avoid PowerShell
|
|
709
|
+
copy/junction friction.
|
|
710
|
+
|
|
711
|
+
### Spec 1.5 — 2026-07-07
|
|
712
|
+
|
|
713
|
+
- Adds the plug-and-play operator guidance in §15, clarifying configuration, automatic
|
|
714
|
+
working-copy discovery, expected benefits, and known trade-offs.
|
|
715
|
+
|
|
716
|
+
### v0.1.6 — 2026-07-07
|
|
717
|
+
|
|
718
|
+
- Expands G4 never-commit guards for common generated output and dependency/cache artifacts:
|
|
719
|
+
`dist/**`, `node_modules/**`, `coverage/**`, `.cache/**`, and `*.tsbuildinfo`.
|
|
720
|
+
|
|
721
|
+
### v0.1.5 — 2026-07-07
|
|
722
|
+
|
|
723
|
+
- Defines plug-and-play global registration: no end-user environment variables and no
|
|
724
|
+
project-specific launch `cwd`.
|
|
725
|
+
- Defines working-copy inference from absolute path inputs so one MCP registration can serve
|
|
726
|
+
multiple SVN working copies.
|
|
727
|
+
- Defines `--readonly` as the normal read-only launch mode; `SVN_AGENT_READONLY=1` remains only as
|
|
728
|
+
a legacy/dev override.
|
|
729
|
+
|
|
730
|
+
### v0.1.4 — 2026-07-07
|
|
731
|
+
|
|
732
|
+
- Defines the root `bin/` source-tree runtime payload and matching release `bin/` payload.
|
|
733
|
+
- Makes bundled SVN and EOL converter binaries the normal runtime path.
|
|
734
|
+
|
|
735
|
+
### v0.1.3 — 2026-07-07
|
|
736
|
+
|
|
737
|
+
- Adds guarded `svn_move`, `svn_rename`, and `svn_copy` contracts.
|
|
738
|
+
|
|
739
|
+
### v0.1.2 — 2026-07-07
|
|
740
|
+
|
|
741
|
+
- Makes ignored-EOL diffs and EOL repair MCP-owned through bundled converter binaries.
|
|
742
|
+
|
|
743
|
+
### v0.1.1 — 2026-07-07
|
|
744
|
+
|
|
745
|
+
- Defines parent-directory handling for nested explicit file adds and commits.
|
|
746
|
+
|
|
747
|
+
### v0.1.0 — 2026-07-07
|
|
748
|
+
|
|
749
|
+
- Establishes the generic TypeScript/Node stdio MCP architecture, tool families, guards, and
|
|
750
|
+
versioned release layout.
|
|
751
|
+
|
|
752
|
+
## 15. Plug-and-play operating model
|
|
753
|
+
|
|
754
|
+
### 15.1 Corrected requirement
|
|
755
|
+
|
|
756
|
+
The intended operating model is:
|
|
757
|
+
|
|
758
|
+
- The SVN MCP is configured once in each MCP-capable client.
|
|
759
|
+
- The MCP is not tied to one SVN repository, project, product, checkout, or launch directory.
|
|
760
|
+
- A machine may contain many unrelated SVN working copies; one MCP registration must serve all
|
|
761
|
+
of them.
|
|
762
|
+
- Normal users do not set environment variables.
|
|
763
|
+
- Environment variables exist only for development and testing this MCP.
|
|
764
|
+
- Clients should not spend turns locating SVN binaries, composing special diff flags, fixing EOL
|
|
765
|
+
by hand, or re-reading SVN policy for routine work.
|
|
766
|
+
|
|
767
|
+
The phrase "auto register itself when an existing SVN repository is located" means automatic
|
|
768
|
+
working-copy discovery after the MCP has been registered once globally. A stdio MCP server should
|
|
769
|
+
not rewrite arbitrary client configuration files at runtime. Client registration is a one-time
|
|
770
|
+
client setup step; repository selection happens per tool call.
|
|
771
|
+
|
|
772
|
+
### 15.2 End-user configuration
|
|
773
|
+
|
|
774
|
+
Write-capable client:
|
|
775
|
+
|
|
776
|
+
```json
|
|
777
|
+
{
|
|
778
|
+
"mcpServers": {
|
|
779
|
+
"svn": {
|
|
780
|
+
"command": "node",
|
|
781
|
+
"args": ["<MCP_HOME>\\svn-agent\\current\\dist\\index.js"]
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Read-only launch using the same generic server name:
|
|
788
|
+
|
|
789
|
+
```json
|
|
790
|
+
{
|
|
791
|
+
"mcpServers": {
|
|
792
|
+
"svn": {
|
|
793
|
+
"command": "node",
|
|
794
|
+
"args": ["<MCP_HOME>\\svn-agent\\current\\dist\\index.js", "--readonly"]
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
Do not set a project-specific launch `cwd`. Do not set normal-use environment variables. For
|
|
801
|
+
zero-friction multi-repository use, pass absolute paths to MCP tools. When absolute paths are
|
|
802
|
+
provided, the MCP finds the nearest SVN working copy root and runs the command there. Relative
|
|
803
|
+
paths remain supported, but they require an explicit per-call `cwd`.
|
|
804
|
+
|
|
805
|
+
### 15.3 Environment-variable policy
|
|
806
|
+
|
|
807
|
+
No environment variable is required for normal end-user operation.
|
|
808
|
+
|
|
809
|
+
`SVN_AGENT_BIN_DIR`, `SVN_AGENT_SVN_PATH`, `SVN_AGENT_DOS2UNIX_DIR`,
|
|
810
|
+
`SVN_AGENT_TIMEOUT_MS`, `SVN_AGENT_MAX_DIFF_LINES`, and legacy `SVN_AGENT_READONLY` are reserved
|
|
811
|
+
for development, tests, diagnostics, and compatibility checks. They must not be required in
|
|
812
|
+
ordinary client setup because they add friction, make the MCP look project-specific, and invite
|
|
813
|
+
configuration drift between machines.
|
|
814
|
+
|
|
815
|
+
Readonly production use should prefer the explicit `--readonly` launch argument.
|
|
816
|
+
|
|
817
|
+
### 15.4 What the MCP handles for clients
|
|
818
|
+
|
|
819
|
+
- Bundled SVN and EOL converter binaries, including required DLLs.
|
|
820
|
+
- Working-copy inference from absolute paths across multiple SVN checkouts.
|
|
821
|
+
- Scoped status, info, log, and diff commands.
|
|
822
|
+
- Rich mixed-revision interpretation that separates revision ranges, local modifications, remote
|
|
823
|
+
HEAD, and stale-base warnings.
|
|
824
|
+
- Status noise filtering and explicit ignored-path audit mode.
|
|
825
|
+
- Repository-URL-at-HEAD log targeting so mixed-revision working-copy roots still show current
|
|
826
|
+
repository history.
|
|
827
|
+
- Ignored-EOL diffs by default: `svn diff --internal-diff -x --ignore-eol-style`.
|
|
828
|
+
- Inconsistent-EOL diff diagnostics with `eol_fix_verified` as the recovery tool.
|
|
829
|
+
- `svn_precommit` as one structured call for status, ignored-EOL diff, EOL inspection, guards,
|
|
830
|
+
and mixed-revision warning.
|
|
831
|
+
- `svn_commit` with a temporary `-F` message file, explicit paths, guard checks, revision
|
|
832
|
+
parsing, and post-status.
|
|
833
|
+
- `svn_add` with parent-directory scheduling for explicit nested file paths.
|
|
834
|
+
- `svn_move`, `svn_rename`, and `svn_copy` with working-copy containment and parent handling.
|
|
835
|
+
- `eol_fix_verified` using bundled `unix2dos` or `dos2unix`, followed by an ignored-EOL diff
|
|
836
|
+
proof.
|
|
837
|
+
- Hard readonly mode for read-only clients.
|
|
838
|
+
- Never-commit guards for generated output, dependency/cache folders, secrets, and other
|
|
839
|
+
high-risk paths.
|
|
840
|
+
- Node-based release preparation through `npm run release:prepare`, avoiding PowerShell wildcard
|
|
841
|
+
and junction-copy pitfalls during MCP packaging.
|
|
842
|
+
- `svn_self_check` for checking the local `current` pointer, release payload counts, startup
|
|
843
|
+
probe, and packaging script health.
|
|
844
|
+
- `svn_diagnose` for one-call troubleshooting of local SVN health, remote reachability,
|
|
845
|
+
authentication failures, lock problems, and working-copy database failures.
|
|
846
|
+
|
|
847
|
+
### 15.5 How it improves client speed and token use
|
|
848
|
+
|
|
849
|
+
The MCP reduces client work by turning repeated shell recipes into structured tool calls. Clients
|
|
850
|
+
no longer need to:
|
|
851
|
+
|
|
852
|
+
- Search for `svn`, `svnadmin`, `svnversion`, `dos2unix`, `unix2dos`, or their DLLs.
|
|
853
|
+
- Reconstruct SVN policy from rules files for every session.
|
|
854
|
+
- Hand-compose ignored-EOL diff commands.
|
|
855
|
+
- Dump large raw diffs into assistant context when a structured per-file summary is enough.
|
|
856
|
+
- Run separate status, diff, EOL check, and guard commands during every commit-prep loop.
|
|
857
|
+
- Manually create commit message files.
|
|
858
|
+
- Add missing parent directories before adding a nested file.
|
|
859
|
+
- Diagnose and repair common EOL churn with ad hoc PowerShell or byte rewrites.
|
|
860
|
+
|
|
861
|
+
The intended daily flow is:
|
|
862
|
+
|
|
863
|
+
1. Client edits files.
|
|
864
|
+
2. Client calls `svn_precommit` on the intended paths.
|
|
865
|
+
3. Client reviews the structured result and asks for targeted diffs only when needed.
|
|
866
|
+
4. Client fixes EOL through `eol_fix_verified` if required.
|
|
867
|
+
5. Client calls `svn_commit` with explicit paths and a message when the slice is verified and
|
|
868
|
+
safe to commit.
|
|
869
|
+
|
|
870
|
+
For common slices this changes a 5-8 command shell loop into one or two MCP calls. That saves
|
|
871
|
+
model turns, reduces repeated command text, keeps diffs smaller, and makes unattended work less
|
|
872
|
+
likely to stall on avoidable prompts.
|
|
873
|
+
|
|
874
|
+
### 15.6 Workflow improvement
|
|
875
|
+
|
|
876
|
+
One global MCP registration supports many SVN repositories on the same machine. Write-capable clients
|
|
877
|
+
can make guarded changes, while read-only clients use the same tool surface in `--readonly` mode
|
|
878
|
+
and cannot mutate SVN state. This gives both roles the same structured evidence without relying
|
|
879
|
+
on product-specific assumptions or project-specific configuration.
|
|
880
|
+
|
|
881
|
+
The workflow is safer because high-risk actions are explicit: updates need paths or
|
|
882
|
+
`updateAll:true`, revert defaults to dry-run, cleanup never removes unversioned files, URL
|
|
883
|
+
copy/move is refused, and commit checks enforce explicit paths plus mechanical risk signals.
|
|
884
|
+
|
|
885
|
+
### 15.7 CLI failsafe mode
|
|
886
|
+
|
|
887
|
+
If the MCP itself fails mechanically, the caller falls back to scoped raw `svn` CLI **for the
|
|
888
|
+
rest of that session** instead of stalling.
|
|
889
|
+
|
|
890
|
+
**Triggers (mechanical failures only):**
|
|
891
|
+
|
|
892
|
+
- The `svn` MCP server is not registered, not running, or tool calls fail at the protocol level
|
|
893
|
+
(client-side tool errors, no envelope returned).
|
|
894
|
+
- Envelopes report the MCP runtime itself broken: `note` contains
|
|
895
|
+
`"MCP svn runtime unavailable"`, or `svn_diagnose`/`svn_self_check` report the bundled
|
|
896
|
+
toolchain unhealthy and unrecoverable in-session.
|
|
897
|
+
- The same read-only tool call fails twice consecutively for reasons that are clearly not
|
|
898
|
+
SVN-level errors (auth, network, locks are SVN-level — CLI would fail identically and is not
|
|
899
|
+
a remedy for them).
|
|
900
|
+
|
|
901
|
+
**Explicit non-triggers:** a guard refusal is a policy decision, not a failure. READONLY
|
|
902
|
+
refusals, never-commit hits, `riskAck` demands, explicit-paths refusals, and working-copy
|
|
903
|
+
containment refusals must **never** be retried through the CLI. A read-only instance
|
|
904
|
+
stays read-only in failsafe mode.
|
|
905
|
+
|
|
906
|
+
**Failsafe behavior:** the same policy in §5 applies, hand-executed:
|
|
907
|
+
|
|
908
|
+
- Scoped commands only; explicit paths; no whole-tree status/diff.
|
|
909
|
+
- Diff: `svn diff --internal-diff -x --ignore-eol-style <paths…>`.
|
|
910
|
+
- Commit: message file + explicit file list (`svn commit -F <msgfile> <path1> …`), never inline
|
|
911
|
+
`-m`; message file created outside the working copy.
|
|
912
|
+
- Never `--force`; updates only on operator request and with `--accept postpone`.
|
|
913
|
+
- The never-commit list (§7 G4) and risky-slice stops (§5.4) remain in force as caller judgment.
|
|
914
|
+
- EOL repair via `unix2dos`/`dos2unix` binaries (bundled `<MCP_HOME>\svn-agent\current\bin` if
|
|
915
|
+
reachable, otherwise PATH), never PowerShell byte rewrites.
|
|
916
|
+
|
|
917
|
+
**Exit:** failsafe lasts for the session. The caller reports that the MCP was unavailable so the
|
|
918
|
+
operator can repair it (`svn_self_check` / `svn_diagnose` once the server is back).
|
|
919
|
+
|
|
920
|
+
### 15.8 Overheads and trade-offs
|
|
921
|
+
|
|
922
|
+
- A one-time MCP client registration is still required.
|
|
923
|
+
- A stdio MCP cannot safely rewrite every possible client configuration file at runtime.
|
|
924
|
+
- Bundled Windows runtime binaries make the source and release payloads larger.
|
|
925
|
+
- The bundled runtime is Windows-oriented; cross-platform packaging would need matching binaries
|
|
926
|
+
or a documented PATH fallback strategy for those platforms.
|
|
927
|
+
- Absolute paths give the best zero-`cwd` multi-repository behavior. Relative-path-only workflows
|
|
928
|
+
need explicit per-call `cwd`.
|
|
929
|
+
- The MCP reduces SVN housekeeping, but it does not remove the caller's responsibility to inspect
|
|
930
|
+
the requested scope, run project-specific tests, and avoid unrelated changes.
|
|
931
|
+
|
|
932
|
+
## 16. Observed SVN/Subversion pain-point matrix
|
|
933
|
+
|
|
934
|
+
This matrix records SVN pain points that shaped the MCP design. The purpose is to keep future
|
|
935
|
+
work grounded in practical workflow friction, not abstract SVN theory.
|
|
936
|
+
|
|
937
|
+
| # | Pain point | How the MCP helps now | Pending / still human or future-tooling work |
|
|
938
|
+
|---:|---|---|---|
|
|
939
|
+
| 1 | Mixed-revision confusion: a working-copy root can be at an older BASE revision while children are newer. | `svn_info` reports parsed revision ranges, local modification flags, remote HEAD, and stale-base state; `svn_log` queries repository URLs at HEAD when possible. | Callers must still understand whether mixed revision is acceptable for the task. |
|
|
940
|
+
| 2 | `svn log <wc-root>` can stop at the root node's old peg revision and hide newer commits. | v0.1.7 resolves working-copy targets to repository URLs and returns `target_mode:"repository-url"`. | URL fallback can fail if `svn info` cannot resolve a URL; then the MCP returns `working-copy-path` mode. |
|
|
941
|
+
| 3 | Concurrent-client overlap: another actor may commit while local work exists; update can merge `G` files silently. | `svn_update` requires explicit paths or `updateAll:true` and always uses `--accept postpone`; status/conflicts are structured. | Semantic overlap still needs review by the operator or caller. |
|
|
942
|
+
| 4 | Unversioned files are easy to miss; `svn commit` does not include them automatically. | `svn_status` exposes `?` paths; `svn_precommit` blocks uncommittable paths; `svn_add` is explicit. | Caller must decide which unversioned files belong to the current slice. |
|
|
943
|
+
| 5 | SVN cannot add a child file under a brand-new unversioned parent directory without adding parents first. | `svn_add` uses `--parents --depth empty` for files and schedules needed parent dirs without adding siblings. | Recursive directory adds still require `allowRecursive:true`. |
|
|
944
|
+
| 6 | Commit scope ambiguity: no Git-style staging area; broad commits can include unrelated WIP. | Mutating tools require explicit paths; `svn_commit` verifies each path is changed/scheduled. | "Commit everything" remains unsafe unless the caller intentionally scopes all paths. |
|
|
945
|
+
| 7 | Partial slice commits create bookkeeping overhead when some work must remain uncommitted. | `svn_precommit`, scoped `svn_status`, scoped `svn_diff`, and explicit `svn_commit` paths support small slices. | The operator or caller still chooses slice boundaries. |
|
|
946
|
+
| 8 | Update-before-commit discipline matters when others are committing remotely. | `svn_update` is guarded and conflict-safe; `svn_info` reports remote HEAD and stale-base state. | The MCP does not force an update before every commit because some workflows deliberately avoid it. |
|
|
947
|
+
| 9 | Direct-to-trunk workflow means every bad commit lands immediately. | Guarded commit, risk signals, read-only mode, and explicit paths reduce accidental commits. | Branch/PR-like review remains outside SVN/MCP v0.1. |
|
|
948
|
+
| 10 | Old design file sprawl: many versioned variants make "current" unclear. | Never-commit guards reduce future generated clutter; scoped status/diff make touched files visible. | The MCP cannot infer canonical design intent; project docs or an archive map are needed. |
|
|
949
|
+
| 11 | Archiving or moving files is risky because external notes or checklists may reference exact paths. | `svn_move`/`svn_rename` are guarded, scoped, and report changed paths. | The MCP does not yet maintain a reference map or warn about out-of-repo links. |
|
|
950
|
+
| 12 | Noisy status from ignored local runtime folders and generated artifacts. | Never-commit guards block common generated/dependency/cache paths; `svn_status hideNoise:true` filters common local clutter. | Project-specific noise may need local conventions or future custom filters. |
|
|
951
|
+
| 13 | `svn status --no-ignore` is useful for audit but noisy for daily work. | Normal MCP status avoids `--no-ignore`; `includeIgnored:true` enables explicit ignored-path audits. | Callers must choose audit mode deliberately. |
|
|
952
|
+
| 14 | EOL problems can make `svn diff` fail instead of merely showing a messy diff. | `svn_diff` defaults to ignored-EOL internal diff; v0.1.7 returns EOL diagnostics and `recovery_tool:"eol_fix_verified"` on inconsistent EOL failures. | Files still need explicit repair through `eol_fix_verified`; the MCP will not mutate implicitly. |
|
|
953
|
+
| 15 | Editing tools can introduce LF into native-CRLF SVN files, causing diff/commit friction. | `eol_check` and `eol_fix_verified` detect and repair single files through bundled converters. | Preventing bad writes at source depends on editor/client behavior outside the MCP. |
|
|
954
|
+
| 16 | PowerShell byte rewrites or redirects are risky for tracked text files. | The MCP uses `unix2dos`/`dos2unix` binaries through `execFile`; docs forbid PowerShell EOL repair. | Callers should use MCP tools rather than ad hoc shell rewrites. |
|
|
955
|
+
| 17 | Raw `svn diff` command flags are easy to forget and waste tokens. | `svn_diff` owns `svn diff --internal-diff -x --ignore-eol-style` by default and returns structured summaries. | Full raw diffs may still be needed for detailed review. |
|
|
956
|
+
| 18 | SVN history lookup is clunkier than modern Git workflows. | `svn_log` returns structured XML-parsed entries and now avoids mixed-root log gaps. | Higher-level "what changed between these revisions" summaries are future tooling. |
|
|
957
|
+
| 19 | Versioned generated/test artifacts can become permanent repository weight if added accidentally. | Never-commit guards now block `dist/**`, `node_modules/**`, `coverage/**`, `.cache/**`, `*.tsbuildinfo`, secrets, and other risky paths. | Project-specific generated paths may need additional local guard rules later. |
|
|
958
|
+
| 20 | Adding bundled binary release payloads is noisy and easy to miscount. | `npm run release:prepare` validates release `dist` and `bin` counts before repointing `current`; `svn_self_check` reports counts. | SVN add output is still verbose for binary payloads. |
|
|
959
|
+
| 21 | Local `current` junction is intentionally ignored, so a clean SVN status does not prove the local runtime pointer is correct. | `svn_self_check` reports `current` target and whether it matches the package version. | None for normal use. |
|
|
960
|
+
| 22 | PowerShell wildcard/copy/junction command differences caused release-packaging hiccups. | `npm run release:prepare` and `npm run clean` are Node scripts with path containment checks. | None for MCP release/clean paths. |
|
|
961
|
+
| 23 | Message quoting and shell command construction are fragile for commits/imports. | `svn_commit` and `svn_import` use temporary UTF-8 `-F` message files and `execFile`, not shell strings. | Human-written raw SVN commits can still bypass this discipline. |
|
|
962
|
+
| 24 | Root-clean does not mean conceptually clean: a clean status can still represent mixed concerns in the last commit. | Post-commit scoped status proves no local residue; risk signals and explicit paths reduce mixed slices. | Conceptual scope review remains a human or caller responsibility. |
|
|
963
|
+
| 25 | Opaque SVN failures waste turns: auth exhaustion, server connection failures, WC locks, and WC database issues all look like generic command failures when raw stderr is fed back into an assistant transcript. | v0.1.10 adds `svn_diagnose` and expands `noteFromRun` so these classes produce structured notes and next-step suggestions. | Repository-specific server outages and credential fixes still happen outside the MCP. |
|
|
964
|
+
| 26 | Existing generic SVN MCPs contain useful diagnostics but may reintroduce friction or risk through PATH/env setup, credential env vars, force flags, optional broad commits, and shell/string command execution. | This MCP borrows the safe read-only diagnostic/error-taxonomy ideas while keeping bundled binaries, no normal-use env vars, no-shell execution, explicit path commits, and guarded mutating tools. | Future external comparisons should be treated as design input, not as a reason to fork or loosen guards. |
|