From db1ec6dd124db180613801a79590960d1d119473 Mon Sep 17 00:00:00 2001 From: katarzyna_koltun Date: Tue, 23 Jun 2026 18:51:33 +0200 Subject: [PATCH 1/2] Connect Snowflake Agent to Mendix --- .../modules/snowflake/snowflake-mcp-agent.md | 474 ++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md diff --git a/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md b/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md new file mode 100644 index 00000000000..9736850a5cb --- /dev/null +++ b/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md @@ -0,0 +1,474 @@ +--- +title: "Connect an AI Agent in Snowflake to a MCP Server Running in Mendix" +linktitle: "Connect Snowflake AI Agents to Mendix" +url: /appstore/modules/snowflake/connect-snowflake-ai-agent-to-mendix/ +description: "Describes the steps required to use a Snowflake-managed MCP server with a Mendix AI agent." +weight: 80 +--- + +## Introduction + +The Model Context Protocol (MCP) is an open protocol that standardizes how Large Language Models (LLMs) can autonomously connect to apps. Many AI platforms and third-party systems have already adopted MCP for easier integration and empowerment of LLMs. Mendix provides an MCP Server module to facilitate an MCP server from a Mendix app, as well as an MCP Client module. For more information, see [Model Context Protocol (MCP)](/agents/mcp/). + +[Snowflake-managed MCP servers](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-mcp) enable AI agents to securely integrate with Snowflake accounts without needing to deploy separate infrastructure. This includes performing CRUD (Create, Read, Update, and Delete) operations on data, as well as leveraging functionalities such as stored procedures and Cortex. Mendix users can configure the [MCP Client Module](/agents/mcp-modules/mcp-client/) to enable the connection from a Mendix AI agent to a Snowflake MCP server. + +### Typical Use Cases + +* A chat interface where the user can retrieve and modify data in Snowflake Cloud by requesting in natural language. +* Reusing existing functionality of stored procedures in Snowflake Cloud by task-oriented AI agents. + +### Prerequisites {#prerequisites} + +To establish a connection between a Mendix AI Agent and a Snowflake-managed MCP server, you can either start with the [Blank GenAI App](https://marketplace.mendix.com/link/component/227934) or [Agent Builder Starter App](https://marketplace.mendix.com/link/component/240369), but make sure to update the [MCP Client](https://marketplace.mendix.com/link/component/244893) module to version 3.1.0 (or higher) when its version is lower. + +Alternatively, to start from scratch or to add the capability to an exsiting application, you must also install the following modules and their prerequisites: + +* [MCP Client](https://marketplace.mendix.com/link/component/244893) (version 3.1.0 or newer) +* [Conversational UI](https://marketplace.mendix.com/link/component/239450) + +## Preparing a Snowflake-Managed MCP Server + +To configure a Snowflake-managed MCP server, follow these steps: + +1. In Snowflake, set up the database and schemas which will be used by the server. + + For a code sample, see [Database and Schema Setup](#code-db-schema) + +2. Create the stored procedures which the MCP server will expose as tools. + + For code samples, see the following: + + * [Procedure to Return Metadata](#code-metadata) + * [Procedure to Retrieve Records](#code-records) + * [Procedure to Insert Records](#code-records-insert) + +3. Create the Snowflake MCP server exposing the stored procedures as tools. + + For more information, see [Create an MCP Server object](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-mcp#create-an-mcp-server-object) in Snowflake documentation. + + For a code sample, see [Generate MCP Server with Procedures as Tools](#code-generate-server). + +4. Create the authentication and access configuration, so it be can invoked by the Mendix application with the MCP Client module. + + 1. Retrieve the IP addresses from where the MCP Server connector. + + For applications running locally in Studio Pro, you can retrieve your own IP address from [whatismyipaddress.com](https://whatismyipaddress.com/). For applications running in Mendix Cloud, see [Mendix IP Addresses: Mendix Cloud](/developerportal/deploy/mendix-ip-addresses/#mendix-cloud). + + 2. Create a `NETWORK RULE` using the IP addresses that you retrieved. + + You may use code similar to the following sample: + + ```sql + --Run under accountadmin rol or securityadmin role + CREATE OR REPLACE NETWORK RULE SNOWFLAKE_MCP_DEMO.MCPSERVERS.MCP_DEMO_ALLOWED_IPS + TYPE = IPV4 + MODE = INGRESS + VALUE_LIST = ('1.2.3.4', '5.4.6.8', '9.10.11.12'); + ``` + + 3. Create a Service-type Snowflake user, to be used for the Mendix Agent. + + You may use code similar to the following sample: + + ```sql + -- Run under useradmin (or higher) role + CREATE USER IF NOT EXISTS MX_AGENT + default_role = SYSADMIN + TYPE = SERVICE + ALLOWED_INTERFACES = (); + ``` + + 4. Create a `NETWORK POLICY` for this user. + + You may use code similar to the following sample: + + ```sql + -- Run under accountadmin rol or securityadmin role + CREATE OR REPLACE NETWORK POLICY MX_AGENT_NETWORK_POLICY + ALLOWED_NETWORK_RULE_LIST = ('SNOWFLAKE_MCP_DEMO.MCPSERVERS.MCP_DEMO_ALLOWED_IPS'); + ``` + + 5. Set the user to use this policy. + + You may use code similar to the following sample: + + ```sql + -- Run under accountadmin rol or securityadmin role + ALTER USER MX_AGENT SET NETWORK_POLICY = MX_AGENT_NETWORK_POLICY; + ``` + + 6. Create a Personal Access Token (PAT) for the user. Because this is a Service-type user, you must also grant them an admin role. + + ```sql + -- Run under accountadmin or securityadmin role + GRANT ROLE SYSADMIN TO USER MX_AGENT; + + ALTER USER MX_AGENT ADD PAT MX_AGENT_MCP_PAT + ROLE_RESTRICTION = SYSADMIN + DAYS_TO_EXPIRY = 30 + COMMENT = 'PAT for MCP demo'; + ``` + +### Sample Code + +In this section, you can find sample code to help you configure a Snowflake-managed MCP server. + +{{% alert color="info" %}} +The code samples are intended to show the range of available options. They are presented as examples only, and may require significant adaptation to work in your own environment. +{{% /alert %}} + +#### Database and Schema Setup {#code-db-schema} + +The following is a code sample including test data: + +```sql +-- You can run this example under the Sysadmin role. For real production screnarios, use proper authorisation. +CREATE DATABASE IF NOT EXISTS SNOWFLAKE_MCP_DEMO; +CREATE SCHEMA IF NOT EXISTS SNOWFLAKE_MCP_DEMO.TOOLS; +CREATE SCHEMA IF NOT EXISTS SNOWFLAKE_MCP_DEMO.MCPSERVERS; +CREATE SCHEMA IF NOT EXISTS SNOWFLAKE_MCP_DEMO.TESTDATA; + +CREATE OR REPLACE TABLE SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS ( +TICKETID NUMBER AUTOINCREMENT START 1 INCREMENT 1, +PRIORITY VARCHAR(10), +TEXT VARCHAR(500) +); + +INSERT INTO SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS (PRIORITY, TEXT) +VALUES + ('High', 'Server is down in production environment'), + ('Medium', 'User unable to reset password'), + ('Low', 'Request for additional monitor'), + ('High', 'Database connection timeout on checkout page'), + ('Medium', 'Email notifications not being sent'); +``` + +#### Procedure to Return Metadata {#code-metadata} + +The following is an example of a generic stored procedure which returns metadata: + +```sql +-- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation +CREATE OR REPLACE PROCEDURE SNOWFLAKE_MCP_DEMO.TOOLS.GET_SCHEMA_METADATA( + db_name VARCHAR, + schema_name VARCHAR +) +RETURNS VARIANT +LANGUAGE PYTHON +RUNTIME_VERSION = '3.11' +PACKAGES = ('snowflake-snowpark-python') +HANDLER = 'run' +AS +$$ +import json +def run(session, db_name, schema_name): + rows = session.sql(f""" + SELECT + c.TABLE_CATALOG, + c.TABLE_SCHEMA, + c.TABLE_NAME, + t.TABLE_TYPE, + t.ROW_COUNT, + t.COMMENT AS TABLE_COMMENT, + c.COLUMN_NAME, + c.ORDINAL_POSITION, + c.DATA_TYPE, + c.IS_NULLABLE, + c.COLUMN_DEFAULT, + c.CHARACTER_MAXIMUM_LENGTH, + c.NUMERIC_PRECISION, + c.NUMERIC_SCALE, + c.COMMENT AS COLUMN_COMMENT + FROM {db_name}.INFORMATION_SCHEMA.COLUMNS c + JOIN {db_name}.INFORMATION_SCHEMA.TABLES t + ON c.TABLE_CATALOG = t.TABLE_CATALOG + AND c.TABLE_SCHEMA = t.TABLE_SCHEMA + AND c.TABLE_NAME = t.TABLE_NAME + WHERE c.TABLE_SCHEMA = '{schema_name}' + ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION + """).collect() + tables = {} + for row in rows: + tname = row["TABLE_NAME"] + if tname not in tables: + tables[tname] = { + "database": row["TABLE_CATALOG"], + "schema": row["TABLE_SCHEMA"], + "table_type": row["TABLE_TYPE"], + "row_count": row["ROW_COUNT"], + "comment": row["TABLE_COMMENT"], + "columns": [] + } + tables[tname]["columns"].append({ + "name": row["COLUMN_NAME"], + "position": row["ORDINAL_POSITION"], + "data_type": row["DATA_TYPE"], + "nullable": row["IS_NULLABLE"], + "default": row["COLUMN_DEFAULT"], + "max_length": row["CHARACTER_MAXIMUM_LENGTH"], + "precision": row["NUMERIC_PRECISION"], + "scale": row["NUMERIC_SCALE"], + "comment": row["COLUMN_COMMENT"] + }) + return tables + $$; +``` + +#### Procedure to Retrieve Records {#code-records} + +The following is an example of a generic stored procedure which retrieves records: + +```sql +-- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation +CREATE OR REPLACE PROCEDURE SNOWFLAKE_MCP_DEMO.TOOLS.RETRIEVE_RECORDS( + fully_qualified_table VARCHAR, + filter_column VARCHAR, + filter_value VARCHAR +) +RETURNS VARCHAR +LANGUAGE PYTHON +RUNTIME_VERSION = '3.11' +PACKAGES = ('snowflake-snowpark-python') +HANDLER = 'run' +AS + $$ + import json + def run(session, fully_qualified_table, filter_column, filter_value): + parts = fully_qualified_table.split('.') + if len(parts) != 3: + return json.dumps({"status": "error", "message": "Table must be fully qualified: DATABASE.SCHEMA.TABLE"}) + try: + if filter_column and filter_value: + escaped = filter_value.replace("'", "''") + sql = f"SELECT * FROM {fully_qualified_table} WHERE {filter_column} = '{escaped}'" + else: + sql = f"SELECT * FROM {fully_qualified_table}" + rows = session.sql(sql).collect() + results = [row.as_dict() for row in rows] + for r in results: + for k, v in r.items(): + if not isinstance(v, (str, int, float, bool, type(None))): + r[k] = str(v) + return json.dumps({"status": "success", "row_count": len(results), "data": results}) + except Exception as e: + return json.dumps({"status": "error", "message": str(e)}) +$$; +``` + +#### Procedure to Insert Records {#code-records-insert} + +The following is an example of a generic stored procedure which inserts records: + +```sql +-- inputs can be for example: +-- fully_qualified_table = 'SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS' +-- column_values: '{"PRIORITY": "Low", "TEKST": "text here"}' + +-- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation + CREATE OR REPLACE PROCEDURE SNOWFLAKE_MCP_DEMO.TOOLS.INSERT_RECORD( + fully_qualified_table VARCHAR, + column_values VARCHAR + ) + RETURNS VARCHAR + LANGUAGE PYTHON + RUNTIME_VERSION = '3.11' + PACKAGES = ('snowflake-snowpark-python') + HANDLER = 'run' + AS + $$ + import json + def run(session, fully_qualified_table, column_values): + parts = fully_qualified_table.split('.') + if len(parts) != 3: + return json.dumps({"status": "error", "message": "Table must be fully qualified: DATABASE.SCHEMA.TABLE"}) + db, schema, table = parts + cols_rows = session.sql(f""" + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE + FROM {db}.INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = '{schema}' AND TABLE_NAME = '{table}' + ORDER BY ORDINAL_POSITION + """).collect() + if not cols_rows: + return json.dumps({"status": "error", "message": f"Table {fully_qualified_table} not found or has no columns"}) + schema_info = {row["COLUMN_NAME"]: row["DATA_TYPE"] for row in cols_rows} + try: + values = json.loads(column_values) + except json.JSONDecodeError as e: + return json.dumps({"status": "error", "message": f"Invalid JSON in column_values: {str(e)}", "expected_columns": list(schema_info.keys())}) + for col_name in values: + if col_name not in schema_info: + return json.dumps({"status": "error", "message": f"Column '{col_name}' does not exist in {fully_qualified_table}", "valid_columns": list(schema_info.keys())}) + col_names = list(values.keys()) + val_parts = [] + for col in col_names: + val = values[col] + if val is None: + val_parts.append("NULL") + elif isinstance(val, (int, float)): + val_parts.append(str(val)) + else: + escaped = str(val).replace("'", "''") + val_parts.append(f"'{escaped}'") + col_list = ", ".join(col_names) + val_list = ", ".join(val_parts) + sql = f"INSERT INTO {fully_qualified_table} ({col_list}) VALUES ({val_list})" + try: + session.sql(sql).collect() + return json.dumps({"status": "success", "message": f"Record inserted into {fully_qualified_table}", "columns_inserted": col_names}) + except Exception as e: + return json.dumps({"status": "error", "message": str(e)}) + $$; +``` + +#### Generate an MCP Server with Procedures as Tools {#code-generate-server} + +The following is an example of the code to generate a Snowflake MCP server with the above stored procedures above as tools: + +```sql +-- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation +CREATE OR REPLACE MCP SERVER SNOWFLAKE_MCP_DEMO.MCPSERVERS.DEMO_MCP_SERVER + FROM SPECIFICATION $$ + tools: + - title: "Get Schema Metadata" + identifier: "SNOWFLAKE_MCP_DEMO.TOOLS.GET_SCHEMA_METADATA" + name: "get_schema_metadata" + type: "GENERIC" + description: "Returns metadata for all tables and columns in a given database schema, including data types, nullability, row counts, and comments." + config: + type: "procedure" + warehouse: "MYWAREHOUSE" + input_schema: + type: "object" + properties: + db_name: + description: "The database name to inspect" + type: "string" + schema_name: + description: "The schema name to inspect" + type: "string" + - title: "Insert Record" + identifier: "SNOWFLAKE_MCP_DEMO.TOOLS.INSERT_RECORD" + name: "insert_record" + type: "GENERIC" + description: "Inserts a single record into a specified table. Accepts a fully qualified table name and a JSON string of column-value pairs." + config: + type: "procedure" + warehouse: "MYWAREHOUSE" + input_schema: + type: "object" + properties: + fully_qualified_table: + description: "Fully qualified table name in DATABASE.SCHEMA.TABLE format" + type: "string" + column_values: + description: "JSON string of column names and values to insert, e.g. {\"PRIORITY\": \"High\", \"TEKST\": \"New ticket\"}" + type: "string" + - title: "Retrieve Records" + identifier: "SNOWFLAKE_MCP_DEMO.TOOLS.RETRIEVE_RECORDS" + name: "retrieve_records" + type: "GENERIC" + description: "Retrieves records from a specified table. Optionally filter by a single column value. Returns all rows if no filter is provided." + config: + type: "procedure" + warehouse: "MYWAREHOUSE" + input_schema: + type: "object" + properties: + fully_qualified_table: + description: "Fully qualified table name in DATABASE.SCHEMA.TABLE format" + type: "string" + filter_column: + description: "Optional column name to filter on. Pass empty string for no filter." + type: "string" + filter_value: + description: "Optional value to match in the filter column. Pass empty string for no filter." + type: "string" + $$; +``` + +## Connecting a Mendix Agent to the MCP Server + +After setting up the MCP server, you can now create a Mendix AI agent and connect it to the MCP server by performing the following steps: + +1. Optional: If your [MCP Client](/agents/mcp-modules/mcp-client/) version is older than 3.1.0, update it to version 3.1.0 or newer. +2. In Studio Pro, create a new app using the [Agent Builder Starter App](https://marketplace.mendix.com/link/component/240369). +3. Create a constant for the Snowflake user PAT that you created in the previous section, and set its value in the Runtime configuration. +4. Go to **App/Marketplace Modules/MCPClient/Example Implementations/MCP Client/** and copy the **GetCredentials_EXAMPLE** microflow to your own app module. + + Give the copied microflow a meaningful name to show that it is used to get Snowflake PAT authentication, for example, **GetCredentials_SF_PAT**. + +5. Change this microflow so it only adds the PAT as Bearer token to the header by performing the following steps: + + 1. Remove the first **Config: Create Http Header And Add to List** activity. + 2. Change the **Value** attribute of the second **Config: Create Http Header And Add to List** activity to `'Bearer ' + @General.SnowflakePAT`. + +6. Start the app and log in. +7. On the **Administrator functionalities** page, use the **LLM connections** section to configure your Large Language Model subscription and retrieve the list of available Large Language Models. + + You can leverage a LLM (Large Language Model) from your Snowflake account in your Mendix application for GenAI functionality. For more information, see [Bring Your Own Snowflake LLM](/appstore/modules/snowflake/bring-your-own-snowflake-llm/). + +8. On the **Consumed MCP Services** page, click **MCP Client** and configure the following properties of your Snowflake MCP server: + + 1. Enter a name. + 2. Specify the MCP endpoint in the following format: `https://.snowflakecomputing.com/api/v2/databases//schemas//mcp-servers/`. + + {{% alert color="info" %}} If your Snowflake account ID contains underscores (`_`), replace them with `-` in the endpoint. This is only required for the account ID, not for the database name, schema name, or MCP server name. + {{% /alert %}} + + 3. For the protocol value, enter *v2025_03_26*. + 4. Enter a version number. + 5. Set the connection time out, for example, 60 seconds. + 6. For the **Get credentials microflow**, select the microflow that you created in step 4 above (**GetCredentials_SF_PAT**). + 7. Save your changes and verify that the **Server status** is now set to **OK** with a green checkmark. + 8. To inspect the MCP server, click **View MCP tools and prompts**. + +9. Create an AI agent and configure the following properties: + + * LLM model + * System prompt - Make sure to specify the schema and table name to use and provide instructions on how to use the MCP server tools. For an example prompt, see [Example System Prompt](#example-prompt). + * In **Tools**, select the Snowflake-managed MCP server that you configured. + +10. Test your agent by asking questions related to the exposed tools of the Snowflake MCP server, for example, *Which tickets have High priority?*. + +### Example System Prompt {#example-prompt} + +The following is an example of a system prompts that specifies the schema and table name, and provides instructions on how to use the MCP server tools: + +```text +You are a support ticket management assistant connected to a Snowflake database via MCP tools. + +You help users create, retrieve, and inspect support tickets stored in the SNOWFLAKE_MCP_DEMO.TESTDATA schema. + +## Available Tools + +You have access to the following MCP tools: + +1. **get_schema_metadata** - Retrieves table and column metadata for a database - Use this FIRST when you need to understand the data structure before performing other operations. +- Parameters: db_name (e.g. "SNOWFLAKE_MCP_DEMO"), schema_name (e.g. "TESTDATA") + +2. **insert_record** - Inserts a single record into a table. +- Parameters: fully_qualified_table (e.g. "SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS"), column_values (JSON string) +- The value for the TICKETID column is automatically generated in the database, so omit this column when inserting. +- Always provide both PRIORITY and TEXT columns. +- PRIORITY must be one of: "High", "Medium", "Low" + +3. **retrieve_records** - Retrieves records from a table with optional filtering. +- Parameters: fully_qualified_table, filter_column (optional), filter_value (optional) +- Pass empty strings for filter_column and filter_value to retrieve all records. + +## Data Model + +The primary table is SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS: +- TICKETID (NUMBER, auto-increment) - Unique ticket identifier +- PRIORITY (VARCHAR) - Ticket priority: High, Medium, or Low +- TEXT (VARCHAR) - Description of the ticket/issue + +## Guidelines + +- When a user asks to create a ticket, extract the priority and description from their request. If priority is not specified, ask for it. +- When a user asks to view or search tickets, use retrieve_records. Use the filter parameters when they want to filter by a specific column. +- If a user asks about the data structure or available tables, use get_schema_metadata. +- Always confirm successful operations by showing the user what was created or retrieved. +- Use fully qualified table names (DATABASE.SCHEMA.TABLE) in all tool calls. +- If a tool call returns an error, explain the issue clearly and suggest a correction. +``` From 058bb106ed6c00370e961bb5add986aad06b9ab3 Mon Sep 17 00:00:00 2001 From: katarzyna_koltun Date: Wed, 24 Jun 2026 09:48:40 +0200 Subject: [PATCH 2/2] updates --- .../modules/snowflake/snowflake-mcp-agent.md | 838 ++++++++---------- 1 file changed, 394 insertions(+), 444 deletions(-) diff --git a/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md b/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md index 9736850a5cb..e8260519a45 100644 --- a/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md +++ b/content/en/docs/marketplace/platform-supported-content/modules/snowflake/snowflake-mcp-agent.md @@ -1,474 +1,424 @@ --- -title: "Connect an AI Agent in Snowflake to a MCP Server Running in Mendix" +title: "Integrate a Mendix MCP Server with a Snowflake Cortex Agent" linktitle: "Connect Snowflake AI Agents to Mendix" url: /appstore/modules/snowflake/connect-snowflake-ai-agent-to-mendix/ -description: "Describes the steps required to use a Snowflake-managed MCP server with a Mendix AI agent." +description: "Describes the steps required to use a Snowflake AI agent with a Mendix-managed MCP server." weight: 80 --- ## Introduction -The Model Context Protocol (MCP) is an open protocol that standardizes how Large Language Models (LLMs) can autonomously connect to apps. Many AI platforms and third-party systems have already adopted MCP for easier integration and empowerment of LLMs. Mendix provides an MCP Server module to facilitate an MCP server from a Mendix app, as well as an MCP Client module. For more information, see [Model Context Protocol (MCP)](/agents/mcp/). - -[Snowflake-managed MCP servers](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-mcp) enable AI agents to securely integrate with Snowflake accounts without needing to deploy separate infrastructure. This includes performing CRUD (Create, Read, Update, and Delete) operations on data, as well as leveraging functionalities such as stored procedures and Cortex. Mendix users can configure the [MCP Client Module](/agents/mcp-modules/mcp-client/) to enable the connection from a Mendix AI agent to a Snowflake MCP server. - -### Typical Use Cases - -* A chat interface where the user can retrieve and modify data in Snowflake Cloud by requesting in natural language. -* Reusing existing functionality of stored procedures in Snowflake Cloud by task-oriented AI agents. - -### Prerequisites {#prerequisites} - -To establish a connection between a Mendix AI Agent and a Snowflake-managed MCP server, you can either start with the [Blank GenAI App](https://marketplace.mendix.com/link/component/227934) or [Agent Builder Starter App](https://marketplace.mendix.com/link/component/240369), but make sure to update the [MCP Client](https://marketplace.mendix.com/link/component/244893) module to version 3.1.0 (or higher) when its version is lower. - -Alternatively, to start from scratch or to add the capability to an exsiting application, you must also install the following modules and their prerequisites: - -* [MCP Client](https://marketplace.mendix.com/link/component/244893) (version 3.1.0 or newer) -* [Conversational UI](https://marketplace.mendix.com/link/component/239450) - -## Preparing a Snowflake-Managed MCP Server - -To configure a Snowflake-managed MCP server, follow these steps: - -1. In Snowflake, set up the database and schemas which will be used by the server. - - For a code sample, see [Database and Schema Setup](#code-db-schema) - -2. Create the stored procedures which the MCP server will expose as tools. - - For code samples, see the following: - - * [Procedure to Return Metadata](#code-metadata) - * [Procedure to Retrieve Records](#code-records) - * [Procedure to Insert Records](#code-records-insert) - -3. Create the Snowflake MCP server exposing the stored procedures as tools. - - For more information, see [Create an MCP Server object](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-mcp#create-an-mcp-server-object) in Snowflake documentation. - - For a code sample, see [Generate MCP Server with Procedures as Tools](#code-generate-server). - -4. Create the authentication and access configuration, so it be can invoked by the Mendix application with the MCP Client module. - - 1. Retrieve the IP addresses from where the MCP Server connector. - - For applications running locally in Studio Pro, you can retrieve your own IP address from [whatismyipaddress.com](https://whatismyipaddress.com/). For applications running in Mendix Cloud, see [Mendix IP Addresses: Mendix Cloud](/developerportal/deploy/mendix-ip-addresses/#mendix-cloud). - - 2. Create a `NETWORK RULE` using the IP addresses that you retrieved. - - You may use code similar to the following sample: - - ```sql - --Run under accountadmin rol or securityadmin role - CREATE OR REPLACE NETWORK RULE SNOWFLAKE_MCP_DEMO.MCPSERVERS.MCP_DEMO_ALLOWED_IPS - TYPE = IPV4 - MODE = INGRESS - VALUE_LIST = ('1.2.3.4', '5.4.6.8', '9.10.11.12'); - ``` - - 3. Create a Service-type Snowflake user, to be used for the Mendix Agent. - - You may use code similar to the following sample: - - ```sql - -- Run under useradmin (or higher) role - CREATE USER IF NOT EXISTS MX_AGENT - default_role = SYSADMIN - TYPE = SERVICE - ALLOWED_INTERFACES = (); - ``` - - 4. Create a `NETWORK POLICY` for this user. - - You may use code similar to the following sample: - - ```sql - -- Run under accountadmin rol or securityadmin role - CREATE OR REPLACE NETWORK POLICY MX_AGENT_NETWORK_POLICY - ALLOWED_NETWORK_RULE_LIST = ('SNOWFLAKE_MCP_DEMO.MCPSERVERS.MCP_DEMO_ALLOWED_IPS'); - ``` - - 5. Set the user to use this policy. - - You may use code similar to the following sample: - - ```sql - -- Run under accountadmin rol or securityadmin role - ALTER USER MX_AGENT SET NETWORK_POLICY = MX_AGENT_NETWORK_POLICY; - ``` - - 6. Create a Personal Access Token (PAT) for the user. Because this is a Service-type user, you must also grant them an admin role. - - ```sql - -- Run under accountadmin or securityadmin role - GRANT ROLE SYSADMIN TO USER MX_AGENT; - - ALTER USER MX_AGENT ADD PAT MX_AGENT_MCP_PAT - ROLE_RESTRICTION = SYSADMIN - DAYS_TO_EXPIRY = 30 - COMMENT = 'PAT for MCP demo'; - ``` - -### Sample Code - -In this section, you can find sample code to help you configure a Snowflake-managed MCP server. - -{{% alert color="info" %}} -The code samples are intended to show the range of available options. They are presented as examples only, and may require significant adaptation to work in your own environment. -{{% /alert %}} - -#### Database and Schema Setup {#code-db-schema} - -The following is a code sample including test data: - -```sql --- You can run this example under the Sysadmin role. For real production screnarios, use proper authorisation. -CREATE DATABASE IF NOT EXISTS SNOWFLAKE_MCP_DEMO; -CREATE SCHEMA IF NOT EXISTS SNOWFLAKE_MCP_DEMO.TOOLS; -CREATE SCHEMA IF NOT EXISTS SNOWFLAKE_MCP_DEMO.MCPSERVERS; -CREATE SCHEMA IF NOT EXISTS SNOWFLAKE_MCP_DEMO.TESTDATA; - -CREATE OR REPLACE TABLE SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS ( -TICKETID NUMBER AUTOINCREMENT START 1 INCREMENT 1, -PRIORITY VARCHAR(10), -TEXT VARCHAR(500) -); - -INSERT INTO SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS (PRIORITY, TEXT) -VALUES - ('High', 'Server is down in production environment'), - ('Medium', 'User unable to reset password'), - ('Low', 'Request for additional monitor'), - ('High', 'Database connection timeout on checkout page'), - ('Medium', 'Email notifications not being sent'); -``` +You can integrate a Mendix application with a Snowflake Cortex Agent using the Model Context Protocol (MCP). When a Mendix app is configured with the MCP module, it acts as an MCP server that exposes microflows as callable tools. A Snowflake Cortex Agent can then invoke those tools to execute Mendix business logic as part of an AI-driven workflow. -#### Procedure to Return Metadata {#code-metadata} - -The following is an example of a generic stored procedure which returns metadata: - -```sql --- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation -CREATE OR REPLACE PROCEDURE SNOWFLAKE_MCP_DEMO.TOOLS.GET_SCHEMA_METADATA( - db_name VARCHAR, - schema_name VARCHAR -) -RETURNS VARIANT -LANGUAGE PYTHON -RUNTIME_VERSION = '3.11' -PACKAGES = ('snowflake-snowpark-python') -HANDLER = 'run' -AS -$$ -import json -def run(session, db_name, schema_name): - rows = session.sql(f""" - SELECT - c.TABLE_CATALOG, - c.TABLE_SCHEMA, - c.TABLE_NAME, - t.TABLE_TYPE, - t.ROW_COUNT, - t.COMMENT AS TABLE_COMMENT, - c.COLUMN_NAME, - c.ORDINAL_POSITION, - c.DATA_TYPE, - c.IS_NULLABLE, - c.COLUMN_DEFAULT, - c.CHARACTER_MAXIMUM_LENGTH, - c.NUMERIC_PRECISION, - c.NUMERIC_SCALE, - c.COMMENT AS COLUMN_COMMENT - FROM {db_name}.INFORMATION_SCHEMA.COLUMNS c - JOIN {db_name}.INFORMATION_SCHEMA.TABLES t - ON c.TABLE_CATALOG = t.TABLE_CATALOG - AND c.TABLE_SCHEMA = t.TABLE_SCHEMA - AND c.TABLE_NAME = t.TABLE_NAME - WHERE c.TABLE_SCHEMA = '{schema_name}' - ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION - """).collect() - tables = {} - for row in rows: - tname = row["TABLE_NAME"] - if tname not in tables: - tables[tname] = { - "database": row["TABLE_CATALOG"], - "schema": row["TABLE_SCHEMA"], - "table_type": row["TABLE_TYPE"], - "row_count": row["ROW_COUNT"], - "comment": row["TABLE_COMMENT"], - "columns": [] - } - tables[tname]["columns"].append({ - "name": row["COLUMN_NAME"], - "position": row["ORDINAL_POSITION"], - "data_type": row["DATA_TYPE"], - "nullable": row["IS_NULLABLE"], - "default": row["COLUMN_DEFAULT"], - "max_length": row["CHARACTER_MAXIMUM_LENGTH"], - "precision": row["NUMERIC_PRECISION"], - "scale": row["NUMERIC_SCALE"], - "comment": row["COLUMN_COMMENT"] - }) - return tables - $$; -``` +This article covers two integration methods: -#### Procedure to Retrieve Records {#code-records} - -The following is an example of a generic stored procedure which retrieves records: - -```sql --- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation -CREATE OR REPLACE PROCEDURE SNOWFLAKE_MCP_DEMO.TOOLS.RETRIEVE_RECORDS( - fully_qualified_table VARCHAR, - filter_column VARCHAR, - filter_value VARCHAR -) -RETURNS VARCHAR -LANGUAGE PYTHON -RUNTIME_VERSION = '3.11' -PACKAGES = ('snowflake-snowpark-python') -HANDLER = 'run' -AS - $$ - import json - def run(session, fully_qualified_table, filter_column, filter_value): - parts = fully_qualified_table.split('.') - if len(parts) != 3: - return json.dumps({"status": "error", "message": "Table must be fully qualified: DATABASE.SCHEMA.TABLE"}) - try: - if filter_column and filter_value: - escaped = filter_value.replace("'", "''") - sql = f"SELECT * FROM {fully_qualified_table} WHERE {filter_column} = '{escaped}'" - else: - sql = f"SELECT * FROM {fully_qualified_table}" - rows = session.sql(sql).collect() - results = [row.as_dict() for row in rows] - for r in results: - for k, v in r.items(): - if not isinstance(v, (str, int, float, bool, type(None))): - r[k] = str(v) - return json.dumps({"status": "success", "row_count": len(results), "data": results}) - except Exception as e: - return json.dumps({"status": "error", "message": str(e)}) -$$; +* Method 1 - Snowflake functions as an intermediary layer. + + The Cortex Agent uses a custom tool backed by a Snowflake SQL wrapper function, which delegates to a Python function that communicates with the Mendix MCP server using Basic Authentication. + +* Method 2 - Direct MCP connector. + + The Cortex Agent connects directly to the Mendix MCP server through a Snowflake external MCP server and API integration using OAuth 2.0. + +Both methods allow a Cortex Agent to invoke Mendix business logic. The method you choose depends on how much control you need over authentication, request construction, and response handling. + +This article uses the [GenAI Showcase App](https://marketplace.mendix.com/link/component/220475) as a reference implementation. The GenAI Showcase App is available in the Mendix Marketplace and demonstrates how to configure a Mendix app as an MCP server and expose microflows as MCP tools. + +## Prerequisites {#prerequisites} + +Before you start, make sure you have completed the following prerequisites: + +* You have a Snowflake environment with access to Cortex Agents. +* You have a Mendix application with the MCP module installed and configured. +* Your Mendix app is deployed to a Mendix Cloud environment that is reachable from Snowflake. +* You have the permissions required to create Snowflake integrations, functions, and agents. +* You have exposed at least one microflow as an MCP tool in your Mendix app. + +### Additional Prerequisites for Method 1 + +For Method 1, you also need the following: + +* A Snowflake external access integration configured for the Mendix MCP endpoint +* A Snowflake secret containing the username and password for Basic Authentication + +### Additional Prerequisites for Method 2 + +For Method 2, you also need the following: + +* An OAuth 2.0 client registration in your identity provider (for example, Microsoft Entra ID) +* The client ID, client secret, token endpoint, authorization endpoint, and API scope for that registration + +## Example Scenario + +The examples in this article use a Mendix MCP tool named *RetrieveNumberOfTicketsInStatus*. This tool is exposed by a Mendix microflow and accepts the following input: + +| Parameter | Type | Description | +| --- | --- | --- | +| Status | String | The ticket status to query | + +The microflow returns the number of tickets for the given status. In this example, the supported values and their results are the following: + +| Status value | Returned count | +| --- | --- | +| Open | 42 | +| Closed | 128 | +| In Progress | 19 | + +If a value other than the supported statuses is provided, the microflow throws an exception. + +A user interacting with the Cortex Agent can ask a question such as, *How many tickets are open?* + +The agent invokes the appropriate tool and returns the result. + +## Method 1 - Using Snowflake Functions as an Intermediary Layer + +In this method, the Cortex Agent does not connect directly to the Mendix MCP server. Instead, it uses a custom tool that is backed by a Snowflake SQL wrapper function. That wrapper function calls a Snowflake Python function, which handles all MCP communication with the Mendix app. + +This approach gives you full control over the following: + +* Authentication (using a Snowflake secret for Basic Authentication credentials) +* MCP session initialization +* Request construction and payload formatting +* HTTP communication and response parsing + +### When to Use Method 1 + +Use this method when you want to: + +* Hide MCP protocol details from the Cortex Agent. +* Expose business-friendly Snowflake functions. +* Control authentication and payload construction inside Snowflake. +* Implement custom validation or response handling logic. + +### Architecture + +The following describes the request flow for Method 1: + +```text +The user sends a prompt to the Snowflake Cortex Agent, for example: How many tickets are open? +The Cortex Agent interprets the prompt and selects the appropriate custom tool based on the tool description. +The custom tool calls the Snowflake SQL wrapper function CALL_MENDIX_MCP_ACCP_WITHBASICAUTH_RETRIEVE_NR_TICKETS, passing the status value extracted from the prompt. +The SQL wrapper function constructs the MCP tools/call payload and delegates execution to the Python function CALL_MENDIX_MCP_ACCP_WITHBASICAUTH. +The Python function retrieves the Basic Authentication credentials from the configured Snowflake secret and constructs the Authorization header. +The Python function initializes an MCP session with the Mendix MCP server and sends the tools/call request. +The Mendix MCP server receives the request and invokes the RetrieveNumberOfTicketsInStatus microflow with the provided status value. +The microflow executes the business logic and returns the ticket count to the MCP server. +The MCP server returns the result to the Python function, which parses the response and returns it as a VARIANT to the Cortex Agent. +The Cortex Agent presents the result to the user. ``` -#### Procedure to Insert Records {#code-records-insert} - -The following is an example of a generic stored procedure which inserts records: - -```sql --- inputs can be for example: --- fully_qualified_table = 'SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS' --- column_values: '{"PRIORITY": "Low", "TEKST": "text here"}' - --- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation - CREATE OR REPLACE PROCEDURE SNOWFLAKE_MCP_DEMO.TOOLS.INSERT_RECORD( - fully_qualified_table VARCHAR, - column_values VARCHAR +### Configuring Method 1 + +To configure your app for method 1, perform the following steps: + +1. Configure the Mendix MCP tool. + + In your Mendix application, make sure the microflow **RetrieveNumberOfTicketsInStatus** is exposed as an MCP tool through the MCP module. The tool must define the following: + + * **Tool name** - `RetrieveNumberOfTicketsInStatus` + * **Input parameter** - `Status` of type `String` + * **Description** - A description that helps the AI agent understand when to call this tool + + For more information on exposing microflows as MCP tools, see the MCP module documentation on the Mendix documentation site. + +2. Create a Snowflake secret for basic authentication. + + Store the Mendix MCP server credentials in a Snowflake secret so that they are not hardcoded in your function. + + ```text + CREATE OR REPLACE SECRET SNOWFLAKE_INTELLIGENCE.TOOLS.MENDIX_MCP_BASIC_SECRET + TYPE = PASSWORD + USERNAME = 'your-mendix-username' + PASSWORD = 'your-mendix-password'; + ``` + + Do not include actual credentials in shared documentation or version control. Use placeholder values and manage secrets through role-based access controls. + +3. Create the external access integration. + + The Python function requires outbound access to the Mendix Cloud endpoint. Create an external access integration to allow this. + + ```text + CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION MENDIX_MCP_ACCP_INTEGRATION_WITHBASICAUTH + ALLOWED_NETWORK_RULES = () + ALLOWED_AUTHENTICATION_SECRETS = (SNOWFLAKE_INTELLIGENCE.TOOLS.MENDIX_MCP_BASIC_SECRET) + ENABLED = TRUE; + ``` + + Replace `` with a network rule that allows outbound access to your Mendix Cloud environment host. + +4. Create the authenticated Python MCP function. + + Create the low-level Snowflake Python function that handles all communication with the Mendix MCP server. This function does the following: + + * Retrieves Basic Authentication credentials from the Snowflake secret. + * Constructs the Authorization header. + * Initializes the MCP session. + * Sends the MCP tools/call request. + * Parses both JSON and text or event-stream responses. + * Returns the result as a `VARIANT`. + + ```text + CREATE OR REPLACE FUNCTION SNOWFLAKE_INTELLIGENCE.TOOLS.CALL_MENDIX_MCP_ACCP_WITHBASICAUTH( + "METHOD" VARCHAR, + "PARAMS" VARCHAR + ) + RETURNS VARIANT + LANGUAGE PYTHON + RUNTIME_VERSION = '3.11' + PACKAGES = ('snowflake-snowpark-python', 'requests') + HANDLER = 'call_mcp' + EXTERNAL_ACCESS_INTEGRATIONS = (MENDIX_MCP_ACCP_INTEGRATION_WITHBASICAUTH) + SECRETS = ('MENDIX_MCP_BASIC_SECRET' = SNOWFLAKE_INTELLIGENCE.TOOLS.MENDIX_MCP_BASIC_SECRET) + AS ' + import requests + import json + import uuid + import base64 + import _snowflake + def get_basic_auth_header(): + creds = _snowflake.get_username_password("MENDIX_MCP_BASIC_SECRET") + username = creds.username + password = creds.password + token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") + return f"Basic {token}" + def parse_response(response, request_id=None): + content_type = response.headers.get("Content-Type", "") + if "text/event-stream" in content_type or not response.text.strip().startswith("{"): + result = None + for line in response.text.splitlines(): + if line.startswith("data:"): + data_str = line[len("data:"):].strip() + if data_str: + try: + msg = json.loads(data_str) + if request_id and msg.get("id") == request_id: + result = msg + elif not request_id: + result = msg + except json.JSONDecodeError: + pass + return result if result else {"raw_sse": response.text} + else: + return response.json() + def call_mcp(method, params): + base_url = "https://.apps..mendixcloud.com/MendixMCP/mcp" + session = requests.Session() + headers = { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-03-26", + "Authorization": get_basic_auth_header() + } + init_id = str(uuid.uuid4()) + init_payload = { + "jsonrpc": "2.0", + "id": init_id, + "method": "initialize", + "params": {"protocolVersion": "2024-11-05", "capabilities": {}} + } + init_resp = session.post(base_url, json=init_payload, headers=headers, timeout=30) + init_resp.raise_for_status() + init_result = parse_response(init_resp, init_id) + session_id = init_resp.headers.get("Mcp-Session-Id", "") + if session_id: + headers["Mcp-Session-Id"] = session_id + notif_payload = { + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {} + } + session.post(base_url, json=notif_payload, headers=headers, timeout=30) + if method == "initialize": + return init_result + if params and params.strip(): + params_obj = json.loads(params) + else: + params_obj = {} + request_id = str(uuid.uuid4()) + payload = { + "jsonrpc": "2.0", + "id": request_id, + "method": method, + "params": params_obj + } + response = session.post(base_url, json=payload, headers=headers, timeout=60) + response.raise_for_status() + return parse_response(response, request_id) + '; + ``` + + Replace `` and `` with the actual hostname of your deployed Mendix Cloud environment. + +5. Create the SQL wrapper function. + + Create a business-specific SQL wrapper function that the Cortex Agent will call. This function accepts the STATUS input, constructs the MCP tools/call payload, and delegates execution to the Python function created in the previous step. + + ```text + CREATE OR REPLACE FUNCTION SNOWFLAKE_INTELLIGENCE.TOOLS.CALL_MENDIX_MCP_ACCP_WITHBASICAUTH_RETRIEVE_NR_TICKETS( + "STATUS" VARCHAR + ) + RETURNS VARIANT + LANGUAGE SQL + AS ' + SELECT SNOWFLAKE_INTELLIGENCE.TOOLS.CALL_MENDIX_MCP_ACCP_WITHBASICAUTH( + ''tools/call'', + TO_VARCHAR( + OBJECT_CONSTRUCT( + ''name'', ''RetrieveNumberOfTicketsInStatus'', + ''arguments'', OBJECT_CONSTRUCT(''Status'', STATUS) + ) + ) ) - RETURNS VARCHAR - LANGUAGE PYTHON - RUNTIME_VERSION = '3.11' - PACKAGES = ('snowflake-snowpark-python') - HANDLER = 'run' - AS - $$ - import json - def run(session, fully_qualified_table, column_values): - parts = fully_qualified_table.split('.') - if len(parts) != 3: - return json.dumps({"status": "error", "message": "Table must be fully qualified: DATABASE.SCHEMA.TABLE"}) - db, schema, table = parts - cols_rows = session.sql(f""" - SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE - FROM {db}.INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = '{schema}' AND TABLE_NAME = '{table}' - ORDER BY ORDINAL_POSITION - """).collect() - if not cols_rows: - return json.dumps({"status": "error", "message": f"Table {fully_qualified_table} not found or has no columns"}) - schema_info = {row["COLUMN_NAME"]: row["DATA_TYPE"] for row in cols_rows} - try: - values = json.loads(column_values) - except json.JSONDecodeError as e: - return json.dumps({"status": "error", "message": f"Invalid JSON in column_values: {str(e)}", "expected_columns": list(schema_info.keys())}) - for col_name in values: - if col_name not in schema_info: - return json.dumps({"status": "error", "message": f"Column '{col_name}' does not exist in {fully_qualified_table}", "valid_columns": list(schema_info.keys())}) - col_names = list(values.keys()) - val_parts = [] - for col in col_names: - val = values[col] - if val is None: - val_parts.append("NULL") - elif isinstance(val, (int, float)): - val_parts.append(str(val)) - else: - escaped = str(val).replace("'", "''") - val_parts.append(f"'{escaped}'") - col_list = ", ".join(col_names) - val_list = ", ".join(val_parts) - sql = f"INSERT INTO {fully_qualified_table} ({col_list}) VALUES ({val_list})" - try: - session.sql(sql).collect() - return json.dumps({"status": "success", "message": f"Record inserted into {fully_qualified_table}", "columns_inserted": col_names}) - except Exception as e: - return json.dumps({"status": "error", "message": str(e)}) - $$; -``` + '; + ``` -#### Generate an MCP Server with Procedures as Tools {#code-generate-server} - -The following is an example of the code to generate a Snowflake MCP server with the above stored procedures above as tools: - -```sql --- You can run this example/demo under sysadmin role, for real production screnario's use proper authorisation -CREATE OR REPLACE MCP SERVER SNOWFLAKE_MCP_DEMO.MCPSERVERS.DEMO_MCP_SERVER - FROM SPECIFICATION $$ - tools: - - title: "Get Schema Metadata" - identifier: "SNOWFLAKE_MCP_DEMO.TOOLS.GET_SCHEMA_METADATA" - name: "get_schema_metadata" - type: "GENERIC" - description: "Returns metadata for all tables and columns in a given database schema, including data types, nullability, row counts, and comments." - config: - type: "procedure" - warehouse: "MYWAREHOUSE" - input_schema: - type: "object" - properties: - db_name: - description: "The database name to inspect" - type: "string" - schema_name: - description: "The schema name to inspect" - type: "string" - - title: "Insert Record" - identifier: "SNOWFLAKE_MCP_DEMO.TOOLS.INSERT_RECORD" - name: "insert_record" - type: "GENERIC" - description: "Inserts a single record into a specified table. Accepts a fully qualified table name and a JSON string of column-value pairs." - config: - type: "procedure" - warehouse: "MYWAREHOUSE" - input_schema: - type: "object" - properties: - fully_qualified_table: - description: "Fully qualified table name in DATABASE.SCHEMA.TABLE format" - type: "string" - column_values: - description: "JSON string of column names and values to insert, e.g. {\"PRIORITY\": \"High\", \"TEKST\": \"New ticket\"}" - type: "string" - - title: "Retrieve Records" - identifier: "SNOWFLAKE_MCP_DEMO.TOOLS.RETRIEVE_RECORDS" - name: "retrieve_records" - type: "GENERIC" - description: "Retrieves records from a specified table. Optionally filter by a single column value. Returns all rows if no filter is provided." - config: - type: "procedure" - warehouse: "MYWAREHOUSE" - input_schema: - type: "object" - properties: - fully_qualified_table: - description: "Fully qualified table name in DATABASE.SCHEMA.TABLE format" - type: "string" - filter_column: - description: "Optional column name to filter on. Pass empty string for no filter." - type: "string" - filter_value: - description: "Optional value to match in the filter column. Pass empty string for no filter." - type: "string" - $$; -``` + This abstraction keeps MCP-specific request construction hidden from the Cortex Agent and exposes a clean, business-oriented function signature. + +6. Configure the Cortex Agent custom tool. + + In Snowflake, create or open a Cortex Agent and add a custom tool that references the wrapper function `CALL_MENDIX_MCP_ACCP_WITHBASICAUTH_RETRIEVE_NR_TICKETS(VARCHAR)`. -## Connecting a Mendix Agent to the MCP Server + In the tool description, specify the following: -After setting up the MCP server, you can now create a Mendix AI agent and connect it to the MCP server by performing the following steps: + * What the tool does + * When the agent should use it + * Which values are supported for the Status argument: **Open**, **In Progress**, **Closed** -1. Optional: If your [MCP Client](/agents/mcp-modules/mcp-client/) version is older than 3.1.0, update it to version 3.1.0 or newer. -2. In Studio Pro, create a new app using the [Agent Builder Starter App](https://marketplace.mendix.com/link/component/240369). -3. Create a constant for the Snowflake user PAT that you created in the previous section, and set its value in the Runtime configuration. -4. Go to **App/Marketplace Modules/MCPClient/Example Implementations/MCP Client/** and copy the **GetCredentials_EXAMPLE** microflow to your own app module. +7. Test the integration. - Give the copied microflow a meaningful name to show that it is used to get Snowflake PAT authentication, for example, **GetCredentials_SF_PAT**. + You can test the wrapper function directly in Snowflake before using it through the agent. -5. Change this microflow so it only adds the PAT as Bearer token to the header by performing the following steps: + ```text + SELECT SNOWFLAKE_INTELLIGENCE.TOOLS.CALL_MENDIX_MCP_ACCP_WITHBASICAUTH_RETRIEVE_NR_TICKETS('Open'); + SELECT SNOWFLAKE_INTELLIGENCE.TOOLS.CALL_MENDIX_MCP_ACCP_WITHBASICAUTH_RETRIEVE_NR_TICKETS('Closed'); + SELECT SNOWFLAKE_INTELLIGENCE.TOOLS.CALL_MENDIX_MCP_ACCP_WITHBASICAUTH_RETRIEVE_NR_TICKETS('In Progress'); + ``` - 1. Remove the first **Config: Create Http Header And Add to List** activity. - 2. Change the **Value** attribute of the second **Config: Create Http Header And Add to List** activity to `'Bearer ' + @General.SnowflakePAT`. + A successful response returns a VARIANT value containing the JSON-RPC result from the Mendix MCP server. -6. Start the app and log in. -7. On the **Administrator functionalities** page, use the **LLM connections** section to configure your Large Language Model subscription and retrieve the list of available Large Language Models. +## Method 2 - Connecting the Cortex Agent Directly to the Mendix MCP Server - You can leverage a LLM (Large Language Model) from your Snowflake account in your Mendix application for GenAI functionality. For more information, see [Bring Your Own Snowflake LLM](/appstore/modules/snowflake/bring-your-own-snowflake-llm/). +In this method, the Cortex Agent connects to the Mendix MCP server through a Snowflake external MCP server and API integration. The Cortex Agent uses the MCP connector directly, without requiring custom Snowflake functions for request construction or authentication. -8. On the **Consumed MCP Services** page, click **MCP Client** and configure the following properties of your Snowflake MCP server: - - 1. Enter a name. - 2. Specify the MCP endpoint in the following format: `https://.snowflakecomputing.com/api/v2/databases//schemas//mcp-servers/`. - - {{% alert color="info" %}} If your Snowflake account ID contains underscores (`_`), replace them with `-` in the endpoint. This is only required for the account ID, not for the database name, schema name, or MCP server name. - {{% /alert %}} - - 3. For the protocol value, enter *v2025_03_26*. - 4. Enter a version number. - 5. Set the connection time out, for example, 60 seconds. - 6. For the **Get credentials microflow**, select the microflow that you created in step 4 above (**GetCredentials_SF_PAT**). - 7. Save your changes and verify that the **Server status** is now set to **OK** with a green checkmark. - 8. To inspect the MCP server, click **View MCP tools and prompts**. +Authentication is handled through OAuth 2.0, using an identity provider such as Microsoft Entra ID. -9. Create an AI agent and configure the following properties: +This method requires less custom code in Snowflake and makes the Mendix MCP server available as a named connector in the agent configuration. - * LLM model - * System prompt - Make sure to specify the schema and table name to use and provide instructions on how to use the MCP server tools. For an example prompt, see [Example System Prompt](#example-prompt). - * In **Tools**, select the Snowflake-managed MCP server that you configured. +### When to Use Method 2 -10. Test your agent by asking questions related to the exposed tools of the Snowflake MCP server, for example, *Which tickets have High priority?*. +Use this method when you want to: -### Example System Prompt {#example-prompt} +* Connect the Cortex Agent to the MCP server more directly. +* Reduce the amount of custom Snowflake function code. +* Rely on Snowflake MCP connector support. +* Manage authentication through OAuth 2.0 integration settings. + +### Architecture + +The following describes the request flow for Method 2: -The following is an example of a system prompts that specifies the schema and table name, and provides instructions on how to use the MCP server tools: - ```text -You are a support ticket management assistant connected to a Snowflake database via MCP tools. - -You help users create, retrieve, and inspect support tickets stored in the SNOWFLAKE_MCP_DEMO.TESTDATA schema. - -## Available Tools - -You have access to the following MCP tools: - -1. **get_schema_metadata** - Retrieves table and column metadata for a database - Use this FIRST when you need to understand the data structure before performing other operations. -- Parameters: db_name (e.g. "SNOWFLAKE_MCP_DEMO"), schema_name (e.g. "TESTDATA") - -2. **insert_record** - Inserts a single record into a table. -- Parameters: fully_qualified_table (e.g. "SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS"), column_values (JSON string) -- The value for the TICKETID column is automatically generated in the database, so omit this column when inserting. -- Always provide both PRIORITY and TEXT columns. -- PRIORITY must be one of: "High", "Medium", "Low" - -3. **retrieve_records** - Retrieves records from a table with optional filtering. -- Parameters: fully_qualified_table, filter_column (optional), filter_value (optional) -- Pass empty strings for filter_column and filter_value to retrieve all records. - -## Data Model - -The primary table is SNOWFLAKE_MCP_DEMO.TESTDATA.TICKETS: -- TICKETID (NUMBER, auto-increment) - Unique ticket identifier -- PRIORITY (VARCHAR) - Ticket priority: High, Medium, or Low -- TEXT (VARCHAR) - Description of the ticket/issue - -## Guidelines - -- When a user asks to create a ticket, extract the priority and description from their request. If priority is not specified, ask for it. -- When a user asks to view or search tickets, use retrieve_records. Use the filter parameters when they want to filter by a specific column. -- If a user asks about the data structure or available tables, use get_schema_metadata. -- Always confirm successful operations by showing the user what was created or retrieved. -- Use fully qualified table names (DATABASE.SCHEMA.TABLE) in all tool calls. -- If a tool call returns an error, explain the issue clearly and suggest a correction. +The user sends a prompt to the Snowflake Cortex Agent, for example: How many tickets are open? +The Cortex Agent interprets the prompt and determines that the MCP connector should be used to fulfill the request. +Snowflake routes the request through the configured external MCP server to the Mendix MCP endpoint. +The Snowflake API integration handles OAuth 2.0 authentication, obtaining and attaching the required access token to the request. +The Mendix MCP server receives the authenticated request and identifies the tool to invoke based on the MCP tools/call payload. +The Mendix MCP server invokes the RetrieveNumberOfTicketsInStatus microflow with the provided status value. +The microflow executes the business logic and returns the ticket count to the MCP server. +The MCP server returns the result through the Snowflake MCP connector to the Cortex Agent. +The Cortex Agent presents the result to the user. ``` + +### Configuring Method 2 + +To configure your app for method 2, perform the following steps: + +1. Register an OAuth 2.0 client in your identity provider. + + Before creating the Snowflake API integration, register an OAuth 2.0 client application in your identity provider (for example, Microsoft Entra ID). Note the following values, which you will need in the next step: + + * Client ID + * Client secret + * Token endpoint + * Authorization endpoint + * API scope + + Keep your client secret confidential. Do not include it in shared documentation, code, or version control systems. + +2. Create a Snowflake API integration that configures OAuth 2.0 access to the Mendix MCP endpoint: + + ```text + USE ROLE ACCOUNTADMIN; + CREATE API INTEGRATION custom_mcp_api_integration + API_PROVIDER = external_mcp + API_ALLOWED_PREFIXES = ('https://.apps..mendixcloud.com/MendixMCP/mcp') + API_USER_AUTHENTICATION = ( + TYPE = OAUTH2 + OAUTH_CLIENT_ID = 'your-client-id' + OAUTH_CLIENT_SECRET = 'your-client-secret' + OAUTH_TOKEN_ENDPOINT = 'https://login.microsoftonline.com//oauth2/v2.0/token' + OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_BASIC + OAUTH_AUTHORIZATION_ENDPOINT = 'https://login.microsoftonline.com//oauth2/v2.0/authorize' + OAUTH_ALLOWED_SCOPES = ('your-api-scope') + OAUTH_REFRESH_TOKEN_VALIDITY = 86400 + ) + ENABLED = TRUE; + + Replace the following placeholders with your environment-specific values: + + | Placeholder | Description | + | --- | --- | + | `` | The subdomain of your Mendix Cloud environment | + | `` | The Mendix Cloud region (for example, `eu-1c`) | + | `your-client-id` | The OAuth 2.0 client ID from your identity provider | + | `your-client-secret` | The OAuth 2.0 client secret from your identity provider | + | `` | The tenant ID of your Microsoft Entra ID directory | + | `your-api-scope` | The API scope registered for the Mendix MCP application | + + The `OAUTH_REFRESH_TOKEN_VALIDITY` value is set in seconds. The example value of `86400` equals 24 hours. + +Step 3: Create the external MCP server +After creating the API integration, create the external MCP server in Snowflake. This makes the Mendix MCP endpoint available as a named connector that can be attached to a Cortex Agent. + + + +USE ROLE ACCOUNTADMIN; +CREATE EXTERNAL MCP SERVER mendix_mcp_server + WITH DISPLAY_NAME = 'Mendix MCP server' + URL = 'https://.apps..mendixcloud.com/MendixMCP/mcp' + API_INTEGRATION = custom_mcp_api_integration; +Step 4: Add the MCP connector to a Cortex Agent +To add the Mendix MCP server as a connector to a Cortex Agent, do the following: + +In Snowflake, navigate to AI & ML > Cortex Agents. + +Create a new agent or open an existing agent. + +In the agent configuration page, locate the MCP Connectors section. + +Click Add MCP server. + +Select Mendix MCP server from the list of available external MCP servers. + +Save the agent configuration. + +Figure 3 – Cortex Agent overview page showing the MCP Connectors section with the Mendix MCP server configured. + +Step 5: Connect and test the MCP connector +When using the agent for the first time, you may be prompted to authorize the OAuth connection. To test the integration, do the following: + +Open the agent preview or runtime chat view. + +In the MCP Connectors panel, click Connect next to Mendix MCP server. + +Complete the OAuth authorization flow if prompted. + +Ask a question that should trigger the Mendix tool, for example: + +How many tickets are open? + +The agent should invoke the RetrieveNumberOfTicketsInStatus tool through the MCP connector and return the result. + +Figure 4 – Cortex Agent runtime view showing the Mendix MCP server connector with the Connect option. +