186 lines
5.3 KiB
JavaScript

#!/usr/bin/env node
import { z } from 'zod';
import { createStripeServer } from './server.js';
/**
* Environment variable schema
*/
const EnvSchema = z.object({
STRIPE_SECRET_KEY: z.string().min(1, 'STRIPE_SECRET_KEY is required'),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
PORT: z.string().optional().default('3000'),
NODE_ENV: z.enum(['development', 'production', 'test']).optional().default('production'),
});
type Env = z.infer<typeof EnvSchema>;
/**
* Validate environment variables
*/
function validateEnv(): Env {
try {
return EnvSchema.parse(process.env);
} catch (error) {
if (error instanceof z.ZodError) {
console.error('❌ Environment validation failed:');
for (const issue of error.issues) {
console.error(` - ${issue.path.join('.')}: ${issue.message}`);
}
console.error('\nRequired environment variables:');
console.error(' STRIPE_SECRET_KEY - Your Stripe secret key (starts with sk_test_ or sk_live_)');
console.error('\nOptional environment variables:');
console.error(' STRIPE_WEBHOOK_SECRET - Webhook signing secret (starts with whsec_)');
console.error(' PORT - HTTP server port for SSE transport (default: 3000)');
console.error('\nExample:');
console.error(' export STRIPE_SECRET_KEY=sk_test_xxxxx');
console.error(' npm start');
process.exit(1);
}
throw error;
}
}
/**
* Health check endpoint data
*/
function getHealthInfo() {
return {
status: 'healthy',
service: '@mcpengine/stripe',
version: '1.0.0',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
node: process.version,
};
}
/**
* Setup graceful shutdown
*/
function setupGracefulShutdown(cleanup: () => Promise<void>): void {
const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
signals.forEach(signal => {
process.on(signal, async () => {
console.error(`\n📡 Received ${signal}, shutting down gracefully...`);
try {
await cleanup();
console.error('✅ Cleanup complete');
process.exit(0);
} catch (error) {
console.error('❌ Error during cleanup:', error);
process.exit(1);
}
});
});
// Handle uncaught errors
process.on('uncaughtException', (error) => {
console.error('❌ Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('❌ Unhandled rejection at:', promise, 'reason:', reason);
process.exit(1);
});
}
/**
* Start HTTP/SSE server for dual transport support
*/
async function startHttpServer(server: ReturnType<typeof createStripeServer>, port: number): Promise<void> {
const { createServer } = await import('http');
const { SSEServerTransport } = await import('@modelcontextprotocol/sdk/server/sse.js');
const httpServer = createServer(async (req, res) => {
// Health check endpoint
if (req.url === '/health' || req.url === '/') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(getHealthInfo(), null, 2));
return;
}
// SSE endpoint
if (req.url === '/sse') {
console.error('📡 SSE client connected');
const transport = new SSEServerTransport('/message', res);
await server.getServer().connect(transport);
// Handle client disconnect
req.on('close', () => {
console.error('📡 SSE client disconnected');
});
return;
}
// 404 for other routes
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
});
httpServer.listen(port, () => {
console.error(`🌐 HTTP/SSE server listening on http://localhost:${port}`);
console.error(` Health: http://localhost:${port}/health`);
console.error(` SSE: http://localhost:${port}/sse`);
});
}
/**
* Main entry point
*/
async function main(): Promise<void> {
console.error('🚀 Starting Stripe MCP Server...\n');
// Validate environment
const env = validateEnv();
console.error('✅ Environment validated');
console.error(` API Key: ${env.STRIPE_SECRET_KEY.substring(0, 12)}...`);
if (env.STRIPE_WEBHOOK_SECRET) {
console.error(` Webhook Secret: ${env.STRIPE_WEBHOOK_SECRET.substring(0, 12)}...`);
}
console.error('');
// Create server
const server = createStripeServer({
apiKey: env.STRIPE_SECRET_KEY,
webhookSecret: env.STRIPE_WEBHOOK_SECRET,
});
// Setup graceful shutdown
setupGracefulShutdown(async () => {
await server.close();
});
// Determine transport mode
const transportMode = process.env.TRANSPORT_MODE || 'stdio';
if (transportMode === 'http' || transportMode === 'dual') {
const port = parseInt(env.PORT, 10);
await startHttpServer(server, port);
}
if (transportMode === 'stdio' || transportMode === 'dual') {
// Connect to stdio (this is the default for MCP)
await server.connectStdio();
}
if (transportMode !== 'http' && transportMode !== 'stdio' && transportMode !== 'dual') {
console.error(`❌ Unknown transport mode: ${transportMode}`);
console.error(' Valid modes: stdio, http, dual');
process.exit(1);
}
console.error('✅ Server ready\n');
}
// Run the server
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});