Skip to content

feat: add query_logs tool for custom log queries - #333

Open
jordienr wants to merge 12 commits into
supabase:mainfrom
jordienr:claude/mcp-log-query-tool-8372a9
Open

feat: add query_logs tool for custom log queries#333
jordienr wants to merge 12 commits into
supabase:mainfrom
jordienr:claude/mcp-log-query-tool-8372a9

Conversation

@jordienr

@jordienr jordienr commented Jul 16, 2026

Copy link
Copy Markdown
Member

What

Adds a new query_logs tool to the debugging feature group. It runs a custom ClickHouse SQL query against a project's unified logs stream, for cases where the get_logs service presets are too coarse (filtering, aggregating, or joining across log fields).

Tracks AI-701. Builds on the ClickHouse logs endpoint work in O11Y-1813 and the get_logs ClickHouse migration (#326).

How it addresses the observability team's concerns

  1. Prompt injection / security — log content is user-controllable (same class of risk as execute_sql), so the result is wrapped in wrapWithUntrustedDataBoundary, the same best-effort guardrail execute_sql uses. Not foolproof, but consistent with the existing arbitrary-query tool.
  2. Cost / scalability — starts conservative: defaults to a 24h window, and the analytics endpoint caps the requested range at 24h server-side. No unbounded queries.
  3. ClickHouse, not BigQuery — hits GET /v1/projects/{ref}/analytics/endpoints/logs (ClickHouse / logs.all.otel) and takes raw ClickHouse-dialect SQL. Does not touch the deprecated BigQuery-backed logs.all.
  4. No POST on v1 — uses GET with sql as a query param. No new POST handler.
  5. CLI / self-hosted — reuses the exact same endpoint as the already-merged get_logs (feat: support edge function runtime logs in get_logs #326) under the same debugging group, so it inherits identical platform availability and introduces no new exposure on CLI/self-hosted.

Details

  • query_logs params: project_id, sql, optional iso_timestamp_start/iso_timestamp_end. Defaults to the last 24h window (matching get_logs).
  • Read-only enforcement is left to the backend (the analytics endpoint is read-only); no client-side SQL parsing.
  • Auto-registers under the already-enabled debugging group.

Verification

  • tsc --noEmit clean, biome ci clean.
  • All unit + integration suites pass (incl. logs.test.ts, server.test.ts). The 5 failing checks are |e2e| tests requiring live SUPABASE_ACCESS_TOKEN / ANTHROPIC_API_KEY, which fork PRs don't receive — they pass when run in the upstream context with secrets.

Note

Per CONTRIBUTING, feature PRs should track an accepted issue — this tracks AI-701.

jordienr added 4 commits July 16, 2026 16:24
Adds a query_logs debugging tool that runs a custom read-only ClickHouse
SQL query against a project's unified logs stream, for cases where the
get_logs service presets are too coarse. Reuses the existing analytics
logs endpoint and validates that queries are SELECT/WITH only.
@jordienr
jordienr marked this pull request as ready for review July 17, 2026 08:57
@jordienr
jordienr requested a review from a team as a code owner July 17, 2026 08:57

@barryroodt barryroodt left a comment

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.

Really clean addition, Jordi. It mirrors the merged get_logs sibling almost exactly (same endpoint, same untrusted-data wrapping, same 24h defaulting), and I reproduced the checks against the PR head: tsc --noEmit, biome ci ., and the server.test.ts + logs.test.ts suites (111 tests) all pass. Nothing here blocks merge. A couple of small things worth a look:

The sql description is missing function_edge_logs. The source list guides the model on what to filter by, and right now it lists function_logs (edge-function runtime console output) but not function_edge_logs (the invocation/request logs the get_logs edge-function preset queries, see logs.ts:37). Since that description is effectively the contract the model writes SQL against, a model following it can't reach invocation logs and will filter on the wrong source. Everything else in the list looks right.

query_logs has no execution test yet. The test change adds it to the tool-listing assertion, which is exactly right for registration, but the new timestamp-defaulting / passthrough / wrapping logic in execute is the bug-prone part and it's currently untested (whereas get_logs has three). server.test.ts:1567 is a ready-made template and the existing /endpoints/logs mock already covers it, so no new mock needed. Happy to leave this as a fast-follow if you'd rather not expand scope here.

Tiny nit: sql: z.string() could take .min(1) to match executeSqlOptionsSchema.query, so an empty query gives a clear validation message instead of an opaque backend error. Very much a nice-to-have.

None of this is load-bearing for shipping. The function_edge_logs line is the one I'd genuinely want fixed before merge; the rest are optional.

- add function_edge_logs to the sql source-hint list so models can reach
  edge function invocation logs
- require a non-empty sql query (.min(1)), matching execute_sql
- add execution tests for query_logs: sql passthrough + timestamp
  defaulting, custom window forwarding, and empty-query rejection
@jordienr

Copy link
Copy Markdown
Member Author

Thanks for the thorough review! Addressed all three in f37cfc5:

  • function_edge_logs missing from the sql description — added it to the source-hint list, so a model can now reach edge function invocation logs (not just function_logs runtime output).
  • No execution test for query_logs — added three, mirroring the get_logs template at server.test.ts: sql passthrough + timestamp defaulting, custom-window forwarding, and empty-query rejection.
  • sql nit — added .min(1) on both the tool schema and queryLogsOptionsSchema, matching executeSqlOptionsSchema.query.

tsc --noEmit, biome ci ., and the server.test.ts + logs.test.ts suites (114 tests) all pass.

🤖 Addressed by Claude Code

@barryroodt barryroodt left a comment

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.

Thanks for the quick turnaround @jordienr

  • function_edge_logs added to the source list
  • .min(1) on the schema
  • behavioral tests for query_logs

LGTM

@mattrossman, @Rodriguespn - since I'm still the new guy, perhaps a quick scan and thumbs-up from either of you would be advisable 😁

@mattrossman mattrossman added the publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/ label Jul 17, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/@supabase/mcp-server-postgrest@c1bdc7f
pnpm add https://pkg.pr.new/@supabase/mcp-server-supabase@c1bdc7f
pnpm add https://pkg.pr.new/@supabase/mcp-utils@c1bdc7f

commit: c1bdc7f

Comment thread packages/mcp-server-supabase/src/platform/api-platform.ts
@Ziinc

Ziinc commented Jul 21, 2026

Copy link
Copy Markdown

on further thought, let's leave both tools and potentially deprecate and remove getLogs in favour of queryLogs.
Note that Clickhouse backed querying is only available for production, cli/self-hosted will not have it yet and we need to document that discrepancy as this will break if people try querying against cli. they should use the getLogs tool until full support is provided.

Comment thread packages/mcp-server-supabase/src/tools/debugging-tools.ts
- mark get_logs as deprecated on hosted projects in favour of query_logs,
  while keeping it as the path for CLI/self-hosted
- document that query_logs (ClickHouse) is hosted-only and will not work
  on CLI/self-hosted yet
@jordienr

Copy link
Copy Markdown
Member Author

Addressed the deprecation + platform-availability guidance in a4d7228:

  • get_logs is now marked deprecated on hosted (production) projects in favour of query_logs, while remaining the documented path for local (CLI) and self-hosted projects.
  • query_logs description now states it's hosted-only — ClickHouse-backed querying isn't available on CLI/self-hosted yet, and to use get_logs there — so a model won't try it against an unsupported project and break.

🤖 Addressed by Claude Code

@barryroodt

Copy link
Copy Markdown
Contributor

Following up with fresh numbers against the current head (ecc456d)

Rerun of our eval A/B against the new head: passed all 3 eval checks on the first attempt, and the a4d7228 deprecation note fully flipped tool selection, 8 query_logs calls and zero get_logs (previously the model mixed both). The first genuine ClickHouse aggregation (countIf(toInt32OrZero(...))) succeeded as-is. For contrast, the get_logs-only baseline on main had one run die on context length counting raw preset rows, so the deprecation direction looks well supported.

One finding: the 24 hour default still silently widens narrow questions. The eval asks about the last 15 minutes; all 8 query_logs calls in the fresh run passed no timestamps, inheriting the 24 hour window, even though the description now mentions iso_timestamp_start/iso_timestamp_end. Mentioning the window permissively isn't landing; in production that means wrong counts whenever older logs exist.

Suggestion: same mechanism as a4d7228 - make the line directive, something like "when the user asks about a specific time range, always pass iso_timestamp_start/iso_timestamp_end to match it".

Unrelated side effect: this exercise surfaced drift in our own evals fixture, fixed in supabase/evals#99.

The permissive mention of iso_timestamp_start/iso_timestamp_end wasn't
steering model behaviour, so narrow time-range questions silently inherited
the 24h default and over-counted. Make it a directive instruction in both
get_logs and query_logs, matching the mechanism that flipped tool selection.
@jordienr

Copy link
Copy Markdown
Member Author

Thanks for the fresh eval run — great to see the deprecation note flip selection cleanly, and good catch on the time-range default.

Fixed in ea6d163: the timestamp guidance is now a directive rather than a permissive mention, using the same mechanism as the deprecation line. Both query_logs and get_logs now say:

When the user asks about a specific time range, always pass iso_timestamp_start and iso_timestamp_end to match it; otherwise the query defaults to the last 24 hours and will return results from a wider window than intended.

Applied to get_logs too since it shares the identical 24h defaulting. Whenever you next rerun the A/B I'd be curious whether the 15-minute question now passes an explicit window.

🤖 Addressed by Claude Code

@barryroodt

Copy link
Copy Markdown
Contributor

Nice! Reran the A/B against ea6d163 and the directive line does exactly what we hoped: the model now passes an explicit 15 minute window matching the question on every analytical query_logs call (4 of 4), and the only windowless calls are orientation probes (a now() check and a quick sample of the data), which is the behaviour you'd want. Still 3/3 checks on the first attempt, still 100% query_logs over get_logs on hosted.

Comment thread packages/mcp-server-supabase/src/tools/debugging-tools.ts
Comment thread packages/mcp-server-supabase/src/tools/debugging-tools.ts
Comment thread packages/mcp-server-supabase/src/tools/debugging-tools.ts
Comment thread packages/mcp-server-supabase/src/tools/debugging-tools.ts Outdated
The description promises iso_timestamp_start defaults to 24h before the end,
but the handler always computed start from now(), so supplying only
iso_timestamp_end produced an inverted/empty window. Derive the end first
(supplied or now), then default start to end - 24h, shared by get_logs and
query_logs.
smeubank added a commit to supabase/supabase that referenced this pull request Jul 24, 2026
…s split

this committ assume we will have merged or should be merged to gether with cahnges on skills and MCP

- supabase/agent-skills#112
- supabase/mcp#333

 query_logs (hosted only, ClickHouse SQL) is now the preferred tool for
  production projects; get_logs remains the only option for local and
  self-hosted. Updates the MCP tools table, log-querying section, and skill
  step 3 in ai-agents.mdx, and the tip admonition in debugging.mdx.
  logs.mdx already had this right at line 321.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants