> ## Documentation Index
> Fetch the complete documentation index at: https://www.truefoundry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up Snowflake MCP Server

> Create a Snowflake managed MCP server with Cortex AI and SQL tools and connect it to the TrueFoundry AI Gateway with OAuth.

Snowflake ships a [Snowflake-managed MCP server](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-mcp) - an MCP endpoint hosted inside a Snowflake account that exposes [Cortex Search](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-search/cortex-search-overview), [Cortex Analyst](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst), SQL execution, [Cortex Agents](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents), and custom (UDF/procedure) tools to MCP clients. You create the MCP server object in Snowflake, create an OAuth security integration, grant access, and register the server's URL in the TrueFoundry AI Gateway.

## How authentication works

There are two independent auth layers - keep them distinct:

1. **Inbound** - how a user or client authenticates *to* the TrueFoundry Gateway. See [Authentication & Security](/docs/ai-gateway/mcp/mcp-gateway-auth-security).
2. **Outbound** - how the Gateway authenticates *to* Snowflake. This guide covers the outbound layer, using [Snowflake OAuth](https://docs.snowflake.com/en/user-guide/oauth-snowflake-overview) (authorization code flow).

<Note>
  **Single account vs. multiple accounts.** If all your users share **one** Snowflake account, one security integration plus one Gateway registration is enough - per-user OAuth + RBAC handles isolation, and setup is fully one-click for end users afterward. If each customer has **its own** Snowflake account, a single client ID/secret can't span accounts (the MCP URL is account-specific and there's no DCR), so each account's `ACCOUNTADMIN` runs the integration SQL once and you register a separate Snowflake MCP entry per account in TrueFoundry.
</Note>

## Prerequisites

