Skip to main content
Glama
index.ts8.61 kB
#!/usr/bin/env node import * as grpc from '@grpc/grpc-js'; import { unlinkSync, existsSync, readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { SandboxServiceService } from '../generated/sandbox.js'; import { createSandboxService } from './sandbox-service.js'; const DEFAULT_SOCKET_PATH = '/tmp/prodisco-sandbox.sock'; const DEFAULT_TCP_HOST = '0.0.0.0'; const DEFAULT_TCP_PORT = 50051; /** * Transport security modes for gRPC communication. * * - `insecure`: No encryption (Unix socket or TCP, for local development) * - `tls`: Server-side TLS (client verifies server identity) * - `mtls`: Mutual TLS (both client and server authenticate each other) */ export type TransportMode = 'insecure' | 'tls' | 'mtls'; export interface TlsConfig { /** Path to the server certificate file */ certPath: string; /** Path to the server private key file */ keyPath: string; /** Path to the CA certificate for verifying client certs (required for mTLS) */ caPath?: string; } export interface ServerConfig { /** Unix socket path for local connections */ socketPath?: string; /** TCP host to bind to (e.g., '0.0.0.0', 'localhost') */ tcpHost?: string; /** TCP port to bind to */ tcpPort?: number; /** Use TCP transport instead of Unix socket (legacy, prefer transportMode) */ useTcp?: boolean; /** Transport security mode */ transportMode?: TransportMode; /** TLS configuration (required for 'tls' and 'mtls' modes) */ tls?: TlsConfig; prometheusUrl?: string; cacheDir?: string; } /** * Clean up existing socket file if present. */ function cleanupSocket(socketPath: string): void { if (existsSync(socketPath)) { try { unlinkSync(socketPath); } catch (error) { // Ignore ENOENT, throw others if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { throw error; } } } } const VALID_TRANSPORT_MODES: TransportMode[] = ['insecure', 'tls', 'mtls']; /** * Get the transport mode from config or environment. * Defaults to 'insecure' for backward compatibility. */ function getTransportMode(config: ServerConfig): TransportMode { // Config takes precedence if (config.transportMode) { return config.transportMode; } // Check environment variable const envMode = process.env.SANDBOX_TRANSPORT_MODE; if (envMode && VALID_TRANSPORT_MODES.includes(envMode as TransportMode)) { return envMode as TransportMode; } return 'insecure'; } /** * Get TLS configuration from config or environment. */ function getTlsConfig(config: ServerConfig): TlsConfig | undefined { if (config.tls) { return config.tls; } const certPath = process.env.SANDBOX_TLS_CERT_PATH; const keyPath = process.env.SANDBOX_TLS_KEY_PATH; const caPath = process.env.SANDBOX_TLS_CA_PATH; if (certPath && keyPath) { return { certPath, keyPath, caPath }; } return undefined; } /** * Create server credentials based on transport mode. */ function createServerCredentials(mode: TransportMode, tls?: TlsConfig): grpc.ServerCredentials { if (mode === 'insecure') { return grpc.ServerCredentials.createInsecure(); } if (!tls) { throw new Error(`TLS configuration required for transport mode: ${mode}`); } const certChain = readFileSync(tls.certPath); const privateKey = readFileSync(tls.keyPath); // For mTLS, load CA to verify client certificates const rootCerts = mode === 'mtls' && tls.caPath ? readFileSync(tls.caPath) : null; return grpc.ServerCredentials.createSsl( rootCerts, [{ cert_chain: certChain, private_key: privateKey }], mode === 'mtls' // checkClientCertificate ); } /** * Determine if Unix socket should be used based on config and environment. */ function shouldUseUnixSocket(config: ServerConfig): boolean { // Explicit useTcp takes precedence if (config.useTcp !== undefined) { return !config.useTcp; } // Check environment variable const envUseTcp = process.env.SANDBOX_USE_TCP; if (envUseTcp === 'true' || envUseTcp === '1') { return false; } // Check if TCP host or port is specified if (config.tcpHost || config.tcpPort || process.env.SANDBOX_TCP_HOST || process.env.SANDBOX_TCP_PORT) { return false; } // Check if socket path is specified if (config.socketPath || process.env.SANDBOX_SOCKET_PATH) { return true; } // Default to Unix socket for local development return true; } /** * Get the binding address based on configuration. */ function getBindAddress(config: ServerConfig): { address: string; isUnixSocket: boolean } { if (shouldUseUnixSocket(config)) { const socketPath = config.socketPath || process.env.SANDBOX_SOCKET_PATH || DEFAULT_SOCKET_PATH; return { address: `unix://${socketPath}`, isUnixSocket: true }; } const host = config.tcpHost || process.env.SANDBOX_TCP_HOST || DEFAULT_TCP_HOST; const port = config.tcpPort || parseInt(process.env.SANDBOX_TCP_PORT || '', 10) || DEFAULT_TCP_PORT; return { address: `${host}:${port}`, isUnixSocket: false }; } /** * Start the gRPC sandbox server. * * Supports Unix socket (default) or TCP transport, with configurable security modes. * * Unix socket (default, insecure): * startServer({ socketPath: '/tmp/sandbox.sock' }) * * TCP (insecure): * startServer({ useTcp: true, tcpHost: '0.0.0.0', tcpPort: 50051 }) * * TCP with TLS (server-side TLS): * startServer({ * useTcp: true, * transportMode: 'tls', * tls: { certPath: '/path/to/tls.crt', keyPath: '/path/to/tls.key' } * }) * * TCP with mTLS (mutual TLS): * startServer({ * useTcp: true, * transportMode: 'mtls', * tls: { certPath: '/path/to/tls.crt', keyPath: '/path/to/tls.key', caPath: '/path/to/ca.crt' } * }) */ export async function startServer(config: ServerConfig = {}): Promise<grpc.Server> { const transportMode = getTransportMode(config); const tlsConfig = getTlsConfig(config); const { address, isUnixSocket } = getBindAddress(config); // Clean up existing socket file if using Unix socket if (isUnixSocket) { const socketPath = address.replace('unix://', ''); cleanupSocket(socketPath); } const server = new grpc.Server(); // Create service with k8s/prometheus context const sandboxService = createSandboxService({ prometheusUrl: config.prometheusUrl || process.env.PROMETHEUS_URL, cacheDir: config.cacheDir || process.env.SCRIPTS_CACHE_DIR, }); server.addService(SandboxServiceService, sandboxService); // Create credentials based on transport mode const credentials = createServerCredentials(transportMode, tlsConfig); return new Promise((resolve, reject) => { server.bindAsync(address, credentials, (error) => { if (error) { reject(error); return; } const securityInfo = transportMode === 'insecure' ? '(insecure)' : transportMode === 'mtls' ? '(mTLS)' : '(TLS)'; console.log(`Sandbox gRPC server listening on ${address} ${securityInfo}`); resolve(server); }); }); } /** * Graceful shutdown handler. */ function setupShutdown(server: grpc.Server, socketPath: string | null): void { const shutdown = (signal: string) => { console.log(`Received ${signal}, shutting down...`); server.tryShutdown((err) => { if (err) { console.error('Error during shutdown', err); server.forceShutdown(); } // Clean up socket file (only for Unix socket) if (socketPath) { cleanupSocket(socketPath); } console.log('Server shut down'); process.exit(0); }); // Set a deadline for graceful shutdown setTimeout(() => { console.warn('Forced shutdown after timeout'); server.forceShutdown(); if (socketPath) { cleanupSocket(socketPath); } process.exit(1); }, 10000); }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); } // CLI entry point const isMainModule = process.argv[1] === fileURLToPath(import.meta.url) || process.argv[1]?.endsWith('/sandbox-server/dist/server/index.js'); if (isMainModule) { const config: ServerConfig = {}; const { address, isUnixSocket } = getBindAddress(config); const socketPath = isUnixSocket ? address.replace('unix://', '') : null; startServer(config) .then((server) => { setupShutdown(server, socketPath); console.log('Sandbox server started'); }) .catch((err) => { console.error('Failed to start sandbox server', err); process.exit(1); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/harche/ProDisco'

If you have feedback or need assistance with the MCP directory API, please join our Discord server