Agentic Data Plane
Preview

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 ACCOUNTADMIN role, 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

<org-name>

myorg

Your Snowflake organization name.

<account-name>

analytics

Your Snowflake account name.

<username>

agent_reader

The Snowflake user that owns the token.

<role>

analyst

A role granted to that user, with access to the data to expose.

<warehouse>

analytics_wh

The warehouse that runs the queries.

<database>

sales_db

The database to query.

<schema>

public

The schema to query.

<table>

orders

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
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.

  1. 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
      );
  2. Assign the policy to the user:

    ALTER USER <username> SET AUTHENTICATION POLICY <database>.<schema>.openapi_pat_auth_policy;
  3. (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_RESTRICTION limits the token to one role. Snowflake requires it for service users and recommends it for everyone else.

  • DAYS_TO_EXPIRY defaults 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

  1. Open MCP Servers in the sidebar and click Add MCP server.

  2. Select OpenAPI from the marketplace picker.

  3. Enter a name, such as snowflake-sql, and an optional description.

  4. In the OpenAPI configuration form, enter the following values:

    Field Value Notes

    Spec

    Leave empty.

    You paste the spec inline instead of hosting it.

    Spec Content

    Paste the contents of snowflake-sql-api.yaml, or load the file.

    The inline limit is 3 MiB. This spec is far smaller.

    Base URL

    Leave empty.

    The servers entry in the spec already names your account.

    Filter > Include Operations

    executeStatement

    Exposes only the execute endpoint. Without this filter, the agent also sees getStatementStatus and cancelStatement and can call them with an empty statement handle.

    Max Schema Depth

    Leave the default of 5.

    Extract Headers

    Leave empty.

    Snowflake returns paging information in the response body, under partitionInfo, not in response headers.

  5. For Auth Method, select Bearer. For Key Secret Ref, select or create a secret that holds the token_secret value from Snowflake, for example SNOWFLAKE_PAT. Secret names use UPPER_SNAKE_CASE.

    The server sends the secret as Authorization: Bearer <token> on every request. Snowflake also accepts an optional X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN header, which Snowflake documents as optional for programmatic access tokens, so this tutorial does not send it.

  6. Click Create server.

Verify your work

Test the tool in the Inspector before you attach the server to an agent.

  1. Open the server’s Inspector tab. The Tools panel lists one tool, executeStatement. If it also lists getStatementStatus and cancelStatement, the operation filter is not applied.

  2. Select executeStatement. In the body group, set statement to a query that needs no table access:

    SELECT CURRENT_USER(), CURRENT_ROLE(), CURRENT_WAREHOUSE();
  3. Run the tool. A successful call returns a result with status_code 200 and a body whose data array holds one row with your user, role, and warehouse. The resultSetMetaData.rowType array names the columns.

  4. 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.

  1. As <username>, remove the token:

    ALTER USER <username> REMOVE PROGRAMMATIC ACCESS TOKEN openapi_tool_token;
  2. 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;
  3. In Agentic Data Plane, delete the MCP server and the SNOWFLAKE_PAT secret.

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 ALTER USER …​ SET AUTHENTICATION POLICY statement ran against <username>.

The Inspector lists three tools, or a call fails because the statement handle is empty

The agent called getStatementStatus instead of executeStatement. Set Include Operations to executeStatement so the server exposes only the execute endpoint.

Snowflake returns an authentication error for a token that used to work

The token expired, or someone rotated or removed it. Run SHOW USER PROGRAMMATIC ACCESS TOKENS; and check expires_at and status. Create a new token and update the SNOWFLAKE_PAT secret. If Snowflake rejects a token that is valid and unexpired, add X-Snowflake-Authorization-Token-Type as a required header parameter on executeStatement, with PROGRAMMATIC_ACCESS_TOKEN as its only allowed value, so the agent sends it on every call.

The tool returns an error with status_code 422

Snowflake could not compile or run the statement, or the role cannot access the referenced objects. The error carries Snowflake’s message and sqlState. Run the statement in a Snowflake worksheet as <role> first, and confirm the grants from Allow token authentication for the user.

The result is a QueryStatus object with a statementHandle instead of rows

The statement ran longer than 45 seconds, so Snowflake returned 202 and kept running it. To support long-running queries, add getStatementStatus to Include Operations and instruct the agent to poll that tool with the returned handle.

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 Service-account OAuth does not work with Snowflake’s built-in OAuth. Configure a Snowflake OAuth provider and select User OAuth on the MCP server instead. See Configure User-Delegated OAuth.