* A TrueFoundry account with permission to add MCP servers, and your TrueFoundry control-plane base URL (used to build the OAuth callback). You'll also need an [MCP Server Group](/docs/ai-gateway/mcp/mcp-server-getting-started).
* A Snowflake account on a **paid/standard tier** - the SQL-execution tool is blocked on trial accounts.
* Snowflake `ACCOUNTADMIN` (or a role with the global `CREATE INTEGRATION` privilege) to create the OAuth security integration.
* A running or resumable **warehouse**.
* Your Snowflake **account URL**, `https://<account_url>` - use hyphens, not underscores. See [Connecting with a URL](https://docs.snowflake.com/en/user-guide/organizations-connect).

## Snowflake setup

Run all SQL in a [Snowsight SQL Worksheet](https://docs.snowflake.com/en/user-guide/ui-snowsight-worksheets-gs) (**Projects → Worksheets**), not the "Add data / Ingestion" screen.

<Steps>
  <Step title="Create the MCP server object">
    The MCP server is a Snowflake object you declare with [`CREATE MCP SERVER`](https://docs.snowflake.com/en/sql-reference/sql/create-mcp-server); its spec lists exactly which tools are reachable. The database, schema, and server name you choose here become part of the connector URL, so pick a writable, non-system schema (**not** `INFORMATION_SCHEMA`, which is read-only) and avoid `$` in the names.

    <Tabs>
      <Tab title="SQL execution (minimal)">
        The simplest setup - no Cortex prerequisites:

        ```sql theme={"dark"}
        CREATE OR REPLACE MCP SERVER <server_name>
          FROM SPECIFICATION $$
            tools:
              - title: "SQL Execution Tool"
                name: "sql_exec_tool"
                type: "SYSTEM_EXECUTE_SQL"
                description: "Execute SQL queries against the connected Snowflake database."
                config:
                  read_only: true          # safer for a shared gateway; set false to allow writes
                  query_timeout: 600
                  warehouse: "<warehouse>"
          $$;

        SHOW MCP SERVERS IN SCHEMA <database>.<schema>;
        DESCRIBE MCP SERVER <server_name>;
        ```
      </Tab>

      <Tab title="With Cortex tools">
        Add entries to the same `tools:` array:

        ```sql theme={"dark"}
        CREATE OR REPLACE MCP SERVER <server_name>
          FROM SPECIFICATION $$
            tools:
              - name: "product-search"
                type: "CORTEX_SEARCH_SERVICE_QUERY"
                identifier: "<data_db>.<data_schema>.<cortex_search_service>"
                title: "Product Search"
                description: "Cortex Search service over product docs"
              - name: "revenue-analyst"
                type: "CORTEX_ANALYST_MESSAGE"
                identifier: "<data_db>.<data_schema>.<semantic_view>"   # semantic VIEW, not a semantic model
                title: "Revenue Analyst"
                description: "Natural-language querying over a revenue semantic view"
          $$;
        ```
      </Tab>
    </Tabs>

    Supported tool types: `CORTEX_SEARCH_SERVICE_QUERY`, `CORTEX_ANALYST_MESSAGE` (semantic **views** only, not semantic models), `SYSTEM_EXECUTE_SQL`, `CORTEX_AGENT_RUN`, and `GENERIC` (UDF/procedure).

    <Note>
      Limits worth knowing: max **50 tools** per MCP server; tool responses are truncated at **250 KB**; and MCP server objects are **not replicated** in failover groups (recreate them on the secondary account). Verify a server with [`SHOW MCP SERVERS`](https://docs.snowflake.com/en/sql-reference/sql/show-mcp-servers) and [`DESCRIBE MCP SERVER`](https://docs.snowflake.com/en/sql-reference/sql/desc-mcp-server).
    </Note>
  </Step>

  <Step title="Create the OAuth security integration">
    Only `ACCOUNTADMIN` can run this. Use `OAUTH_CLIENT = CUSTOM`, and set the redirect URI to the callback shown on the TrueFoundry **Add MCP Server** form:

    ```txt theme={"dark"}
    https://<tfy-control-plane-base-url>/api/svc/v1/llm-gateway/mcp-servers/oauth2/callback
    ```

    ```sql theme={"dark"}
    USE ROLE ACCOUNTADMIN;

    CREATE SECURITY INTEGRATION <oauth_integration>
      TYPE = OAUTH
      ENABLED = TRUE
      OAUTH_CLIENT = CUSTOM
      OAUTH_CLIENT_TYPE = 'CONFIDENTIAL'          -- secret held server-side by the Gateway
      OAUTH_REDIRECT_URI = 'https://<tfy-control-plane-base-url>/api/svc/v1/llm-gateway/mcp-servers/oauth2/callback'
      OAUTH_ISSUE_REFRESH_TOKENS = TRUE
      OAUTH_REFRESH_TOKEN_VALIDITY = 86400        -- refresh-token lifetime in seconds
      OAUTH_ENFORCE_PKCE = TRUE;                  -- Snowflake recommends requiring PKCE (S256)
    ```

    <Warning>
      Do **not** add `BLOCKED_ROLES_LIST = ('ACCOUNTADMIN','SECURITYADMIN', ...)` to this statement. The account parameter `OAUTH_ADD_PRIVILEGED_ROLES_TO_BLOCKED_LIST` (on by default) already blocks `ACCOUNTADMIN`, `ORGADMIN`, `GLOBALORGADMIN`, and `SECURITYADMIN`; listing them explicitly throws an error. Only list *custom* roles you want to block.
    </Warning>

    Retrieve the client ID and secret with [`SYSTEM$SHOW_OAUTH_CLIENT_SECRETS`](https://docs.snowflake.com/en/sql-reference/functions/system_show_oauth_client_secrets) (the integration name must be **uppercase**):

    ```sql theme={"dark"}
    SELECT SYSTEM$SHOW_OAUTH_CLIENT_SECRETS('<OAUTH_INTEGRATION_UPPERCASE>');
    -- Endpoints are also visible via: DESC SECURITY INTEGRATION <oauth_integration>;
    ```

    <Tip>
      See [Configure Snowflake OAuth for custom clients](https://docs.snowflake.com/en/user-guide/oauth-custom) for the full parameter reference and [Managing user consent for OAuth](https://docs.snowflake.com/en/user-guide/oauth-consent).
    </Tip>
  </Step>

  <Step title="Create a non-admin role, grant access, set user defaults">
    The authenticating user must land on a **non-privileged** default role (admin roles are blocked for OAuth), with a default warehouse set, and needs `USAGE` on the MCP server plus per-tool privileges - access to the server alone does **not** grant access to the tools.

    ```sql theme={"dark"}
    CREATE ROLE IF NOT EXISTS <session_role>;
    GRANT ROLE <session_role> TO USER <username>;

    GRANT USAGE ON WAREHOUSE  <warehouse>            TO ROLE <session_role>;
    GRANT USAGE ON DATABASE   <database>             TO ROLE <session_role>;
    GRANT USAGE ON SCHEMA     <database>.<schema>    TO ROLE <session_role>;
    GRANT USAGE ON MCP SERVER <server_name>          TO ROLE <session_role>;   -- connect + discover tools

    -- Per-tool / per-data grants (uncomment the ones your tools need):
    -- GRANT USAGE  ON CORTEX SEARCH SERVICE <data_db>.<data_schema>.<service> TO ROLE <session_role>;
    -- GRANT SELECT ON SEMANTIC VIEW         <data_db>.<data_schema>.<view>    TO ROLE <session_role>;

    -- Required for the OAuth session to initialize:
    ALTER USER <username> SET DEFAULT_ROLE = '<session_role>' DEFAULT_WAREHOUSE = '<warehouse>';
    ```
  </Step>

  <Step title="Allow Gateway egress (network policy / PrivateLink)">
    If the Snowflake account has [network policies](https://docs.snowflake.com/en/user-guide/network-policies) enabled, allowlist TrueFoundry's egress IPs. The Gateway connects server-to-server from TrueFoundry infrastructure, not from the end user's browser - so browser-based access working does **not** imply the Gateway can connect.

    <Note>
      If the account uses PrivateLink, register the **public** account URL in the Gateway and keep the token endpoint on the public URL - the server-to-server token call can't resolve a PrivateLink hostname.
    </Note>
  </Step>
</Steps>

## Compute the MCP server URL

The Snowflake managed MCP URL has this format:

```txt theme={"dark"}
https://<account_url>/api/v2/databases/<database>/schemas/<schema>/mcp-servers/<server_name>
```

The matching OAuth endpoints for the same account are:

```txt theme={"dark"}
Authorization: https://<account_url>/oauth/authorize
Token:         https://<account_url>/oauth/token-request
```

## Register in TrueFoundry

1. In your MCP Server Group, click **Add MCP Server** and select **Connect any Remote MCP Server**.
2. Enter the [MCP server URL](#compute-the-mcp-server-url) and select **OAuth2** as the authentication type, with grant type **Authorization Code**.
3. Fill in the OAuth2 fields:
   * **Authorization URL**: `https://<account_url>/oauth/authorize`
   * **Token URL**: `https://<account_url>/oauth/token-request`
   * **Client ID** / **Client Secret**: the values from `SYSTEM$SHOW_OAUTH_CLIENT_SECRETS`
   * **Code Challenge Methods Supported**: `S256`
   * **JWT Source**: Access Token
   * **Scopes**: `refresh_token`
4. Set access control (teams/users), then **Save**.
5. Open the server's **Tools** section (or **Add Tool/MCP Servers** in the Playground) and click **Connect Now**. After OAuth succeeds, the Snowflake tools appear and can be tried from the [Agent Playground](/docs/ai-gateway/mcp/mcp-server-getting-started).

<Tip>
  Store the Client ID and Client Secret in the [TrueFoundry secrets store](/docs/manage-secrets) and reference their FQN instead of inlining them.
</Tip>

<Note>
  **Scopes.** Snowflake OAuth has only two scope values, both optional: `refresh_token` and `session:role:<role>`. Keep `refresh_token` - without it, sessions expire roughly every 10 minutes and users are re-prompted. **Drop** `session:role:<role>`: the managed MCP server ignores it (it uses `DEFAULT_ROLE`) and it adds a case-sensitivity failure mode. Snowflake expects the `scope` value space-delimited.
</Note>

## Security notes

* Use `read_only: true` for SQL tools unless agents must write data.
* Grant access to underlying Cortex resources separately; `USAGE` on the MCP server alone is not enough.
* Each user operates under their own Snowflake RBAC permissions, resolved through their `DEFAULT_ROLE`.
* Rotate the OAuth client secret periodically and prefer storing it in the TrueFoundry secrets store.
