Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ SECRET_TOKEN_ERP=your_erp_token

# Google Tag Manager (optional)
# GOOGLE_TAG_MANAGER_ID=GTM-XXXXXXX

# -----------------------------
# Optional MCP (Model Context Protocol) settings
# -----------------------------
# Control QueryWeaver's built-in MCP endpoints (default: enabled)
# Set to "true" to disable mounting the MCP HTTP surface without editing code
# DISABLE_MCP=false
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jsonschema = "~=4.25.0"
tqdm = "~=4.67.1"
python-multipart = "~=0.0.10"
jinja2 = "~=3.1.4"
fastapi-mcp = "~=0.4.0"

[dev-packages]
pytest = "~=8.4.1"
Expand Down
153 changes: 140 additions & 13 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,51 @@ docker run -p 5000:5000 --env-file .env falkordb/queryweaver

For a complete list of available configuration options, see the `.env.example` file in the repository.

## MCP server: host or connect (optional)

QueryWeaver includes optional support for the Model Context Protocol (MCP). You can either have QueryWeaver expose an MCP-compatible HTTP surface (so other services can call QueryWeaver as an MCP server), or configure QueryWeaver to call an external MCP server for model/context services.

What QueryWeaver provides
- The app registers MCP operations focused on Text2SQL flows:
- `list_databases`
- `connect_database`
- `database_schema`
- `query_database`

- To disable the built-in MCP endpoints set `DISABLE_MCP=true` in your `.env` or environment (default: MCP enabled).
- Configuration

- `DISABLE_MCP` — disable QueryWeaver's built-in MCP HTTP surface. Set to `true` to disable. Default: `false` (MCP enabled).

Examples

Disable the built-in MCP when running with Docker:

```bash
docker run -p 5000:5000 -it --env DISABLE_MCP=true falkordb/queryweaver
```
Calling the built-in MCP endpoints (example)
- The MCP surface is exposed as HTTP endpoints.


### Server Configuration
Below is a minimal example `mcp.json` client configuration that targets a local QueryWeaver instance exposing the MCP HTTP surface at `/mcp`.

```json
{
"servers": {
"queryweaver": {
"type": "http",
"url": "http://127.0.0.1:5000/mcp",
"headers": {
"Authorization": "Bearer your_token_here"
}
}
},
"inputs": []
}
```

## Testing

QueryWeaver includes a comprehensive test suite with both unit and End-to-End (E2E) tests.
Expand Down
2 changes: 1 addition & 1 deletion api/agents/relevancy_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class RelevancyAgent(BaseAgent):
# pylint: disable=too-few-public-methods
"""Agent for determining relevancy of queries to database schema."""

def __init__(self, queries_history: list, result_history: list):
def __init__(self, queries_history: list[str], result_history: list[str]):
"""Initialize the relevancy agent with query and result history."""
if result_history is None:
self.messages = []
Expand Down
24 changes: 21 additions & 3 deletions api/app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import RedirectResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi_mcp import FastApiMCP
from starlette.middleware.sessions import SessionMiddleware
from starlette.middleware.base import BaseHTTPMiddleware

Expand Down Expand Up @@ -51,10 +52,9 @@ def create_app():
"Text2SQL with "
"Graph-Powered Schema Understanding"
),
)

app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
)

app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")

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.

💡 Verification agent

🧩 Analysis chain

Don't trust all proxies; fix ProxyHeadersMiddleware config.

Passing trusted_hosts="*" lets any upstream spoof X-Forwarded headers. Restrict to known proxies or make it configurable via env. Also pass a list, not a raw string.

-    app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
+    trusted_proxies = os.getenv("PROXY_TRUSTED_IPS", "127.0.0.1,::1")
+    app.add_middleware(
+        ProxyHeadersMiddleware,
+        trusted_hosts=[ip.strip() for ip in trusted_proxies.split(",") if ip.strip()]
+    )

If you actually need to trust all proxies in a controlled environment, make it explicit in .env (PROXY_TRUSTED_IPS=*), not hardcoded.


🏁 Script executed:

#!/bin/bash
python3 - << 'EOF'
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
import inspect
print(inspect.getsource(ProxyHeadersMiddleware.__init__))
EOF

Length of output: 131


🏁 Script executed:

#!/bin/bash
set -e
pip install starlette
python3 - << 'EOF'
import inspect
from starlette.middleware.proxy_headers import ProxyHeadersMiddleware
print(inspect.signature(ProxyHeadersMiddleware.__init__))
EOF

Length of output: 1041


🏁 Script executed:

#!/bin/bash
set -e
pip install uvicorn
python3 - << 'EOF'
import inspect
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
print("Constructor signature:", inspect.signature(ProxyHeadersMiddleware.__init__))
print("Docstring:", ProxyHeadersMiddleware.__doc__)
EOF
rg -nP "ProxyHeadersMiddleware" -C3 .

Length of output: 2321


🏁 Script executed:

#!/bin/bash
set -e
pip install uvicorn
python3 - << 'EOF'
import inspect
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
print(inspect.getsource(ProxyHeadersMiddleware))
EOF

Length of output: 2827


🏁 Script executed:

#!/bin/bash
set -e
pip install uvicorn
python3 - << 'EOF'
import inspect
from uvicorn.middleware.proxy_headers import _TrustedHosts
print(inspect.getsource(_TrustedHosts))
EOF

Length of output: 3609


Restrict ProxyHeadersMiddleware’s trusted_hosts via env—remove hardcoded "*"
Replace the middleware call at api/app_factory.py:57:

-    app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
+    trusted_ips = os.getenv("PROXY_TRUSTED_IPS", "127.0.0.1,::1")
+    app.add_middleware(
+        ProxyHeadersMiddleware,
+        trusted_hosts=[ip.strip() for ip in trusted_ips.split(",") if ip.strip()],
+    )

If you truly need to trust all proxies, set PROXY_TRUSTED_IPS="*" in your environment—don’t hardcode it.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In api/app_factory.py around line 57, the ProxyHeadersMiddleware call currently
hardcodes trusted_hosts="*"; change it to read PROXY_TRUSTED_IPS from the
environment (os.getenv("PROXY_TRUSTED_IPS")) and use that value instead: if the
env var is "*" pass "*" to trusted_hosts, otherwise split the comma-separated
string into a list of stripped host/IP entries and pass that list (or None if
the env var is empty) to ProxyHeadersMiddleware; ensure you import os and handle
empty/missing env gracefully so the default behavior is not to trust all
proxies.


# Add session middleware with explicit settings to ensure OAuth state persists
app.add_middleware(
Expand All @@ -81,9 +81,27 @@ def create_app():
app.include_router(auth_router)
app.include_router(graphs_router, prefix="/graphs")
app.include_router(database_router)
# app.include_router(mcp_router, prefix="/mcp")

setup_oauth_handlers(app, app.state.oauth)

# Control MCP endpoints via environment variable DISABLE_MCP
# Default: MCP is enabled unless DISABLE_MCP is set to true
disable_mcp = os.getenv("DISABLE_MCP", "false").lower() in ("1", "true", "yes")
if disable_mcp:
logging.info("MCP endpoints disabled via DISABLE_MCP environment variable")
else:
mcp = FastApiMCP(app,
name="queryweaver",
description="QueryWeaver MCP API, provides Text2SQL capabilities",
include_operations=["list_databases",
"connect_database",
"database_schema",
"query_database"]
)

mcp.mount_http()

@app.exception_handler(Exception)
Comment on lines +88 to 105

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.

💡 Verification agent

🧩 Analysis chain

Mount MCP under an explicit base path to match docs and avoid surprises.

README points clients to /mcp, but mount_http() is called with no base path. Mount explicitly and keep docs consistent.

-        mcp.mount_http()
+        # Mount under explicit base path to match README and client examples
+        mcp.mount_http("/mcp")

Also verify the operation_id strings exist on the registered routes.


🏁 Script executed:

#!/bin/bash
# Verify operation_ids exist (expect 4 matches).
rg -nP 'operation_id\s*=\s*"(list_databases|connect_database|database_schema|query_database)"' -g 'api/**/*.py' -C1

Length of output: 756


Mount MCP under explicit /mcp base path
Change the call to include “/mcp”, matching the README and client examples:

-        mcp.mount_http()
+        mcp.mount_http("/mcp")

Operation IDs for list_databases, connect_database, database_schema, and query_database are already present in api/routes (no changes needed).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Control MCP endpoints via environment variable DISABLE_MCP
# Default: MCP is enabled unless DISABLE_MCP is set to true
disable_mcp = os.getenv("DISABLE_MCP", "false").lower() in ("1", "true", "yes")
if disable_mcp:
logging.info("MCP endpoints disabled via DISABLE_MCP environment variable")
else:
mcp = FastApiMCP(app,
name="queryweaver",
description="QueryWeaver MCP API, provides Text2SQL capabilities",
include_operations=["list_databases",
"connect_database",
"database_schema",
"query_database"]
)
mcp.mount_http()
@app.exception_handler(Exception)
# Control MCP endpoints via environment variable DISABLE_MCP
# Default: MCP is enabled unless DISABLE_MCP is set to true
disable_mcp = os.getenv("DISABLE_MCP", "false").lower() in ("1", "true", "yes")
if disable_mcp:
logging.info("MCP endpoints disabled via DISABLE_MCP environment variable")
else:
mcp = FastApiMCP(app,
name="queryweaver",
description="QueryWeaver MCP API, provides Text2SQL capabilities",
include_operations=["list_databases",
"connect_database",
"database_schema",
"query_database"]
)
mcp.mount_http("/mcp")
@app.exception_handler(Exception)
🤖 Prompt for AI Agents
In api/app_factory.py around lines 88 to 105, the MCP is mounted at the app root
but should be mounted under the explicit "/mcp" base path per README and client
examples; update the FastApiMCP creation/mount to include the base path (for
example pass base_path="/mcp" or set the mount path argument) so the MCP
endpoints are served under /mcp (e.g., /mcp/list_databases,
/mcp/connect_database, etc.), then call mount_http as before.

async def handle_oauth_error(request: Request, exc: Exception):
"""Handle OAuth-related errors gracefully"""
Expand Down
Loading
Loading