#!/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 };