Skip to content

Commit ca43ce4

Browse files
Alexander MorisseAlexander Morisse
authored andcommitted
Add npm package support for graphistry-mcp
- Create package.json for @silkspace/graphistry-mcp - Add Node.js wrapper (index.js) that spawns Python MCP server - Add postinstall.js for automatic Python dependency installation - Add .npmignore to exclude unnecessary files from npm package - Tested: Server starts successfully with venv Python - Ready for npm publishing
1 parent d756401 commit ca43ce4

4 files changed

Lines changed: 385 additions & 0 deletions

File tree

‎.npmignore‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Node
2+
node_modules/
3+
package-lock.json
4+
5+
# Python
6+
__pycache__/
7+
*.pyc
8+
*.pyo
9+
*.pyd
10+
.Python
11+
*.so
12+
*.egg
13+
*.egg-info/
14+
dist/
15+
build/
16+
.eggs/
17+
.pytest_cache/
18+
19+
# Virtual environments
20+
.venv/
21+
venv/
22+
env/
23+
ENV/
24+
25+
# IDE
26+
.vscode/
27+
.idea/
28+
*.swp
29+
*.swo
30+
*~
31+
32+
# OS
33+
.DS_Store
34+
Thumbs.db
35+
36+
# Git
37+
.git/
38+
.gitignore
39+
.gitattributes
40+
41+
# Testing
42+
tests/
43+
*.test.js
44+
*.spec.js
45+
46+
# Documentation (keep README.md)
47+
docs/
48+
*.md
49+
!README.md
50+
51+
# Logs
52+
logs/
53+
*.log
54+
55+
# Archive
56+
archive/
57+
58+
# GitHub
59+
.github/
60+
61+
# MCP Config
62+
.mcp.json
63+
.mcp.json.example
64+
65+
# Environment
66+
.env
67+
.env.local
68+
.env.*.local
69+
70+
# Scripts (keep the main ones)
71+
setup-graphistry-mcp.sh
72+
start-graphistry-mcp.sh
73+
74+
# Lock files
75+
uv.lock

‎index.js‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Graphistry MCP Server
4+
*
5+
* Wrapper script that spawns the Python MCP server process.
6+
* Handles stdio communication between Claude Code and the Python server.
7+
*/
8+
9+
const { spawn } = require('child_process');
10+
const path = require('path');
11+
const fs = require('fs');
12+
13+
// Find Python executable (prefer venv, then python3, fallback to python)
14+
function findPython() {
15+
// Check for venv Python first (installed by postinstall)
16+
const venvPaths = [
17+
path.join(__dirname, '.venv', 'bin', 'python'), // Unix
18+
path.join(__dirname, '.venv', 'Scripts', 'python.exe'), // Windows
19+
];
20+
21+
for (const venvPath of venvPaths) {
22+
if (fs.existsSync(venvPath)) {
23+
return venvPath;
24+
}
25+
}
26+
27+
// Fallback to system Python
28+
const candidates = ['python3', 'python'];
29+
30+
for (const cmd of candidates) {
31+
try {
32+
const { spawnSync } = require('child_process');
33+
const result = spawnSync(cmd, ['--version'], { encoding: 'utf8' });
34+
if (result.status === 0) {
35+
console.warn('Warning: Using system Python. Dependencies may not be available.');
36+
console.warn('If you see import errors, try running: npm install again');
37+
return cmd;
38+
}
39+
} catch (e) {
40+
// Try next candidate
41+
}
42+
}
43+
44+
console.error('Error: Python not found. Please install Python 3.10+ from python.org');
45+
process.exit(1);
46+
}
47+
48+
// Path to the Python MCP server
49+
const serverPath = path.join(__dirname, 'run_graphistry_mcp.py');
50+
51+
// Verify server file exists
52+
if (!fs.existsSync(serverPath)) {
53+
console.error(`Error: MCP server not found at ${serverPath}`);
54+
process.exit(1);
55+
}
56+
57+
// Find Python
58+
const pythonCmd = findPython();
59+
60+
// Spawn the Python MCP server
61+
const pythonProcess = spawn(pythonCmd, [serverPath], {
62+
stdio: 'inherit',
63+
env: {
64+
...process.env,
65+
PYTHONUNBUFFERED: '1', // Disable Python output buffering
66+
GRAPHISTRY_VENV_ACTIVE: '1', // Prevent re-exec loop in run_graphistry_mcp.py
67+
}
68+
});
69+
70+
// Handle process termination
71+
pythonProcess.on('error', (err) => {
72+
console.error('Failed to start Python MCP server:', err.message);
73+
process.exit(1);
74+
});
75+
76+
pythonProcess.on('exit', (code, signal) => {
77+
if (code !== null && code !== 0) {
78+
console.error(`Python MCP server exited with code ${code}`);
79+
process.exit(code);
80+
}
81+
if (signal) {
82+
console.error(`Python MCP server killed by signal ${signal}`);
83+
process.exit(1);
84+
}
85+
});
86+
87+
// Forward termination signals to Python process
88+
['SIGTERM', 'SIGINT'].forEach(signal => {
89+
process.on(signal, () => {
90+
pythonProcess.kill(signal);
91+
});
92+
});

