@fanduzi/deltascope-mcp 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/bin/deltascope-mcp.js +37 -20
- package/lib/download.js +34 -15
- package/lib/releases.js +20 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ This package does not implement DeltaScope MCP tools itself. It downloads the ma
|
|
|
8
8
|
|
|
9
9
|
The launcher verifies the downloaded archive against the official DeltaScope release checksums before it updates the local cache.
|
|
10
10
|
|
|
11
|
+
## Supported Audit Surface
|
|
12
|
+
|
|
13
|
+
The launched `deltascope-mcp` server exposes a unified `audit_sql` surface for:
|
|
14
|
+
|
|
15
|
+
- MySQL offline audit
|
|
16
|
+
- TiDB offline audit
|
|
17
|
+
- PostgreSQL offline audit on the main PG-capable release binaries across the supported macOS and Linux platforms
|
|
18
|
+
|
|
19
|
+
Connection-backed metadata-aware audit remains limited to MySQL/TiDB-compatible instances. PostgreSQL requests must stay offline-only on the MCP surface.
|
|
20
|
+
|
|
11
21
|
## Version Contract
|
|
12
22
|
|
|
13
23
|
- npm package version should track the DeltaScope release version it boots
|
package/bin/deltascope-mcp.js
CHANGED
|
@@ -4,37 +4,45 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
|
|
6
6
|
import { downloadAndExtractBinary } from "../lib/download.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ensureExecutable,
|
|
9
|
+
formatBootstrapContext,
|
|
10
|
+
spawnBinary,
|
|
11
|
+
} from "../lib/launcher.js";
|
|
8
12
|
import {
|
|
9
13
|
resolveArchiveName,
|
|
10
14
|
resolveArchiveURL,
|
|
11
15
|
resolveChecksumsURL,
|
|
12
16
|
resolveDeltaScopeVersion,
|
|
13
|
-
resolvePlatform
|
|
17
|
+
resolvePlatform,
|
|
14
18
|
} from "../lib/releases.js";
|
|
15
19
|
|
|
16
20
|
const packageJson = JSON.parse(
|
|
17
|
-
await fs.readFile(new URL("../package.json", import.meta.url), "utf8")
|
|
21
|
+
await fs.readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
18
22
|
);
|
|
19
23
|
const version = resolveDeltaScopeVersion({
|
|
20
24
|
packageVersion: packageJson.version,
|
|
21
|
-
envVersion: process.env.DELTASCOPE_MCP_VERSION ?? ""
|
|
25
|
+
envVersion: process.env.DELTASCOPE_MCP_VERSION ?? "",
|
|
22
26
|
});
|
|
23
27
|
const platform = resolvePlatform();
|
|
24
28
|
const archiveURL = resolveArchiveURL({
|
|
25
29
|
baseURL: process.env.DELTASCOPE_MCP_BASE_URL ?? "",
|
|
26
30
|
version,
|
|
27
31
|
os: platform.os,
|
|
28
|
-
arch: platform.arch
|
|
32
|
+
arch: platform.arch,
|
|
29
33
|
});
|
|
30
34
|
const archiveName = resolveArchiveName({
|
|
31
35
|
version,
|
|
32
36
|
os: platform.os,
|
|
33
|
-
arch: platform.arch
|
|
37
|
+
arch: platform.arch,
|
|
34
38
|
});
|
|
35
|
-
const checksumsURL = resolveChecksumsURL({
|
|
36
|
-
|
|
39
|
+
const checksumsURL = resolveChecksumsURL({ version });
|
|
40
|
+
const preferredChecksumsURL = resolveChecksumsURL({
|
|
41
|
+
version,
|
|
42
|
+
os: platform.os,
|
|
43
|
+
arch: platform.arch,
|
|
37
44
|
});
|
|
45
|
+
const checksumsURLs = [...new Set([preferredChecksumsURL, checksumsURL])];
|
|
38
46
|
|
|
39
47
|
function log(message) {
|
|
40
48
|
process.stderr.write(`[deltascope-mcp-launcher] ${message}\n`);
|
|
@@ -52,33 +60,42 @@ const binaryPath = await ensureExecutable({
|
|
|
52
60
|
(async () => {
|
|
53
61
|
log(`cache miss; downloading ${archiveURL}`);
|
|
54
62
|
log(`cache target ${destinationPath}`);
|
|
55
|
-
log(
|
|
63
|
+
log(
|
|
64
|
+
checksumsURLs.length > 1
|
|
65
|
+
? `verifying archive against ${checksumsURLs[0]} (fallback ${checksumsURLs[1]})`
|
|
66
|
+
: `verifying archive against ${checksumsURLs[0]}`,
|
|
67
|
+
);
|
|
56
68
|
try {
|
|
57
69
|
const result = await downloadAndExtractBinary({
|
|
58
70
|
archiveURL,
|
|
59
|
-
checksumsURL,
|
|
71
|
+
checksumsURL: preferredChecksumsURL,
|
|
72
|
+
checksumsURLs,
|
|
60
73
|
archiveName,
|
|
61
|
-
destinationPath
|
|
74
|
+
destinationPath,
|
|
62
75
|
});
|
|
63
|
-
log(
|
|
76
|
+
log(
|
|
77
|
+
`downloaded archive and staged native binary for ${destinationPath}`,
|
|
78
|
+
);
|
|
64
79
|
return result;
|
|
65
80
|
} catch (error) {
|
|
66
|
-
process.stderr.write(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
process.stderr.write(
|
|
82
|
+
`${formatBootstrapContext({
|
|
83
|
+
version,
|
|
84
|
+
platform,
|
|
85
|
+
archiveURL,
|
|
86
|
+
destinationPath,
|
|
87
|
+
})}\n`,
|
|
88
|
+
);
|
|
72
89
|
throw error;
|
|
73
90
|
}
|
|
74
|
-
})()
|
|
91
|
+
})(),
|
|
75
92
|
});
|
|
76
93
|
|
|
77
94
|
log(`launching native binary ${binaryPath}`);
|
|
78
95
|
|
|
79
96
|
const child = spawnBinary(binaryPath, process.argv.slice(2), {
|
|
80
97
|
stdio: "inherit",
|
|
81
|
-
env: process.env
|
|
98
|
+
env: process.env,
|
|
82
99
|
});
|
|
83
100
|
|
|
84
101
|
child.on("error", (error) => {
|
package/lib/download.js
CHANGED
|
@@ -44,6 +44,7 @@ function parseChecksums(text) {
|
|
|
44
44
|
export async function downloadAndExtractBinary({
|
|
45
45
|
archiveURL,
|
|
46
46
|
checksumsURL,
|
|
47
|
+
checksumsURLs = [],
|
|
47
48
|
archiveName,
|
|
48
49
|
destinationPath,
|
|
49
50
|
fetchImpl = globalThis.fetch
|
|
@@ -52,33 +53,51 @@ export async function downloadAndExtractBinary({
|
|
|
52
53
|
throw new Error("fetch implementation is required");
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
]);
|
|
56
|
+
const requestedChecksumsURLs = [
|
|
57
|
+
...checksumsURLs,
|
|
58
|
+
...(checksumsURL ? [checksumsURL] : [])
|
|
59
|
+
].filter(Boolean);
|
|
60
|
+
const uniqueChecksumsURLs = [...new Set(requestedChecksumsURLs)];
|
|
61
|
+
if (uniqueChecksumsURLs.length === 0) {
|
|
62
|
+
throw new Error("at least one checksums URL is required");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const archiveResponse = await fetchImpl(archiveURL);
|
|
59
66
|
|
|
60
67
|
if (!archiveResponse.ok) {
|
|
61
68
|
throw new Error(`failed to download ${archiveURL}`);
|
|
62
69
|
}
|
|
63
|
-
if (!checksumsResponse.ok) {
|
|
64
|
-
throw new Error(`failed to download ${checksumsURL}`);
|
|
65
|
-
}
|
|
66
70
|
|
|
67
71
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "deltascope-mcp-archive-"));
|
|
68
72
|
const archivePath = path.join(tempDir, "archive.tar.gz");
|
|
69
73
|
const extractDir = path.join(tempDir, "extract");
|
|
70
74
|
const archiveBuffer = Buffer.from(await archiveResponse.arrayBuffer());
|
|
71
|
-
const checksums = parseChecksums(await checksumsResponse.text());
|
|
72
|
-
const expectedChecksum = checksums.get(archiveName);
|
|
73
75
|
const actualChecksum = sha256(archiveBuffer);
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
const resolveExpectedChecksum = async () => {
|
|
78
|
+
let lastError = new Error(`missing checksum for ${archiveName}`);
|
|
79
|
+
for (const candidateURL of uniqueChecksumsURLs) {
|
|
80
|
+
const checksumsResponse = await fetchImpl(candidateURL);
|
|
81
|
+
if (!checksumsResponse.ok) {
|
|
82
|
+
lastError = new Error(`failed to download ${candidateURL}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const checksums = parseChecksums(await checksumsResponse.text());
|
|
86
|
+
const expectedChecksum = checksums.get(archiveName);
|
|
87
|
+
if (!expectedChecksum) {
|
|
88
|
+
lastError = new Error(`missing checksum for ${archiveName}`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (actualChecksum !== expectedChecksum) {
|
|
92
|
+
throw new Error(`checksum mismatch for ${archiveName}`);
|
|
93
|
+
}
|
|
94
|
+
return expectedChecksum;
|
|
81
95
|
}
|
|
96
|
+
throw lastError;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const expectedChecksum = await resolveExpectedChecksum();
|
|
82
101
|
await fs.mkdir(extractDir, { recursive: true });
|
|
83
102
|
await fs.writeFile(archivePath, archiveBuffer);
|
|
84
103
|
await runTar(["-xzf", archivePath, "-C", extractDir]);
|
package/lib/releases.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
|
|
3
|
-
export function resolvePlatform({
|
|
4
|
-
|
|
3
|
+
export function resolvePlatform({
|
|
4
|
+
platform = os.platform(),
|
|
5
|
+
arch = os.arch(),
|
|
6
|
+
} = {}) {
|
|
7
|
+
const resolvedPlatform =
|
|
8
|
+
platform === "darwin" || platform === "linux" ? platform : null;
|
|
5
9
|
if (resolvedPlatform === null) {
|
|
6
10
|
throw new Error(`unsupported platform: ${platform}`);
|
|
7
11
|
}
|
|
@@ -39,11 +43,20 @@ export function resolveArchiveName({ version, os, arch }) {
|
|
|
39
43
|
return `deltascope_${version.replace(/^v/, "")}_${os}_${arch}.tar.gz`;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export function resolveChecksumsName({ version }) {
|
|
43
|
-
|
|
46
|
+
export function resolveChecksumsName({ version, os = "", arch = "" }) {
|
|
47
|
+
const rawVersion = version.replace(/^v/, "");
|
|
48
|
+
if (os && arch) {
|
|
49
|
+
return `deltascope_${rawVersion}_${os}_${arch}_checksums.txt`;
|
|
50
|
+
}
|
|
51
|
+
return `deltascope_${rawVersion}_checksums.txt`;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
export function resolveArchiveURL({
|
|
54
|
+
export function resolveArchiveURL({
|
|
55
|
+
repo = "Fanduzi/DeltaScope",
|
|
56
|
+
version,
|
|
57
|
+
os,
|
|
58
|
+
arch,
|
|
59
|
+
}) {
|
|
47
60
|
const baseURL = arguments[0].baseURL ?? "";
|
|
48
61
|
if (baseURL) {
|
|
49
62
|
return `${baseURL.replace(/\/$/, "")}/${version}/${resolveArchiveName({ version, os, arch })}`;
|
|
@@ -51,6 +64,6 @@ export function resolveArchiveURL({ repo = "Fanduzi/DeltaScope", version, os, ar
|
|
|
51
64
|
return `https://github.com/${repo}/releases/download/${version}/${resolveArchiveName({ version, os, arch })}`;
|
|
52
65
|
}
|
|
53
66
|
|
|
54
|
-
export function resolveChecksumsURL({ repo = "Fanduzi/DeltaScope", version }) {
|
|
55
|
-
return `https://github.com/${repo}/releases/download/${version}/${resolveChecksumsName({ version })}`;
|
|
67
|
+
export function resolveChecksumsURL({ repo = "Fanduzi/DeltaScope", version, os = "", arch = "" }) {
|
|
68
|
+
return `https://github.com/${repo}/releases/download/${version}/${resolveChecksumsName({ version, os, arch })}`;
|
|
56
69
|
}
|