initConfig.ts•3.96 kB
import path from 'node:path';
import os from 'node:os';
import fs from 'node:fs';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { logger } from './utils/logger.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageJson = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'),
);
// Determine Claude config path based on OS platform
let claudeConfigPath: string;
const platform = os.platform();
if (platform === 'win32') {
// Windows path - using %APPDATA%
// For Node.js, we access %APPDATA% via process.env.APPDATA
claudeConfigPath = path.join(
process.env.APPDATA || '',
'Claude',
'claude_desktop_config.json',
);
} else {
// macOS and Linux path (according to official docs)
claudeConfigPath = path.join(
os.homedir(),
'Library',
'Application Support',
'Claude',
'claude_desktop_config.json',
);
}
const MCP_NEON_SERVER = 'neon';
type Args =
| {
command: 'start:sse';
analytics: boolean;
}
| {
command: 'start';
neonApiKey: string;
analytics: boolean;
}
| {
command: 'init';
executablePath: string;
neonApiKey: string;
analytics: boolean;
}
| {
command: 'export-tools';
};
const commands = ['init', 'start', 'start:sse', 'export-tools'] as const;
export const parseArgs = (): Args => {
const args = process.argv;
if (args.length < 3) {
logger.error('Invalid number of arguments');
process.exit(1);
}
if (args.length === 3 && args[2] === 'start:sse') {
return {
command: 'start:sse',
analytics: true,
};
}
if (args.length === 3 && args[2] === 'export-tools') {
return {
command: 'export-tools',
};
}
const command = args[2];
if (!commands.includes(command as (typeof commands)[number])) {
logger.error(`Invalid command: ${command}`);
process.exit(1);
}
if (command === 'export-tools') {
return {
command: 'export-tools',
};
}
if (args.length < 4) {
logger.error(
'Please provide a NEON_API_KEY as a command-line argument - you can get one through the Neon console: https://neon.tech/docs/manage/api-keys',
);
process.exit(1);
}
return {
executablePath: args[1],
command: args[2] as 'start' | 'init',
neonApiKey: args[3],
analytics: !args[4]?.includes('no-analytics'),
};
};
export function handleInit({
executablePath,
neonApiKey,
analytics,
}: {
executablePath: string;
neonApiKey: string;
analytics: boolean;
}) {
// If the executable path is a local path to the dist/index.js file, use it directly
// Otherwise, use the name of the package to always load the latest version from remote
const serverPath = executablePath.includes('dist/index.js')
? executablePath
: packageJson.name;
const neonConfig = {
command: 'npx',
args: [
'-y',
serverPath,
'start',
neonApiKey,
analytics ? '' : '--no-analytics',
],
};
const configDir = path.dirname(claudeConfigPath);
if (!fs.existsSync(configDir)) {
console.log(chalk.blue('Creating Claude config directory...'));
fs.mkdirSync(configDir, { recursive: true });
}
const existingConfig = fs.existsSync(claudeConfigPath)
? JSON.parse(fs.readFileSync(claudeConfigPath, 'utf8'))
: { mcpServers: {} };
if (MCP_NEON_SERVER in (existingConfig?.mcpServers || {})) {
console.log(chalk.yellow('Replacing existing Neon MCP config...'));
}
const newConfig = {
...existingConfig,
mcpServers: {
...existingConfig.mcpServers,
[MCP_NEON_SERVER]: neonConfig,
},
};
fs.writeFileSync(claudeConfigPath, JSON.stringify(newConfig, null, 2));
console.log(chalk.green(`Config written to: ${claudeConfigPath}`));
console.log(
chalk.blue(
'The Neon MCP server will start automatically the next time you open Claude.',
),
);
}