
Security News
RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.
@skilljack/mcp-server-manager
Advanced tools
MCP server lifecycle manager with health checks, auto-restart, and graceful shutdown
A loosely coupled lifecycle manager for MCP (Model Context Protocol) servers with health checks, auto-restart, and graceful shutdown capabilities.
npm install @skilljack/mcp-server-manager
Peer Dependency: Requires @modelcontextprotocol/sdk ^1.0.0
import {
ServerManager,
createServerConfig,
createStdioConfig,
} from '@skilljack/mcp-server-manager';
// Create a manager with server configurations
const manager = ServerManager.fromConfig({
servers: [
createServerConfig('my-server', createStdioConfig('npx', ['-y', '@modelcontextprotocol/server-everything']))
]
});
// Listen for events
manager.on('server:connected', (event) => {
console.log(`Server ${event.serverName} connected`);
});
// Start all servers
await manager.start();
// Use the MCP client
const client = manager.getClient('my-server');
if (client) {
const tools = await client.listTools();
}
// Graceful shutdown
await manager.shutdown();
interface ServerConfig {
name: string; // Unique server identifier
connection: ServerConnectionConfig; // Stdio or HTTP config
lifecycle?: LifecycleConfig; // Optional lifecycle overrides
autoStart?: boolean; // Auto-start with manager (default: true)
}
Spawns a child process and communicates via stdin/stdout pipes.
import { createStdioConfig } from '@skilljack/mcp-server-manager';
const config = createStdioConfig('npx', ['-y', '@modelcontextprotocol/server-everything'], {
env: { DEBUG: 'true' }, // Optional environment variables
cwd: '/path/to/dir', // Optional working directory
});
Connects to an existing HTTP-based MCP server.
import { createHttpConfig } from '@skilljack/mcp-server-manager';
const config = createHttpConfig('http://localhost:3000', {
headers: { 'Authorization': 'Bearer token' }, // Optional headers
});
Override default lifecycle behavior per-server or globally:
interface LifecycleConfig {
healthCheckEnabled?: boolean; // Enable health checks (default: true)
healthCheckIntervalMs?: number; // Check interval (default: 30000)
healthCheckTimeoutMs?: number; // Check timeout (default: 5000)
unhealthyThreshold?: number; // Failures before unhealthy (default: 3)
autoRestartEnabled?: boolean; // Enable auto-restart (default: true)
maxRestartAttempts?: number; // Max restart tries (default: 5)
restartBackoffBaseMs?: number; // Backoff base delay (default: 1000)
restartBackoffMaxMs?: number; // Max backoff delay (default: 30000)
shutdownTimeoutMs?: number; // Graceful shutdown timeout (default: 10000)
}
const manager = ServerManager.fromConfig({
// Global defaults (optional)
defaults: {
healthCheckIntervalMs: 60000,
maxRestartAttempts: 3,
},
servers: [
{
name: 'primary',
connection: { type: 'stdio', command: 'node', args: ['server.js'] },
autoStart: true,
},
{
name: 'secondary',
connection: { type: 'http', url: 'http://localhost:8080' },
autoStart: false,
lifecycle: {
healthCheckEnabled: false, // Override for this server
},
},
],
});
disconnected ──start()──▶ connecting ──success──▶ connected
│ │
│ │ health check failures
▼ ▼
failed ◀────────────── unhealthy
▲ │
│ │ auto-restart
│ ▼
└──max attempts──── restarting
│
│ success
▼
connecting
Any state ──stop()──▶ stopped
| Status | Description |
|---|---|
disconnected | Initial state, not yet started |
connecting | Connection in progress |
connected | Successfully connected and healthy |
unhealthy | Health checks failing |
restarting | Auto-restart in progress |
failed | Max restart attempts exceeded |
stopped | Manually stopped |
| Event | Description | Key Properties |
|---|---|---|
server:connecting | Connection starting | serverName |
server:connected | Successfully connected | serverName, pid? |
server:connection-failed | Connection failed | serverName, error |
server:healthy | Health check passed | serverName, healthCheck |
server:unhealthy | Health checks failing | serverName, consecutiveFailures |
server:crashed | Process crashed | serverName, exitCode, signal, willRestart |
server:restarting | Restart initiated | serverName, attempt, maxAttempts, reason |
server:restart-succeeded | Restart successful | serverName, attempts, pid? |
server:restart-failed | All restart attempts failed | serverName, attempts, error |
server:status-changed | Any status change | serverName, previousStatus, newStatus |
server:stopped | Server stopped | serverName, graceful |
| Event | Description | Key Properties |
|---|---|---|
manager:ready | All auto-start servers started | serverCount |
manager:shutdown | Shutdown complete | graceful |
manager:state-snapshot | State snapshot emitted | servers (array of summaries) |
// Specific event
manager.on('server:connected', (event) => {
console.log(`${event.serverName} connected with PID ${event.pid}`);
});
// All lifecycle events
manager.on('*', (event) => {
console.log(`Event: ${event.type}`, event);
});
// All manager events
manager.on('manager:*', (event) => {
console.log(`Manager event: ${event.type}`);
});
// Typed event helpers
manager.onLifecycleEvent('server:unhealthy', (event) => {
console.log(`${event.serverName} unhealthy after ${event.consecutiveFailures} failures`);
});
manager.onManagerEvent('manager:ready', (event) => {
console.log(`Manager ready with ${event.serverCount} servers`);
});
// Create from config object
ServerManager.fromConfig(config: ManagerConfig, options?: ServerManagerOptions): ServerManager
// Create from config file
ServerManager.fromConfigFile(filePath: string, options?: ServerManagerOptions): Promise<ServerManager>
// Lifecycle
start(): Promise<void> // Start all auto-start servers
shutdown(): Promise<void> // Stop all servers gracefully
startServer(name: string): Promise<void>
stopServer(name: string): Promise<void>
restartServer(name: string): Promise<void>
// State
getServerStatus(name: string): ServerStatus | undefined
getServerState(name: string): ServerStateSummary | undefined
getAllServerStates(): ServerStateSummary[]
isServerConnected(name: string): boolean
areAllServersConnected(): boolean
// Clients
getClient(name: string): Client | null
getConnectedClients(): Map<string, Client>
// Management
getServerNames(): string[]
getServerCount(): number
addServer(config: ServerConfig): void
removeServer(name: string): Promise<void>
// Events
emitStateSnapshot(): void
interface ServerStateSummary {
name: string;
status: ServerStatus;
healthy: boolean;
timeInStatus: number; // milliseconds
pid?: number;
lastLatencyMs?: number; // last health check latency
restartAttempts: number;
error?: string;
}
import { ServerManager, ConsoleLoggerFactory } from '@skilljack/mcp-server-manager';
const manager = ServerManager.fromConfig(config, {
loggerFactory: new ConsoleLoggerFactory('debug'),
});
For advanced use cases, you can use the underlying components:
import {
ServerLifecycle,
ProcessManager,
HealthMonitor,
HttpConnection,
} from '@skilljack/mcp-server-manager';
// Create a single server lifecycle
const lifecycle = new ServerLifecycle(serverConfig, lifecycleDefaults);
lifecycle.on('*', (event) => console.log(event));
await lifecycle.start();
import { retry, withTimeout, calculateBackoff } from '@skilljack/mcp-server-manager';
// Retry an operation
const result = await retry(
async () => fetchData(),
{ maxAttempts: 3, backoff: { baseMs: 1000, maxMs: 10000 } }
);
// Add timeout to a promise
const data = await withTimeout(fetchData(), 5000);
ServerManager
│
├── ServerLifecycle (one per server)
│ ├── ProcessManager (stdio transport)
│ │ └── Child Process (stdin/stdout pipes)
│ │
│ ├── HttpConnection (http transport)
│ │
│ ├── MCP Client
│ │ └── Transport (StdioClientTransport or StreamableHTTPClientTransport)
│ │
│ └── HealthMonitor
│ └── Periodic ping checks
│
└── Event Emitter
└── Forwards all lifecycle events
MIT
FAQs
MCP server lifecycle manager with health checks, auto-restart, and graceful shutdown
We found that @skilljack/mcp-server-manager demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.

Security News
Federal audit finds NIST lacked a plan to clear the NVD backlog, wasted funds on duplicate work, and delayed use of CISA data.