@sknoble/slvsx-mcp-server 0.1.1 → 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,14 +23,22 @@ import { fileURLToPath } from 'url';
23
23
  const __filename = fileURLToPath(import.meta.url);
24
24
  const __dirname = path.dirname(__filename);
25
25
 
26
- // Find slvsx binary - check env var, PATH, then local build
26
+ // Find slvsx binary - check env var, bundled, PATH, then local build
27
27
  function findSlvsxBinary() {
28
+ const ext = process.platform === 'win32' ? '.exe' : '';
29
+
28
30
  // 1. Check explicit env var
29
31
  if (process.env.SLVSX_BINARY) {
30
32
  return process.env.SLVSX_BINARY;
31
33
  }
32
34
 
33
- // 2. Check if slvsx is in PATH
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
34
42
  try {
35
43
  const which = execSync('which slvsx 2>/dev/null || where slvsx 2>nul', { encoding: 'utf-8' }).trim();
36
44
  if (which) return which.split('\n')[0];
@@ -38,15 +46,14 @@ function findSlvsxBinary() {
38
46
  // Not in PATH
39
47
  }
40
48
 
41
- // 3. Check local build
42
- const localBuild = './target/release/slvsx';
49
+ // 4. Check local build
50
+ const localBuild = `./target/release/slvsx${ext}`;
43
51
  if (fs.existsSync(localBuild)) {
44
52
  return localBuild;
45
53
  }
46
54
 
47
- // 4. Check relative to this script (for development)
48
- const scriptDir = path.dirname(__filename);
49
- const devBuild = path.join(scriptDir, 'target/release/slvsx');
55
+ // 5. Check relative to this script (for development)
56
+ const devBuild = path.join(__dirname, 'target/release', `slvsx${ext}`);
50
57
  if (fs.existsSync(devBuild)) {
51
58
  return devBuild;
52
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sknoble/slvsx-mcp-server",
3
- "version": "0.1.1",
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
+