@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 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
@@ -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 { ensureExecutable, formatBootstrapContext, spawnBinary } from "../lib/launcher.js";
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
- version
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(`verifying archive against ${checksumsURL}`);
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(`downloaded archive and staged native binary for ${destinationPath}`);
76
+ log(
77
+ `downloaded archive and staged native binary for ${destinationPath}`,
78
+ );
64
79
  return result;
65
80
  } catch (error) {
66
- process.stderr.write(`${formatBootstrapContext({
67
- version,
68
- platform,
69
- archiveURL,
70
- destinationPath
71
- })}\n`);
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 [archiveResponse, checksumsResponse] = await Promise.all([
56
- fetchImpl(archiveURL),
57
- fetchImpl(checksumsURL)
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
- try {
76
- if (!expectedChecksum) {
77
- throw new Error(`missing checksum for ${archiveName}`);
78
- }
79
- if (actualChecksum !== expectedChecksum) {
80
- throw new Error(`checksum mismatch for ${archiveName}`);
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({ platform = os.platform(), arch = os.arch() } = {}) {
4
- const resolvedPlatform = platform === "darwin" || platform === "linux" ? platform : null;
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
- return `deltascope_${version.replace(/^v/, "")}_checksums.txt`;
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({ repo = "Fanduzi/DeltaScope", version, os, arch }) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanduzi/deltascope-mcp",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Launcher package for the DeltaScope MCP stdio server",
5
5
  "type": "module",
6
6
  "repository": {