LangChain agents on Cortex AI Gateway with MCP tools and traces¶
External LangChain agents reason through Cortex AI Gateway, act through a Snowflake MCP server, and are analyzed from gateway traces and per-user cost.
| Stage | Operate, Build |
| Topics | Cortex AI Gateway, Snowflake MCP, AI Observability |
| Status | Preview |
| Time to complete | 45min |
| Edition | Any edition with Cortex AI Gateway and Snowflake-managed MCP servers |
| Regions | AWS commercial regions only (preview), excluding Asia Pacific (New Zealand), Asia Pacific (Malaysia), and Europe (Spain) |
| Privileges | ACCOUNTADMIN for sql/01, 02, 03 and 99 (setup, network policy, the token, and teardown); the agent runs as a service user and the analyzer as a role with MONITOR and no gateway USAGE of its own |
| Related skills | cost-intelligence (bundled with Cortex Code), semantic-view-patterns (Snowflake-Labs/coco-skills) |
| Last verified | 2026-10-04 |
| Authors | Parker Erickson, Bala Krishnamoorthy |
| Code | View all files or download langchain-mcp-agent-tracing.zip, which includes this page as README.md. Teardown: sql/99_teardown.sql |
This recipe is a reference implementation: review the code and adapt it to your environment before production use.
Run LangChain agents outside Snowflake that do their model reasoning through Cortex AI Gateway and act on Snowflake data through a Snowflake-managed MCP server, authenticated only with a Snowflake token. Then analyze what they did from the gateway’s traces and what they cost from metered usage, with a small Streamlit app that sees only this recipe’s traffic.
When to use this¶
Cortex AI Gateway is in preview and available only on AWS commercial regions (excluding Asia Pacific (New Zealand), Asia Pacific (Malaysia), and Europe (Spain)). Use this recipe when:
- You already build agents in LangChain or LangGraph and want Snowflake to govern their model access and data access, without moving the agent into Snowflake.
- You want both Claude (Anthropic format) and OpenAI-format models behind one endpoint, with one credential.
- You need to reconstruct what an external agent did, turn by turn, and attribute its cost.
When not to use it:
- You want threads, feedback, and evaluations. Those are Cortex Agents features. An external agent on the gateway writes gateway spans only.
- You need to see tool execution in the traces. The gateway records model calls. MCP tool calls are visible only as content inside the next model call, and only if payload capture is on.
- You need exact per-request billing from traces. Traces are best effort; bill from
AI_GATEWAY_USAGE_HISTORY.
How it works¶
- Two Snowflake endpoints, one token. Model calls go to the gateway
base_urlfromSHOW AI GATEWAYS. Tool calls go tohttps://<host>/api/v2/databases/<db>/schemas/<schema>/mcp-servers/<name>. Both accept the service user’s PAT as a Bearer token. - Claude through
ChatAnthropic, OpenAI models throughChatOpenAI. Claude is served on/v1/messagesonly.ChatAnthropicpoints at the gateway base URL and carries the Bearer token indefault_headers. - The agent shapes its own traces. A fresh W3C
traceparentper user turn groups that turn’s model calls under onetrace_id.x-snowflake-ai-gateway-conversation-idgroups turns. The recipe’s conversation ids start withcookbook-lcmcp-<agent>-, which is how the analyzer and the SQL scope themselves. - Least privilege on both sides. The agent role can reach the gateway, the MCP server, and the SELECT grants its tools need. The analyst role has
MONITORon the gateway and one cost view, and no gatewayUSAGEof its own. It can still send inference whilePUBLICholdsUSAGEon the gateway, which is the default and which this recipe leaves in place.
Prerequisites¶
| Requirement | Notes |
|---|---|
| Account on AWS commercial region with Cortex AI Gateway | Preview. SHOW AI GATEWAYS returns one row. |
Gateway logging.enabled: true | DESCRIBE AI GATEWAY SNOWFLAKE. Payload capture (capture_payload.request_response: true) is needed only for the analyzer’s Conversations and Search tabs. This recipe does not change the spec; to change it safely, use the snapshot and restore in gateway-governance-quickstart. |
ACCOUNTADMIN | For sql/01 to 03 and 99. |
| The agent host’s IPv4 egress address | A TYPE = SERVICE user can generate or use a programmatic access token only while it is subject to a network policy. sql/01 creates COOKBOOK_LANGCHAIN_MCP_AGENT_TRACING_SVC_POLICY and sets it on the service user only. It allows client_cidr, which defaults to CURRENT_IP_ADDRESS() of the setup session as a /32. If client/agent.py runs on another host, set client_cidr in sql/01 to that host’s address. The account-level network policy, if any, is not changed. |
| Models | Defaults are claude-sonnet-4-6 and openai-gpt-5-mini. Override with AGENT_CLAUDE_MODEL and AGENT_OPENAI_MODEL. |
| Python 3.11+ | pip install -r client/requirements.txt for the agent, and pip install -r app/requirements.txt for the analyzer. |
Quickstart¶
Object names are fixed: they also appear in the MCP server spec, the cost view, and the client. Besides the warehouse size and the models, the value you may change is client_cidr in sql/01.
-
sql/01_setup.sql: database, warehouse, AGENT and ANALYST roles, the service user (default role and warehouse set, as MCP sessions require) and its network policy, and the cost view. Copybase_urlfrom the last result. -
sql/02_data_and_mcp_server.sql: synthetic Northwind data, the semantic view, the Cortex Search service, the MCP server, and the grants. -
sql/03_service_user_pat.sql: run it yourself (snow sql -f sql/03_service_user_pat.sql) and copytoken_secret. -
Run the agent:
-
After about a minute, run
sql/04_trace_queries.sqlas the ANALYST role, or start the analyzer: -
sql/99_teardown.sql.
Walkthrough¶
Service user and network policy (sql/01)¶
A service user cannot generate or use a PAT unless it is subject to a network policy. Setup gives the service user its own policy rather than relying on an account-level one, so the token works only from client_cidr, for both the gateway and the MCP server. Before it creates the rule, setup refuses any value that is not one IPv4 address or a range from /16 to /32, so 0.0.0.0/0 cannot slip in. A user-level policy takes precedence over the account-level policy for that user only.
USAGE on the gateway is granted to PUBLIC by default, and every role inherits PUBLIC. In a default account the ANALYST role can therefore still send inference. This recipe leaves that grant unchanged, because removing it is an account-wide decision. To make the split real, first grant USAGE to every role that should keep reaching the gateway, then run REVOKE USAGE ON AI GATEWAY SNOWFLAKE FROM ROLE PUBLIC;. Teardown does not touch the PUBLIC grant.
MCP server and grants (sql/02)¶
USAGE on the MCP server lets a client list tools. Each tool still needs its own privilege on the object behind it. CREATE OR REPLACE drops the grants on the replaced objects, so the script grants them again on every run. The semantic view uses CREATE OR ALTER, which keeps its grants.
execute_sql lets the model run SQL that Cortex Analyst wrote. It is read-only, and it runs with the agent role, which can SELECT only the two recipe tables. The Snowflake docs recommend exposing a Cortex Agent as the only tool on a server for governed business questions, and putting raw SQL on a separate server with a least-privileged role if you need it. This recipe keeps the three tools together to show tool chaining in LangChain, and relies on the narrow role.
The agent (client/agent.py)¶
gateway_base_url() refuses any URL that is not https://.../api/v2/aigateways/..., which catches the common mistake of pointing at /api/v2/cortex. The script builds a new model object per turn so that each turn gets its own traceparent, then calls langchain.agents.create_agent(llm, tools, system_prompt=...). A numbers question typically makes three model calls: one to call query_campaigns, one to call execute_sql, and one to answer.
Traces and cost (sql/04, the analyzer)¶
Every trace query filters on the service user and the conversation prefix:
The account has one gateway, so without this filter an analyzer shows every user’s traffic. The app applies the same filter, passes the window, user, prefix, trace id, and search text as bind parameters (session.sql(sql, params=[...])), and never builds SQL from input. Text that comes from traces (model names, captured prompts and responses) is untrusted. It is rendered in dataframes and st.code, or passed through esc(), which HTML-escapes and then Markdown-escapes it. The app does not use unsafe_allow_html.
Cost comes from OBS.AGENT_GATEWAY_COST, a view over ACCOUNT_USAGE.AI_GATEWAY_USAGE_HISTORY. It is filtered to the service user and takes credits per model from CREDITS_GRANULAR. The view is owned by ACCOUNTADMIN, so the analyst role needs no ACCOUNT_USAGE access. Attribution is per user: every agent that shares this service user shares its cost line. For per-agent cost, give each agent its own service user.
Verify¶
- After
sql/01,SHOW PARAMETERS LIKE 'NETWORK_POLICY' IN USER COOKBOOK_LANGCHAIN_MCP_AGENT_TRACING_SVCreturnsCOOKBOOK_LANGCHAIN_MCP_AGENT_TRACING_SVC_POLICYat levelUSER. client/agent.pylists the toolsquery_campaigns, search_strategy_docs, execute_sql. It answers four questions. The first usesquery_campaignsandexecute_sql, and reports Q4 2025 ROAS of Email 12.7x, Paid Search 3.3x, Social Media 2.1x, and Display 1.2x.sql/04query 1 returns, for one run, four turns across three conversations, with agentscampaign-analystandstrategy-advisor, 2 to 5 model calls per turn, and statusSUCCESS.- Query 3 shows only operation
chat: no tool spans. - Query 4, once
ACCOUNT_USAGEcatches up, listsclaude-sonnet-4-6andopenai-gpt-5-mini, and its request counts include this run’s model calls. Setup reuses the service user name, so the counts also include earlier runs in the 7-day window. - The analyzer’s Overview shows the same counts. With payload capture on (
capture_payload.request_response: true), a search such asbenchmarkreturns rows; with it off, the Search and Conversations tabs have no payloads to match, so that check does not apply. Either way, a search containing quotes or SQL keywords returns no rows and no error.
Gotchas¶
| Symptom | Cause | Fix |
|---|---|---|
ImportError: cannot import name 'streamablehttp_client' | langchain-mcp-adapters 0.1.x with mcp 2.x | Install the pinned set in client/requirements.txt |
| Agent exits “Refusing … only /api/v2/aigateways” | AI_GATEWAY_BASE_URL set to /api/v2/cortex or a hand-built URL | Use base_url from SHOW AI GATEWAYS |
| HTTP 400 or 503 from a Claude model | Sent through ChatOpenAI to /v1/chat/completions | Use ChatAnthropic; Claude is served on /v1/messages only |
| HTTP 400 “Unsupported parameter: ‘max_tokens’” | gpt-5 models | Use max_completion_tokens |
| TLS or connection error to the gateway or MCP server | Account host contains an underscore | Replace _ with - in the host (the script does this) |
| Tools listed but a tool call fails with a privilege error | USAGE on the MCP server does not cover the tool’s object | Grant SELECT on the semantic view and tables, and USAGE on the search service |
Tools vanish after rerunning sql/02 | CREATE OR REPLACE dropped grants | The script re-grants; if you edit it, keep the grants after the replace |
| MCP session fails to initialize | Service user has no default warehouse or default role | sql/01 sets both |
| Issuing the PAT fails with a network policy error, or the agent’s calls with a valid token are rejected | The service user is not under a network policy, or the agent calls from an address outside client_cidr | Set client_cidr in sql/01 to the agent host’s egress address, rerun sql/01, then rerun sql/03 |
sql/01 stops with “client_cidr must be one IPv4 address or range from /16 to /32” | The setup session reached Snowflake over IPv6, or client_cidr is malformed or broader than /16 | Set client_cidr to the agent host’s IPv4 address with /32 |
| The ANALYST role can still call the gateway | PUBLIC holds USAGE on the gateway by default, and the recipe leaves it | Grant USAGE to every role that needs it, then REVOKE USAGE ON AI GATEWAY SNOWFLAKE FROM ROLE PUBLIC (an account-wide change) |
| Analyzer: “must have MONITOR granted on AI GATEWAY” | Running with a role that lacks MONITOR | Use the ANALYST role |
| Conversations tab says no payloads | capture_payload.request_response is off in the gateway spec | Expected; turn it on only if you accept storing prompts and responses |
| Cost tab is empty right after a run | ACCOUNT_USAGE latency | Check again later; traces show activity sooner |
| A turn is missing from traces | Trace delivery is best effort | Expected occasionally; do not bill from traces |
| Earlier runs still show up after teardown and setup | Spans stay in the gateway’s event table and are matched by user name, which setup reuses | Narrow the window, or filter on a conversation id from the current run |
Teardown¶
Run sql/99_teardown.sql as ACCOUNTADMIN. It removes the network policy from the service user, drops the user (revoking its token) and the policy, then drops the database (tables, semantic view, search service, MCP server, cost view, network rule), the warehouse, and both roles. Every name comes from the script’s SET block, which matches setup. Dropping the roles removes their grants on the gateway. The recipe does not change the gateway specification, so there is nothing to restore. Teardown does not remove the agent’s spans from AGENT_TRACE_TABLE or its rows from AI_GATEWAY_USAGE_HISTORY. If payload capture was on, those spans hold the prompts and responses.
Adapt this recipe¶
Keep these, so a customized version keeps the recipe’s security model:
- Least privilege:
ACCOUNTADMINonly for the scripts listed under Privileges; everything else runs as the recipe’s own role. - No grants to
PUBLIC. - Service users stay
TYPE = SERVICEand sign in with a key pair or token, never a password. - The network policy the recipe creates: programmatic access tokens need one.
- Credentials: create keys, tokens, and secrets yourself, in a separate terminal, and read them from environment variables. Never put them in a script or give them to an agent.
- Dynamic SQL, if you add any, uses bind variables or an allowlist of identifiers, never text concatenated from user or model input.
Change these to fit your use case:
- Object names. Rename them in every script, including teardown: the recipe repeats them as literals, including inside
$$bodies, so change them all at once, then check that teardown still removes everything. - Roles, personas, and users: map them to your own, each granted no more than the recipe grants.
- Data: point the scripts at your tables instead of the sample data, then run Verify again.
- Models and warehouse sizes.
To have a coding agent adapt it:
Run it with a coding agent¶
Related skills¶
cost-intelligence(bundled): cost questions across all AI features, includingAI_GATEWAY_USAGE_HISTORY. This recipe’s cost view covers only its own service user.semantic-view-patterns(coco-skills): semantic view design and diagnostics. This recipe’s semantic view is minimal tool data for the MCP server; use the skill to build a production one.