Set Up Snowflake as an OpenAPI MCP Server
This tutorial connects Snowflake to Redpanda Agentic Data Plane so an agent can run SQL against your warehouse. You authenticate with a Snowflake programmatic access token and expose the Snowflake SQL API as a tool with the OpenAPI managed MCP server.
After reading this page, you will be able to:
-
Create a Snowflake programmatic access token and the authentication policy it requires
-
Configure an OpenAPI managed MCP server that runs SQL through the Snowflake SQL API
-
Verify Snowflake queries from the Inspector before you attach the server to an agent
Prerequisites
-
A Snowflake account and a Snowflake user whose role can read the data you want agents to query. See Account identifiers in the Snowflake documentation.
-
A Snowflake administrator. Two statements in this tutorial need the
ACCOUNTADMINrole, or a role that can create authentication policies and owns the user. See CREATE AUTHENTICATION POLICY in the Snowflake documentation for the privileges involved. -
Permission to create MCP servers and secrets in Agentic Data Plane. See Roles and Permissions Reference.
-
Familiarity with the OpenAPI managed MCP server. See OpenAPI Managed MCP Server.
What you’ll build
An agent calls one tool, executeStatement. The OpenAPI managed MCP server turns that call into a POST /api/v2/statements request to the Snowflake SQL API. It authenticates with a programmatic access token from the Agentic Data Plane secret store and returns the result rows to the agent.
The token belongs to one Snowflake user and one role, so every query runs with that role’s privileges. If you need each end user to query Snowflake as themselves, see Troubleshooting.
Gather your Snowflake account details
Run this statement in a Snowflake worksheet to collect the values you need throughout the tutorial:
SELECT
CURRENT_ORGANIZATION_NAME() AS org_name,
CURRENT_ACCOUNT_NAME() AS account_name,
CURRENT_USER() AS username,
CURRENT_ROLE() AS role,
CURRENT_WAREHOUSE() AS warehouse;
The rest of this tutorial refers to these values as placeholders:
| Placeholder | Example | Description |
|---|---|---|
|
|
Your Snowflake organization name. |
|
|
Your Snowflake account name. |
|
|
The Snowflake user that owns the token. |
|
|
A role granted to that user, with access to the data to expose. |
|
|
The warehouse that runs the queries. |
|
|
The database to query. |
|
|
The schema to query. |
|
|
A table in that schema, used in the verification queries. |
Your Snowflake base URL combines the organization and account names:
https://<org-name>-<account-name>.snowflakecomputing.com
For example, https://myorg-analytics.snowflakecomputing.com.
| If your account name contains underscores, Snowflake also accepts the URL with hyphens in their place. |
Allow token authentication for the user
By default, Snowflake lets a user create and use programmatic access tokens only when that user is subject to a network policy. An authentication policy with NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED removes that requirement while still enforcing any network policy that does apply to the user.
These statements need the ACCOUNTADMIN role, or a role that has the CREATE AUTHENTICATION POLICY privilege on the schema and owns the user.
-
Create the authentication policy in a schema of your choice:
USE ROLE ACCOUNTADMIN; CREATE OR REPLACE AUTHENTICATION POLICY <database>.<schema>.openapi_pat_auth_policy PAT_POLICY = ( NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED ); -
Assign the policy to the user:
ALTER USER <username> SET AUTHENTICATION POLICY <database>.<schema>.openapi_pat_auth_policy; -
(Optional) Grant the role access to the data, if it doesn’t have it already:
GRANT USAGE ON DATABASE <database> TO ROLE <role>; GRANT USAGE ON SCHEMA <database>.<schema> TO ROLE <role>; GRANT SELECT ON ALL TABLES IN SCHEMA <database>.<schema> TO ROLE <role>; GRANT SELECT ON ALL VIEWS IN SCHEMA <database>.<schema> TO ROLE <role>; GRANT USAGE ON WAREHOUSE <warehouse> TO ROLE <role>;
| If someone else administers Snowflake, send them the first two statements with your values filled in. They are the only required steps in this tutorial that need administrator privileges. |
Create a programmatic access token
Snowflake users can create tokens for themselves without extra privileges. Run this statement as <username>:
ALTER USER <username> ADD PROGRAMMATIC ACCESS TOKEN openapi_tool_token
ROLE_RESTRICTION = '<role>'
DAYS_TO_EXPIRY = 30
COMMENT = 'Agentic Data Plane OpenAPI MCP server';
-
ROLE_RESTRICTIONlimits the token to one role. Snowflake requires it for service users and recommends it for everyone else. -
DAYS_TO_EXPIRYdefaults to 15 days. The maximum is 365 days unless an authentication policy sets a lower limit.
The output has two columns. token_name is the name you gave the token. token_secret is the token itself.
| The token secret appears only in the output of this statement. No other SQL command returns it. Copy it now. You store it in the Agentic Data Plane secret store in the next section. |
If you lose the secret, rotate the token to get a new one:
ALTER USER <username> ROTATE PROGRAMMATIC ACCESS TOKEN openapi_tool_token;
To confirm the token exists and see when it expires, list the current user’s tokens:
SHOW USER PROGRAMMATIC ACCESS TOKENS;
The expires_at and status columns tell you whether the token is still usable.
Describe the SQL API in an OpenAPI spec
Save the following spec as snowflake-sql-api.yaml. Replace the placeholder in the servers block and the default values under StatementRequest with your account details.
The spec declares three operations. You filter the MCP server down to executeStatement in the next section, so the agent never calls the status or cancel endpoints with an empty statement handle. Keep the other two operations in the spec if you plan to support long-running queries later.
openapi: "3.0.3"
info:
title: Snowflake SQL API
description: >
Execute SQL against Snowflake with the SQL API.
version: "1.0.0"
servers:
- url: https://<org-name>-<account-name>.snowflakecomputing.com
description: Snowflake account
paths:
/api/v2/statements:
post:
operationId: executeStatement
tags:
- sql
summary: Submit a SQL statement for execution
description: >
Executes a SQL statement against Snowflake and returns the result set.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/StatementRequest"
parameters:
- name: async
in: query
required: false
schema:
type: boolean
default: false
description: Set to true to execute asynchronously.
- name: nullable
in: query
required: false
schema:
type: boolean
default: true
description: Set to false to return SQL NULL as the string "null".
- name: requestId
in: query
required: false
schema:
type: string
format: uuid
description: Optional idempotency key (UUID).
responses:
"200":
description: Statement executed successfully. Result set returned.
content:
application/json:
schema:
$ref: "#/components/schemas/ResultSet"
"202":
description: Execution in progress. Use the statementHandle to poll.
content:
application/json:
schema:
$ref: "#/components/schemas/QueryStatus"
"408":
description: Statement execution timed out.
"422":
description: SQL compilation or execution error.
content:
application/json:
schema:
$ref: "#/components/schemas/QueryFailure"
/api/v2/statements/{statementHandle}:
get:
operationId: getStatementStatus
tags:
- sql
summary: Check status or fetch results of a previously submitted statement
parameters:
- name: statementHandle
in: path
required: true
schema:
type: string
description: Handle returned by the POST call.
- name: partition
in: query
required: false
schema:
type: integer
default: 0
description: Partition number for large result sets.
- name: requestId
in: query
required: false
schema:
type: string
format: uuid
responses:
"200":
description: Statement completed. Result set returned.
content:
application/json:
schema:
$ref: "#/components/schemas/ResultSet"
"202":
description: Execution still in progress.
content:
application/json:
schema:
$ref: "#/components/schemas/QueryStatus"
/api/v2/statements/{statementHandle}/cancel:
post:
operationId: cancelStatement
tags:
- sql
summary: Cancel a running statement
parameters:
- name: statementHandle
in: path
required: true
schema:
type: string
description: Handle of the statement to cancel.
- name: requestId
in: query
required: false
schema:
type: string
format: uuid
responses:
"200":
description: Statement cancelled successfully.
content:
application/json:
schema:
type: object
properties:
code:
type: string
message:
type: string
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: >
A Snowflake programmatic access token, sent as a bearer token.
schemas:
StatementRequest:
type: object
required:
- statement
properties:
statement:
type: string
description: The SQL statement to execute.
warehouse:
type: string
description: Warehouse to use.
default: "<warehouse>"
database:
type: string
description: Database context.
default: "<database>"
schema:
type: string
description: Schema context.
default: "<schema>"
role:
type: string
description: Role for the session.
default: "<role>"
timeout:
type: integer
description: Statement timeout in seconds.
default: 60
parameters:
type: object
description: Optional session parameters.
properties:
TIMEZONE:
type: string
default: "UTC"
bindings:
type: object
description: Optional bind variables for parameterized queries.
additionalProperties:
type: object
properties:
type:
type: string
value:
type: string
ResultSet:
type: object
properties:
code:
type: string
message:
type: string
statementHandle:
type: string
statementStatusUrl:
type: string
sqlState:
type: string
createdOn:
type: integer
format: int64
statementHandles:
type: array
items:
type: string
description: Present when multiple statements were submitted.
resultSetMetaData:
type: object
properties:
numRows:
type: integer
format:
type: string
rowType:
type: array
items:
type: object
properties:
name:
type: string
type:
type: string
nullable:
type: boolean
precision:
type: integer
scale:
type: integer
length:
type: integer
partitionInfo:
type: array
items:
type: object
properties:
rowCount:
type: integer
uncompressedSize:
type: integer
compressedSize:
type: integer
data:
type: array
items:
type: array
items:
type: string
nullable: true
description: Row data as arrays of string values.
QueryStatus:
type: object
properties:
code:
type: string
message:
type: string
statementHandle:
type: string
statementStatusUrl:
type: string
QueryFailure:
type: object
properties:
code:
type: string
message:
type: string
sqlState:
type: string
statementHandle:
type: string
security:
- bearerAuth: []
The default values in StatementRequest appear in the generated tool’s input schema so that agents can pick them up, but the MCP server sends only the fields the caller supplies. When a request omits warehouse, database, schema, or role, Snowflake falls back to the user’s DEFAULT_WAREHOUSE, DEFAULT_NAMESPACE, and DEFAULT_ROLE. Set those defaults on <username> in Snowflake as well, so a bare statement still runs.
|
Create the MCP server
-
Open MCP Servers in the sidebar and click Add MCP server.
-
Select OpenAPI from the marketplace picker.
-
Enter a name, such as
snowflake-sql, and an optional description. -
In the OpenAPI configuration form, enter the following values:
Field Value Notes SpecLeave empty.
You paste the spec inline instead of hosting it.
Spec ContentPaste the contents of
snowflake-sql-api.yaml, or load the file.The inline limit is 3 MiB. This spec is far smaller.
Base URLLeave empty.
The
serversentry in the spec already names your account.Filter>Include OperationsexecuteStatementExposes only the execute endpoint. Without this filter, the agent also sees
getStatementStatusandcancelStatementand can call them with an empty statement handle.Max Schema DepthLeave the default of 5.
Extract HeadersLeave empty.
Snowflake returns paging information in the response body, under
partitionInfo, not in response headers. -
For
Auth Method, selectBearer. ForKey Secret Ref, select or create a secret that holds thetoken_secretvalue from Snowflake, for exampleSNOWFLAKE_PAT. Secret names useUPPER_SNAKE_CASE.The server sends the secret as
Authorization: Bearer <token>on every request. Snowflake also accepts an optionalX-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKENheader, which Snowflake documents as optional for programmatic access tokens, so this tutorial does not send it. -
Click Create server.
Verify your work
Test the tool in the Inspector before you attach the server to an agent.
-
Open the server’s Inspector tab. The Tools panel lists one tool,
executeStatement. If it also listsgetStatementStatusandcancelStatement, the operation filter is not applied. -
Select
executeStatement. In thebodygroup, setstatementto a query that needs no table access:SELECT CURRENT_USER(), CURRENT_ROLE(), CURRENT_WAREHOUSE(); -
Run the tool. A successful call returns a result with
status_code200 and abodywhosedataarray holds one row with your user, role, and warehouse. TheresultSetMetaData.rowTypearray names the columns. -
Run a second query against your data, such as
SELECT COUNT(*) FROM <database>.<schema>.<table>;, to confirm the role’s grants.
After the Inspector calls succeed, attach the server to an agent (see Create an Agent) and try prompts that increase in complexity:
-
"What tables are available in the
<schema>schema?" -
"How many rows does
<table>have?" -
"Show me the ten most recent rows in
<table>, ordered by date."
See Test an MCP Server’s Tools with the Inspector for general Inspector usage.
Clean up
When you finish the tutorial, or need to revoke access, remove the token and the authentication policy.
-
As
<username>, remove the token:ALTER USER <username> REMOVE PROGRAMMATIC ACCESS TOKEN openapi_tool_token; -
As
ACCOUNTADMIN, detach the authentication policy from the user, then drop it. Snowflake refuses to drop a policy that is still assigned to a user or account.ALTER USER <username> UNSET AUTHENTICATION POLICY; DROP AUTHENTICATION POLICY IF EXISTS <database>.<schema>.openapi_pat_auth_policy; -
In Agentic Data Plane, delete the MCP server and the
SNOWFLAKE_PATsecret.
Troubleshooting
| Symptom | What to check |
|---|---|
Creating the token, or the first tool call, fails with an error about a network policy being required |
The authentication policy is not assigned to the user. Repeat Allow token authentication for the user, and confirm the |
The Inspector lists three tools, or a call fails because the statement handle is empty |
The agent called |
Snowflake returns an authentication error for a token that used to work |
The token expired, or someone rotated or removed it. Run |
The tool returns an error with |
Snowflake could not compile or run the statement, or the role cannot access the referenced objects. The error carries Snowflake’s |
The result is a |
The statement ran longer than 45 seconds, so Snowflake returned |
You want each end user to query Snowflake as themselves |
Snowflake’s OAuth for custom clients documents the authorization code and refresh token grants, not the client credentials grant, so |