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 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.0",
15
+ version: "0.2.1",
16
16
  title: "Snowflake Analytics MCP Server",
17
17
  });
18
18
  registerTools(server);
@@ -15,6 +15,7 @@ export interface SnowflakeConfig {
15
15
  readOnly: boolean;
16
16
  cortexEnabled: boolean;
17
17
  cortexModel: string;
18
+ csid: string;
18
19
  }
19
20
  export declare function loadConfig(): SnowflakeConfig;
20
21
  export declare function getConfig(): SnowflakeConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"snowflake.d.ts","sourceRoot":"","sources":["../src/snowflake.ts"],"names":[],"mappings":"AAyBA,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;CACrB;AAUD,wBAAgB,UAAU,IAAI,eAAe,CA0C5C;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"}
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 authenticator = (process.env.SNOWFLAKE_AUTHENTICATOR || "SNOWFLAKE")
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("SNOWFLAKE_AUTHENTICATOR=SNOWFLAKE_JWT requires SNOWFLAKE_PRIVATE_KEY or SNOWFLAKE_PRIVATE_KEY_PATH");
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: "AnalyticsModel_MCP",
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.0",
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",