snowflake-analytics-mcp-server 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1 -1
- package/dist/snowflake.d.ts +1 -0
- package/dist/snowflake.d.ts.map +1 -1
- package/dist/snowflake.js +53 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ These are additive generative-AI tools (token-billed, higher latency than the SQ
|
|
|
103
103
|
| `cortex_summarize` | `text` | Concise summary of a text |
|
|
104
104
|
| `cortex_classify` | `text`, `categories[]` | Classify text into your categories via AI_CLASSIFY |
|
|
105
105
|
| `cortex_translate` | `text`, `to_language`, `from_language?` | Translate text (source auto-detected when omitted) |
|
|
106
|
-
|
|
106
|
+
| `SNOWFLAKE_PARTNER_CSID` | recommended | AnalyticsModel_MCP | Partner Connection String Identifier registered on SPN; sent as the Snowflake application tag for Connected-Application telemetry |
|
|
107
107
|
`cortex_ask` generates SQL and always restricts it to read-only statements, regardless of `SNOWFLAKE_READ_ONLY`.
|
|
108
108
|
|
|
109
109
|
**Cortex prerequisites:** the account must be in a region that supports Cortex, and the connecting role needs the `SNOWFLAKE.CORTEX_USER` database role plus the `USE AI FUNCTIONS` privilege. If a Cortex call fails on region or privileges, the tool returns a hint explaining what to grant.
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { registerTools } from "./tools/index.js";
|
|
|
12
12
|
async function main() {
|
|
13
13
|
const server = new McpServer({
|
|
14
14
|
name: "snowflake-analytics-mcp-server",
|
|
15
|
-
version: "0.2.
|
|
15
|
+
version: "0.2.1",
|
|
16
16
|
title: "Snowflake Analytics MCP Server",
|
|
17
17
|
});
|
|
18
18
|
registerTools(server);
|
package/dist/snowflake.d.ts
CHANGED
package/dist/snowflake.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snowflake.d.ts","sourceRoot":"","sources":["../src/snowflake.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"snowflake.d.ts","sourceRoot":"","sources":["../src/snowflake.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAyDD,wBAAgB,UAAU,IAAI,eAAe,CAmD5C;AAKD,wBAAgB,SAAS,IAAI,eAAe,CAG3C;AAiCD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,OAAO,EAAO,GACpB,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CASR;AAMD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAchE;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAWrF;AAKD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
package/dist/snowflake.js
CHANGED
|
@@ -28,20 +28,65 @@ function req(name) {
|
|
|
28
28
|
}
|
|
29
29
|
return v.trim();
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate the partner CSID before it's used as the `application` connection
|
|
33
|
+
* option. The snowflake-sdk validates this field itself (first character must
|
|
34
|
+
* be a LETTER, followed by letters/digits/`._-`, with a length cap), so we
|
|
35
|
+
* mirror that here to fail loudly with a clear message rather than a cryptic
|
|
36
|
+
* driver error at connect time.
|
|
37
|
+
*
|
|
38
|
+
* NOTE: confirm the exact bounds against your INSTALLED driver version — they
|
|
39
|
+
* can shift between releases:
|
|
40
|
+
* grep -rn "application" node_modules/snowflake-sdk/lib/connection/connection_config.js
|
|
41
|
+
* Align this regex to whatever that shows, and also to section 4 of the
|
|
42
|
+
* Snowflake Native Connector Best Practice Guide (the CSID naming convention).
|
|
43
|
+
*/
|
|
44
|
+
function validateCsid(csid) {
|
|
45
|
+
if (!/^[A-Za-z][A-Za-z0-9._-]{0,49}$/.test(csid)) {
|
|
46
|
+
throw new Error(`Invalid SNOWFLAKE_PARTNER_CSID "${csid}". Must start with a letter, then ` +
|
|
47
|
+
`letters, digits, and . _ - (max 50 chars).`);
|
|
48
|
+
}
|
|
49
|
+
return csid;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Normalize a private key into valid PEM regardless of how it survived transport.
|
|
53
|
+
* Handles: proper multi-line PEM, PEM with literal `\n`, PEM flattened onto one
|
|
54
|
+
* line (newlines stripped by a single-line form field), and a bare base64 body
|
|
55
|
+
* with no header/footer at all. Preserves the header type (e.g. ENCRYPTED).
|
|
56
|
+
*/
|
|
57
|
+
function normalizePrivateKey(raw) {
|
|
58
|
+
let k = raw.trim().replace(/\\n/g, "\n");
|
|
59
|
+
const bodyMatch = k.match(/-----BEGIN ([A-Z0-9 ]*PRIVATE KEY)-----([\s\S]*?)-----END [A-Z0-9 ]*PRIVATE KEY-----/);
|
|
60
|
+
let header = "PRIVATE KEY";
|
|
61
|
+
let body;
|
|
62
|
+
if (bodyMatch) {
|
|
63
|
+
header = bodyMatch[1].trim();
|
|
64
|
+
body = bodyMatch[2].replace(/\s+/g, "");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// No header present — treat the whole value as the base64 body.
|
|
68
|
+
body = k.replace(/\s+/g, "");
|
|
69
|
+
}
|
|
70
|
+
const wrapped = body.match(/.{1,64}/g)?.join("\n") ?? body;
|
|
71
|
+
return `-----BEGIN ${header}-----\n${wrapped}\n-----END ${header}-----\n`;
|
|
72
|
+
}
|
|
31
73
|
export function loadConfig() {
|
|
32
|
-
const
|
|
74
|
+
const hasKey = !!(process.env.SNOWFLAKE_PRIVATE_KEY || process.env.SNOWFLAKE_PRIVATE_KEY_PATH);
|
|
75
|
+
// Auto-select key-pair auth when a private key is provided, so callers don't
|
|
76
|
+
// have to set SNOWFLAKE_AUTHENTICATOR explicitly. An explicit value still wins.
|
|
77
|
+
const authenticator = (process.env.SNOWFLAKE_AUTHENTICATOR || (hasKey ? "SNOWFLAKE_JWT" : "SNOWFLAKE"))
|
|
33
78
|
.trim()
|
|
34
79
|
.toUpperCase();
|
|
35
80
|
let privateKey;
|
|
36
81
|
if (authenticator === "SNOWFLAKE_JWT") {
|
|
37
82
|
if (process.env.SNOWFLAKE_PRIVATE_KEY) {
|
|
38
|
-
privateKey = process.env.SNOWFLAKE_PRIVATE_KEY;
|
|
83
|
+
privateKey = normalizePrivateKey(process.env.SNOWFLAKE_PRIVATE_KEY);
|
|
39
84
|
}
|
|
40
85
|
else if (process.env.SNOWFLAKE_PRIVATE_KEY_PATH) {
|
|
41
|
-
privateKey = readFileSync(process.env.SNOWFLAKE_PRIVATE_KEY_PATH.trim(), "utf8");
|
|
86
|
+
privateKey = normalizePrivateKey(readFileSync(process.env.SNOWFLAKE_PRIVATE_KEY_PATH.trim(), "utf8"));
|
|
42
87
|
}
|
|
43
88
|
else {
|
|
44
|
-
throw new Error("
|
|
89
|
+
throw new Error("Key-pair auth requires SNOWFLAKE_PRIVATE_KEY or SNOWFLAKE_PRIVATE_KEY_PATH");
|
|
45
90
|
}
|
|
46
91
|
}
|
|
47
92
|
return {
|
|
@@ -63,6 +108,9 @@ export function loadConfig() {
|
|
|
63
108
|
// Cortex AI tools are on by default; set SNOWFLAKE_CORTEX_ENABLED=false to hide them.
|
|
64
109
|
cortexEnabled: (process.env.SNOWFLAKE_CORTEX_ENABLED || "true").toLowerCase() !== "false",
|
|
65
110
|
cortexModel: (process.env.SNOWFLAKE_CORTEX_MODEL || "llama3.1-8b").trim(),
|
|
111
|
+
// Partner Connection String Identifier registered on SPN. Override via
|
|
112
|
+
// SNOWFLAKE_PARTNER_CSID if it ever changes.
|
|
113
|
+
csid: validateCsid((process.env.SNOWFLAKE_PARTNER_CSID || "Analytics-model").trim()),
|
|
66
114
|
};
|
|
67
115
|
}
|
|
68
116
|
let cfg = null;
|
|
@@ -78,7 +126,7 @@ function buildConnection(c) {
|
|
|
78
126
|
username: c.username,
|
|
79
127
|
authenticator: c.authenticator,
|
|
80
128
|
clientSessionKeepAlive: true,
|
|
81
|
-
application:
|
|
129
|
+
application: c.csid,
|
|
82
130
|
};
|
|
83
131
|
if (c.password)
|
|
84
132
|
opts.password = c.password;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snowflake-analytics-mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Snowflake MCP server for the Analytics Model platform. Table discovery, SQL query, and Cortex AI tools over stdio, runnable via npx.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|