@sknoble/slvsx-mcp-server 0.1.0 → 0.1.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/mcp-server.js CHANGED
@@ -23,8 +23,45 @@ import { fileURLToPath } from 'url';
23
23
  const __filename = fileURLToPath(import.meta.url);
24
24
  const __dirname = path.dirname(__filename);
25
25
 
26
- // Check if slvsx binary exists
27
- const SLVSX_BINARY = process.env.SLVSX_BINARY || './target/release/slvsx';
26
+ // Find slvsx binary - check env var, bundled, PATH, then local build
27
+ function findSlvsxBinary() {
28
+ const ext = process.platform === 'win32' ? '.exe' : '';
29
+
30
+ // 1. Check explicit env var
31
+ if (process.env.SLVSX_BINARY) {
32
+ return process.env.SLVSX_BINARY;
33
+ }
34
+
35
+ // 2. Check bundled binary (installed via postinstall)
36
+ const bundled = path.join(__dirname, 'bin', `slvsx${ext}`);
37
+ if (fs.existsSync(bundled)) {
38
+ return bundled;
39
+ }
40
+
41
+ // 3. Check if slvsx is in PATH
42
+ try {
43
+ const which = execSync('which slvsx 2>/dev/null || where slvsx 2>nul', { encoding: 'utf-8' }).trim();
44
+ if (which) return which.split('\n')[0];
45
+ } catch (e) {
46
+ // Not in PATH
47
+ }
48
+
49
+ // 4. Check local build
50
+ const localBuild = `./target/release/slvsx${ext}`;
51
+ if (fs.existsSync(localBuild)) {
52
+ return localBuild;
53
+ }
54
+
55
+ // 5. Check relative to this script (for development)
56
+ const devBuild = path.join(__dirname, 'target/release', `slvsx${ext}`);
57
+ if (fs.existsSync(devBuild)) {
58
+ return devBuild;
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ const SLVSX_BINARY = findSlvsxBinary();
28
65
 
29
66
  // Load documentation embeddings if available
30
67
  let docsIndex = null;
@@ -586,10 +623,23 @@ class SlvsxServer {
586
623
  }
587
624
 
588
625
  // Check if slvsx binary exists
589
- if (!fs.existsSync(SLVSX_BINARY)) {
590
- console.error(`Error: SLVSX binary not found at ${SLVSX_BINARY}`);
591
- console.error('Please build the project first with: cargo build --release');
592
- console.error('Or set SLVSX_BINARY environment variable to point to the binary');
626
+ if (!SLVSX_BINARY || !fs.existsSync(SLVSX_BINARY)) {
627
+ console.error('Error: SLVSX binary not found.');
628
+ console.error('');
629
+ console.error('The MCP server requires the slvsx CLI binary. Install it via one of:');
630
+ console.error('');
631
+ console.error(' Option 1: Install via Homebrew (macOS/Linux)');
632
+ console.error(' brew install sknoble/tap/slvsx');
633
+ console.error('');
634
+ console.error(' Option 2: Build from source');
635
+ console.error(' git clone https://github.com/snoble/slvsx-cli');
636
+ console.error(' cd slvsx-cli && cargo build --release');
637
+ console.error(' export SLVSX_BINARY=$PWD/target/release/slvsx');
638
+ console.error('');
639
+ console.error(' Option 3: Download binary from GitHub releases');
640
+ console.error(' https://github.com/snoble/slvsx-cli/releases');
641
+ console.error('');
642
+ console.error('Then restart the MCP server.');
593
643
  process.exit(1);
594
644
  }
595
645
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sknoble/slvsx-mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for SLVSX geometric constraint solver",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "scripts": {
11
11
  "start": "node mcp-server.js",
12
12
  "build:docs": "npx tsx scripts/build-docs.ts",
13
- "test": "node tests/mcp-server.test.js"
13
+ "test": "node tests/mcp-server.test.js",
14
+ "postinstall": "node scripts/postinstall.js"
14
15
  },
15
16
  "dependencies": {
16
17
  "@langchain/textsplitters": "^0.1.0",
@@ -38,6 +39,7 @@
38
39
  "files": [
39
40
  "mcp-server.js",
40
41
  "dist/docs.json",
42
+ "scripts/postinstall.js",
41
43
  "README.md"
42
44
  ],
43
45
  "engines": {
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script that downloads the slvsx binary for the current platform
5
+ * from GitHub releases.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import https from 'https';
12
+ import { createWriteStream } from 'fs';
13
+ import { exec } from 'child_process';
14
+ import { promisify } from 'util';
15
+
16
+ const execAsync = promisify(exec);
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+ const packageRoot = path.resolve(__dirname, '..');
21
+
22
+ // Map Node.js platform/arch to our release asset naming
23
+ function getPlatformInfo() {
24
+ const platform = process.platform;
25
+ const arch = process.arch;
26
+
27
+ // Map to actual release asset names like: slvsx-macos-arm64.tar.gz, slvsx-linux.tar.gz
28
+ if (platform === 'darwin') {
29
+ if (arch === 'arm64') {
30
+ return { assetPattern: 'macos-arm64', extension: '' };
31
+ } else if (arch === 'x64') {
32
+ return { assetPattern: 'macos-x86_64', extension: '' };
33
+ }
34
+ } else if (platform === 'linux') {
35
+ // Linux release is just "linux" (x86_64)
36
+ if (arch === 'x64') {
37
+ return { assetPattern: 'linux', extension: '' };
38
+ }
39
+ } else if (platform === 'win32') {
40
+ if (arch === 'x64') {
41
+ return { assetPattern: 'windows', extension: '.exe' };
42
+ }
43
+ }
44
+
45
+ return null;
46
+ }
47
+
48
+ async function getLatestRelease() {
49
+ return new Promise((resolve, reject) => {
50
+ const options = {
51
+ hostname: 'api.github.com',
52
+ path: '/repos/snoble/slvsx-cli/releases/latest',
53
+ headers: {
54
+ 'User-Agent': 'slvsx-mcp-server-installer'
55
+ }
56
+ };
57
+
58
+ https.get(options, (res) => {
59
+ let data = '';
60
+ res.on('data', chunk => data += chunk);
61
+ res.on('end', () => {
62
+ try {
63
+ resolve(JSON.parse(data));
64
+ } catch (e) {
65
+ reject(new Error('Failed to parse release info'));
66
+ }
67
+ });
68
+ }).on('error', reject);
69
+ });
70
+ }
71
+
72
+ async function downloadFile(url, dest) {
73
+ return new Promise((resolve, reject) => {
74
+ const follow = (url) => {
75
+ https.get(url, (res) => {
76
+ if (res.statusCode === 302 || res.statusCode === 301) {
77
+ follow(res.headers.location);
78
+ return;
79
+ }
80
+
81
+ if (res.statusCode !== 200) {
82
+ reject(new Error(`Download failed with status ${res.statusCode}`));
83
+ return;
84
+ }
85
+
86
+ const file = createWriteStream(dest);
87
+ res.pipe(file);
88
+ file.on('finish', () => {
89
+ file.close();
90
+ resolve();
91
+ });
92
+ }).on('error', reject);
93
+ };
94
+
95
+ follow(url);
96
+ });
97
+ }
98
+
99
+ async function extractTarGz(file, dest) {
100
+ await execAsync(`tar -xzf "${file}" -C "${dest}"`);
101
+ }
102
+
103
+ async function extractZip(file, dest) {
104
+ if (process.platform === 'win32') {
105
+ await execAsync(`powershell -Command "Expand-Archive -Path '${file}' -DestinationPath '${dest}'"`);
106
+ } else {
107
+ await execAsync(`unzip -o "${file}" -d "${dest}"`);
108
+ }
109
+ }
110
+
111
+ async function main() {
112
+ const binDir = path.join(packageRoot, 'bin');
113
+ const binaryPath = path.join(binDir, process.platform === 'win32' ? 'slvsx.exe' : 'slvsx');
114
+
115
+ // Skip if binary already exists
116
+ if (fs.existsSync(binaryPath)) {
117
+ console.log('slvsx binary already installed.');
118
+ return;
119
+ }
120
+
121
+ const platformInfo = getPlatformInfo();
122
+ if (!platformInfo) {
123
+ console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
124
+ console.error('Please install slvsx manually from https://github.com/snoble/slvsx-cli/releases');
125
+ process.exit(0); // Don't fail install, just warn
126
+ }
127
+
128
+ console.log(`Installing slvsx for ${process.platform}-${process.arch}...`);
129
+
130
+ try {
131
+ const release = await getLatestRelease();
132
+
133
+ if (!release.assets) {
134
+ console.error('No release assets found. Please install slvsx manually.');
135
+ process.exit(0);
136
+ }
137
+
138
+ // Find the right asset
139
+ const asset = release.assets.find(a =>
140
+ a.name.includes(platformInfo.assetPattern) &&
141
+ (a.name.endsWith('.tar.gz') || a.name.endsWith('.zip'))
142
+ );
143
+
144
+ if (!asset) {
145
+ console.error(`No binary found for ${process.platform}-${process.arch} (looking for '${platformInfo.assetPattern}')`);
146
+ console.error('Available assets:', release.assets.map(a => a.name).join(', '));
147
+ console.error('Please install slvsx manually from https://github.com/snoble/slvsx-cli/releases');
148
+ process.exit(0);
149
+ }
150
+
151
+ // Create bin directory
152
+ fs.mkdirSync(binDir, { recursive: true });
153
+
154
+ // Download
155
+ const tempFile = path.join(binDir, asset.name);
156
+ console.log(`Downloading ${asset.name}...`);
157
+ await downloadFile(asset.browser_download_url, tempFile);
158
+
159
+ // Extract
160
+ console.log('Extracting...');
161
+ if (asset.name.endsWith('.tar.gz')) {
162
+ await extractTarGz(tempFile, binDir);
163
+ } else {
164
+ await extractZip(tempFile, binDir);
165
+ }
166
+
167
+ // Clean up archive
168
+ fs.unlinkSync(tempFile);
169
+
170
+ // Make executable on Unix
171
+ if (process.platform !== 'win32') {
172
+ fs.chmodSync(binaryPath, 0o755);
173
+ }
174
+
175
+ // Verify
176
+ if (fs.existsSync(binaryPath)) {
177
+ console.log(`✓ slvsx installed successfully to ${binaryPath}`);
178
+ } else {
179
+ // Binary might be in a subdirectory after extraction
180
+ const files = fs.readdirSync(binDir);
181
+ console.log('Extracted files:', files);
182
+ console.error('Binary not found at expected location. Please check extraction.');
183
+ }
184
+
185
+ } catch (error) {
186
+ console.error('Failed to download slvsx:', error.message);
187
+ console.error('Please install manually from https://github.com/snoble/slvsx-cli/releases');
188
+ process.exit(0); // Don't fail the npm install
189
+ }
190
+ }
191
+
192
+ main();
193
+