Skip to main content
Version: v1.7.5-11 🚧

Query Snowflake with an external IdP token

This guide shows you how to build a custom MCP (Model Context Protocol) tool that lets an agent query Snowflake as you, instead of through a shared account. The tool reads the access token that your external identity provider (IdP) issued when you signed in, and sends that token to Snowflake. Any provider that Keycloak can broker works the same way, including PingOne, PingFederate, and Azure AD.

Each query runs as the Snowflake user that your token maps to, so Snowflake role-based access control applies to every agent request, and Enterprise h2oGPTe stores no Snowflake credentials.

By the end, you'll have a tool with four sub-tools that an agent can call in a chat: show your current Snowflake user, list tables, describe a table, and run a single SELECT statement.

Overview​

When your Enterprise h2oGPTe deployment federates sign-in through an external IdP, Keycloak keeps a copy of the token the IdP issued, and Enterprise h2oGPTe passes it to any custom MCP tool that declares the H2OGPT_SESSION_EXTERNAL_IDP_TOKEN variable. The token takes the same path on every request:

  1. You sign in to Keycloak, which federates the sign-in to your external IdP.
  2. Keycloak stores the access token that the external IdP returns.
  3. When you ask the agent a question, Enterprise h2oGPTe retrieves that stored token from Keycloak and injects it into your tool as an environment variable.
  4. Your tool calls the Snowflake SQL API with the token.
  5. Snowflake returns only the rows your own role is authorized to read, and your tool passes that result back to you through Enterprise h2oGPTe.

Enterprise h2oGPTe caches the retrieved token in encrypted form for at most 5 minutes, and re-reads it from Keycloak as the cached copy nears expiry. A failed retrieval is never cached, so a Keycloak misconfiguration shows up on every request rather than only on the first. Keycloak can still hand back a token that is close to expiry, so handle an authentication failure from Snowflake instead of assuming the token is fresh.

This mechanism depends on your interactive sign-in session. It works in the chat interface and from the Python client when you create the client with a token_provider. A request authenticated with an API key alone carries no session, so no token is available and Enterprise h2oGPTe does not set the variable for your tool. If you intend to drive the agent from a script or a scheduled job, plan for the missing token before you start.

Select a Snowflake authentication method​

Enterprise h2oGPTe supports five ways to authenticate to Snowflake. Use the following table to confirm that this guide describes the method you want. Every method except the custom tool in this guide runs the Snowflake MCP server, which needs a config.yaml naming the Snowflake Cortex AI services to expose.

MethodQuery runs asRequiresUse when
Snowflake account credentialsOne shared Snowflake user, the same for everyoneSNOWFLAKE_ACCOUNT and SNOWFLAKE_USER in Agents > Authentication, plus config.yamlYou want the quickest setup and a single set of permissions is acceptable. See Snowflake MCP Server.
Built-in Snowflake Database toolThe Snowflake user that each person authorizesThe Snowflake OAuth connector, configured by an administrator and authorized once per person, SNOWFLAKE_ACCOUNT, plus config.yamlYou want per-user access and Snowflake is the identity provider. See Data connectors and OAuth.
Built-in Snowflake Database (SSO) toolThe Snowflake user that the IdP token maps toKeycloak Identity Broker, a Snowflake External OAuth security integration, SNOWFLAKE_ACCOUNT, plus config.yamlYou want the identity model in this guide without writing any code. Add it from the Library page. See Browsing and adding predefined tools.
Custom remote MCP tool running the Snowflake MCP serverThe Snowflake user that the IdP token maps toKeycloak Identity Broker, a Snowflake External OAuth security integration, SNOWFLAKE_ACCOUNT, plus config.yamlYou want the identity model in this guide without writing code, and you want to select which of the server's sub-tools the agent can call.
Custom tool with the external IdP tokenThe Snowflake user that the IdP token maps toKeycloak Identity Broker and a Snowflake External OAuth security integrationYou want the agent to have only the operations you write, rather than a subset of the server's 16, or errors in your own format. It can also screen the agent's SQL, though that is a safeguard against mistakes rather than a security boundary. This guide.

The three methods that use the external IdP token all run each query as the signed-in user's Snowflake identity and need the same identity setup, listed in Prerequisites. Confirm those items first, whichever of the three you use.

