server.js•9.12 kB
#!/usr/bin/env node
// Load .env file if it exists
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { spawn } = require('child_process');
const { randomUUID } = require('crypto');
const app = express();
const PORT = process.env.PORT || 3000;
console.log('🚀 Starting Algorand MCP Server...');
console.log(`📍 Port: ${PORT}`);
console.log(`🌐 ALGORAND_NETWORK: ${process.env.ALGORAND_NETWORK || 'mainnet'}`);
app.use(cors());
app.use(express.json({ limit: '10mb' }));
let mcpProcess = null;
let mcpInitialized = false;
const pendingRequests = new Map();
async function initializeMCP() {
return new Promise((resolve, reject) => {
console.log('Initializing MCP...');
console.log('Environment for MCP:', {
ALGORAND_NETWORK: process.env.ALGORAND_NETWORK || 'mainnet'
});
mcpProcess = spawn('node', ['./mcp/index.js'], {
env: {
...process.env,
ALGORAND_NETWORK: process.env.ALGORAND_NETWORK || 'mainnet'
},
stdio: ['pipe', 'pipe', 'pipe']
});
let buffer = '';
let initTimeout = setTimeout(() => {
reject(new Error('MCP initialization timeout'));
}, 30000);
mcpProcess.stdout.on('data', (data) => {
buffer += data.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim()) {
try {
const response = JSON.parse(line);
if (response.id === 'init' && response.result) {
clearTimeout(initTimeout);
console.log('✅ MCP initialized');
// Send initialized notification
mcpProcess.stdin.write(JSON.stringify({
jsonrpc: '2.0',
method: 'notifications/initialized',
params: {}
}) + '\n');
// Mark as initialized after notification
setTimeout(() => {
mcpInitialized = true;
console.log('✅ MCP ready for requests');
}, 500);
resolve(response.result);
}
if (response.id && pendingRequests.has(response.id)) {
const { resolve } = pendingRequests.get(response.id);
pendingRequests.delete(response.id);
resolve(response);
}
} catch (e) {
// Ignore parse errors for non-JSON lines
}
}
}
});
mcpProcess.stderr.on('data', (data) => {
console.error('MCP stderr:', data.toString());
});
mcpProcess.on('error', (err) => {
clearTimeout(initTimeout);
console.error('MCP error:', err);
reject(err);
});
mcpProcess.on('exit', (code) => {
console.log(`MCP exited: ${code}`);
mcpInitialized = false;
});
// Send initialize request
setTimeout(() => {
mcpProcess.stdin.write(JSON.stringify({
jsonrpc: '2.0',
method: 'initialize',
params: {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: {
name: 'algorand-mcp-http-server',
version: '1.0.0'
}
},
id: 'init'
}) + '\n');
}, 1000);
});
}
async function sendMCPRequest(method, params = {}) {
if (!mcpInitialized) {
throw new Error('MCP not initialized');
}
return new Promise((resolve, reject) => {
const id = randomUUID();
const timeout = setTimeout(() => {
pendingRequests.delete(id);
reject(new Error('Request timeout'));
}, 30000);
pendingRequests.set(id, {
resolve: (response) => {
clearTimeout(timeout);
resolve(response);
},
reject
});
mcpProcess.stdin.write(JSON.stringify({
jsonrpc: '2.0',
method,
params,
id
}) + '\n');
});
}
// Root endpoint
app.get('/', (req, res) => {
res.json({
name: 'Algorand MCP Server',
version: '0.1.0',
status: mcpInitialized ? 'operational' : 'offline',
blockchain: 'Algorand',
supportedNetworks: ['mainnet', 'testnet', 'betanet'],
protocol: 'MCP',
endpoints: ['/health', '/info', '/mcp']
});
});
// Health check endpoint
app.get('/health', (req, res) => {
const healthy = mcpInitialized || mcpProcess !== null;
res.status(healthy ? 200 : 503).json({
status: healthy ? 'healthy' : 'unhealthy',
service: 'Algorand MCP',
version: '1.0.0',
blockchain: 'Algorand',
timestamp: new Date().toISOString()
});
});
// Info endpoint
app.get('/info', (req, res) => {
res.json({
name: 'Algorand MCP',
description: 'MCP server for Algorand blockchain integration',
version: '1.0.0',
supportedNetworks: ['mainnet', 'testnet', 'betanet'],
network: process.env.ALGORAND_NETWORK || 'mainnet',
capabilities: [
'Account management and creation',
'Transaction processing and tracking',
'Asset creation and management (ASA)',
'Smart contract deployment (TEAL/Python/TypeScript)',
'NFT minting and transfers',
'DeFi operations and staking',
'Network monitoring and analytics'
]
});
});
// MCP Protocol endpoint (JSON-RPC)
app.post('/mcp', async (req, res) => {
try {
const { jsonrpc, method, params, id, tool } = req.body;
// Handle legacy format (tool + params)
if (tool && !method) {
if (!mcpInitialized) {
return res.status(503).json({
error: 'MCP not ready',
message: 'Please wait for initialization'
});
}
try {
const response = await sendMCPRequest('tools/call', {
name: tool,
arguments: params || {}
});
return res.json({
result: response.result,
error: response.error || null
});
} catch (error) {
return res.status(500).json({
error: error.message
});
}
}
// Handle standard JSON-RPC format
if (jsonrpc !== '2.0') {
return res.status(400).json({
jsonrpc: '2.0',
error: { code: -32600, message: 'Invalid Request' },
id: id || null
});
}
switch (method) {
case 'initialize':
res.json({
jsonrpc: '2.0',
result: {
protocolVersion: '2024-11-05',
serverInfo: {
name: 'Algorand MCP',
version: '1.0.0'
},
capabilities: { tools: {} }
},
id
});
break;
case 'tools/list':
if (!mcpInitialized) throw new Error('MCP not ready');
const listResponse = await sendMCPRequest('tools/list');
res.json({
jsonrpc: '2.0',
result: listResponse.result,
id
});
break;
case 'tools/call':
if (!mcpInitialized) throw new Error('MCP not ready');
const callResponse = await sendMCPRequest('tools/call', params);
res.json({
jsonrpc: '2.0',
result: callResponse.result,
id
});
break;
default:
res.status(404).json({
jsonrpc: '2.0',
error: { code: -32601, message: `Method not found: ${method}` },
id
});
}
} catch (error) {
console.error('MCP error:', error);
res.status(500).json({
jsonrpc: '2.0',
error: { code: -32603, message: error.message },
id: req.body.id || null
});
}
});
// MCP Discovery endpoint (GET)
app.get('/mcp', (req, res) => {
res.json({
name: 'Algorand MCP Server',
version: '1.0.0',
protocol_version: '2024-11-05',
endpoint: '/mcp',
status: mcpInitialized ? 'ready' : 'offline',
description: 'Full-featured MCP server for Algorand blockchain',
features: [
'Account management and creation',
'Transaction processing',
'Asset and NFT operations',
'Smart contract interaction',
'Network monitoring'
]
});
});
// Initialize MCP
initializeMCP()
.then(() => console.log('✅ MCP ready'))
.catch((error) => {
console.error('⚠️ MCP init failed:', error.message);
console.log('Server will run with limited functionality');
});
// Start server
const server = app.listen(PORT, '0.0.0.0', () => {
console.log(`\n🚀 Algorand MCP Server running on port ${PORT}`);
console.log('📍 Health check: http://localhost:' + PORT + '/health');
console.log('📍 Info: http://localhost:' + PORT + '/info');
console.log('📍 MCP endpoint: http://localhost:' + PORT + '/mcp');
console.log('\n✨ Ready for MCP connections!\n');
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down...');
server.close(() => console.log('HTTP server closed'));
if (mcpProcess) mcpProcess.kill('SIGTERM');
setTimeout(() => process.exit(0), 5000);
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down...');
server.close(() => console.log('HTTP server closed'));
if (mcpProcess) mcpProcess.kill('SIGTERM');
setTimeout(() => process.exit(0), 5000);
});