Add MCP server endpoint at /api/mcp#1050
Conversation
Stateless Streamable HTTP MCP server mounted on the existing Fastify instance, so cloud and self-hosted deployments both expose it at <instance>/api/mcp. Authenticates with the existing user API keys (Authorization: Bearer); tool calls dispatch in-process to the documented REST routes via fastify.inject(), inheriting API key verification, site access checks, rate limits, and time validation. Ships 16 read-only tools: list_sites, overview/time-series, dimension breakdowns, live stats, sessions, events, errors, web vitals, retention, journeys, goals, funnels (list + analyze), and a get_query_schema + run_query escape hatch over the scoped custom query endpoint. Notes: - @modelcontextprotocol/sdk pinned to 1.17.5: the 1.18+ zod v3/v4 compat types blow up tsc memory (OOM at 8GB heap) - zod bumped 3.24.4 -> 3.25.76 (drop-in): the SDK's zod-to-json-schema requires the zod/v3 subpath introduced in 3.25 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a stateless, authenticated MCP endpoint with 39 REST-backed tools, API-key and OAuth support, discovery metadata, CORS handling, validation, sanitization, end-to-end tests, documentation, and a reusable analytics event schema. ChangesMCP foundation
Authentication and transport
Tool surface
OAuth integration and discovery
CORS policy
Validation and documentation
Reusable analytics schema
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant MCPClient
participant MCPRoute
participant Authenticator
participant MCPTool
participant RESTAPI
MCPClient->>MCPRoute: POST /api/mcp
MCPRoute->>Authenticator: Validate bearer credential
Authenticator-->>MCPRoute: Authentication context
MCPRoute->>MCPTool: Dispatch tools/call
MCPTool->>RESTAPI: Forward authenticated REST request
RESTAPI-->>MCPTool: Return REST data or error
MCPTool-->>MCPRoute: Sanitize and format tool result
MCPRoute-->>MCPClient: Return JSON-RPC response
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
server/src/mcp/apiClient.ts (1)
56-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider including
messagefrom error responses.Fastify's default error responses (e.g., for schema validation) often include a detailed
messageproperty alongsideerror(e.g.,{ "error": "Bad Request", "message": "querystring must have required property 'id'" }). Currently, this only extracts theerrorproperty, which might just be a generic HTTP status string like"Bad Request", masking the actual validation failure context from the MCP client.♻️ Proposed improvement
- const message = - parsed !== null && typeof parsed === "object" && "error" in parsed - ? String((parsed as { error: unknown }).error) - : `Request failed with status ${response.statusCode}`; + let message = `Request failed with status ${response.statusCode}`; + if (parsed !== null && typeof parsed === "object") { + const p = parsed as Record<string, unknown>; + if (typeof p.message === "string") { + message = p.message; + } else if (typeof p.error === "string") { + message = p.error; + } + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/mcp/apiClient.ts` around lines 56 - 59, Update the error-response parsing in the apiClient request failure handling to include the response object's message property when present, alongside or in preference to the generic error property. Preserve the status-based fallback when neither useful property exists, so detailed Fastify validation context reaches the MCP client.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@server/src/mcp/apiClient.ts`:
- Around line 56-59: Update the error-response parsing in the apiClient request
failure handling to include the response object's message property when present,
alongside or in preference to the generic error property. Preserve the
status-based fallback when neither useful property exists, so detailed Fastify
validation context reaches the MCP client.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 39c46ff7-3347-42e5-810d-f3f971a0b7f7
⛔ Files ignored due to path filters (1)
server/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (8)
server/package.jsonserver/src/api/analytics/generateCustomQuery.tsserver/src/index.tsserver/src/mcp/apiClient.tsserver/src/mcp/index.tsserver/src/mcp/inputs.tsserver/src/mcp/mcp.test.tsserver/src/mcp/tools.ts
Ports the protocol polish from codex/hosted-mcp onto the proxy architecture and tightens the data boundary: - Verify the API key before processing any MCP message (401 + WWW-Authenticate, 429 + Retry-After, 503 on verifier failure); tool calls still inherit the REST routes' own access checks - Bump @modelcontextprotocol/sdk to 1.29.0 and add outputSchema + structuredContent to every tool (top-level shapes pinned, tolerant below, since REST shapes are owned by the dashboard endpoints) - Gate get_sessions/get_events/run_query/get_query_schema behind MCP_ENABLE_RAW_DATA_TOOLS (default off); redact visitor IPs from all MCP output even when enabled - Strip control/bidi-override chars from proxied strings; return generic messages for unexpected errors and log details server-side - MCP-aware CORS: MCP_ALLOWED_ORIGINS allowlist for /api/mcp, MCP protocol headers in preflight, reject malformed Origin values - Cache-Control: no-store and a 1MB body limit on the endpoint - Move EVENT_SCHEMA to a leaf module so MCP tests stop importing the better-auth chain (fixes samlify/camelcase ESM collection failures) - Add docs page (docs/mcp.mdx) and env/docker-compose wiring Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/src/mcp/tools.ts (1)
608-635: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftBlock IP leakage from aliased
run_queryresults
ok(..., { redactKeys: IP_KEYS })only removes literalipkeys, soSELECT ip AS visitor_ip FROM scoped_eventsstill returns the address under a different field name. Sincerun_queryaccepts arbitrary SQL, redaction needs to be structural, not key-name based.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/mcp/tools.ts` around lines 608 - 635, Update the run_query handler around the api.call result and ok invocation to apply structural redaction to returned query rows, so IP addresses remain removed even when aliased (for example, visitor_ip) or nested under other fields. Do not rely solely on redactKeys: IP_KEYS; reuse the existing recursive redaction utility or add the smallest equivalent transformation before returning the result.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@server/src/mcp/tools.ts`:
- Around line 608-635: Update the run_query handler around the api.call result
and ok invocation to apply structural redaction to returned query rows, so IP
addresses remain removed even when aliased (for example, visitor_ip) or nested
under other fields. Do not rely solely on redactKeys: IP_KEYS; reuse the
existing recursive redaction utility or add the smallest equivalent
transformation before returning the result.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 83feb54c-be3f-4ba0-8855-7e49485fcfc8
⛔ Files ignored due to path filters (1)
server/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
.env.exampledocker-compose.cloud.ymldocker-compose.ymldocs/content/docs/(docs)/mcp.mdxdocs/content/docs/(docs)/meta.jsonserver/.env.exampleserver/package.jsonserver/src/api/analytics/generateCustomQuery.tsserver/src/api/analytics/utils/eventSchema.tsserver/src/lib/cors.test.tsserver/src/lib/cors.tsserver/src/mcp/auth.tsserver/src/mcp/index.tsserver/src/mcp/mcp.test.tsserver/src/mcp/tools.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- server/package.json
Grows the MCP surface PostHog-style while keeping the in-process proxy architecture (tools dispatch to existing REST routes via fastify.inject, inheriting their auth, role checks, and rate limits): - Add 22 tools: reads (get_site, get_session, get_users, get_user, list_members, list_teams) and writes for goals (create/update/delete), funnels (save/delete), sites (create/update config/delete), org members and teams (add member, site access, team CRUD), and persons (identify, update traits, GDPR delete) - Remove the MCP_ENABLE_RAW_DATA_TOOLS gate: raw-data tools and run_query are always registered - Remove IP redaction for full REST parity; keep control/bidi-char sanitization of tracked-traffic text - Split tools.ts into tools/ with one file per domain and shared helpers; widen RybbitApiClient to PUT/DELETE - Annotate every tool explicitly: the five delete_* tools carry destructiveHint, updates are idempotent, reads are read-only - Extend the 403 hint to explain admin/owner key-role requirements - Rewrite the docs page for the read-write surface with permissions and destructive-tools sections; drop the gate from env/compose files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/src/mcp/tools/users.ts`:
- Around line 92-95: Update the identify request in the guard callback to map
the input properties anonymous_id and user_id to camelCase JSON keys anonymousId
and userId, while preserving their values and the existing traits payload. Keep
the POST endpoint and surrounding api.call flow unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fc76f811-70e3-4434-8a96-22596ff55bc5
📒 Files selected for processing (18)
.env.exampledocker-compose.cloud.ymldocker-compose.ymldocs/content/docs/(docs)/mcp.mdxserver/.env.exampleserver/src/mcp/apiClient.tsserver/src/mcp/index.tsserver/src/mcp/inputs.tsserver/src/mcp/mcp.test.tsserver/src/mcp/tools/analytics.tsserver/src/mcp/tools/funnels.tsserver/src/mcp/tools/goals.tsserver/src/mcp/tools/index.tsserver/src/mcp/tools/organizations.tsserver/src/mcp/tools/rawData.tsserver/src/mcp/tools/shared.tsserver/src/mcp/tools/sites.tsserver/src/mcp/tools/users.ts
💤 Files with no reviewable changes (4)
- server/.env.example
- docker-compose.cloud.yml
- docker-compose.yml
- .env.example
🚧 Files skipped from review as they are similar to previous changes (1)
- server/src/mcp/apiClient.ts
| guard(async ({ site_id, anonymous_id, user_id, traits }) => | ||
| ok(await api.call("POST", `/sites/${site_id}/users/identify`, { body: { anonymous_id, user_id, traits } })) | ||
| ) | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Map JSON body payload properties to camelCase.
The backend REST API expects JSON body payloads to use camelCase (as evidenced by your usage of siteFeatureInputs, goalType, etc. in other tools). Passing anonymous_id and user_id directly in the POST body will likely cause the backend to drop or ignore these fields, resulting in a failed identity merge.
🐛 Proposed fix
- guard(async ({ site_id, anonymous_id, user_id, traits }) =>
- ok(await api.call("POST", `/sites/${site_id}/users/identify`, { body: { anonymous_id, user_id, traits } }))
+ guard(async ({ site_id, anonymous_id, user_id, traits }) =>
+ ok(await api.call("POST", `/sites/${site_id}/users/identify`, { body: { anonymousId: anonymous_id, userId: user_id, traits } }))
)📝 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.
| guard(async ({ site_id, anonymous_id, user_id, traits }) => | |
| ok(await api.call("POST", `/sites/${site_id}/users/identify`, { body: { anonymous_id, user_id, traits } })) | |
| ) | |
| ); | |
| guard(async ({ site_id, anonymous_id, user_id, traits }) => | |
| ok(await api.call("POST", `/sites/${site_id}/users/identify`, { body: { anonymousId: anonymous_id, userId: user_id, traits } })) | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/mcp/tools/users.ts` around lines 92 - 95, Update the identify
request in the guard callback to map the input properties anonymous_id and
user_id to camelCase JSON keys anonymousId and userId, while preserving their
values and the existing traits payload. Keep the POST endpoint and surrounding
api.call flow unchanged.
Implements the MCP authorization flow so OAuth-capable clients can connect without manually configured API keys: - Enable the better-auth mcp plugin (authorization-code + PKCE, dynamic client registration, built-in consent page, /login as the login page) - Serve RFC 8414/9728 discovery documents at the domain root: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource (plus the /api/mcp-suffixed variant), backed by the plugin's metadata builders - Advertise resource_metadata in the 401 WWW-Authenticate challenge from /api/mcp and expose the header for browser clients - Accept OAuth access tokens wherever API keys work: the MCP authenticator and the REST layer (checkApiKey/getUserIdFromRequest) fall back to auth.api.getMcpSession with expiry checks, so proxied tool calls inherit the same org-role authorization - Add oauthApplication/oauthAccessToken/oauthConsent tables to the drizzle schema (requires a one-time `npm run db:push`, not run here) - CORS: discovery documents are public; /api/auth/mcp/* honors MCP_ALLOWED_ORIGINS like /api/mcp - Docs: OAuth connect section and self-host migration notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/src/lib/auth-utils.ts`:
- Around line 352-356: Restrict OAuth token validation to Authorization-header
credentials by passing bearerToken, not apiKey, to getOAuthTokenUserId at both
affected sites: server/src/lib/auth-utils.ts lines 352-356 and 392-397. Keep the
existing resolveBearerUserOrgRole flow unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3b85f626-8b1a-4fb3-ac56-193a282144e9
📒 Files selected for processing (11)
docs/content/docs/(docs)/mcp.mdxserver/src/db/postgres/schema.tsserver/src/index.tsserver/src/lib/auth-utils.tsserver/src/lib/auth.tsserver/src/lib/cors.tsserver/src/mcp/auth.tsserver/src/mcp/index.tsserver/src/mcp/mcp.test.tsserver/src/mcp/wellKnown.test.tsserver/src/mcp/wellKnown.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- server/src/index.ts
- server/src/mcp/index.ts
- server/src/lib/cors.ts
- server/src/mcp/mcp.test.ts
- docs/content/docs/(docs)/mcp.mdx
/api/mcp and the OAuth endpoints under /api/auth/mcp/* now follow the standard CORS policy: trusted origins (BASE_URL + dev origins) or no Origin header at all, which is what server-side MCP clients send. The OAuth discovery documents stay publicly fetchable and the MCP protocol headers stay in the shared preflight allowlist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MCP clients fetch /.well-known/oauth-* at the domain root; without this handle those requests land on the client app and OAuth discovery fails. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This update introduces a new entry with index 10, version 7, and associated timestamp, enhancing the journal's tracking capabilities.
MCP clients try authorization-server discovery via RFC 8414 AND OIDC
discovery (/.well-known/openid-configuration), including path-inserted
variants for the /api/mcp resource. Only the base RFC 8414 path was
served, so clients like Claude Code choked on the client app's HTML 404
("JSON Parse error: Unrecognized token '<'"). Serve the same metadata
at all four variants, mark them CORS-public, and route the OIDC path to
the backend in Caddy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OAuth token exchanges are application/x-www-form-urlencoded per RFC 6749, but the better-auth mount only registered a passthrough parser for application/json, so Fastify replied 415 before the auth handler ran. Dynamic client registration (JSON) worked while every token exchange failed, leaving MCP OAuth logins permanently unauthenticated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bearer credentials were all-or-nothing (full user org role); this adds a
scope layer so an agent can be handed a read-only or narrowly-scoped
credential. Scopes constrain bearer credentials only — cookie sessions
bypass by construction, and scopes never elevate (role checks still apply).
- lib/scopes.ts: resource:action taxonomy (15 resources), parsers for
API-key permissions (Record) and OAuth token scopes (space string),
hasScope reusing better-auth's role().authorize matcher (write⊇read),
and the createApiKey zod schema. null statements = unrestricted (legacy
keys / standard-only OAuth grants); {} = deny-all.
- auth-utils: checkApiKey returns BearerAuthResult carrying statements;
OAuth fallback carries token scopes; getUserHasAccessToSitePublic
threads the scope into its own bearer fallback.
- auth-middleware: the 6 bearer-capable guards become scope factories;
the check sits inside the bearer branch only, replies 403
{ error: "Insufficient scope", required } distinct from role 403s.
- index.ts: per-route scope assignment via scoped guard helpers; account
and billing routes are "deny-scoped".
- createApiKey: optional permissions body param, validated + forwarded.
- auth.ts: register the custom scopes in the mcp plugin's oidcConfig and
advertise them in scopes_supported (AS + protected-resource metadata).
- MCP: authenticator carries scopes into tool registration; tools/list is
filtered to the credential's scopes (list_sites always shown); tool
calls still enforce authoritatively through the REST guards.
- trackEvent: trusted server-side ingestion requires ingest:write.
Backward compatible: existing keys and OAuth tokens read as unrestricted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lets a user mint a least-privilege API key from Settings → Account: an optional "Restrict permissions" toggle reveals a resource:action grid (read/write per resource), and the selected permissions are sent to POST /api/user/api-keys. Off by default = full-access key, unchanged. - Promote the scope taxonomy to @rybbit/shared (SCOPE_MATRIX, types, ALL_SCOPE_STRINGS, plus SCOPE_DESCRIPTORS with UI labels) as pure data; server/src/lib/scopes.ts now imports it and keeps only the zod + better-auth matcher logic. Single source of truth, no client/server drift. - ApiKeyScopePicker: checkbox grid over SCOPE_DESCRIPTORS; selecting write keeps read on and locked (mirrors the server's write⊇read rule); resources with a single action show one checkbox. - ApiKeyManager: restrict toggle + picker; client-side guard against an empty restricted set (which the server rejects); reset on success. - useCreateApiKey: accept optional permissions in the request body. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses the top verified findings from the branch review: - #2 Member PII past scopes: GET /organizations returned every member's name and email to any bearer credential (even a read-only or deny-all key), while the sibling /members route is org:read-gated. Cookie-session dashboard requests still get the roster; bearer credentials get sites only (what list_sites needs) and use the org:read /members route for member data. - #3 Bearer parsing fidelity: the MCP gate accepted a looser header form (lowercase scheme, extra spaces) than the REST routes it proxies via startsWith("Bearer ")+substring(7), so initialize/tools-list would authenticate and then every tools/call 401'd. Align extractBearerToken to REST exactly. - #6 Silent wrong data: an LLM filling both past_minutes and a date range got the trailing window returned as the requested range. Reject the combination with a message the model can act on. - #10 Discovery gap: custom scopes were set on oidcConfig.metadata, which only feeds the RFC 9728 doc; better-auth builds the RFC 8414 doc from a top-level option it doesn't expose. Inject scopes_supported in wellKnown.ts where we re-serve the root document; fix the wrong comment. - #7 get_retention advertised range min 1 but the endpoint clamps to a 7-day floor; reject <7 instead of returning a wider window than asked. - #11 list_sites output schema is a strict boolean while site.public is nullable; coalesce so the entry-point tool can't throw on a null row. - #4 Scope picker: write auto-added read even for write-only resources (ingest), producing a body the server 400s; only add read where valid. Tests: extractBearerToken parity, past_minutes/date conflict, and a getMyOrganizations PII test (session sees roster, bearer does not). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The verify -> rate-limit -> OAuth-fallback -> expiry ladder was implemented three times (checkApiKey, getUserIdFromRequest, the MCP gate), which is what let the gate and the REST routes drift apart on Bearer parsing. Extract one resolveBearerIdentity into a leaf module (lib/bearerAuth.ts) that takes injected deps, so the MCP layer and its tests can use it without loading the better-auth chain. All three call sites now share it, including the single canonical extractBearerToken. #9 (2x rate-limit burn): a tool call verified the key at the MCP gate and again inside the proxied REST route, and better-auth's verifyApiKey has no non-counting mode, so a rate-limited key exhausted twice as fast. The gate now registers an in-process handoff (a crypto-random, credential-bound, request-scoped nonce passed on the inject header); the REST guards consume it and skip the second verify. It is a pure optimization that fails safe — a missing, stale, forged, or mismatched nonce simply falls through to normal verification, so it can never authenticate as another user. Org-role resolution still runs per route (it's a DB query, not rate-limited), so access checks are unchanged. Tests: resolveBearerIdentity ladder (key/oauth/expiry/rate-limit/error), handoff safety (match, token-mismatch, missing, release), and a checkApiKey test asserting verifyApiKey is NOT called on a valid handoff but IS on a mismatched one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alues The scope picker imported SCOPE_DESCRIPTORS/SCOPE_MATRIX as runtime values from @rybbit/shared. Every other client import of that package is a type (erased at compile), so the picker was the first to force Turbopack to bundle the `file:../shared` workspace package at runtime — which it can't resolve, failing the production build with "Can't resolve '@rybbit/shared'". Inline the display taxonomy (matrix + labels) into the picker, matching how the client has always used shared (types only). No runtime import from shared remains, so no next.config transpilePackages/alias workaround is needed. Move the UI-only SCOPE_DESCRIPTORS out of shared; the server keeps SCOPE_MATRIX/ALL_SCOPE_STRINGS as its source of truth. Verified: client production build compiles successfully with zero module-resolution errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Introduced a new PermissionsBadge component to display API key permissions with tooltips for better user experience. - Updated ApiKeyManager to handle API key creation and deletion more effectively, including improved clipboard functionality for copying keys. - Enhanced ApiKeyScopePicker to include a clear selection option and improved UI for better accessibility and usability. - Adjusted styles for better visual consistency across components. These changes aim to improve the user interface and experience when managing API keys and their associated permissions.
Stateless Streamable HTTP MCP server mounted on the existing Fastify instance, so cloud and self-hosted deployments both expose it at /api/mcp. Authenticates with the existing user API keys (Authorization: Bearer); tool calls dispatch in-process to the documented REST routes via fastify.inject(), inheriting API key verification, site access checks, rate limits, and time validation.
Ships 16 read-only tools: list_sites, overview/time-series, dimension breakdowns, live stats, sessions, events, errors, web vitals, retention, journeys, goals, funnels (list + analyze), and a get_query_schema + run_query escape hatch over the scoped custom query endpoint.
Notes:
Summary by CodeRabbit
New Features
Documentation
Bug Fixes