Start with the built-in Snowflake Database (SSO) tool. It needs no code and exposes all 16 sub-tools of the Snowflake MCP server. Select Sub-tools in Agents > Tools is unavailable for a predefined tool, so adding the same server as a custom remote MCP tool is what narrows the agent to a subset of those 16, still without code. Build the custom tool in this guide when you want the agent to have only the operations you write, screening of the SQL it sends, or errors in a specific shape. To add the built-in tool instead, follow Browsing and adding predefined tools, then confirm the items in Prerequisites and query Snowflake from a chat. Steps 1 through 4 of this guide do not apply to it.

To set up the remote tool, add the following configuration in Agents > Tools > + Custom Tool > Remote MCP Tools. It is the configuration from Snowflake MCP Server with the token settings in place of SNOWFLAKE_USER:

{
"snowflake": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/h2oai/snowflake-cortex-ai-mcp",
"mcp-server-snowflake",
"--service-config-file",
"config.yaml"
],
"env": {
"SNOWFLAKE_ACCOUNT": "os.environ/SNOWFLAKE_ACCOUNT",
"SNOWFLAKE_AUTHENTICATOR": "oauth",
"SNOWFLAKE_TOKEN": "os.environ/H2OGPT_SESSION_EXTERNAL_IDP_TOKEN"
}
}
}

A remote tool has no envs.json. The os.environ/H2OGPT_SESSION_EXTERNAL_IDP_TOKEN reference in the env block is the declaration, and it is what opts the tool in to receive the token.

Two of the values in that configuration come from outside the JSON:

  • config.yaml, named by the --service-config-file argument, is a file you attach to the tool. Upload it with the Replace Tool File (Optional) control in the same Remote MCP Tools dialog.
  • SNOWFLAKE_ACCOUNT, referenced as os.environ/SNOWFLAKE_ACCOUNT, is an agent key. Create it in Agents > Authentication, then assign it to the tool with the Assign Env Vars row action in Agents > Tools.

Leave H2OGPT_SESSION_EXTERNAL_IDP_TOKEN unassigned. That, not SNOWFLAKE_TOKEN, is the name Enterprise h2oGPTe takes from the os.environ/ reference and lists in Assign Env Vars, and it fills the value from the session on every request.

Prerequisites​

The external IdP token method depends on configuration in three systems. Before you build the tool, confirm each of the following items with the administrator who owns the system it belongs to. This guide tells you which settings each item depends on, not how to apply them, and links to the reference where one exists.

In Keycloak​

Enterprise h2oGPTe calls the Keycloak Identity Broker token endpoint to read the token that Keycloak already stored at sign-in. It does not exchange one token for another, so the Keycloak token exchange feature does not need to be turned on.

