Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ You would see that the MDL and connection info are loaded. Then, you can use `Ct

### 3. Set Environment Variables

There are two ways to set the required environment variables:
- Set up `.env` file in the root directory of the MCP server.
Make sure all required environment variables are properly configured, either in your system or within a `.env` file.
- Set up system environment variables in MCP configuration. See the next step.

### 4. Configure the MCP Server

Expand All @@ -87,6 +90,11 @@ Create a configuration file with the following structure:
"run",
"app/wren.py"
],
"env": {
"WREN_URL": "localhost:8000",
"CONNECTION_INFO_FILE": "/path-to-connection-info/connection.json",
"MDL_PATH": "/path-to-mdl/mdl.json"
},
"autoApprove": [],
"disabled": false
}
Expand All @@ -107,6 +115,7 @@ The following AI agents are compatible with Wren MCP Server and deploy the MCP c

- **[Claude Desktop](https://modelcontextprotocol.io/quickstart/user)**
- **[Cline](https://docs.cline.bot/mcp-servers/mcp-quickstart)**
- **[VsCode MCP Extension](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)**

### 6. Check the Wren Engine is Connected

Expand All @@ -115,6 +124,18 @@ You can ask the AI agent to perform a health check for Wren Engine.
### 7. Start the Conversation

Now, you can start asking questions through your AI agent and interact with Wren Engine.
Tip: prime your agent with a short instruction so it knows how to use the Wren MCP tools.

Recommended prompt:
```
Use the get_wren_guide() tool to learn how to use Wren Engine and discover available tools and examples.
```

Optional follow-ups:
- "Open the Wren guide."
- "What Wren MCP tools are available?"
- "Show me the available tables in Wren Engine."
- "Query Wren Engine to get ... (your question here)."

---

Expand Down
89 changes: 79 additions & 10 deletions mcp-server/app/wren.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@


async def make_query_request(sql: str, dry_run: bool = False):
headers = {"User-Agent": USER_AGENT, "Accept": "application/json"}
headers = {"User-Agent": USER_AGENT, "Accept": "application/json", "x-wren-fallback_disable": "true"}
async with httpx.AsyncClient() as client:
try:
response = await client.post(
f"http://{WREN_URL}/v3/connector/{data_source}/query?dry_run={dry_run}",
f"http://{WREN_URL}/v3/connector/{data_source}/query?dryRun={dry_run}",
headers=headers,
json={
"sql": sql,
Expand Down Expand Up @@ -82,6 +82,17 @@ async def make_constraints_list_request():
except Exception as e:
return e

async def make_get_available_functions_request():
headers = {"User-Agent": USER_AGENT, "Accept": "application/json"}
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"http://{WREN_URL}/v3/connector/{data_source}/functions",
headers=headers,
)
return response.text
except Exception as e:
return e

@mcp.resource("resource://mdl_json_schema")
async def get_mdl_json_schema() -> str:
Expand Down Expand Up @@ -148,8 +159,8 @@ async def dry_run(sql: str) -> str:
dry run before running the actual query.
"""
try:
await make_query_request(sql, True)
return "Query is valid"
response = await make_query_request(sql, True)
return response.text
except Exception as e:
return e

Expand Down Expand Up @@ -183,13 +194,13 @@ async def get_available_tables_resource() -> str:


@mcp.tool()
async def get_available_tables() -> str:
async def get_available_tables() -> list[str]:
"""
Get the available tables in Wren Engine
"""
mdl = orjson.loads(base64.b64decode(mdl_base64).decode("utf-8"))
# return only table name
return [table["name"] for table in mdl["models"]]
return [table["name"] for table in mdl["models"]]


@mcp.tool()
Expand Down Expand Up @@ -288,11 +299,69 @@ async def get_relationships() -> str:
return orjson.dumps(mdl["relationships"]).decode("utf-8")


# TODO: implement this tool
# @mcp.tool()
# async def get_available_functions() -> str:
# pass
@mcp.tool()
async def get_available_functions() -> str:
"""
Get the available functions of connected DataSource Type
"""
response = await make_get_available_functions_request()
return response

@mcp.tool()
async def get_current_data_source_type() -> str:
"""
Get the current data source type
"""
if data_source is None:
return "No data source connected. Please deploy the MDL first and assign `dataSource` field."
return data_source

@mcp.tool()
async def get_wren_guide() -> str:
"""
Understand how to use Wren Engine effectively to query your database
"""

tips = f"""
## Tips for using Wren Engine with {data_source.capitalize()}
You are connected to a {data_source.capitalize()} database via Wren Engine.
Here are some tips to use {data_source.capitalize()} effectively:
"""

if data_source == "snowflake":
tips += """
1. Snowflake supports semi-structured data types like VARIANT, OBJECT, and ARRAY. You can use these data types to store and query JSON data.
2. Snowflake has a rich set of built-in functions to process semi-structured data. You can use functions like GET_PATH, TO_VARIANT, TO_ARRAY, etc.
3. For process semi-structure data type (e.g. `VARIANT`), you can use `get_path` function to extract the value from the semi-structure data.
4. For process array data type (e.g. `ARRAY`), you can use `UNNEST` function to flatten the array data. `UNNEST` only accepts array column as input. If you extract an array value by `get_path` function, you need to cast it to array type (by `to_array` function) before using `UNNEST`.
"""
else:
tips += f"""
1. Use {data_source.capitalize()}'s specific functions and features to optimize your queries.
2. Refer to {data_source.capitalize()}'s documentation for more details on how to use its features effectively.
"""
Comment on lines +325 to +342
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add guard for None data source to prevent AttributeError.

Lines 326-328 and 340-341 call data_source.capitalize() without checking if data_source is None. After fixing the module-level initialization, you must add a guard in this function to handle the case where MDL hasn't been deployed.

Apply this diff to add the guard:

 @mcp.tool()
 async def get_wren_guide() -> str:
     """
     Understand how to use Wren Engine effectively to query your database
     """
+    
+    if data_source is None:
+        return "No data source connected. Please deploy the MDL first and assign `dataSource` field."
 
     tips = f"""
     ## Tips for using Wren Engine with {data_source.capitalize()}
🤖 Prompt for AI Agents
In mcp-server/app/wren.py around lines 325 to 342, the code calls
data_source.capitalize() and compares data_source to "snowflake" without
guarding against data_source being None; add a guard at the top of this block
that normalizes data_source into a safe variable (e.g. if not data_source:
safe_name = "Unknown" and safe_source = "" or safe_source = data_source.lower())
or return/emit a default tips string when MDL isn't deployed, then use safe_name
for display (safe_name.capitalize()) and safe_source for the snowflake
comparison (safe_source == "snowflake") so no AttributeError occurs when
data_source is None.


return f"""
Wren Engine is a semantic layer to help you query your database easily using SQL. It supports ANSI SQL to query your database.
Wren SQL doesn't equal to your database SQL. Wren Engine translates your Wren SQL to your database SQL internally.
Avoid to use database specific SQL syntax in your Wren SQL.
The models you can access are defined in the MDL (Model Definition Language) schema you deployed.

Here are some tips to use Wren Engine effectively:
1. Use simple SQL queries to get data from your tables. You can use SELECT, WHERE, JOIN, GROUP BY, ORDER BY, etc.
2. Use table and column names as defined in the MDL schema.
3. Use aliases to make your queries more readable.
4. Use functions supported by your data source type. You can get the list of available functions using the `get_available_functions` tool.
5. Avoid to use `LATERAL` statement in your queries, as Wren Engine may not support it well. Use normal `JOIN` or `CROSS JOIN UNNEST` instead.
6. Check the tips below for some general tips and some tips specific to the connected DataSource Type.
{tips}

## General Tips
1. If you encounter any issues, please check the health of Wren Engine using the `health_check` tool.
2. You can also get the deployed MDL schema using the `get_manifest` tool.
3. If the number of deployed tables and columns is huge, you can use `get_available_tables`, `get_table_info`, and `get_column_info` tools to get only the required table and column info.
4. You can validate your SQL query using the `dry_run` tool before running the actual query.
"""

@mcp.tool()
async def health_check() -> str:
Expand Down
2 changes: 1 addition & 1 deletion mcp-server/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.4.1",
"mcp[cli]>=1.19.0",
"orjson>=3.10.15",
"pydantic>=2.10.6",
"python-dotenv>=1.0.1",
Expand Down
Loading