Skip to content

feat: Add analytics catalog and query tools#57

Open
codebreach wants to merge 2 commits intomainfrom
sql-api
Open

feat: Add analytics catalog and query tools#57
codebreach wants to merge 2 commits intomainfrom
sql-api

Conversation

@codebreach
Copy link
Copy Markdown
Contributor

@codebreach codebreach commented May 6, 2026

User description

Overview

This PR introduces new Analytics tools to the SpotDraft MCP server, allowing users to discover schemas and execute read-only GoogleSQL queries against their workspace's analytics data. Additionally, it improves the core API client by enforcing strict handling of HTTP redirects to prevent unintended request alterations.

Key Changes

1. New Analytics Tools

  • get_analytics_catalog: Added a new tool to fetch allowlisted analytics tables and column schemas. It includes an optional search parameter to filter results by table or column name/description.
  • run_analytics_query: Added a new tool to execute read-only GoogleSQL against analytics data. Features include:
    • Strict character limit validation (Max 10,000 characters).
    • Support for the optional user_scoped boolean parameter to toggle workspace-wide vs. user-restricted record visibility.
    • Embedded rich documentation mapping directly to the upstream OpenAPI spec (analytics_query_docs.ts).

2. HTTP Redirect Handling in SpotDraftClient

  • Updated the SpotDraftClient GET and POST methods to use redirect: 'manual'.
  • Introduced rejectIfRedirect, a helper method that catches HTTP 3xx responses and throws an explicit APIError. This prevents fetch from silently following redirects (which is critical, as following a redirect can strip payloads and convert POST requests into GET requests).

3. Documentation & Tool Registration

  • README.md: Listed the new Analytics section and its available tools.
  • AGENTS.md: Updated the central tool listing and added a dedicated src/tools/analytics/AGENTS.md file to provide maintainers with upstream API context and internal component responsibilities.
  • src/tools/index.ts: Registered get_analytics_catalogTool and run_analytics_queryTool in the global tool registry.

Generated description

Below is a concise technical summary of the changes proposed in this PR:
Add new analytics catalog discovery and query tools so users can inspect allowlisted schemas and execute read-only GoogleSQL through get_analytics_catalog and run_analytics_query. Document their availability in the registry and reference materials to guide maintainers on the analytics surface.

TopicDetails
Analytics tools Deploy analytics catalog discovery and query tools with schema-aware validation, rich OpenAPI-aligned docs, and registry wiring while updating README/AGENTS to describe get_analytics_catalog and run_analytics_query usage.
Modified files (7)
  • README.md
  • src/tools/AGENTS.md
  • src/tools/analytics/AGENTS.md
  • src/tools/analytics/analytics_query_docs.ts
  • src/tools/analytics/get_analytics_catalog.ts
  • src/tools/analytics/run_analytics_query.ts
  • src/tools/index.ts
Latest Contributors(2)
UserCommitDate
madhav@spotdraft.comfeat: introduce get_an...May 06, 2026
pranavpandey2511@gmail...Mcp response compactio...May 05, 2026
Redirect handling Enforce manual redirect handling in SpotDraftClient GET/POST so the new rejectIfRedirect helper surfaces HTTP 3xx responses instead of silently following redirects.
Modified files (1)
  • src/spotdraft_client.ts
Latest Contributors(2)
UserCommitDate
madhav@spotdraft.comfeat: add run_analytic...May 06, 2026
achuth10fix: Add 'Enforce-User...March 05, 2026
This pull request is reviewed by Baz. Review like a pro on (Baz).

codebreach added 2 commits May 6, 2026 18:39
- Introduced `run_analytics_query` tool for executing SQL analytics against contract data.
- Added detailed documentation for the tool in AGENTS.md and analytics_query_docs.ts.
- Implemented error handling for redirect responses in SpotDraftClient.
- Updated README.md and AGENTS.md to include the new analytics tool.
- Added `get_analytics_catalog` tool for retrieving allowlisted analytics tables and column schemas.
- Updated README.md and AGENTS.md to include the new analytics tool.
- Enhanced analytics_query_docs.ts with guidance on using `get_analytics_catalog` for accurate schema discovery.
- Revised AGENTS.md in the analytics directory to reflect the new tool and its usage.
@sachin-spotdraft sachin-spotdraft added the size/m < 500 lines of changes label May 6, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e9c7aef22

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

body.user_scoped = request.user_scoped;
}

return spotdraftClient.post('/v2.1/public/analytics/query', body);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Call analytics query endpoint with trailing slash

run_analytics_query posts to /v2.1/public/analytics/query (no trailing slash), but this tool’s own upstream contract documents the route as /v2.1/public/analytics/query/. In this same commit, SpotDraftClient.post() was changed to redirect: 'manual' and now throws on any 3xx via rejectIfRedirect, so environments that canonicalize the missing-slash URL with a 301/308 will now fail every analytics query with a 502 instead of executing it.

