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.
Files changed (165) hide show
  1. package/.editorconfig +12 -0
  2. package/.gitattributes +7 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.svn-mcp-policy.json +9 -0
  5. package/CHANGELOG.md +248 -0
  6. package/CONTRIBUTING.md +49 -0
  7. package/LICENSE +201 -0
  8. package/README.md +143 -0
  9. package/SECURITY.md +21 -0
  10. package/THIRD_PARTY_CHECKSUMS.txt +52 -0
  11. package/THIRD_PARTY_NOTICES.md +46 -0
  12. package/bin/SlikSvn-DB44-20-x64.dll +0 -0
  13. package/bin/SlikSvn-libapr-1.dll +0 -0
  14. package/bin/SlikSvn-libaprutil-1.dll +0 -0
  15. package/bin/SlikSvn-libcrypto-3-x64.dll +0 -0
  16. package/bin/SlikSvn-libintl.dll +0 -0
  17. package/bin/SlikSvn-libsasl21.dll +0 -0
  18. package/bin/SlikSvn-libssl-3-x64.dll +0 -0
  19. package/bin/SlikSvn-libsvn_client-1.dll +0 -0
  20. package/bin/SlikSvn-libsvn_delta-1.dll +0 -0
  21. package/bin/SlikSvn-libsvn_diff-1.dll +0 -0
  22. package/bin/SlikSvn-libsvn_fs-1.dll +0 -0
  23. package/bin/SlikSvn-libsvn_fs_base-1.dll +0 -0
  24. package/bin/SlikSvn-libsvn_fs_fs-1.dll +0 -0
  25. package/bin/SlikSvn-libsvn_fs_util-1.dll +0 -0
  26. package/bin/SlikSvn-libsvn_fs_x-1.dll +0 -0
  27. package/bin/SlikSvn-libsvn_ra-1.dll +0 -0
  28. package/bin/SlikSvn-libsvn_repos-1.dll +0 -0
  29. package/bin/SlikSvn-libsvn_subr-1.dll +0 -0
  30. package/bin/SlikSvn-libsvn_wc-1.dll +0 -0
  31. package/bin/System64/concrt140.dll +0 -0
  32. package/bin/System64/msvcp140.dll +0 -0
  33. package/bin/System64/msvcp140_1.dll +0 -0
  34. package/bin/System64/msvcp140_2.dll +0 -0
  35. package/bin/System64/msvcp140_atomic_wait.dll +0 -0
  36. package/bin/System64/msvcp140_codecvt_ids.dll +0 -0
  37. package/bin/System64/vccorlib140.dll +0 -0
  38. package/bin/System64/vcruntime140.dll +0 -0
  39. package/bin/System64/vcruntime140_1.dll +0 -0
  40. package/bin/dos2unix.exe +0 -0
  41. package/bin/engines/capi.dll +0 -0
  42. package/bin/libsvnjavahl-1.dll +0 -0
  43. package/bin/mac2unix.exe +0 -0
  44. package/bin/svn-populate-node-origins-index.exe +0 -0
  45. package/bin/svn.exe +0 -0
  46. package/bin/svnadmin.exe +0 -0
  47. package/bin/svnauthz-validate.exe +0 -0
  48. package/bin/svnauthz.exe +0 -0
  49. package/bin/svnbench.exe +0 -0
  50. package/bin/svndumpfilter.exe +0 -0
  51. package/bin/svnfsfs.exe +0 -0
  52. package/bin/svnlook.exe +0 -0
  53. package/bin/svnmucc.exe +0 -0
  54. package/bin/svnrdump.exe +0 -0
  55. package/bin/svnserve.exe +0 -0
  56. package/bin/svnsync.exe +0 -0
  57. package/bin/svnversion.exe +0 -0
  58. package/bin/unix2dos.exe +0 -0
  59. package/bin/unix2mac.exe +0 -0
  60. package/dist/envelope.d.ts +39 -0
  61. package/dist/envelope.d.ts.map +1 -0
  62. package/dist/envelope.js +137 -0
  63. package/dist/envelope.js.map +1 -0
  64. package/dist/eol.d.ts +15 -0
  65. package/dist/eol.d.ts.map +1 -0
  66. package/dist/eol.js +119 -0
  67. package/dist/eol.js.map +1 -0
  68. package/dist/guards.d.ts +26 -0
  69. package/dist/guards.d.ts.map +1 -0
  70. package/dist/guards.js +306 -0
  71. package/dist/guards.js.map +1 -0
  72. package/dist/index.d.ts +8 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +221 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/parse/commitText.d.ts +2 -0
  77. package/dist/parse/commitText.d.ts.map +1 -0
  78. package/dist/parse/commitText.js +9 -0
  79. package/dist/parse/commitText.js.map +1 -0
  80. package/dist/parse/diffText.d.ts +7 -0
  81. package/dist/parse/diffText.d.ts.map +1 -0
  82. package/dist/parse/diffText.js +55 -0
  83. package/dist/parse/diffText.js.map +1 -0
  84. package/dist/parse/infoXml.d.ts +3 -0
  85. package/dist/parse/infoXml.d.ts.map +1 -0
  86. package/dist/parse/infoXml.js +35 -0
  87. package/dist/parse/infoXml.js.map +1 -0
  88. package/dist/parse/logXml.d.ts +10 -0
  89. package/dist/parse/logXml.d.ts.map +1 -0
  90. package/dist/parse/logXml.js +39 -0
  91. package/dist/parse/logXml.js.map +1 -0
  92. package/dist/parse/statusXml.d.ts +6 -0
  93. package/dist/parse/statusXml.d.ts.map +1 -0
  94. package/dist/parse/statusXml.js +64 -0
  95. package/dist/parse/statusXml.js.map +1 -0
  96. package/dist/parse/updateText.d.ts +6 -0
  97. package/dist/parse/updateText.d.ts.map +1 -0
  98. package/dist/parse/updateText.js +67 -0
  99. package/dist/parse/updateText.js.map +1 -0
  100. package/dist/runner.d.ts +41 -0
  101. package/dist/runner.d.ts.map +1 -0
  102. package/dist/runner.js +270 -0
  103. package/dist/runner.js.map +1 -0
  104. package/dist/tools/composite.d.ts +15 -0
  105. package/dist/tools/composite.d.ts.map +1 -0
  106. package/dist/tools/composite.js +238 -0
  107. package/dist/tools/composite.js.map +1 -0
  108. package/dist/tools/diagnose.d.ts +6 -0
  109. package/dist/tools/diagnose.d.ts.map +1 -0
  110. package/dist/tools/diagnose.js +131 -0
  111. package/dist/tools/diagnose.js.map +1 -0
  112. package/dist/tools/mutating.d.ts +80 -0
  113. package/dist/tools/mutating.d.ts.map +1 -0
  114. package/dist/tools/mutating.js +558 -0
  115. package/dist/tools/mutating.js.map +1 -0
  116. package/dist/tools/readonly.d.ts +52 -0
  117. package/dist/tools/readonly.d.ts.map +1 -0
  118. package/dist/tools/readonly.js +638 -0
  119. package/dist/tools/readonly.js.map +1 -0
  120. package/dist/tools/selfcheck.d.ts +5 -0
  121. package/dist/tools/selfcheck.d.ts.map +1 -0
  122. package/dist/tools/selfcheck.js +91 -0
  123. package/dist/tools/selfcheck.js.map +1 -0
  124. package/dist/types.d.ts +66 -0
  125. package/dist/types.d.ts.map +1 -0
  126. package/dist/types.js +2 -0
  127. package/dist/types.js.map +1 -0
  128. package/docs/README.md +11 -0
  129. package/docs/SPEC.md +964 -0
  130. package/docs/decisions/ADR-001-use-spec-v1-architecture.md +25 -0
  131. package/docs/decisions/ADR-002-use-capability-based-generic-svn-policy.md +31 -0
  132. package/docs/decisions/ADR-003-use-plug-and-play-global-registration.md +39 -0
  133. package/docs/decisions/ADR-004-borrow-diagnostics-not-external-mcp-surface.md +39 -0
  134. package/docs/svnrules.md +84 -0
  135. package/docs/toolrules.md +34 -0
  136. package/jest.config.cjs +14 -0
  137. package/package.json +51 -0
  138. package/scripts/clean.mjs +27 -0
  139. package/scripts/prepare-release.mjs +84 -0
  140. package/src/envelope.ts +182 -0
  141. package/src/eol.ts +137 -0
  142. package/src/guards.ts +362 -0
  143. package/src/index.ts +364 -0
  144. package/src/parse/commitText.ts +9 -0
  145. package/src/parse/diffText.ts +66 -0
  146. package/src/parse/infoXml.ts +51 -0
  147. package/src/parse/logXml.ts +64 -0
  148. package/src/parse/statusXml.ts +87 -0
  149. package/src/parse/updateText.ts +76 -0
  150. package/src/runner.ts +344 -0
  151. package/src/tools/composite.ts +289 -0
  152. package/src/tools/diagnose.ts +150 -0
  153. package/src/tools/mutating.ts +694 -0
  154. package/src/tools/readonly.ts +786 -0
  155. package/src/tools/selfcheck.ts +97 -0
  156. package/src/types.ts +76 -0
  157. package/tests/README.md +9 -0
  158. package/tests/diff-redaction.test.ts +146 -0
  159. package/tests/entrypoint.test.ts +49 -0
  160. package/tests/guards-eol.test.ts +170 -0
  161. package/tests/integration-svn.test.ts +709 -0
  162. package/tests/parsers.test.ts +119 -0
  163. package/tests/runner.test.ts +159 -0
  164. package/tests/selfcheck.test.ts +22 -0
  165. package/tsconfig.json +30 -0
@@ -0,0 +1,25 @@
1
+ # ADR-001: Use The SPEC v1.0 Architecture
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Date
8
+
9
+ 2026-07-07
10
+
11
+ ## Context
12
+
13
+ The project needs a new SVN MCP that reduces agent SVN housekeeping, prevents line-ending churn, and exposes guarded SVN operations. The existing plan in `docs/SPEC.md` already captures pain points, rejected alternatives, the process model, tool contracts, guard rules, and phased verification gates.
14
+
15
+ ## Decision
16
+
17
+ Implement the project from scratch as an ESM TypeScript/Node stdio MCP server following `docs/SPEC.md` version 1.0. Keep the server small and auditable, with process execution isolated in a runner module, structured response envelopes, explicit guard logic, parser modules, and separated read-only/composite/mutating tool families.
18
+
19
+ ## Consequences
20
+
21
+ - The implementation has one written source of truth.
22
+ - Future changes that affect tool behavior should update the spec deliberately.
23
+ - Release layout and registration should follow the `releases/v<version>` plus `current` junction convention from the spec.
24
+ - This ADR records the initial v1.0 architecture decision; the current spec version may be newer
25
+ and remains authoritative for tool contracts and release status.
@@ -0,0 +1,31 @@
1
+ # ADR-002: Use Capability-Based Generic SVN Policy
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Date
8
+
9
+ 2026-07-07
10
+
11
+ ## Context
12
+
13
+ The MCP must describe SVN safety in terms of client capabilities instead of deployment-specific
14
+ tool names. The same SVN problems occur across many projects, and write-capable or read-only
15
+ capabilities can be assigned to different clients in different deployments.
16
+
17
+ ## Decision
18
+
19
+ Make the MCP specification generic and capability-based. The implementation will refer to
20
+ write-capable clients, read-only clients, project working copies, and deployment-provided paths. It will not
21
+ hard-code deployment-specific names, hostnames, paths, or product names into behavior or
22
+ documentation.
23
+
24
+ ## Consequences
25
+
26
+ - One MCP can serve multiple SVN projects with the same safety model.
27
+ - Read-only safety is enforced by launching that MCP instance with `--readonly`, independent of
28
+ which client is assigned read-only behavior. The legacy/dev `SVN_AGENT_READONLY=1` override remains
29
+ available for tests.
30
+ - Project-specific path, hook, auto-props, build, and allowlist examples must remain templates or
31
+ deployment notes.
@@ -0,0 +1,39 @@
1
+ # ADR-003: Use Plug-And-Play Global Registration
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Date
8
+
9
+ 2026-07-07
10
+
11
+ ## Context
12
+
13
+ The MCP may be used on machines with many SVN working copies. Tying the MCP client registration to
14
+ a fixed launch `cwd`, or requiring end-user environment variables, creates avoidable setup friction
15
+ and causes clients to spend setup time locating tools or reconfiguring per project.
16
+
17
+ The runtime already bundles SVN and EOL converter binaries, so the remaining friction was
18
+ working-copy selection and read-only client configuration.
19
+
20
+ ## Decision
21
+
22
+ Register the SVN MCP once globally without a project-specific launch `cwd` and without normal-use
23
+ environment variables. Tool calls that provide absolute paths infer the nearest SVN working copy
24
+ for those paths. Relative paths continue to resolve against explicit per-call `cwd`, or the MCP
25
+ process cwd when no `cwd` is provided.
26
+
27
+ Read-only clients use the `--readonly` launch argument. `SVN_AGENT_*` variables remain only
28
+ as development/test escape hatches.
29
+
30
+ ## Consequences
31
+
32
+ - One MCP registration can serve many SVN repositories on the same machine.
33
+ - End users do not need to locate `svn`, `svnadmin`, `dos2unix`, `unix2dos`, or configure PATH.
34
+ - Clients should prefer absolute paths for zero-friction multi-repo tool calls.
35
+ - Client registration remains static; the MCP does not rewrite client configuration at runtime.
36
+ - Relative-path-only workflows still need either a meaningful client process cwd or explicit
37
+ per-call `cwd`.
38
+ - `svn_self_check` verifies the local release pointer and bundled runtime; `svn_diagnose` verifies
39
+ read-only working-copy health without adding repo-specific client configuration.
@@ -0,0 +1,39 @@
1
+ # ADR-004: Borrow Diagnostics, Not External SVN MCP Semantics
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Date
8
+
9
+ 2026-07-07
10
+
11
+ ## Context
12
+
13
+ Existing public SVN MCP projects contain useful operator ideas: health checks, command
14
+ diagnostics, and clearer categorization for common SVN errors. Some also expose behavior that
15
+ conflicts with this MCP's purpose: PATH-based setup, end-user environment variables for
16
+ credentials and working directory, permissive force/update options, optional broad commit scope,
17
+ shell/string command execution, and plain-text-oriented responses.
18
+
19
+ This MCP is meant to be plug-and-play, multi-working-copy, bundled, no-shell, explicit-path,
20
+ guarded, and safe for separate write-capable and read-only clients.
21
+
22
+ ## Decision
23
+
24
+ Use external SVN MCPs as reference material only. Borrow read-only diagnostics, error taxonomy,
25
+ and documentation lessons when they fit the guard model. Do not fork or import mutating semantics,
26
+ credential configuration, launch configuration, or shell execution patterns that increase setup
27
+ friction or weaken commit/update safety.
28
+
29
+ The v0.1.10 implementation applies this by adding `svn_diagnose` and expanding SVN error
30
+ classification while preserving bundled runtime binaries, zero normal-use environment variables,
31
+ explicit-path commits, readonly mode, and structured envelopes.
32
+
33
+ ## Consequences
34
+
35
+ - Future comparisons should produce a small "borrow/reject" list, not a rewrite.
36
+ - Diagnostics may improve without expanding the mutating surface.
37
+ - End users still configure the MCP once globally and do not set SVN credential env vars.
38
+ - The MCP remains opinionated about safety even when generic SVN tools expose more permissive
39
+ flags.
@@ -0,0 +1,84 @@
1
+ # SVN Rules
2
+
3
+ Read when source-changing work reaches diff/commit prep, imports, EOL problems, or tasks
4
+ specifically about SVN state.
5
+
6
+ ## Principle
7
+
8
+ SVN is for preserving completed work, not for warming up. Do the requested work first. Use SVN only when it is needed for exact diff, EOL diagnosis, conflict diagnosis, or commit.
9
+
10
+ ## When SVN is allowed
11
+
12
+ - **Before editing:** no SVN command unless the task is about SVN/EOL, the target file is ambiguous, or the user asks.
13
+ - **After editing tracked files:** run scoped diff/status only on touched/intended paths when needed for handoff or commit.
14
+ - **Commit prep:** use scoped MCP `svn_status` and `svn_diff` on intended paths. `svn_diff` runs `svn diff --internal-diff -x "--ignore-eol-style"` by default. Confirm the diff is exactly the requested change.
15
+ - **Property work:** use MCP `svn_propget` and guarded `svn_propset` for generic working-copy properties. Use `svn_propset_eol_style` for `svn:eol-style` when normalizing EOL policy.
16
+ - **`svn update .`:** only when the user explicitly asks or the task is specifically to update/sync the working copy. Never run it as default preflight.
17
+
18
+ ## MCP Registration
19
+
20
+ Register the SVN MCP once globally. Do not configure a project-specific launch `cwd` or normal-use
21
+ environment variables. For frictionless multi-repo use, pass absolute paths to MCP tools; the MCP
22
+ will infer the matching SVN working copy. Use explicit per-call `cwd` only when working with
23
+ relative paths.
24
+
25
+ Use `svn_self_check` when the MCP release pointer or bundled toolchain is in question. Use
26
+ `svn_diagnose` when SVN behavior is unclear or raw SVN errors mention authentication, network
27
+ access, working-copy locks, database problems, or stale remote reachability.
28
+
29
+ ## Failsafe: raw svn CLI when the MCP is down
30
+
31
+ If the SVN MCP fails mechanically — the server is not registered or not responding, tool calls
32
+ error at the protocol level, or envelopes/`svn_diagnose` report "MCP svn runtime unavailable" —
33
+ switch to scoped raw `svn` CLI for the rest of the session instead of stalling:
34
+
35
+ - Follow the same rules in this file by hand: scoped explicit paths, ignored-EOL diff
36
+ (`svn diff --internal-diff -x --ignore-eol-style <paths…>`), commit via `-F` message file plus
37
+ explicit file list, never `--force`, update only on user request with `--accept postpone`,
38
+ never-commit list still applies, EOL repair via `unix2dos`/`dos2unix` binaries only.
39
+ - A guard refusal is not a failure. Never use the CLI to redo something the MCP refused
40
+ (READONLY, never-commit, riskAck, explicit-paths, containment). Read-only instances stay
41
+ read-only in failsafe mode.
42
+ - SVN-level errors (authentication, network, working-copy locks) are not failsafe triggers —
43
+ the CLI fails the same way; use `svn_diagnose` output to report them instead.
44
+ - Report that the MCP was unavailable and failsafe was used, so it can be repaired;
45
+ verify with `svn_self_check` once it is back.
46
+
47
+ ## Frequent commits
48
+
49
+ - Write-capable clients commit after completed, verified, requested-scope, non-risky slices.
50
+ - A commit is never the first step. Implement → verify → inspect diff → commit.
51
+ - Commit only intended paths, one slice at a time.
52
+ - Use a message file and explicit file list: `svn commit -F <msgfile> <path1> <path2>`. Never use bare inline `-m` from an automated session.
53
+ - After commit, report the revision and clean scoped status.
54
+ - If verification fails, fix it or report the exact failure. Do not commit a known-bad slice.
55
+
56
+ ## Risky slices stop before commit
57
+
58
+ Ask for explicit approval before committing when the slice is large, destructive, schema-changing, version-bumping, build-system-changing, delete-heavy, security-sensitive, or scope-unclear.
59
+
60
+ ## Read-Only Clients
61
+
62
+ Read-only instances never commit, stage, revert, update, or change SVN state. They may
63
+ describe the intended fix or commit plan for a write-capable client.
64
+
65
+ ## EOL and encoding
66
+
67
+ - Preserve existing encoding and EOL. Do not normalize whole files.
68
+ - EOL-only churn is not a code change. Check with MCP `svn_diff`; it ignores EOL style by default.
69
+ - If SVN reports inconsistent line endings, use MCP `eol_fix_verified` on that one file. The MCP chooses `unix2dos` or `dos2unix` from `svn:eol-style`, runs the real binary, then re-checks the ignored-EOL diff.
70
+ - Do not use PowerShell byte rewrites, scripts, pipes, or redirects on tracked text files.
71
+
72
+ ## Never commit
73
+
74
+ In managed project working copies, never commit `bin/`, `dist/`, `node_modules/`, `coverage/`, `obj/`, `.vs/`, `.cache/`, `*.tsbuildinfo`, generated output, `*.db`, `scratch/**`, secrets, keys, certificates, tool caches, or unrelated drive-by changes. The root `bin/` folder in this MCP repository is versioned source for the bundled runtime tools.
75
+
76
+ ## Commit message format
77
+
78
+ ```text
79
+ <short summary>
80
+
81
+ - <logical change group>
82
+ - <verification performed>
83
+ - <behavior impact, or "No behavior changes">
84
+ ```
@@ -0,0 +1,34 @@
1
+ # Tool And PowerShell Rules
2
+
3
+ Read only when choosing a command/tool, debugging shell behavior, or doing mechanical edits. Do not
4
+ run capability sweeps unless the task is about tool availability.
5
+
6
+ ## Local toolchain
7
+
8
+ Prefer these tools when available: `rg`, `fd`, `sg`, `jq`, `yq`, `duckdb`, `xsv`, `uv`,
9
+ `python`, `node`, `npm`, `npx`, `dotnet`, `git`, `svn`, `bat`, `glow`, `pandoc`, `ffmpeg`,
10
+ and `ctags`. Project-specific build commands must come from that project's README,
11
+ project instructions, or build scripts.
12
+
13
+ - `rg` before manual text inspection; `fd` / `rg --files` only to locate a specific named file/pattern (not broad discovery); `sg` for
14
+ syntax-aware search/edits; `jq`/`duckdb`/`xsv` for structured data.
15
+ - Read-only review roles may only read/search/extract; scripts must print/extract only, never
16
+ write files or run project code.
17
+ - `graphify` is read/navigation only; the graph lives in the parent repo `.graphify/`. Do not rebuild
18
+ it unless the user asks.
19
+ - If a tool is missing, say so and use the nearest installed fallback.
20
+
21
+ ## PowerShell
22
+
23
+ - Prefer simple, targeted commands. Avoid broad recursive `Get-ChildItem` when `rg --files`/`fd` works.
24
+ - Do not use `Set-Content`/`Out-File` on tracked text files; use the Edit/Write tool. Keep tracked
25
+ text UTF-8 (no BOM where the file already is) and the file's existing EOL.
26
+ - For line slices use `$lines = Get-Content -LiteralPath path; $lines[120..170]`, or
27
+ `Get-Content path | Select-Object -Skip 120 -First 50`. Do not pass `120..170` directly to
28
+ `Select-Object -Index`; PowerShell treats it as a string in some invocation contexts.
29
+ - Use single-quoted strings for literal Windows paths. Use `curl.exe`, not `curl`, for real HTTP.
30
+ - Multi-line strings to native exes: single-quoted here-strings with the closing `'@` at column 0.
31
+ - For SVN EOL repair, use MCP `eol_fix_verified` or the bundled `unix2dos`/`dos2unix` binaries.
32
+ Do not use PowerShell byte rewrites or redirects on tracked text files.
33
+ - For SVN property reads/writes, use MCP `svn_propget` and guarded `svn_propset` before falling
34
+ back to raw `svn propget` / `svn propset`.
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ preset: "ts-jest/presets/default-esm",
3
+ testEnvironment: "node",
4
+ extensionsToTreatAsEsm: [".ts"],
5
+ transform: {
6
+ "^.+\\.tsx?$": ["ts-jest", { useESM: true }]
7
+ },
8
+ moduleNameMapper: {
9
+ "^(\\.{1,2}/.*)\\.js$": "$1"
10
+ },
11
+ roots: ["<rootDir>/src", "<rootDir>/tests"],
12
+ testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
13
+ collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"]
14
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "svn-agent-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Strict SVN Model Context Protocol server for agent-safe SVN workflows.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/shrishailrana-maker/svn-agent-mcp-v1.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/shrishailrana-maker/svn-agent-mcp-v1/issues"
14
+ },
15
+ "homepage": "https://github.com/shrishailrana-maker/svn-agent-mcp-v1#readme",
16
+ "bin": {
17
+ "svn-agent-mcp": "dist/index.js"
18
+ },
19
+ "scripts": {
20
+ "dev": "tsx src/index.ts",
21
+ "build": "tsc -p tsconfig.json",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --passWithNoTests",
24
+ "test:watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch",
25
+ "prepare:local": "npm run build && npm run release:prepare",
26
+ "release:prepare": "node scripts/prepare-release.mjs",
27
+ "clean": "node scripts/clean.mjs"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "svn",
32
+ "subversion",
33
+ "model-context-protocol"
34
+ ],
35
+ "license": "Apache-2.0",
36
+ "engines": {
37
+ "node": ">=20.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.29.0",
41
+ "fast-xml-parser": "^5.9.3",
42
+ "zod": "^4.4.3"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^26.1.0",
46
+ "jest": "^30.4.2",
47
+ "ts-jest": "^29.4.11",
48
+ "tsx": "^4.23.0",
49
+ "typescript": "^6.0.3"
50
+ }
51
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+ const dryRun = process.argv.includes("--dry-run");
9
+ const targets = ["dist"];
10
+
11
+ for (const name of targets) {
12
+ const target = path.join(root, name);
13
+ assertInside(root, target);
14
+ if (dryRun) {
15
+ console.log(`Would remove ${target}`);
16
+ continue;
17
+ }
18
+ await fs.rm(target, { recursive: true, force: true });
19
+ console.log(`Removed ${target}`);
20
+ }
21
+
22
+ function assertInside(base, candidate) {
23
+ const relative = path.relative(base, candidate);
24
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
25
+ throw new Error(`refusing to remove outside project root: ${candidate}`);
26
+ }
27
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+ const packageJsonPath = path.join(root, "package.json");
9
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
10
+ const version = packageJson.version;
11
+
12
+ if (!version) {
13
+ throw new Error("package.json version is required");
14
+ }
15
+
16
+ const sourceDist = path.join(root, "dist");
17
+ const sourceBin = path.join(root, "bin");
18
+ const releasesRoot = path.join(root, "releases");
19
+ const releaseRoot = path.join(releasesRoot, `v${version}`);
20
+ const releaseDist = path.join(releaseRoot, "dist");
21
+ const releaseBin = path.join(releaseRoot, "bin");
22
+ const current = path.join(root, "current");
23
+
24
+ for (const target of [releasesRoot, releaseRoot, releaseDist, releaseBin, current]) {
25
+ assertInside(root, target);
26
+ }
27
+
28
+ await assertDirectory(sourceDist, "dist is missing; run npm run build first");
29
+ await assertDirectory(sourceBin, "bin is missing; bundled runtime payload is required");
30
+
31
+ await fs.rm(releaseRoot, { recursive: true, force: true });
32
+ await fs.mkdir(releaseRoot, { recursive: true });
33
+ await fs.cp(sourceDist, releaseDist, { recursive: true });
34
+ await fs.cp(sourceBin, releaseBin, { recursive: true });
35
+
36
+ const binFileCount = await countFiles(releaseBin);
37
+ const distFileCount = await countFiles(releaseDist);
38
+ if (binFileCount < 35) {
39
+ throw new Error(`release bin payload is incomplete: expected at least 35 files, found ${binFileCount}`);
40
+ }
41
+ if (distFileCount < 60) {
42
+ throw new Error(`release dist payload is incomplete: expected at least 60 files, found ${distFileCount}`);
43
+ }
44
+
45
+ await fs.rm(current, { recursive: true, force: true });
46
+ await fs.symlink(path.relative(root, releaseRoot), current, process.platform === "win32" ? "junction" : "dir");
47
+
48
+ console.log(`Prepared release v${version}`);
49
+ console.log(` dist: ${releaseDist} (${distFileCount} files)`);
50
+ console.log(` bin: ${releaseBin} (${binFileCount} files)`);
51
+ console.log(` current -> ${releaseRoot}`);
52
+
53
+ async function assertDirectory(candidate, message) {
54
+ try {
55
+ const stat = await fs.stat(candidate);
56
+ if (stat.isDirectory()) {
57
+ return;
58
+ }
59
+ } catch {
60
+ // handled below
61
+ }
62
+ throw new Error(message);
63
+ }
64
+
65
+ function assertInside(base, candidate) {
66
+ const relative = path.relative(base, candidate);
67
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
68
+ throw new Error(`refusing to write outside project root: ${candidate}`);
69
+ }
70
+ }
71
+
72
+ async function countFiles(directory) {
73
+ const entries = await fs.readdir(directory, { withFileTypes: true });
74
+ let count = 0;
75
+ for (const entry of entries) {
76
+ const child = path.join(directory, entry.name);
77
+ if (entry.isDirectory()) {
78
+ count += await countFiles(child);
79
+ } else if (entry.isFile()) {
80
+ count += 1;
81
+ }
82
+ }
83
+ return count;
84
+ }
@@ -0,0 +1,182 @@
1
+ import type { ChangedPath, Conflict, Envelope, RunResult } from "./types.js";
2
+
3
+ const DEFAULT_SUMMARY_LINES = 200;
4
+
5
+ export function redactArgv(executable: string, args: string[]): string {
6
+ const redacted = [executable, ...args];
7
+ for (let index = 0; index < redacted.length; index += 1) {
8
+ const value = redacted[index] ?? "";
9
+ const arg = value.toLowerCase();
10
+ if ((arg === "--password" || arg === "--username") && index + 1 < redacted.length) {
11
+ redacted[index + 1] = "***";
12
+ continue;
13
+ }
14
+ if (arg.startsWith("--password=") || arg.startsWith("--username=")) {
15
+ const equalsIndex = value.indexOf("=");
16
+ redacted[index] = `${value.slice(0, equalsIndex)}=***`;
17
+ continue;
18
+ }
19
+ redacted[index] = redactText(redacted[index] ?? "");
20
+ }
21
+
22
+ return redacted.map(quoteArgForDisplay).join(" ");
23
+ }
24
+
25
+ export function quoteArgForDisplay(arg: string): string {
26
+ if (arg.length === 0) {
27
+ return '""';
28
+ }
29
+
30
+ if (/[\s"]/.test(arg)) {
31
+ return `"${arg.replace(/"/g, '\\"')}"`;
32
+ }
33
+
34
+ return arg;
35
+ }
36
+
37
+ export function redactText(value: string): string {
38
+ const withSafeUrl = redactUrlUserinfo(value);
39
+ return withSafeUrl.replace(
40
+ /([?&](?:access_token|api[_-]?key|auth|key|pass(?:word)?|secret|token)=)[^&#\s]*/gi,
41
+ "$1***"
42
+ );
43
+ }
44
+
45
+ function redactUrlUserinfo(value: string): string {
46
+ return value.replace(/([a-z][a-z0-9+.-]*:\/\/)(?:[^\s/?#]*@)+/gi, "$1***:***@");
47
+ }
48
+
49
+ export function summarizeText(text: string, maxLines = DEFAULT_SUMMARY_LINES): { text: string; truncated: boolean } {
50
+ if (!text) {
51
+ return { text: "", truncated: false };
52
+ }
53
+
54
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
55
+ const lines = normalized.split("\n");
56
+ if (lines.length <= maxLines) {
57
+ return { text: normalized, truncated: false };
58
+ }
59
+
60
+ return {
61
+ text: lines.slice(0, maxLines).join("\n"),
62
+ truncated: true
63
+ };
64
+ }
65
+
66
+ export function createEnvelope(input: {
67
+ ok: boolean;
68
+ command: string;
69
+ cwd: string;
70
+ revision?: number | null | undefined;
71
+ changed_paths?: ChangedPath[] | undefined;
72
+ conflicts?: Conflict[] | undefined;
73
+ stdout?: string | undefined;
74
+ stderr?: string | undefined;
75
+ truncated?: boolean | undefined;
76
+ note?: string | undefined;
77
+ }): Envelope {
78
+ const stdout = summarizeText(redactText(input.stdout ?? ""));
79
+ const stderr = summarizeText(redactText(input.stderr ?? ""));
80
+
81
+ return {
82
+ ok: input.ok,
83
+ command: input.command,
84
+ cwd: input.cwd,
85
+ revision: input.revision ?? null,
86
+ changed_paths: input.changed_paths ?? [],
87
+ conflicts: input.conflicts ?? [],
88
+ stdout_summary: stdout.text,
89
+ stderr_summary: stderr.text,
90
+ truncated: Boolean(input.truncated || stdout.truncated || stderr.truncated),
91
+ note: input.note ?? ""
92
+ };
93
+ }
94
+
95
+ export function envelopeFromRun(input: {
96
+ ok?: boolean | undefined;
97
+ run: RunResult;
98
+ revision?: number | null | undefined;
99
+ changed_paths?: ChangedPath[] | undefined;
100
+ conflicts?: Conflict[] | undefined;
101
+ note?: string | undefined;
102
+ truncated?: boolean | undefined;
103
+ }): Envelope {
104
+ return createEnvelope({
105
+ ok: input.ok ?? input.run.exitCode === 0,
106
+ command: input.run.command,
107
+ cwd: input.run.cwd,
108
+ revision: input.revision ?? null,
109
+ changed_paths: input.changed_paths,
110
+ conflicts: input.conflicts,
111
+ stdout: input.run.stdout,
112
+ stderr: input.run.stderr,
113
+ truncated: input.truncated ?? input.run.truncated,
114
+ note: input.note ?? noteFromRun(input.run)
115
+ });
116
+ }
117
+
118
+ export function failEnvelope(command: string, cwd: string, note: string, extra?: Partial<Envelope>): Envelope {
119
+ return {
120
+ ...createEnvelope({
121
+ ok: false,
122
+ command,
123
+ cwd,
124
+ note
125
+ }),
126
+ ...extra,
127
+ ok: false,
128
+ command,
129
+ cwd,
130
+ note
131
+ };
132
+ }
133
+
134
+ export function noteFromRun(run: RunResult): string {
135
+ if (run.timedOut) {
136
+ return "svn timed out";
137
+ }
138
+
139
+ if (run.errorCode === "ENOENT" || run.errorCode === "EACCES" || run.errorCode === "EPERM") {
140
+ return "MCP svn runtime unavailable (executable failed to launch) - failsafe: use scoped raw svn CLI for this session per svnrules; guard refusals are not failures and must never be bypassed via CLI";
141
+ }
142
+
143
+ const text = `${run.stderr}\n${run.stdout}`;
144
+ if (/E155004|E155036/.test(text)) {
145
+ return "working copy locked - run svn_cleanup";
146
+ }
147
+ if (/E215004|No more credentials|we tried too many times|E170001|authorization failed|Authentication failed/i.test(text)) {
148
+ return "authentication failed - fix svn cached auth outside the MCP";
149
+ }
150
+ if (/E175002|Unable to connect|Connection refused|Network is unreachable|Could not resolve hostname/i.test(text)) {
151
+ return "network or repository connection failed";
152
+ }
153
+ if (/E155007/.test(text)) {
154
+ return "path is not inside a working copy";
155
+ }
156
+ if (/E135000|inconsistent newlines|inconsistent line ending style/i.test(text)) {
157
+ return "inconsistent line endings - run eol_fix_verified on affected files";
158
+ }
159
+ if (/E200009|is not a working copy|unversioned/i.test(text)) {
160
+ return "target not versioned";
161
+ }
162
+ if (/E200030|sqlite|database is locked|database disk image is malformed/i.test(text)) {
163
+ return "working copy database problem - run svn_cleanup";
164
+ }
165
+
166
+ return run.exitCode === 0 ? "" : "svn command failed";
167
+ }
168
+
169
+ export function toToolResult<T extends Envelope>(payload: T): {
170
+ content: Array<{ type: "text"; text: string }>;
171
+ structuredContent: T;
172
+ } {
173
+ return {
174
+ content: [
175
+ {
176
+ type: "text",
177
+ text: JSON.stringify(payload, null, 2)
178
+ }
179
+ ],
180
+ structuredContent: payload
181
+ };
182
+ }