Confirm the following with your Keycloak administrator:

  • The external IdP is registered as an identity provider, and you know its alias. Keycloak fixes the alias when the entry is saved, so a later change means creating a new entry.
  • The client authentication method on the Keycloak entry matches the token endpoint authentication method on the external IdP application. A PingOne application set to Client Secret Post, for example, pairs with Client secret sent in the request body in Keycloak.
  • Keycloak reads the external IdP's OpenID discovery document, usually at <ISSUER>/.well-known/openid-configuration, and can reach that host on port 443. The same document supplies the issuer and signing key URL that the Snowflake security integration needs, so ask your identity provider administrator to record both values while registering the provider.
  • The external IdP issues a Snowflake role scope in addition to the usual OpenID scopes. Snowflake reads the session role from the token scope and rejects a token that carries none. Two settings must agree:
    • The scope is defined in the external IdP, on the same resource whose audience Snowflake trusts, and the application is allowed to request it. A provider such as PingOne issues only the scopes that meet both conditions.
    • The Keycloak identity provider entry requests that scope. Keycloak sends only the scopes listed in the entry's Scopes field, so the field lists the role scope your deployment uses, for example openid profile email session:role-any. A scope that the external IdP defines but Keycloak never requests does not reach the stored token.
  • You know which role scope your deployment uses. session:role-any applies each user's Snowflake default role and needs the least configuration. A session:role:<ROLE_NAME> scope, defined on the same resource for each role you want to pin, bounds privileges instead of inheriting whatever the default role holds.
  • The external IdP application allows the refresh token grant alongside the authorization code grant, so that Keycloak stores a refresh token with the access token. Without one, Keycloak can only hand back the access token it already holds, and an expired token needs a fresh sign-in rather than a retry.
  • You know the access token lifetime the external IdP issues, which is set per resource on a provider such as PingOne. A short lifetime makes it more likely that a token expires part-way through a session and a later query fails to authenticate.
  • The Keycloak identity provider entry for your external IdP has Store Tokens turned on, so that Keycloak keeps the token the external IdP issued. If Store Tokens is off, Keycloak rejects the request with 400 Bad Request and does not support this operation when Enterprise h2oGPTe requests the token.
  • The signed-in user holds the read-token role of the Keycloak broker client, and the client that Enterprise h2oGPTe uses has that role in its scope. Turning on Stored Tokens Readable grants the role only to users who link the identity provider entry for the first time after the change. Users who signed in through that entry before the change need an administrator to assign the role separately, for example through the realm default role. For more information, see Retrieving external IDP tokens.
  • A mapper adds the identity_provider claim to the access token. To produce that claim, add a User Session Note mapper that sets both the session note and the claim to identity_provider, and turn on Add to access token. Enterprise h2oGPTe compares the claim to the configured provider alias with an exact, case-sensitive match. If the two differ in capitalization or surrounding whitespace, Enterprise h2oGPTe treats every user as a direct Keycloak user and skips the token without reporting an error.

In Enterprise h2oGPTe​

Confirm the following with your Enterprise h2oGPTe administrator:

  • An administrator has turned on the Keycloak Identity Broker feature and set the provider alias to match the Keycloak alias. For the Helm values, see Helm configuration.
  • You sign in through the external IdP rather than directly through Keycloak.
  • Your role holds the three permissions that building the custom tool needs.

The following table lists those permissions, the name each one carries on the Roles page, and what you cannot do without it.

Permission on the Roles pageIdentifierWithout itHeld by default
Show Agent Tools settingsh2ogpte/display/configure_agentsThe Agents page does not open, so you cannot reach step 2.admin, default, guest
Add and edit custom agent toolsh2ogpte/agent/manage_custom_toolThe + Custom Tool control does not appear in Agents > Tools, so you cannot upload the tool in step 2.admin, default, user
Show LLM and Agentic settingsh2ogpte/display/developer_settingsThe Customize control and the Configuration tab do not appear in a chat, so you cannot turn the tool on in step 3.admin, default, guest

Only the admin and default roles hold all three. The user role can add a custom tool but holds neither display permission, so it cannot open the Agents page in step 2 or the Configuration tab in step 3. The guest role can open both but cannot add a tool. Removing a custom tool later needs Delete custom agent tools (h2ogpte/agent/delete_custom_tool). For the full matrix, see Roles and permissions reference.

In Snowflake​

Confirm the following with your Snowflake administrator. A mismatch in any one of the security integration properties causes Snowflake to reject the token:

  • A security integration of type EXTERNAL_OAUTH trusts your external IdP, with ENABLED = TRUE.
  • EXTERNAL_OAUTH_ISSUER and EXTERNAL_OAUTH_JWS_KEYS_URL match the issuer and jwks_uri values in the external IdP's OpenID discovery document. The EXTERNAL_OAUTH_JWS_KEYS_URL property takes the JSON Web Key Set (JWKS) URL.
  • EXTERNAL_OAUTH_TYPE names the provider: PING_FEDERATE for PingFederate, AZURE for Azure AD, OKTA for Okta, and CUSTOM for PingOne and any other provider without a dedicated value.
  • EXTERNAL_OAUTH_AUDIENCE_LIST matches the aud claim of the tokens the external IdP issues. The audience value is not automatic. In PingOne it is the Audience field of a custom resource that your identity provider administrator creates, and it defaults to the resource name. Agree on one neutral value such as snowflake, and use it in both PingOne and EXTERNAL_OAUTH_AUDIENCE_LIST. PingOne does not accept an audience value that contains pingone or pingidentity.
  • EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM names a claim the token actually carries. Some external IdPs omit email from access tokens, in which case sub is the usual choice.
  • EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE names the claim that carries the scopes when your external IdP does not use scp. The default is scp, but PingOne emits scope. Snowflake accepts this property only when EXTERNAL_OAUTH_TYPE is CUSTOM, and its only valid values are scp and scope.
  • EXTERNAL_OAUTH_ANY_ROLE_MODE is ENABLE if you use the session:role-any scope, or ENABLE_FOR_PRIVILEGE if your administrator prefers to grant role switching per user through the USE_ANY_ROLE privilege. The default, DISABLE, prevents the token from using a role it does not name. If you pin a role with session:role:<ROLE_NAME> instead, the default works and this property needs no change.
  • A Snowflake user exists whose EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE, such as LOGIN_NAME, equals the claim value in your token. That user holds a role with access to the database and schema you plan to query, and USAGE on a warehouse to run the queries.
  • The mapped role holds only USAGE and SELECT on the objects the agent may read, and no INSERT, UPDATE, DELETE, TRUNCATE, CREATE TABLE, or OWNERSHIP grants. A MERGE statement is authorized by INSERT, UPDATE, and DELETE, so there is no separate MERGE grant to look for. The absence of those write grants, not the checks inside the tool, is what prevents a write. Run SHOW GRANTS TO ROLE <ROLE_NAME> to confirm.

For a description of each security integration property, see the Snowflake External OAuth documentation.

Step 1: Create the tool files​

Create a directory for the tool, for example snowflake-idp-tool, and add the following three files to it:

snowflake-idp-tool/
├── server.py
├── envs.json
└── description.md
note

A local MCP tool ZIP has no dependency-installation step. Enterprise h2oGPTe extracts the directory as it is and ignores a requirements.txt inside the ZIP, so limit server.py to packages the agent runtime already provides. This example imports only httpx and the FastMCP server bundled in the mcp SDK. A tool whose imports are unavailable can still list its sub-tools and then fail at query time, so a successful Discover sub-tools is not proof that the imports resolve. Confirm the import works in your deployment with the first verification prompt in Step 4.

server.py​

This MCP server reads the injected token at call time and calls the Snowflake SQL API. Replace the account identifier, warehouse, database, and schema with your own values. For the account identifier, see Account identifiers in the Snowflake documentation. Ask your Snowflake administrator for the warehouse, database, and schema your role can use, which are the ones they confirmed in Prerequisites.

"""MCP server that queries Snowflake with the user's external IdP token.

The token is read at call time. This server does not return the token to the
agent and does not write it to a log.
"""

import json
import os
import re
import uuid

import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("snowflake_idp")

# Account identifier in <orgname>-<account_name> form; write any underscores
# in the account name as hyphens. A legacy account locator needs its region
# and cloud instead, for example xy12345.us-east-2.aws.
SNOWFLAKE_URL = "https://MYORG-MYACCOUNT.snowflakecomputing.com/api/v2/statements"
WAREHOUSE = "MY_WAREHOUSE"
DATABASE = "MY_DATABASE"
# PUBLIC is the Snowflake default schema. Change it if your tables live elsewhere.
SCHEMA = "PUBLIC"
# Snowflake returns 202 once a statement has run for 45 seconds, so allow more
# than that on each HTTP call or the client gives up before the response.
HTTP_TIMEOUT = 60
# Seconds Snowflake may spend on the statement before it cancels it. Without
# this the account default applies and an abandoned statement keeps running
# after this tool has stopped waiting for it.
STATEMENT_TIMEOUT = 60
# Rows returned to the agent. Every row enters the conversation history, so
# keep this small enough to stay inside the model's context.
MAX_ROWS = 200


def _format_result(payload: dict) -> str:
"""Serialize the result compactly and say so when rows are missing."""
notes = []
partitions = payload.get("resultSetMetaData", {}).get("partitionInfo") or []
if len(partitions) > 1:
# Only the first partition is returned inline; this example does not
# fetch the rest.
notes.append(
f"Snowflake split this result into {len(partitions)} partitions "
"and only the first is shown."
)
rows = payload.get("data")
if isinstance(rows, list) and len(rows) > MAX_ROWS:
payload["data"] = rows[:MAX_ROWS]
notes.append(f"Showing the first {MAX_ROWS} of {len(rows)} rows.")
out = json.dumps(payload)
if notes:
out += (
"\n\nINCOMPLETE RESULT: "
+ " ".join(notes)
+ " Narrow the request, or raise MAX_ROWS in server.py."
)
return out