‎package.json‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@silkspace/graphistry-mcp",
3+
"version": "0.1.0",
4+
"description": "Graphistry MCP server - GPU-accelerated graph visualization for Claude Code",
5+
"main": "index.js",
6+
"bin": {
7+
"graphistry-mcp": "./index.js"
8+
},
9+
"scripts": {
10+
"postinstall": "node postinstall.js"
11+
},
12+
"keywords": [
13+
"mcp",
14+
"graphistry",
15+
"graph-visualization",
16+
"claude",
17+
"network-analysis",
18+
"gpu-accelerated",
19+
"ai",
20+
"llm"
21+
],
22+
"author": "Graphistry Community & Alexander Morisse <silkspace@users.noreply.github.com>",
23+
"license": "MIT",
24+
"repository": {
25+
"type": "git",
26+
"url": "https://github.com/graphistry/graphistry-mcp.git"
27+
},
28+
"bugs": {
29+
"url": "https://github.com/graphistry/graphistry-mcp/issues"
30+
},
31+
"homepage": "https://github.com/graphistry/graphistry-mcp#readme",
32+
"files": [
33+
"index.js",
34+
"postinstall.js",
35+
"run_graphistry_mcp.py",
36+
"pyproject.toml",
37+
"setup.py",
38+
"src/**/*.py",
39+
".env.example",
40+
"README.md"
41+
],
42+
"engines": {
43+
"node": ">=18.0.0"
44+
},
45+
"os": [
46+
"darwin",
47+
"linux"
48+
]
49+
}

