mcp-weave-patch 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/bin/run.js +78 -0
- package/package.json +24 -0
- package/scripts/install.js +152 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# mcp-weave-patch
|
|
2
|
+
|
|
3
|
+
MCP server for structured file patching using V4A diffs. Create, update, and delete files in one atomic operation.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Add to your MCP configuration:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"weave": {
|
|
12
|
+
"command": "npx",
|
|
13
|
+
"args": ["-y", "mcp-weave-patch"]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That's it. The binary is automatically downloaded on first run.
|
|
19
|
+
|
|
20
|
+
## Why Use This?
|
|
21
|
+
|
|
22
|
+
- **One call replaces 5+ separate Edit/Write/Create calls** — saves tokens and round-trips
|
|
23
|
+
- **Multi-file changes land atomically** — no broken intermediate states
|
|
24
|
+
- **Context-based matching** survives code drift; line numbers don't
|
|
25
|
+
- **Standard diff format** — reviewers understand changes instantly
|
|
26
|
+
|
|
27
|
+
## Build from Source
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cargo build --release
|
|
31
|
+
```
|
package/bin/run.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
const isWindows = process.platform === "win32";
|
|
9
|
+
const cacheBinDir = path.join(os.homedir(), ".weave-patch", "bin");
|
|
10
|
+
const binName = isWindows ? "weave-patch-mcp.exe" : "weave-patch-mcp";
|
|
11
|
+
const cachedBinPath = path.join(cacheBinDir, binName);
|
|
12
|
+
const expectedVersion = require("../package.json").version;
|
|
13
|
+
const installer = path.join(__dirname, "..", "scripts", "install.js");
|
|
14
|
+
|
|
15
|
+
function spawnBinary(binPath) {
|
|
16
|
+
const child = require("child_process").spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
17
|
+
child.on("exit", (code) => process.exit(code ?? 1));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getInstalledVersion(binPath) {
|
|
21
|
+
try {
|
|
22
|
+
const out = require("child_process").execSync(`"${binPath}" --version`, {
|
|
23
|
+
timeout: 5000,
|
|
24
|
+
encoding: "utf-8",
|
|
25
|
+
});
|
|
26
|
+
return out.trim();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function runInstaller() {
|
|
33
|
+
const res = spawnSync(process.execPath, [installer], { stdio: ["inherit", process.stderr.fd, "inherit"] });
|
|
34
|
+
if (res.status !== 0) {
|
|
35
|
+
console.error("Installer failed.");
|
|
36
|
+
process.exit(res.status || 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
(function main() {
|
|
41
|
+
try {
|
|
42
|
+
// Check if cached binary exists and is the correct version
|
|
43
|
+
if (fs.existsSync(cachedBinPath)) {
|
|
44
|
+
const installedVersion = getInstalledVersion(cachedBinPath);
|
|
45
|
+
if (installedVersion !== expectedVersion) {
|
|
46
|
+
console.error(`Cached binary is v${installedVersion || "unknown"}, need v${expectedVersion}. Reinstalling...`);
|
|
47
|
+
try { fs.unlinkSync(cachedBinPath); } catch (_) {}
|
|
48
|
+
runInstaller();
|
|
49
|
+
}
|
|
50
|
+
if (fs.existsSync(cachedBinPath)) {
|
|
51
|
+
spawnBinary(cachedBinPath);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fallback: try local node_modules bin path (for installed package)
|
|
57
|
+
const localBin = path.join(__dirname, "..", "bin", binName);
|
|
58
|
+
if (fs.existsSync(localBin)) {
|
|
59
|
+
spawnBinary(localBin);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// If missing, run installer
|
|
64
|
+
console.error("Binary not found in cache. Installing to global cache...");
|
|
65
|
+
runInstaller();
|
|
66
|
+
|
|
67
|
+
if (fs.existsSync(cachedBinPath)) {
|
|
68
|
+
spawnBinary(cachedBinPath);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.error("Binary installation completed but binary not found.");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-weave-patch",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "MCP server for structured file patching using compact syntax. Create, update, and delete files in one atomic operation.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/tuanhung303/weave-patch-mcp"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"mcp-weave-patch": "bin/run.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/",
|
|
15
|
+
"scripts/",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {},
|
|
19
|
+
"os": ["darwin", "linux", "win32"],
|
|
20
|
+
"cpu": ["x64", "arm64"],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=16"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { execSync } = require("child_process");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
const VERSION = require("../package.json").version;
|
|
9
|
+
const REPO = "tuanhung303/weave-patch-mcp";
|
|
10
|
+
|
|
11
|
+
function getPlatformKey() {
|
|
12
|
+
const platform = process.platform;
|
|
13
|
+
const arch = process.arch;
|
|
14
|
+
|
|
15
|
+
const map = {
|
|
16
|
+
"darwin-arm64": "aarch64-apple-darwin",
|
|
17
|
+
"darwin-x64": "x86_64-apple-darwin",
|
|
18
|
+
"linux-x64": "x86_64-unknown-linux-gnu",
|
|
19
|
+
"linux-arm64": "aarch64-unknown-linux-gnu",
|
|
20
|
+
"win32-x64": "x86_64-pc-windows-msvc",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const key = `${platform}-${arch}`;
|
|
24
|
+
if (!map[key]) {
|
|
25
|
+
console.error(`Unsupported platform: ${key}`);
|
|
26
|
+
console.error(`Supported: ${Object.keys(map).join(", ")}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
return map[key];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function download(url) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const handler = (res) => {
|
|
35
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
36
|
+
return https.get(res.headers.location, handler).on("error", reject);
|
|
37
|
+
}
|
|
38
|
+
if (res.statusCode !== 200) {
|
|
39
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
40
|
+
}
|
|
41
|
+
const chunks = [];
|
|
42
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
43
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
44
|
+
res.on("error", reject);
|
|
45
|
+
};
|
|
46
|
+
https.get(url, { headers: { "User-Agent": "weave-patch-mcp-installer" } }, handler).on("error", reject);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function verifySha256(filePath, expectedHex) {
|
|
51
|
+
const hash = crypto.createHash("sha256");
|
|
52
|
+
hash.update(fs.readFileSync(filePath));
|
|
53
|
+
const actual = hash.digest("hex");
|
|
54
|
+
if (actual !== expectedHex) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`SHA256 mismatch!\n Expected: ${expectedHex}\n Actual: ${actual}\n File: ${filePath}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function doInstall() {
|
|
62
|
+
const platformKey = getPlatformKey();
|
|
63
|
+
const isWindows = process.platform === "win32";
|
|
64
|
+
const ext = isWindows ? ".zip" : ".tar.gz";
|
|
65
|
+
const assetName = `weave-patch-mcp-v${VERSION}-${platformKey}${ext}`;
|
|
66
|
+
const shaAssetName = `${assetName}.sha256`;
|
|
67
|
+
const assetUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${assetName}`;
|
|
68
|
+
const shaUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${shaAssetName}`;
|
|
69
|
+
|
|
70
|
+
const cacheBinDir = path.join(os.homedir(), ".weave-patch", "bin");
|
|
71
|
+
const binName = isWindows ? "weave-patch-mcp.exe" : "weave-patch-mcp";
|
|
72
|
+
const binPath = path.join(cacheBinDir, binName);
|
|
73
|
+
|
|
74
|
+
// Check if correct version is already installed
|
|
75
|
+
if (fs.existsSync(binPath)) {
|
|
76
|
+
try {
|
|
77
|
+
const out = require("child_process").execSync(`"${binPath}" --version`, {
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
encoding: "utf-8",
|
|
80
|
+
});
|
|
81
|
+
const installed = out.toString().trim();
|
|
82
|
+
if (installed === VERSION) {
|
|
83
|
+
console.error(`weave-patch-mcp v${VERSION} already installed.`);
|
|
84
|
+
return binPath;
|
|
85
|
+
}
|
|
86
|
+
console.error(`Cached binary is v${installed}, need v${VERSION}. Reinstalling...`);
|
|
87
|
+
fs.unlinkSync(binPath);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Binary crashed or can't run — force reinstall
|
|
90
|
+
console.error(`Cached binary broken (${e.message || "unknown error"}). Reinstalling...`);
|
|
91
|
+
try { fs.unlinkSync(binPath); } catch (_) { /* ignore */ }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.error(`Downloading weave-patch-mcp v${VERSION} for ${platformKey}...`);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Download the binary and its SHA256 checksum in parallel
|
|
99
|
+
const [binaryData, shaData] = await Promise.all([
|
|
100
|
+
download(assetUrl),
|
|
101
|
+
download(shaUrl),
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const tmpFile = path.join(os.tmpdir(), assetName);
|
|
105
|
+
const tmpShaFile = path.join(os.tmpdir(), shaAssetName);
|
|
106
|
+
fs.writeFileSync(tmpFile, binaryData);
|
|
107
|
+
fs.writeFileSync(tmpShaFile, shaData);
|
|
108
|
+
|
|
109
|
+
// Verify SHA256 checksum before extracting
|
|
110
|
+
const expectedSha256 = shaData.toString("utf-8").trim().split(/\s+/)[0];
|
|
111
|
+
console.error("Verifying SHA256 checksum...");
|
|
112
|
+
verifySha256(tmpFile, expectedSha256);
|
|
113
|
+
console.error("SHA256 checksum verified.");
|
|
114
|
+
|
|
115
|
+
fs.mkdirSync(cacheBinDir, { recursive: true });
|
|
116
|
+
|
|
117
|
+
if (isWindows) {
|
|
118
|
+
execSync(`powershell -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${cacheBinDir}' -Force"`, { stdio: "inherit" });
|
|
119
|
+
} else {
|
|
120
|
+
execSync(`tar xzf "${tmpFile}" -C "${cacheBinDir}"`, { stdio: "inherit" });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Ensure executable permissions
|
|
124
|
+
try { fs.chmodSync(binPath, 0o755); } catch (e) { /* ignore */ }
|
|
125
|
+
|
|
126
|
+
// Remove macOS quarantine attribute if present
|
|
127
|
+
if (process.platform === 'darwin') {
|
|
128
|
+
try { execSync(`xattr -d com.apple.quarantine "${binPath}"`, { stdio: 'ignore' }); } catch (e) { /* ignore — attribute not present */ }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try { fs.unlinkSync(tmpFile); } catch (e) { /* ignore */ }
|
|
132
|
+
try { fs.unlinkSync(tmpShaFile); } catch (e) { /* ignore */ }
|
|
133
|
+
|
|
134
|
+
console.error(`weave-patch-mcp v${VERSION} installed to ${binPath}.`);
|
|
135
|
+
return binPath;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (err.message.includes("SHA256 mismatch")) {
|
|
138
|
+
console.error(`SECURITY: ${err.message}`);
|
|
139
|
+
} else {
|
|
140
|
+
console.error(`Failed to download binary: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
console.error(`URL: ${assetUrl}`);
|
|
143
|
+
console.error("You can build from source: cargo build --release");
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { doInstall };
|
|
149
|
+
|
|
150
|
+
if (require.main === module) {
|
|
151
|
+
doInstall().catch((e) => process.exit(1));
|
|
152
|
+
}
|