def _run_sql(sql: str) -> str:
token = os.environ.get("H2OGPT_SESSION_EXTERNAL_IDP_TOKEN", "").strip()
if not token:
return (
"H2OGPT_SESSION_EXTERNAL_IDP_TOKEN is empty. Confirm that you signed in "
"through the external identity provider and that an administrator has "
"turned on the Keycloak Identity Broker."
)
headers = {
"Authorization": f"Bearer {token}",
"X-Snowflake-Authorization-Token-Type": "OAUTH",
"Content-Type": "application/json",
"Accept": "application/json",
}
body = {
"statement": sql,
"warehouse": WAREHOUSE,
"database": DATABASE,
"schema": SCHEMA,
"timeout": STATEMENT_TIMEOUT,
}
response = None
try:
# A unique requestId identifies this submission in the Snowflake logs.
response = httpx.post(
SNOWFLAKE_URL,
headers=headers,
json=body,
params={"requestId": str(uuid.uuid4())},
timeout=HTTP_TIMEOUT,
)
if response.status_code == 202:
# Snowflake answers 202 once a statement has run for 45 seconds, and
# keeps running it. This example reports that instead of polling for
# the result.
return (
"The statement is still running after 45 seconds. Do not send the "
"same query again: narrow it with a WHERE clause or a LIMIT, "
"because each call starts a new statement."
)
if response.status_code != 200:
# Bound the body: everything returned here enters the conversation.
return f"HTTP {response.status_code}: {response.text[:500]}"
return _format_result(response.json())
except (httpx.HTTPError, httpx.InvalidURL) as exc:
# Report the transport failure without including the token.
return f"Request to Snowflake failed: {exc}"
except ValueError:
# A non-JSON body usually means a proxy answered instead of Snowflake.
text = response.text[:500] if response is not None else "(no response)"
return f"Snowflake returned a response that is not JSON: {text}"


@mcp.tool()
def snowflake_current_user() -> str:
"""Return the Snowflake user and role that the injected token maps to."""
return _run_sql("SELECT CURRENT_USER(), CURRENT_ROLE()")


@mcp.tool()
def snowflake_list_tables() -> str:
"""List the tables in the configured database and schema."""
# SHOW TABLES fails instead of truncating above 10,000 rows, so bound it.
return _run_sql(f"SHOW TABLES IN SCHEMA {DATABASE}.{SCHEMA} LIMIT {MAX_ROWS}")


@mcp.tool()
def snowflake_describe_table(table: str) -> str:
"""Describe the columns of a table in the configured database and schema.

Pass a bare table name of ASCII letters, digits, and underscores only, and
do not start it with a digit.
"""
name = table.strip()
# Reject an unexpected name rather than stripping characters out of it, so
# a mistake is reported instead of becoming a "table not found" error.
if (
not name
or name[0].isdigit()
or not all(c.isascii() and (c.isalnum() or c == "_") for c in name)
):
return (
"Rejected: the table name must contain only ASCII letters (A-Z, "
"a-z), digits, and underscores, and must not start with a digit. "
"Pass a bare table name without a database or schema."
)
return _run_sql(f"DESCRIBE TABLE {DATABASE}.{SCHEMA}.{name}")


@mcp.tool()
def snowflake_query(sql: str) -> str:
"""Run one SELECT statement, with or without a leading WITH clause.

Always include a LIMIT. At most 200 rows are returned. Read-only access is
enforced by your Snowflake role, not by this tool.
"""
statement = sql.strip().rstrip(";").strip()
# The SQL API already rejects multi-statement requests; this mainly stops
# a Snowflake Scripting body.
if ";" in statement:
return (
"Rejected: the statement must not contain a semicolon. If the "
"semicolon belongs inside a string literal, write it as CHR(59) so "
"that the meaning of the query does not change."
)
if not re.match(r"^(select|with)\b", statement, re.IGNORECASE):
return (
"Rejected: the statement must begin with the SELECT or WITH keyword. "
"A leading comment or parenthesis is not accepted."
)
return _run_sql(statement)


if __name__ == "__main__":
mcp.run()

If you raise MAX_ROWS, update the snowflake_query docstring in the same edit. The agent reads that docstring as the sub-tool's description, and it states the 200-row cap literally.

The tool sends one request per call and does not poll. A statement that runs for longer than 45 seconds comes back as HTTP 202, which the tool reports as a message asking you to narrow the query. Keep the queries the agent sends short enough to finish inside a single agent turn, which is 120 seconds at the default Standard accuracy.

The tool passes identifiers to Snowflake unquoted, so Snowflake folds them to uppercase. If your objects were created with quoted lowercase names, such as "sales_2024", the folded identifiers do not match and the query fails. For those objects, double-quote the identifiers in server.py and pass the exact stored case.

envs.json​

Declaring a session variable is what opts the tool in to receive it. Declare only H2OGPT_SESSION_EXTERNAL_IDP_TOKEN: the matching H2OGPT_SESSION_EXTERNAL_IDP_REFRESH_TOKEN has a longer lifetime and can mint new access tokens, which a single-call tool does not need. For the full list, see Receiving session tokens in custom MCP tools.

{
"H2OGPT_SESSION_EXTERNAL_IDP_TOKEN": ""
}

Enterprise h2oGPTe stores only the key names from envs.json, so the value is a placeholder: an empty string and an os.environ/ reference behave the same.

warning

Do not add a tool that returns the token itself, and do not write the token to a log or an error message. Any value a tool returns becomes part of the conversation history. The external IdP token grants access to Snowflake for as long as it remains valid.

description.md​

This file supplies the text shown in the Description column of Agents > Tools. Without it, that column shows a dash.

Queries Snowflake with the signed-in user's external IdP token. Each query runs
as the Snowflake user that the token maps to, so Snowflake role-based access
control applies to every request.

Step 2: Upload the tool​

Enterprise h2oGPTe takes a local MCP tool as a ZIP file that contains the tool directory. Package the directory, then upload it in Agents > Tools:

  1. Run the following command to package the directory as a ZIP file:

    zip -r snowflake-idp-tool.zip snowflake-idp-tool/

    The ZIP must contain the tool directory, not only its contents. Enterprise h2oGPTe takes the tool name from the directory that holds server.py, so a server.py at the root of the ZIP produces a tool named Extracted instead of Snowflake Idp Tool.

  2. Optional: Check the package before you upload it. Pass the ZIP to inspect_mcp_tool with the Python client, which lists the sub-tools Enterprise h2oGPTe finds, so a packaging mistake shows up before the tool reaches Agents > Tools:

    from h2ogpte import H2OGPTE

    client = H2OGPTE(address="https://<YOUR_DOMAIN>", api_key="<API_KEY>")
    print(client.inspect_mcp_tool(file_path="snowflake-idp-tool.zip"))

    Replace <YOUR_DOMAIN> with your Enterprise h2oGPTe address and <API_KEY> with a personal API key. Inspecting a package does not add the tool.

  3. In the main navigation, click Agents.

  4. Select the Tools tab.

  5. Click + Custom Tool.

  6. Select Local MCP Tools.

  7. Upload snowflake-idp-tool.zip.

  8. In the Sub-tools panel, click Discover sub-tools. Enterprise h2oGPTe inspects the package and lists the four sub-tools, all selected.

  9. Optional: Clear the checkbox for any sub-tool you do not want the agent to use.

  10. Optional: Clear the Enable by Default checkbox. It is selected, so the tool joins your default tool set and is on in every new chat. Clear it to turn the tool on one chat at a time.

  11. Click Add Tool.

For more information about this dialog, see Create local MCP tools.

To change server.py after the tool is in place, edit the file and run the same zip command again. Then open the Edit Tool row action in Agents > Tools and upload the new ZIP with the Replace Tool File (Optional) control. Any change to a value in server.py, such as WAREHOUSE, STATEMENT_TIMEOUT, or MAX_ROWS, needs this step to take effect.

note

Agents > Tools now shows the tool with a red status icon and the tooltip Not Ready to work, because Enterprise h2oGPTe reports every variable declared in envs.json as a required key. This is expected: leave H2OGPT_SESSION_EXTERNAL_IDP_TOKEN unassigned so that Enterprise h2oGPTe fills it for each request. Assignment happens in the Assign Env Vars row action, not in Agents > Authentication, which only creates the keys that row action offers. An unselected key, a blank value, and an os.environ/<VAR> reference all count as unset; a real value overrides the session token until you remove it.

Step 3: Turn on the tool in a chat​

Tool selection applies to a single chat. Turn the tool on in the chat where you want the agent to use it:

  1. Open a new chat.
  2. Select the agent you want to use.
  3. Click the Customize icon.
  4. Select the Configuration tab.
  5. Under Tools, select the checkbox for the tool you uploaded. Enterprise h2oGPTe title-cases the directory name, so snowflake-idp-tool appears as Snowflake Idp Tool. The list groups tools by type, and a local MCP tool appears under Custom.
warning

The same list shows the built-in Snowflake Database (SSO) tool under API Apps. Clear its checkbox in this chat: it uses the same identity as your tool, but it duplicates the capability and adds write sub-tools such as create_object and drop_object. If the list also shows Snowflake Database, clear that too. It appears when an administrator has turned on connector linking. That tool authenticates with the Snowflake account you authorized in the connector rather than the account your IdP token maps to, so the two can run under different Snowflake roles and grants.

Step 4: Verify the tool​

In the prompts that follow, replace SAMPLE_DATA with a table that exists in the database and schema you configured in server.py and that your Snowflake role can read.

The agent summarizes a tool result in its reply. To read exactly what the tool returned, click Details in the agent response and open the Steps tab. For more information, see Review agent behavior.

Send each of the following prompts to the agent and confirm the result. The names in the prompts are the sub-tools defined in server.py:

  1. Confirm the token maps to the Snowflake user you expect:

    Use snowflake_current_user to show my current Snowflake user and role.

    The tool returns the Snowflake SQL API response, in which the column names and the values arrive separately:

    {
    "resultSetMetaData": {
    "numRows": 1,
    "rowType": [{"name": "CURRENT_USER()"}, {"name": "CURRENT_ROLE()"}]
    },
    "data": [["JDOE", "ANALYST"]]
    }

    Confirm that the first value is the Snowflake user whose mapping attribute matches your token, not a shared service account.

  2. Confirm the tool can read metadata, and find a table name to use in the next two prompts:

    Use snowflake_list_tables to list the tables available.

    The response lists the tables in the database and schema you configured, and reports no authentication error.

  3. Confirm the tool can describe an object:

    Use snowflake_describe_table to describe the SAMPLE_DATA table.

    The response lists the column names and types of the table.

  4. Confirm the tool can read data:

    Use snowflake_query to run: SELECT * FROM SAMPLE_DATA LIMIT 10

    The response returns rows from the table, up to the limit you asked for.

    If the third or fourth prompt returns a message ending in does not exist or not authorized, the table name is wrong for your account. Do not mistake that failure for the authorization check in the fifth prompt.

  5. Confirm that Snowflake still denies what your role cannot read. Ask for an object your role has no grant on, such as an account usage view:

    Use snowflake_query to run: SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.USERS LIMIT 10

    Snowflake rejects the query with a message ending in does not exist or not authorized. Together with the first prompt, this shows that the query runs under your own role rather than a shared account.

    If the query succeeds, ask your Snowflake administrator whether your role holds IMPORTED PRIVILEGES on the SNOWFLAKE database or the SECURITY_VIEWER database role, either of which grants this view. If it does, repeat the prompt against an object your role has no grant on. If it does not, the tool is not using your token.

Remove the tool​

To stop the agent from using the tool, clear its checkbox in the chat's Configuration tab. Clearing the checkbox applies to that chat only, and the tool stays available in other chats. To take the tool out of your default set for new chats, turn off its Default toggle in Agents > Tools.

To remove it from the deployment, select it in Agents > Tools and click Delete Tools. Deleting a tool needs the Delete custom agent tools permission (h2ogpte/agent/delete_custom_tool). Because Enterprise h2oGPTe stores no Snowflake credentials for this method, removing the tool leaves nothing behind to revoke.

Next steps​


Feedback