MP-301c · Module 1
Connection Pooling & Resource Management
3 min read
Database-backed MCP tools that create a new connection per tool call will collapse under moderate load. Connection creation takes 50-200ms for PostgreSQL, 10-50ms for Redis, and involves TLS handshakes for cloud-hosted databases. Connection pooling amortizes this cost by maintaining a pool of pre-established connections and lending them to tool calls on demand. The tool handler borrows a connection, executes its query, and returns the connection to the pool — the next tool call gets the same connection without creation overhead.
Pool sizing is a tuning exercise. Too few connections and tool calls queue up waiting for a free connection. Too many and you overwhelm the database server with concurrent queries. The formula: start with pool_size = number_of_CPU_cores * 2 + 1 for the database server (not your MCP server), then measure. If pool wait time is consistently above 10ms, increase the pool. If the database CPU is consistently above 80%, decrease the pool. Monitor both metrics continuously — the optimal pool size changes with load patterns.
import pg from "pg";
// Singleton connection pool — shared across all tool handlers
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // Max connections in pool
idleTimeoutMillis: 30_000, // Close idle connections after 30s
connectionTimeoutMillis: 5_000, // Fail if can't connect in 5s
});
// Log pool events for monitoring
pool.on("error", (err) => {
console.error(JSON.stringify({ event: "pool_error", error: err.message }));
});
pool.on("connect", () => {
console.error(JSON.stringify({
event: "pool_connect",
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
}));
});
// Resource-safe query wrapper
export async function query<T>(sql: string, params?: unknown[]): Promise<T[]> {
const client = await pool.connect();
try {
const result = await client.query(sql, params);
return result.rows as T[];
} finally {
client.release(); // ALWAYS release back to pool
}
}
// Graceful shutdown
process.on("SIGTERM", async () => {
console.error(JSON.stringify({ event: "pool_shutdown" }));
await pool.end();
process.exit(0);
});
- Create a singleton pool Initialize the connection pool once at server startup. Export a query wrapper that borrows, uses, and releases connections. Never expose the pool directly to handlers.
- Set timeouts aggressively Connection timeout: 5s (fail fast if DB is down). Idle timeout: 30s (release unused connections). Query timeout: match your tool's latency budget.
- Monitor pool utilization Log total, idle, and waiting counts on every connection event. Alert when waiting > 0 for sustained periods — this means your pool is too small or your queries are too slow.