digital-samba-mcp-server
Version:
Digital Samba MCP Server - Model Context Protocol server for Digital Samba's video conferencing API
301 lines (268 loc) • 10.3 kB
JavaScript
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import { parseArgs } from 'node:util';
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Detect if we're running in stdio mode (for Claude Desktop)
// This happens when:
// 1. Not a TTY (piped)
// 2. Has --stdio flag
// 3. MCP_MODE environment variable is set
const isStdioMode = !process.stdout.isTTY || process.argv.includes('--stdio') || process.env.MCP_MODE === 'stdio';
// Set environment variable to indicate we're in JSON-RPC mode when running via MCP
if (isStdioMode) {
process.env.MCP_JSON_RPC_MODE = 'true';
process.env.NO_CONSOLE_OUTPUT = 'true'; // Complete silence in JSON-RPC mode
// Completely replace stdout write to ensure ONLY JSON is written
const originalStdoutWrite = process.stdout.write;
process.stdout.write = function(buffer, encoding, callback) {
// Only allow properly formatted JSON-RPC messages
const str = buffer.toString();
if (str.trim().startsWith('{') && str.includes('"jsonrpc":"2.0"')) {
return originalStdoutWrite.apply(this, arguments);
}
// For non-JSON messages, log to stderr instead
process.stderr.write(`[FILTERED]: ${str}\n`);
// Pretend we wrote it to maintain the expected behavior
if (callback) callback();
return true;
};
// Completely suppress all console output to stdout
console.log = () => {};
console.info = () => {};
console.debug = () => {};
// Only keep minimal error logging to stderr
console.warn = (...args) => process.stderr.write(`[WARN] ${args.join(' ')}\n`);
console.error = (...args) => process.stderr.write(`[ERROR] ${args.join(' ')}\n`);
}
// Check if we're in JSON-RPC mode (for MCP communication)
const isJsonRpcMode = process.env.MCP_JSON_RPC_MODE === 'true';
// Parse command-line arguments
// Handle positional arguments for API key
const positionalArgs = [];
let hasExplicitApiKey = false;
for (let i = 2; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg === '--api-key' || arg === '-k') {
hasExplicitApiKey = true;
break;
}
// If not starting with a dash, treat it as a positional argument
if (!arg.startsWith('-') && !process.argv[i-1]?.startsWith('-')) {
positionalArgs.push(arg);
}
}
const { values: args, positionals } = parseArgs({
options: {
port: {
type: 'string',
short: 'p',
default: '4521'
},
'api-key': {
type: 'string',
short: 'k'
},
'api-url': {
type: 'string',
short: 'u',
default: 'https://api.digitalsamba.com/api/v1'
},
'log-level': {
type: 'string',
short: 'l',
default: 'info'
},
'webhook-secret': {
type: 'string',
short: 'w'
},
'webhook-endpoint': {
type: 'string',
short: 'e',
default: '/webhooks/digitalsamba'
},
'public-url': {
type: 'string',
default: ''
},
stdio: {
type: 'boolean',
default: false
},
help: {
type: 'boolean',
short: 'h',
default: false
}
},
allowPositionals: true
});
// Display help information if requested
if (args.help) {
console.log(`
Digital Samba MCP Server
Usage: npx digital-samba-mcp-server [options] [API_KEY]
Options:
-p, --port <port> Port to run the server on (default: 4521)
-k, --api-key <key> Digital Samba API key
-u, --api-url <url> Digital Samba API URL (default: https://api.digitalsamba.com/api/v1)
-l, --log-level <level> Log level (default: info)
-w, --webhook-secret <secret> Secret for webhook verification
-e, --webhook-endpoint <path> Webhook endpoint path (default: /webhooks/digitalsamba)
--public-url <url> Public URL for the server (for webhook callbacks)
--stdio Run in stdio mode for Claude Desktop (auto-detected)
-h, --help Display this help message
Environment Variables:
PORT Port to run the server on
DIGITAL_SAMBA_API_KEY Digital Samba API key
DIGITAL_SAMBA_API_URL Digital Samba API URL
LOG_LEVEL Log level
WEBHOOK_SECRET Secret for webhook verification
WEBHOOK_ENDPOINT Webhook endpoint path
PUBLIC_URL Public URL for the server
Examples:
npx digital-samba-mcp-server YOUR_API_KEY
npx digital-samba-mcp-server --api-key YOUR_API_KEY
npx digital-samba-mcp-server --port 4000 --api-key YOUR_API_KEY --log-level debug
`);
process.exit(0);
}
// Set environment variables from command-line arguments
process.env.PORT = args.port;
process.env.LOG_LEVEL = args['log-level'];
process.env.DIGITAL_SAMBA_API_URL = args['api-url'];
process.env.WEBHOOK_SECRET = args['webhook-secret'];
process.env.WEBHOOK_ENDPOINT = args['webhook-endpoint'];
process.env.PUBLIC_URL = args['public-url'] || `http://localhost:${args.port}`;
// If NO_CONSOLE_OUTPUT is set, completely suppress console output
if (process.env.NO_CONSOLE_OUTPUT === 'true') {
// Save original console methods
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug
};
// Replace with no-op functions
console.log = () => {};
console.error = () => {};
console.warn = () => {};
console.info = () => {};
console.debug = () => {};
}
// Set API key from options or positional arguments or environment variable
if (args['api-key']) {
process.env.DIGITAL_SAMBA_API_KEY = args['api-key'];
if (!process.env.MCP_JSON_RPC_MODE) {
console.log(`Using API key from --api-key option: ${args['api-key'].substring(0, 5)}...`);
}
} else if (positionals && positionals.length > 0) {
// Use the first positional argument as the API key
process.env.DIGITAL_SAMBA_API_KEY = positionals[0];
if (!process.env.MCP_JSON_RPC_MODE) {
console.log(`Using API key from positional argument: ${positionals[0].substring(0, 5)}...`);
}
} else if (positionalArgs.length > 0) {
// Fallback for older Node.js versions
process.env.DIGITAL_SAMBA_API_KEY = positionalArgs[0];
if (!process.env.MCP_JSON_RPC_MODE) {
console.log(`Using API key from positional argument: ${positionalArgs[0].substring(0, 5)}...`);
}
} else if (process.env.DIGITAL_SAMBA_API_KEY) {
// API key already set in environment variable
if (!process.env.MCP_JSON_RPC_MODE) {
console.log(`Using API key from environment: ${process.env.DIGITAL_SAMBA_API_KEY.substring(0, 5)}...`);
}
} else {
if (!process.env.MCP_JSON_RPC_MODE) {
console.error('No API key provided. Please specify an API key using --api-key or as a positional argument, or set DIGITAL_SAMBA_API_KEY environment variable.');
}
process.exit(1);
}
// Start the server
try {
// Determine if we should use stdio mode
const useStdioMode = isStdioMode || args.stdio;
if (useStdioMode) {
// Use stdio mode for Claude Desktop
console.error('[INFO] Starting in stdio mode for Claude Desktop');
// Execute the direct stdio server
import('./stdio-direct.js').catch(error => {
console.error('Failed to start stdio server:', error);
process.exit(1);
});
} else {
// Use HTTP server mode
import('../dist/src/index.js').then(module => {
// Check if we're in JSON-RPC mode (for MCP communication)
const isJsonRpcMode = process.env.MCP_JSON_RPC_MODE === 'true' || !process.stdout.isTTY;
if (!isJsonRpcMode) {
console.log('Starting Digital Samba MCP Server...');
}
// Check if module has the startServer function
if (typeof module.startServer !== 'function') {
console.error('ERROR: startServer function not found in module. This may indicate a build issue.');
console.error('Module exports:', Object.keys(module));
process.exit(1);
}
// Start the server with enhanced error handling
try {
// Set a handler for uncaught exceptions to prevent crash
process.on('uncaughtException', (err) => {
console.error('Uncaught exception:', err.message);
console.error(err.stack);
// Don't exit process on uncaught exceptions to maintain MCP connection
});
// Set a handler for unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled promise rejection:', reason);
// Don't exit process on unhandled rejections to maintain MCP connection
});
// Start server with special handling for non-TTY environments
const server = module.startServer({
enableSilentMode: isJsonRpcMode
});
// Add error handling for the server
if (server && typeof server.on === 'function') {
server.on('error', (err) => {
console.error('Server error:', err.message);
// Don't exit in JSON-RPC mode
if (!isJsonRpcMode) {
process.exit(1);
}
});
}
// Keep the process alive
if (isJsonRpcMode) {
// Set up interval to keep the process alive in JSON-RPC mode
const keepAlive = setInterval(() => {}, 60000);
// Clean up on SIGTERM
process.on('SIGTERM', () => {
clearInterval(keepAlive);
process.exit(0);
});
}
} catch (startError) {
console.error('Error starting server:', startError.message);
console.error(startError.stack);
if (!isJsonRpcMode) {
process.exit(1);
}
}
}).catch(importError => {
console.error('Failed to import server module:', importError.message);
console.error('This may indicate a build issue or missing files.');
console.error('Try running "npm run build" before starting the server.');
console.error(importError.stack);
process.exit(1);
});
}
} catch (error) {
console.error('Critical error loading server:', error.message);
console.error(error.stack);
process.exit(1);
}