#!/usr/bin/env node
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { createInterface } from 'readline';
/**
* Interactive Succinct Prover MCP Setup Tool
* Combines Base MCP approach with official Succinct workflow
* Provides guided interactive setup experience
*/
const SUCCINCT_GREEN = '\x1b[32m';
const SUCCINCT_BLUE = '\x1b[34m';
const SUCCINCT_YELLOW = '\x1b[33m';
const SUCCINCT_RED = '\x1b[31m';
const RESET = '\x1b[0m';
function log(message: string, color: string = RESET) {
console.log(`${color}${message}${RESET}`);
}
interface SetupChoices {
setupMode: 'interactive' | 'auto';
aiClient?: 'claude' | 'cursor' | 'both' | 'manual';
proverAddress?: string;
privateKey?: string;
collectCredentials?: boolean;
}
// Simple prompt implementation using ES modules import
function createPrompt(question: string, defaultValue?: string): Promise<string> {
return new Promise((resolve) => {
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
rl.question(prompt, (answer: string) => {
rl.close();
resolve(answer.trim() || defaultValue || '');
});
});
}
function createSelectPrompt(question: string, choices: Array<{name: string, value: string}>): Promise<string> {
return new Promise((resolve) => {
log(`\n${question}`, SUCCINCT_BLUE);
choices.forEach((choice, index) => {
log(`${index + 1}. ${choice.name}`);
});
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('\nSelect option (1-' + choices.length + '): ', (answer: string) => {
rl.close();
const index = parseInt(answer) - 1;
if (index >= 0 && index < choices.length) {
resolve(choices[index].value);
} else {
log('Invalid selection, using first option', SUCCINCT_YELLOW);
resolve(choices[0].value);
}
});
});
}
function createEnvTemplate(credentials?: { proverAddress?: string; privateKey?: string }): string {
return `# Succinct Prover MCP Configuration
# Official Succinct Network Documentation: https://docs.succinct.xyz/docs/provers/running-a-prover/quickstart
# š REQUIRED: Your Credentials (Replace with actual values)
PROVER_ADDRESS=${credentials?.proverAddress || "REPLACE_WITH_YOUR_PROVER_CONTRACT_ADDRESS"}
PRIVATE_KEY=${credentials?.privateKey || "REPLACE_WITH_YOUR_64_CHARACTER_PRIVATE_KEY_NO_0X"}
# š NETWORK: Succinct Network Configuration (DO NOT CHANGE)
SUCCINCT_RPC_URL=https://rpc-production.succinct.xyz
SUCCINCT_STAKING_ADDRESS=0x8F1B4633630b90C273E834C14924298ab6D1DA02
PROVE_TOKEN_ADDRESS=0xC47B85472b40435c0D96db5Df3e9B4C368DA6A8C
# āļø HARDWARE: Performance Parameters (Update after calibration)
PGUS_PER_SECOND=19463
PROVE_PER_BPGU=25.12
HARDWARE_MODE=cpu
# š WORKFLOW:
# 1. Get testnet tokens (Sepolia ETH + PROVE) from official faucet
# 2. Create prover at: https://staking.sepolia.succinct.xyz/prover
# 3. Stake 1000+ PROVE tokens
# 4. Ask AI: "Calibrate my prover hardware"
# 5. Ask AI: "Run Succinct Prover"
# š SECURITY: Never commit this file to version control!
# š TESTNET: Only works with Sepolia testnet - safe for testing
`;
}
function createMcpConfig(projectPath: string, credentials?: { proverAddress: string; privateKey: string }): string {
// Use relative path with cwd for better portability
const indexPath = "./build/index.js";
return JSON.stringify({
mcpServers: {
"prover-mcp": {
command: "node",
args: [indexPath],
cwd: projectPath,
env: {
PROVER_ADDRESS: credentials?.proverAddress || "REPLACE_WITH_YOUR_PROVER_CONTRACT_ADDRESS",
PRIVATE_KEY: credentials?.privateKey || "REPLACE_WITH_YOUR_64_CHARACTER_PRIVATE_KEY_NO_0X",
SUCCINCT_RPC_URL: "https://rpc-production.succinct.xyz",
SUCCINCT_STAKING_ADDRESS: "0x8F1B4633630b90C273E834C14924298ab6D1DA02",
PROVE_TOKEN_ADDRESS: "0xC47B85472b40435c0D96db5Df3e9B4C368DA6A8C",
PGUS_PER_SECOND: "19463",
PROVE_PER_BPGU: "25.12",
HARDWARE_MODE: "cpu"
},
disabled: false,
autoApprove: []
}
}
}, null, 2);
}
function detectAiClients(): { claude: boolean; cursor: boolean } {
const claudeConfigPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
const cursorConfigPath = join(homedir(), '.cursor', 'mcp.json');
return {
claude: existsSync(dirname(claudeConfigPath)),
cursor: existsSync(dirname(cursorConfigPath))
};
}
function installMcpConfig(aiClient: 'claude' | 'cursor', configContent: string, projectPath: string): boolean {
try {
let configPath: string;
if (aiClient === 'claude') {
configPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
} else {
configPath = join(homedir(), '.cursor', 'mcp.json');
}
// Create directory if it doesn't exist
const configDir = dirname(configPath);
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}
// Handle existing config
if (existsSync(configPath)) {
try {
const existingConfig = JSON.parse(readFileSync(configPath, 'utf8'));
const newConfig = JSON.parse(configContent);
// Merge configs
existingConfig.mcpServers = existingConfig.mcpServers || {};
existingConfig.mcpServers['prover-mcp'] = newConfig.mcpServers['prover-mcp'];
writeFileSync(configPath, JSON.stringify(existingConfig, null, 2));
log(`ā
Updated existing ${aiClient} configuration`, SUCCINCT_GREEN);
} catch (error) {
// If existing config is invalid, backup and create new
const backupPath = `${configPath}.backup.${Date.now()}`;
if (existsSync(configPath)) {
writeFileSync(backupPath, readFileSync(configPath));
log(`š Backed up existing config to: ${backupPath}`, SUCCINCT_YELLOW);
}
writeFileSync(configPath, configContent);
log(`ā
Created new ${aiClient} configuration`, SUCCINCT_GREEN);
}
} else {
writeFileSync(configPath, configContent);
log(`ā
Created ${aiClient} configuration`, SUCCINCT_GREEN);
}
return true;
} catch (error) {
log(`ā Failed to install ${aiClient} config: ${error}`, SUCCINCT_RED);
return false;
}
}
function showSuccinctWorkflow(): void {
log('\nš Official Succinct 5-Step Workflow:', SUCCINCT_BLUE);
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
log('ā 1. ā
Requirements (Automated by this tool) ā');
log('ā ⢠Fresh Ethereum wallet with Sepolia ETH ā');
log('ā ⢠1000+ PROVE tokens from official faucet ā');
log('ā ⢠Docker Desktop running ā');
log('ā ā');
log('ā 2. šļø Create Prover Contract (Manual) ā');
log('ā š https://staking.sepolia.succinct.xyz/prover ā');
log('ā ⢠Connect wallet & create prover ā');
log('ā ⢠Save the prover address (different from wallet!) ā');
log('ā ā');
log('ā 3. š° Stake PROVE Tokens (Manual) ā');
log('ā š https://staking.sepolia.succinct.xyz ā');
log('ā ⢠Stake minimum 1000 PROVE tokens ā');
log('ā ā');
log('ā 4. āļø Calibrate Hardware (AI Assisted) ā');
log('ā š¬ "Calibrate my prover hardware" ā');
log('ā ā');
log('ā 5. š Run Prover (AI Assisted) ā');
log('ā š¬ "Run Succinct Prover" ā');
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
}
async function getInteractiveChoices(): Promise<SetupChoices> {
log('\nš§ Setup Mode Selection:', SUCCINCT_BLUE);
const setupMode = await createSelectPrompt('How would you like to proceed?', [
{ name: 'š Interactive Setup (Recommended) - Guide me through the process', value: 'interactive' },
{ name: 'ā” Auto Setup - Create config files automatically', value: 'auto' }
]);
if (setupMode === 'auto') {
// Auto setup detects clients and configures both
const aiClients = detectAiClients();
let autoAiClient: 'both' | 'claude' | 'cursor' | 'manual' = 'manual';
if (aiClients.claude && aiClients.cursor) {
autoAiClient = 'both';
} else if (aiClients.claude) {
autoAiClient = 'claude';
} else if (aiClients.cursor) {
autoAiClient = 'cursor';
}
return {
setupMode: 'auto',
aiClient: autoAiClient,
collectCredentials: false
};
}
// Interactive mode
const aiClients = detectAiClients();
const aiClientChoices = [];
if (aiClients.claude && aiClients.cursor) {
aiClientChoices.push(
{ name: 'š¤ Both Claude & Cursor (Recommended)', value: 'both' },
{ name: 'š§ Claude Desktop only', value: 'claude' },
{ name: 'š§ Cursor IDE only', value: 'cursor' },
{ name: 'š Manual setup (I\'ll configure myself)', value: 'manual' }
);
} else if (aiClients.claude) {
aiClientChoices.push(
{ name: 'š§ Claude Desktop', value: 'claude' },
{ name: 'š Manual setup', value: 'manual' }
);
} else if (aiClients.cursor) {
aiClientChoices.push(
{ name: 'š§ Cursor IDE', value: 'cursor' },
{ name: 'š Manual setup', value: 'manual' }
);
} else {
aiClientChoices.push(
{ name: 'š Manual setup (no AI clients detected)', value: 'manual' }
);
}
const aiClient = await createSelectPrompt('Which AI client(s) would you like to configure?', aiClientChoices);
const collectCredentials = await createSelectPrompt('Do you want to add your credentials now?', [
{ name: 'ā
Yes - I have my prover address and private key ready', value: 'yes' },
{ name: 'ā° Later - I\'ll add them after creating my prover', value: 'no' }
]);
let proverAddress, privateKey;
if (collectCredentials === 'yes') {
log('\nš Credential Collection:', SUCCINCT_YELLOW);
log('ā ļø SECURITY: Only use Sepolia testnet credentials!');
proverAddress = await createPrompt('Enter your prover address (0x...)');
if (proverAddress && !proverAddress.startsWith('0x')) {
log('ā ļø Adding 0x prefix to prover address', SUCCINCT_YELLOW);
proverAddress = '0x' + proverAddress;
}
privateKey = await createPrompt('Enter your private key (64 hex chars, no 0x)');
if (privateKey && privateKey.startsWith('0x')) {
log('ā ļø Removing 0x prefix from private key', SUCCINCT_YELLOW);
privateKey = privateKey.slice(2);
}
}
return {
setupMode: 'interactive',
aiClient: aiClient as any,
proverAddress,
privateKey,
collectCredentials: collectCredentials === 'yes'
};
}
async function main() {
log(`
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā š SUCCINCT PROVER MCP - INTERACTIVE SETUP ā
ā Following Official Succinct Network Workflow ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`, SUCCINCT_BLUE);
const projectPath = process.cwd();
const envPath = join(projectPath, '.env');
// Check current status
const envExists = existsSync(envPath);
const aiClients = detectAiClients();
log(`\nš Environment Analysis:`, SUCCINCT_YELLOW);
log(` š Project: ${projectPath}`);
log(` š .env file: ${envExists ? 'ā
Exists' : 'ā Missing'}`);
log(` š¤ Claude Desktop: ${aiClients.claude ? 'ā
Detected' : 'ā Not found'}`);
log(` š§ Cursor IDE: ${aiClients.cursor ? 'ā
Detected' : 'ā Not found'}`);
// Get user choices
const choices = await getInteractiveChoices();
// Create credentials object if provided
const credentials = choices.collectCredentials && choices.proverAddress && choices.privateKey ? {
proverAddress: choices.proverAddress,
privateKey: choices.privateKey
} : undefined;
// Step 1: Create .env file
log('\nš Creating .env file...', SUCCINCT_YELLOW);
writeFileSync(envPath, createEnvTemplate(credentials));
log(`ā
Created: ${envPath}`, SUCCINCT_GREEN);
// Step 2: Handle MCP configuration based on choice
if (choices.aiClient !== 'manual') {
log('\nāļø Setting up AI Client Integration:', SUCCINCT_BLUE);
const mcpConfig = createMcpConfig(projectPath, credentials);
if (choices.aiClient === 'both') {
if (aiClients.claude) installMcpConfig('claude', mcpConfig, projectPath);
if (aiClients.cursor) installMcpConfig('cursor', mcpConfig, projectPath);
} else if (choices.aiClient === 'claude') {
installMcpConfig('claude', mcpConfig, projectPath);
} else if (choices.aiClient === 'cursor') {
installMcpConfig('cursor', mcpConfig, projectPath);
}
} else {
// Manual setup
log('\nš Creating template config...', SUCCINCT_YELLOW);
const templatePath = join(projectPath, 'mcp_config.json');
writeFileSync(templatePath, createMcpConfig(projectPath, credentials));
log(`ā
Template created: ${templatePath}`, SUCCINCT_GREEN);
log('š Manual setup instructions:');
log(' ⢠For Claude: Copy to ~/Library/Application Support/Claude/claude_desktop_config.json');
log(' ⢠For Cursor: Copy to ~/.cursor/mcp.json');
}
// Show official workflow
showSuccinctWorkflow();
// Show next steps based on whether credentials were provided
if (credentials) {
log('\nš Setup Complete with Credentials!', SUCCINCT_GREEN);
log('š Please restart your AI client completely');
log('\nš Ready to use:');
log(' š¬ "Calibrate my prover hardware" - Optimize performance');
log(' š¬ "Run Succinct Prover" - Start earning PROVE tokens!');
} else {
log('\nšÆ Next Steps:', SUCCINCT_RED);
log('1. š Create prover: https://staking.sepolia.succinct.xyz/prover');
log('2. š° Get tokens: https://docs.google.com/forms/d/e/1FAIpQLSfgTpBL_wMWyyoxT6LxuMhiu-bex0cBg9kRTmxoKw3XOluOCA/viewform');
log('3. š° Stake 1000+ PROVE tokens to your prover');
log('4. āļø Add your credentials to configuration');
log('5. š Restart your AI client');
log('\nš Then:');
log(' š¬ "Calibrate my prover hardware"');
log(' š¬ "Run Succinct Prover"');
}
log('\nš Security Reminders:', SUCCINCT_YELLOW);
log(' ⢠Only use Sepolia testnet wallets');
log(' ⢠Never commit credentials to version control');
log(' ⢠Files are automatically git-ignored');
log('\nš Welcome to the Succinct Prover Network!', SUCCINCT_GREEN);
}
// Run if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export { main as initSuccinctProver };