‎postinstall.js‎

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Post-install script for Graphistry MCP
4+
*
5+
* Installs Python dependencies using uv (preferred) or pip (fallback).
6+
* Runs after npm install to ensure Python deps are available.
7+
*/
8+
9+
const { execSync, spawnSync } = require('child_process');
10+
const fs = require('fs');
11+
const path = require('path');
12+
13+
// ANSI color codes
14+
const colors = {
15+
reset: '\x1b[0m',
16+
bright: '\x1b[1m',
17+
green: '\x1b[32m',
18+
yellow: '\x1b[33m',
19+
red: '\x1b[31m',
20+
cyan: '\x1b[36m',
21+
};
22+
23+
function log(msg, color = colors.reset) {
24+
console.log(`${color}${msg}${colors.reset}`);
25+
}
26+
27+
function checkCommand(cmd) {
28+
try {
29+
spawnSync(cmd, ['--version'], { stdio: 'ignore' });
30+
return true;
31+
} catch (e) {
32+
return false;
33+
}
34+
}
35+
36+
function findPython() {
37+
const candidates = ['python3', 'python'];
38+
for (const cmd of candidates) {
39+
if (checkCommand(cmd)) {
40+
return cmd;
41+
}
42+
}
43+
return null;
44+
}
45+
46+
function installWithUv(pythonCmd) {
47+
log('\n🚀 Installing Python dependencies with uv...', colors.cyan);
48+
49+
try {
50+
// Check if pyproject.toml exists
51+
const pyprojectPath = path.join(__dirname, 'pyproject.toml');
52+
if (!fs.existsSync(pyprojectPath)) {
53+
throw new Error('pyproject.toml not found');
54+
}
55+
56+
// Install with uv sync (creates venv and installs deps)
57+
execSync('uv sync --no-dev', {
58+
cwd: __dirname,
59+
stdio: 'inherit',
60+
env: {
61+
...process.env,
62+
PYTHONUNBUFFERED: '1',
63+
}
64+
});
65+
66+
log('✅ Python dependencies installed with uv\n', colors.green);
67+
return true;
68+
} catch (e) {
69+
log(`⚠️ uv installation failed: ${e.message}`, colors.yellow);
70+
return false;
71+
}
72+
}
73+
74+
function installWithPip(pythonCmd) {
75+
log('\n📦 Installing Python dependencies with pip...', colors.cyan);
76+
77+
try {
78+
// Install the package in editable mode from pyproject.toml
79+
execSync(`${pythonCmd} -m pip install -e ".[dev]"`, {
80+
cwd: __dirname,
81+
stdio: 'inherit',
82+
env: {
83+
...process.env,
84+
PYTHONUNBUFFERED: '1',
85+
}
86+
});
87+
88+
log('✅ Python dependencies installed with pip\n', colors.green);
89+
return true;
90+
} catch (e) {
91+
log(`❌ pip installation failed: ${e.message}`, colors.red);
92+
return false;
93+
}
94+
}
95+
96+
function main() {
97+
log('\n' + '='.repeat(60), colors.bright);
98+
log(' Graphistry MCP - Python Dependencies Setup', colors.bright);
99+
log('='.repeat(60) + '\n', colors.bright);
100+
101+
// Check for Python
102+
const pythonCmd = findPython();
103+
if (!pythonCmd) {
104+
log('❌ Python not found!', colors.red);
105+
log('\nPlease install Python 3.10+ from https://python.org', colors.yellow);
106+
log('Then run: npm install again\n', colors.yellow);
107+
process.exit(1);
108+
}
109+
110+
log(`✓ Found Python: ${pythonCmd}`, colors.green);
111+
112+
// Try uv first (recommended)
113+
const hasUv = checkCommand('uv');
114+
115+
if (hasUv) {
116+
log('✓ Found uv (recommended package manager)', colors.green);
117+
if (installWithUv(pythonCmd)) {
118+
printSuccessMessage(true);
119+
return;
120+
}
121+
log('\nFalling back to pip...', colors.yellow);
122+
} else {
123+
log('ℹ uv not found - using pip instead', colors.yellow);
124+
log(' (Install uv for faster dependency management: https://github.com/astral-sh/uv)\n', colors.cyan);
125+
}
126+
127+
// Fallback to pip
128+
if (installWithPip(pythonCmd)) {
129+
printSuccessMessage(false);
130+
} else {
131+
log('\n❌ Failed to install Python dependencies!\n', colors.red);
132+
log('Please install manually:', colors.yellow);
133+
log(' uv sync', colors.cyan);
134+
log(' OR', colors.yellow);
135+
log(' pip install -e ".[dev]"\n', colors.cyan);
136+
process.exit(1);
137+
}
138+
}
139+
140+
function printSuccessMessage(usedUv) {
141+
log('\n' + '='.repeat(60), colors.bright);
142+
log(' ✅ Installation Complete!', colors.green);
143+
log('='.repeat(60) + '\n', colors.bright);
144+
145+
log('Next steps:', colors.cyan);
146+
log('1. Get a free Graphistry account:', colors.reset);
147+
log(' https://hub.graphistry.com\n', colors.cyan);
148+
149+
log('2. Set your Graphistry credentials:', colors.reset);
150+
log(' export GRAPHISTRY_USERNAME="your_username"', colors.cyan);
151+
log(' export GRAPHISTRY_PASSWORD="your_password"\n', colors.cyan);
152+
153+
log('3. Configure in Claude Code MCP settings:', colors.reset);
154+
log(' {', colors.reset);
155+
log(' "graphistry": {', colors.reset);
156+
log(' "command": "npx",', colors.reset);
157+
log(' "args": ["-y", "@silkspace/graphistry-mcp"],', colors.reset);
158+
log(' "env": {', colors.reset);
159+
log(' "GRAPHISTRY_USERNAME": "your_username",', colors.reset);
160+
log(' "GRAPHISTRY_PASSWORD": "your_password"', colors.reset);
161+
log(' }', colors.reset);
162+
log(' }', colors.reset);
163+
log(' }\n', colors.reset);
164+
165+
log('Documentation: https://github.com/graphistry/graphistry-mcp\n', colors.cyan);
166+
}
167+
168+
// Run installation
169+
main();

0 commit comments

Comments
 (0)