Skip to content

fix(connectors): unblock read-only CTE SQL, surface upstream errors, harden Odds/WhatsApp adapters#345

Merged
keysersoft merged 6 commits into
mainfrom
keysersoft/connector-mcp-usage-analysis
Jun 19, 2026
Merged

fix(connectors): unblock read-only CTE SQL, surface upstream errors, harden Odds/WhatsApp adapters#345
keysersoft merged 6 commits into
mainfrom
keysersoft/connector-mcp-usage-analysis

Conversation

@keysersoft

Copy link
Copy Markdown
Contributor

Fixes uncovered while analysing 5 days of production connector/MCP usage (10 errors / 87 calls). Every root cause was verified by calling the upstream APIs directly with the real user credentials.

Changes

database.engine.ts — read-only SQL guard (real bug)
The guard rejected any query not starting with SELECT, so legitimate read-only WITH … SELECT CTEs — the heaviest user's BI queries — failed with "Only SELECT queries are allowed". Now:

  • accepts a leading WITH,
  • blocks data-modifying CTEs (WITH x AS (INSERT … RETURNING …)),
  • rejects stacked statements (SELECT 1; DROP …),
  • strips string literals/comments first so keyword checks aren't fooled (WHERE action = 'DELETE').

The previous keyword loop was dead code (&& !startsWith('SELECT')). Adds 8 tests.

dynamic-mcp-tools.ts — error observability
The persisted tool_invocations.error stored only the bare axios message (Request failed with status code 400), hiding the cause. The upstream response body is now folded into the stored error, so failures are diagnosable from the audit log (e.g. weclapp unknown property: customerName, Odds Missing eventId, Graph code 190 invalid token). The full detail returned to the MCP client is unchanged.

adapters/intl/odds-api.json — new catalog adapter
The previous user-built custom connectors failed because /odds requires both eventId and a valid bookmaker name (not all), and there is no soccer_world_cup slug. Adds list_sports / list_bookmakers / list_leagues / list_events / get_event_odds with the required params, the correct discovery workflow, and the apiKey injected from {{ODDS_API_KEY}}.

adapters/intl/whatsapp-business.json
list_phone_numbers / list_message_templates required the model to pass businessAccountId, which it left empty (//phone_numbers → 401). The WABA id is now injected from {{WHATSAPP_BUSINESS_ACCOUNT_ID}} and the param dropped.

Not fixable in code (user-config; called out for support follow-up)

  • WhatsApp: the stored token is invalid (code 190) — user must regenerate a permanent System User token and set WHATSAPP_BUSINESS_ACCOUNT_ID.
  • Odds API: free plan caps selected bookmakers — use an allowed bookmaker or upgrade.
  • Sofascore: intrinsic slowness/unreliability of the third-party Apify scraper (the new error surfacing makes the run-failed cause visible).
  • weclapp: already resolved in prod by feat(weclapp): add quotations, recurring invoices and opportunities tools #342 (the customerName NOTE is present); errors predated it.

Testing

No node_modules in this workspace, so the build/jest suite must run in CI. The SQL guard logic was validated standalone against 16 cases (including the two real production CTE queries and the created_at / 'DELETE' false-positive guards), catalog.ts regenerated (180 adapters), and both edited JSON files validated.

…harden Odds/WhatsApp adapters

Fixes found while analysing 5 days of production connector/MCP usage.

database.engine: the SQL guard rejected any query not starting with SELECT, so
legitimate read-only `WITH … SELECT` CTEs (the heaviest user's BI queries) failed
with "Only SELECT queries are allowed". Now accept a leading WITH, block
data-modifying CTEs (`WITH x AS (INSERT …)`), reject stacked statements, and strip
string literals/comments first so keyword checks aren't fooled (`action = 'DELETE'`).
The previous keyword loop was dead code (`&& !startsWith('SELECT')`). Adds tests.

dynamic-mcp-tools: the persisted invocation error stored only the bare axios message
("Request failed with status code 400"), hiding the real cause. Fold the upstream
response body into the stored error so failures are diagnosable from the audit log
(e.g. weclapp "unknown property", Odds "Missing eventId", Graph "code 190").

odds-api: new catalog adapter. The previous custom connectors failed because /odds
requires both eventId and a valid bookmaker (not "all") and there is no
"soccer_world_cup" slug. Adds list_sports/bookmakers/leagues/events + get_event_odds
with the required params and the correct discovery workflow; apiKey injected from env.

whatsapp-business: list_phone_numbers/list_message_templates required the model to
pass businessAccountId, which it left empty (// path → 401). Inject the WABA id from
{{WHATSAPP_BUSINESS_ACCOUNT_ID}} instead, dropping the param.
@keysersoft keysersoft requested a review from D3nisty as a code owner June 19, 2026 07:49
Comment thread packages/backend/src/connectors/engines/database.engine.ts Fixed
…nomial-regex ReDoS

CodeQL flagged the block-comment regex as polynomial on untrusted query input
(/*a/*a/*…). Replace the three regexes with a single O(n) character scan and add
a ReDoS-resistance test.
Comment thread packages/backend/src/connectors/engines/database.engine.ts Fixed
@keysersoft keysersoft merged commit 6a59f32 into main Jun 19, 2026
11 checks passed
@keysersoft keysersoft deleted the keysersoft/connector-mcp-usage-analysis branch June 19, 2026 10:18
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants