Building a Snowflake MCP Server
Query a Snowflake warehouse from an MCP tool, with a bounded row count and awareness of per-second compute billing.
Quick Answer / TL;DR
A Snowflake MCP server uses the official snowflake-sdk driver to run read-only SQL, with results capped after retrieval since Snowflake bills by warehouse compute time rather than bytes scanned like BigQuery.
Key Takeaways
- Snowflake bills by warehouse size times time running, not by query - a small warehouse left running idle costs the same as one running a query.
- The Node driver uses a callback-based execute() API, not promises natively - wrap it once in a helper rather than repeating the callback pattern per tool.
- Use SHOW and DESCRIBE commands for schema discovery instead of querying INFORMATION_SCHEMA directly - they're the idiomatic Snowflake approach and avoid a full metadata scan.
Why not just copy a reference implementation?
The official MCP servers repository is upfront about its purpose: "The servers in this repository are intended as reference implementations to demonstrate MCP features and SDK usage... not as production-ready solutions." That pattern, applied to Snowflake, means no write-statement rejection and no bound on how many rows come back - both are handled explicitly in the helper below rather than assumed.
A promise-wrapped query helper
snowflake-sdk's connection.execute() takes a completion callback rather than returning a promise, so the first thing worth writing is a thin wrapper that turns it into one the rest of the tool code can await normally.
import snowflake from "snowflake-sdk";
const connection = snowflake.createConnection({
account: process.env.SNOWFLAKE_ACCOUNT!,
username: process.env.SNOWFLAKE_USER!,
password: process.env.SNOWFLAKE_PASSWORD!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE ?? "COMPUTE_WH",
database: process.env.SNOWFLAKE_DATABASE,
});
function execute(sqlText: string, binds: unknown[] = []): Promise<Record<string, unknown>[]> {
return new Promise((resolve, reject) => {
connection.execute({
sqlText,
binds: binds as snowflake.Binds,
complete: (err, _stmt, rows) => (err ? reject(err) : resolve(rows ?? [])),
});
});
}
async function queryTool(sql: string) {
if (/\b(INSERT|UPDATE|DELETE|DROP|MERGE)\b/i.test(sql)) {
throw new Error("Only SELECT queries are permitted through this tool.");
}
const rows = await execute(sql);
return rows.slice(0, 200);
}Building a Snowflake MCP Server FAQs
Direct answers for developers, operators, and Indian teams evaluating MCP.