Useful? React with 👍 / 👎.

Comment thread src/spotdraft_client.ts
Comment on lines +130 to +133
throw new APIError(
`SpotDraft API returned HTTP ${response.status} redirect (${location ?? 'missing Location header'}). Set baseUrl to the canonical API URL so requests do not redirect (scheme, host, path, trailing slashes).`,
502,
{ endpoint, method, redirectStatus: response.status, location, requestUrl: url },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since location/requestUrl are passed verbatim into APIError and MCPError.toJSON() serializes context unchanged, should we omit or redact them from client-facing error context?

Finding type: Basic Security Patterns | Severity: 🔴 High


Want Baz to fix this for you? Activate Fixer

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
src/spotdraft_client.ts around lines 124-135 in the rejectIfRedirect method, stop
including redirect target and full request URL in the thrown APIError context. Instead
of passing location and requestUrl verbatim into the context, either omit those fields
entirely or sanitize them to only safe data (e.g., endpoint and API origin without query
string/internal path); ensure MCPError.toJSON (and any shared context serialization)
does not re-emit these sensitive values unchanged. Update the corresponding call sites
for rejectIfRedirect in get (around lines 137-168) and post (around lines 195-205) if
they rely on the removed context fields, and adjust tests/docs accordingly.

Comment on lines +34 to +40
const runAnalyticsQuery = async (
request: RunAnalyticsQueryRequest,
spotdraftClient: SpotDraftClient,
): Promise<unknown> => {
const query = request.query?.trim() ?? '';
if (query === '') {
throw new ValidationError('query is required and cannot be empty.', { field: 'query' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

request isn’t defaulted, so if MCP arguments is omitted the handler can pass undefined and request.query?.trim() throws before ValidationError('query is required...') can run—should we normalize to {} first?

Finding type: Logical Bugs | Severity: 🟠 Medium


Want Baz to fix this for you? Activate Fixer

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
src/tools/analytics/run_analytics_query.ts around lines 34-40 in the runAnalyticsQuery
function, the code uses request.query?.trim() but assumes request is always defined; if
the dispatcher passes undefined, this can throw a TypeError before the existing
ValidationError logic runs. Refactor the function signature or first line to normalize
request to an empty object when it’s undefined (e.g., default request = {}). Then
compute and validate the trimmed query from that normalized object so the “query is
required and cannot be empty” ValidationError is consistently returned for omitted or
empty inputs.

body.user_scoped = request.user_scoped;
}

return spotdraftClient.post('/v2.1/public/analytics/query', body);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

run_analytics_query calls /v2.1/public/analytics/query but the contract expects POST /v2.1/public/analytics/query/, and with SpotDraftClient rejecting 3xx redirects this should be updated to the slash-suffixed path.

Finding type: Breaking Changes | Severity: 🔴 High


Want Baz to fix this for you? Activate Fixer

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
`src/tools/analytics/run_analytics_query.ts` around lines 54-54 inside the
`runAnalyticsQuery` function, update the `spotdraftClient.post` path from
`'/v2.1/public/analytics/query'` to the canonical slash-suffixed endpoint
`'/v2.1/public/analytics/query/'` as specified by the analytics contract/docs. This
prevents a 3xx redirect that now gets rejected (causing a 502) due to `SpotDraftClient`
using `redirect: 'manual'`. After changing the path, quickly scan the repo for any other
calls to the no-slash analytics query endpoint and align them to the slash-suffixed
version.

Comment on lines +23 to +30
const getAnalyticsCatalog = async (
request: GetAnalyticsCatalogRequest,
spotdraftClient: SpotDraftClient,
): Promise<unknown> => {
const endpoint = '/v2.1/public/analytics/catalog/';
const search = request.search?.trim();
const queryParams = search !== undefined && search !== '' ? new URLSearchParams({ search }) : undefined;
return spotdraftClient.get(endpoint, queryParams);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

handleCallToolRequest() reads request.search even though request.params.arguments can be omitted per MCP, so empty calls can throw before tools can return the no-search catalog—can we default request to {} (or in the dispatcher) before accessing search?

Finding type: Logical Bugs | Severity: 🔴 High


Want Baz to fix this for you? Activate Fixer

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
src/tools/analytics/get_analytics_catalog.ts around lines 23-30, in the
`getAnalyticsCatalog` function, make the `request` parameter resilient to being omitted
by defaulting it to an empty object (e.g., treat `undefined` as `{}`) before reading
`request.search`. This prevents dereferencing/reading `search` when the MCP `tools/call`
omits `arguments` and ensures the tool returns the full catalog when no search is
provided. Apply the normalization at the entry point of `getAnalyticsCatalog` (or
equivalently in the dispatcher that calls it) and keep the existing trim/empty-string
behavior for `search`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m < 500 lines of changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants