vTilt
Why vTiltHow It WorksFeaturesFAQDocs
Docs / MCP server
Quick startEvent forwarding
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceGoogle Ads
Client setup
CursorClaude DesktopVS CodeCodex
Realtime
Debug ViewRealtime Dashboard
Integration guides
Frontend frameworks
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus
Backend frameworks
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails
Backend languages
PythonPHPRubyElixirGoJava.NET / C#Rust
Stack guides
Vue + PHP
SDK
Browser SDK
InstallScript bundlesEvent trackingAutocaptureIdentify & aliasWeb VitalsSession recordingChat widgetFeature readinessRemote configurationReverse proxyDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdown

Documentation

vTilt
Quick startEvent forwarding

MCP server

OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceGoogle Ads

Realtime

Debug ViewRealtime Dashboard

Integration guides

SDK

MCP server

Connect Claude, Cursor, ChatGPT and other AI clients to your vTilt project. Ask "how many active users last week?" and your agent calls vTilt MCP tools to answer.

The vTilt MCP server lets your AI client — Cursor, Claude Desktop (via mcp-remote), VS Code, ChatGPT, Codex, custom agents — talk directly to your vTilt project data over the Model Context Protocol. The agent calls tools like kpis-get, persons-list, query-run, or memory-ask; vTilt runs them with the same privileges your dashboard uses, scoped to whatever project you've pinned. Almost every tool is read-only; project-update is the only write tool today, and only credentials minted with explicit write access can see or invoke it.

#Documentation map

Use the sidebar Guides and Client setup groups the same way as this table: concepts first, then wire your editor.

TopicPage
Personal API keys, headers, errorsAuthentication
Browser OAuth (Claude, ChatGPT, Cursor, …)OAuth
Slash-command prompts (/vtilt:…)Agent skills (prompts)
AI memory, VQL :embed(), memory-ask, person-memory-getAI intelligence
CursorCursor
Claude DesktopClaude Desktop
VS CodeVS Code
ChatGPT / CodexCodex

Tip

Tip: New here? Authentication → pick your client setup guide. Prefer sign-in with your vTilt account instead of copying a long key? Start with OAuth.

#Supported tools

The server defines a core of twenty-five tools — twenty-four read-oriented surfaces plus one write demonstrator (project-update) — plus a Google Ads tool set (3 read + 17 write) that is always listed and becomes callable once a Google Ads account is connected to the project. Each tool exposes both an input schema (the arguments it accepts) and an output schema (the shape of its successful result) over MCP tools/list, so your agent can reason about the data it's about to receive without first making a probe call. Successful responses are validated server-side against the output schema before they leave vTilt — if a tool ever returns a payload that doesn't match its declared shape, the call surfaces as an error rather than a malformed result.

The catalog spans orientation (vtilt-guide, entities-schema-get, context-get), analytics & events, persons, session recordings, campaigns, event destinations, the open-ended VQL query layer (including AI memory tables when enabled), natural-language AI intelligence helpers, the public-docs reader (docs-search), project metadata + configuration + writes + navigation, and — when connected — Google Ads account reads and writes. switch-project, feature-gated tools, and write tools are omitted from the list when they cannot apply (see the notes after the table).

Tip

Tip: On any non-trivial task, point your agent at vtilt-guide first and context-get (includes data_model_hints for traffic sources). For "where did visitors come from?" / Google Ads vs direct, use attribution-breakdown-get — not kpis-get top_sources (gclid-only paid clicks appear as "direct" there).

ToolDescription
vtilt-guideThe platform mental model for agents — the four-entity data model, a decision tree, the full tool map, and worked end-to-end examples. No arguments, always available. Call it first when unsure which tool to use or how vTilt data is organised.
entities-schema-getWhere each kind of data lives (events vs persons vs AI memory vs derived sessions): useFor / notFor, key property paths with ready-made VQL JSONExtractString fragments, and an attribution block ($initial_* on persons.properties, not events.$referrer). No arguments, always available. Call before writing SQL for traffic sources or "where users came from".
context-getSituational awareness in one call: the active project + how it was pinned, your permissions, whether your credential can write, and which features (replay / AI memory / chat) are enabled. Always available.
kpis-getHeadline volume KPIs (visits, pageviews, bounce rate, top countries/browsers) for a date range. top_sources is session-entry referrer — Google Ads without an HTTP referrer appears as "direct". Use attribution-breakdown-get for acquisition. Requires analytics:read.
attribution-breakdown-getFirst-touch traffic source breakdown from persons.properties ($initial_gclid, $initial_gad_source, $initial_referring_domain). Splits Google Ads (Search vs Search Partners vs gclid-only) from true direct. Optional date_from / date_to filter by person first-seen. Requires person:read.
events-recentNewest-first event feed. Optional filters: event_name, distinct_id, since. Parses properties as JSON; for $autocapture, adds synthetic properties.$element from stored $elements_chain. Requires analytics:read.
event-types-listDiscover the project's event vocabulary — distinct event names with per-name counts and last-seen timestamps. The right first call when an agent doesn't yet know which custom events your SDK emits. Optional substring filter and since. Requires analytics:read.
persons-listPaginated list of identified persons. Optional substring filter against name/email/id. Requires person:read.
persons-getOne person's profile (name, email, properties) plus every distinct id linked across merges. Requires person:read.
recordings-listSession recording metadata (id, distinct id, duration, first url, click/keypress counts). Requires replay:read.
campaigns-listList campaigns on the active project — id, name, description, status, AI model, schedule type. Optional status filter (draft / active / paused / archived). Requires campaign:read.
campaign-getFetch one campaign by id — targeting query, business context, conversion definition, schedule config, branding metadata. The full rendered HTML body is intentionally omitted. Requires campaign:read.
event-destinations-listList event-forwarding destinations (Facebook CAPI, GA4 server-side, PostHog, gtag-based GA4 / Google Ads) with names, types, enable state, consent category, and a non-secret credential summary. Bearer tokens are never returned. Requires settings:read.
event-destination-getFetch one destination by id with the full forwarding config (event filters, mappings, gtag proxy mode, conversion mappings). Bearer tokens / Meta access keys are never included. Requires settings:read.
query-runExecute a VQL query (a single ClickHouse SELECT against a curated catalog of analytics tables) and return the rows as JSON. Tenant scoping and read-only safety are enforced server-side. When rows include event + properties, $autocapture responses add synthetic properties.$element (parsed from $elements_chain).
query-validateDry-run a VQL query (parse + catalog + permissions) without touching ClickHouse. Returns structured { code, message, hint } errors so agents can self-correct before paying the round-trip.
schema-getReturn the VQL virtual-table catalog the caller can query: table names, column lists, descriptions, required permissions. Tables the caller cannot access are omitted. Pair with entities-schema-get when you need to know which entity owns a field (especially $initial_* attribution keys).
memory-askNatural-language question over AI memory — the server plans SQL, runs it, and returns { intent, sql, results, answer? } so the agent can audit what executed. Requires analytics:read and AI memory enabled on the project. See AI intelligence.
person-memory-getCanonical snapshot of one person's latest AI memory row (hot ai JSON fields flattened). Requires person:read. See AI intelligence.
docs-searchSearch the vTilt public documentation. Returns matched pages with title, URL, description, and a body snippet — useful when an agent needs to remember a config option, an SDK signature, or an integration step. Available to every authenticated MCP client.
project-getActive project's id, slug, settings, timestamps.
project-config-getA project's feature configuration (not its data): session-recording masking (mask_all_inputs / mask_all_text), replay sample rate, autocapture, consent / DNT, chat config. The right tool for "are inputs masked in recordings?". No secrets are returned. Requires settings:read.
project-updateWrite. Rename the active project. Requires project:update and a write-scoped credential (mcp:write OAuth scope or a personal API key created with Read + write access). Every successful or failed call lands a row in the MCP audit log.
projects-listEnumerate the projects this credential can read, plus the active project id and how it was pinned (header / query / session / default). The discovery tool agents reach for when the user names a project conversationally or you need to confirm which project the next tool will target. Always available.
switch-projectPin a different project for this MCP lane's Redis session. Accepts project_id (UUID, preferred) or project_name — matched fuzzily (exact → prefix → substring, case-insensitive); when no name matches, the error lists the closest project names so the agent can retry. Omitted from tools/list when you have only one accessible project, or when the project is fixed with x-vtilt-project-id / ?project_id=.

Note

Note: Read-only personal API keys and OAuth tokens without the mcp:write scope never see project-update in tools/list. switch-project is omitted when you have only one accessible project, or when the project is pinned via x-vtilt-project-id / ?project_id= — the exact number of tools your agent sees depends on permissions, write access, and pinning.

Tip

Tip: Every project-scoped tool (everything in the table except projects-list, switch-project, and docs-search) accepts an optional project_id argument as a per-call override. The argument is rarely needed in conversations that stay on one project, but it is the cleanest way to read two projects in one chat ("compare Marketing and Product") or to run parallel chats off the same MCP connection without racing on the Redis session pin. See Parallel chats on one MCP config below for the full pattern.

Note

Note: Tools that need a specific dashboard permission (analytics:read, person:read, replay:read, chat:read, campaign:read, settings:read, project:update, …) are silently hidden from tools/list when the caller lacks that permission on the active project. Org owners and admins always see the full list. The VQL trio (query-run, query-validate, schema-get) is always listed because VQL enforces permissions per referenced table at compile time — schema-get only returns tables you are allowed to read. docs-search, vtilt-guide, entities-schema-get, and context-get are always exposed.

Note

Note: Tools are also hidden when the active project has the feature they depend on turned off — recordings-list requires session replay to be enabled, and the AI-memory tools (memory-ask, person-memory-get) require AI memory to be enabled. context-get reports which features are on, so an agent can check before reaching for a tool that wouldn't apply.

Note

Note: The Google Ads tools (google-ads-search, google-ads-resource-metadata-get, google-ads-customers-list, and the google-ads-* write tools) are always listed in tools/list; calls return feature_disabled until a Google Ads account is connected to the project. They act on your Google Ads advertiser account, not on vTilt analytics — reads need google_ads:read, writes need google_ads:write and a write-scoped credential, and every write is audited. After switch-project, check features.google_ads in the response. See Google Ads for the connection steps and full tool list.

#What you can ask

These prompts work as soon as the server is connected and a project is pinned:

PromptTools the agent will reach for
"What's our top traffic source?" / "Is it Google Ads or direct?"attribution-breakdown-get (not kpis-get top_sources)
"How many visits did we have last week vs the previous one?"kpis-get
"What events am I tracking on this project?"event-types-list
"Show me the 20 most recent events for distinct id device_abc."events-recent
"What button did this user click?" / recent $autocaptureevents-recent with event_name=$autocapture, or query-run with event, properties
"Find the person with email alice@example.com and list their last 10 sessions."persons-list → persons-get → recordings-list
"What's the bounce rate this month for www.example.com? Compare it to last month."kpis-get (twice)
"Summarise the last 50 recordings — which pages are users dropping off on?"recordings-list
"Which campaigns are currently active and what's their schedule?"campaigns-list
"Show me the targeting query for the Win-back churned users campaign."campaigns-list → campaign-get
"Is my GA4 destination wired up and forwarding purchase_completed?"event-destinations-list → event-destination-get
"Switch me to project Marketing site and tell me which browsers are most used."switch-project → kpis-get
"How do I install the browser SDK in a Next.js app?"docs-search
"What did we learn from sessions that look like rage-quits on checkout?"memory-ask or VQL with :embed() — see AI intelligence
"Summarise everything we know about person abc123 from AI memory."person-memory-get
"Rename this project to Acme Marketing." (write-scoped credential required)project-update
"Which Google Ads campaigns spent the most last month?" (account connected)google-ads-resource-metadata-get → google-ads-search
"Pause the Brand - Exact Google Ads campaign." (write-scoped credential required)google-ads-search → google-ads-campaign-status-set

The agent picks the tools, the project, and the time ranges automatically — you just describe what you want.

#How it works

Your AI client (Cursor / Claude Desktop / ChatGPT / …)
        │
        ▼   POST https://www.vtilt.com/api/mcp + Authorization: Bearer vtu_…
vTilt MCP server (hosted)
        │
        ▼
Tool registry  ──►  Entity action  ──►  Postgres / ClickHouse / Redis

Three properties matter:

  1. Reads are read-only; writes are explicit and audited. The read tools touch no customer data and write nothing to your account except a per-lane "pinned project" pointer in Redis (see Pinning a project). The first write tool (project-update) requires a personal API key created with Read + write access (or an OAuth token with the mcp:write scope) — it isn't visible to read-only credentials at all. Every write call lands a durable row in the MCP audit log so admins can answer "who renamed this project?" months later.
  2. It's scoped to your projects. A vtu_ key is owned by you, not by a project. Tools resolve the active project from a header, query parameter, or per-lane session pin — you can switch projects mid-conversation with switch-project.
  3. You control the budget. Every key counts against a 60-request-per-minute sliding window. Hit the limit and the next call returns a JSON-RPC error with a retryAfter hint.

#Connect a client

ClientGuide
CursorCursor
Claude DesktopClaude Desktop
VS Code (Copilot)VS Code
ChatGPT / CodexCodex
Anything elsePoint it at https://www.vtilt.com/api/mcp with a Bearer header — the authentication page lists the exact header format.

#Pinning a project

Most clients let you pass headers or query parameters per connection. vTilt accepts:

MethodExample
Headerx-vtilt-project-id: 7c5c…
Headerx-vtilt-organization-id: 123e… (project-less reads)
Headerx-vtilt-mcp-session-id: <uuid> (optional — isolates switch-project pins per agent when multiple MCP connections share one key; see Cursor)
Query parameterhttps://www.vtilt.com/api/mcp?project_id=7c5c…
Session — switch-project toolthe agent calls it for you on demand

If you only have one project, you don't need to pin anything — the server auto-pins to your single project.

Note

Note: Until you add x-vtilt-mcp-session-id, every MCP connection for the same user credential shares one Redis pin namespace — switch-project in one agent can change the project another agent sees. Use a static project pin (x-vtilt-project-id / ?project_id=) on duplicate MCP server entries, or send a distinct UUID per entry via x-vtilt-mcp-session-id.

#Switching projects conversationally

You don't need to copy a UUID from the dashboard — once the MCP server is connected, just say what you want:

"Switch to the Marketing site project and tell me last week's bounce rate."
text

The agent will:

  1. Call projects-list to discover the catalog (id, organization, name for every project this key can read) and to see which project is currently active.
  2. Call switch-project with { "project_name": "Marketing site" } — case-insensitive, trimmed. If the name matches a unique project the pin sticks for the rest of the conversation; if it matches more than one, the tool returns the matching ids so the agent can disambiguate.
  3. Call the analytics tool (kpis-get in this example) against the newly active project.

If the agent already has the UUID (e.g. you pasted one), switch-project with project_id is preferred — it's unambiguous.

Tip

Tip: Ask "which projects do I have access to?" any time to make the agent call projects-list — handy when you can't remember the exact display name.

#Parallel chats on one MCP config

If the same MCP entry in your mcp.json powers several chats at once (Cursor tabs, VS Code chats, cloud agents) and each chat is working on a different project, switch-project is the wrong tool — its Redis session pin is shared across the connection and concurrent calls overwrite each other. Pick one of:

  • Per-call project_id (recommended). Every data tool — kpis-get, events-recent, persons-list, query-run, project-update, … — accepts an optional project_id argument that overrides the active project for that single call only. The session pin is not touched, so two chats can hit different projects on one MCP config without racing. Ask each chat "work on the Marketing project" / "work on the Product project" and the agent will append project_id automatically.
  • One-off comparisons ("compare Marketing and Product"). Same pattern — pass project_id per call instead of switch-project-ing back and forth. The audit log records the effective project per row (project_id_source = 'param' for overrides, 'session' / 'default' for pin-path reads).
  • Distinct lane UUIDs — for older agents that don't pass project_id per call, add a different x-vtilt-mcp-session-id per duplicate server entry so each connection gets its own Redis pin namespace (Cursor shows the exact JSON).
  • Per-project server entries — register vtilt-marketing and vtilt-product as separate MCP servers, each with its own static x-vtilt-project-id header. Useful when the integrator wants a hard guarantee that one connection never touches the other project.

Note

Note: A static pin (x-vtilt-project-id or ?project_id=) wins outright — switch-project is hidden from tools/list and supplying a different project_id argument errors with -32602 "conflicts with the header pin". That is intentional: integrator policy beats agent preference.

#Querying with VQL

The three query tools (query-run, query-validate, schema-get) let an agent ask questions that don't fit the curated readers — funnels, custom segmentation, joins between events and persons, and (when AI memory is enabled) semantic search over distilled sessions, chats, recordings, and per-person intelligence. They share one pipeline (the VQL compiler) that enforces tenant scoping, read-only safety, and per-table permissions before any SQL touches ClickHouse. Vector similarity uses the :embed('text') macro documented on AI intelligence.

#What VQL is

A controlled subset of ClickHouse SQL:

  • Single SELECT statement (with optional WITH / CTEs, JOIN, GROUP BY, window functions). No INSERT, UPDATE, DELETE, CREATE, ALTER, SET, etc. Multi-statement input is rejected.
  • Curated catalog of virtual tables — analytics core (events, persons, person_distinct_ids, person_overrides) plus AI memory tables such as session_summaries and person_memory_latest when your project has AI memory turned on. Database-qualified names (system.users) and table functions (remote(), s3(), file()) are blocked at compile time. schema-get is the source of truth for the exact table and column list your caller can use.
  • Curated function allowlist — standard aggregations (count, sum, uniq, quantile*), JSON helpers (JSONExtract*), date helpers (toStartOfDay, dateDiff, now), string / array / conditional functions, and the vector distance family (cosineDistance, L2Distance, L2SquaredDistance, dotProduct) where embeddings exist. System / cluster / file / dictionary functions are rejected.
  • Tenant scoping is automatic. The server applies a project_id filter to every catalog table at the storage engine level (ClickHouse additional_table_filters). Do not add project_id to your WHERE clause — you'd be filtering an already-filtered set.
  • Hard ceilings. readonly = 2, 30s execution timeout, 50M-row scan cap, 1000-row result cap (clamp the result smaller via limit).

#Tools

ToolUse it when
schema-getDiscover what tables and columns exist for the active project. Tables you cannot read (per project permissions) are omitted entirely.
query-validateDry-run a query through the compiler — parse + catalog + permission check. Returns { valid, referencedTables, error: { code, message, position?, hint? } }. No CH call.
query-runExecute the compiled query and return { ok: true, rows, rowCount, … } on success. Compile failures return { ok: false, error: { code, message, hint? } } in the normal tool result (not a generic execution error). Runtime failures use { error: { code, message, next, details } } on isError: true. Truncates string cells over 4 KB.

The recommended agent loop is schema-get → write VQL → query-validate → fix any errors using the structured code + hint → query-run. The validate step is cheap (no ClickHouse round-trip) and catches almost every class of failure agents make.

#$autocapture clicks in tool responses

The browser SDK stores compact autocapture: $elements_chain (encoded string) plus $el_text, not the verbose $elements array. ClickHouse keeps that shape; MCP tools enrich at read time:

FieldStored in ClickHouseIn events-recent / query-run responses
$elements_chainYesYes
$el_textYesYes
$elementsRarely (legacy opt-out)When present
$elementNoYes — parsed clicked node (JSON)

Do not JSON.parse($elements_chain) or expect properties.$elements on modern sites. Invoke /vtilt:conventions for the full data-model rules.

#Permissions

VQL enforces permissions per referenced table. The matrix below is the analytics core; AI memory tables (session_summaries, conversation_summaries, recording_summaries, person_memory_latest, ai_usage_log) each carry their own requirement — see the table on AI intelligence.

Virtual tableRequired permission
eventsanalytics:read
personsperson:read
person_distinct_idsperson:read
person_overridesperson:read

Org owners and admins always pass. A regular member sees permission_denied from query-run / query-validate and gets the table omitted from schema-get if they lack the permission for it. The query trio itself is always exposed in tools/list because the per-table check happens at compile time, not at registry-filter time.

#Example: pageviews per day, last 14 days

Find the top 10 distinct ids by pageview count over the last 14 days,
ordered descending. Use VQL.
text

The agent will typically call schema-get, write something like:

SELECT distinct_id, count() AS pageviews
FROM events
WHERE event = '$pageview'
  AND timestamp > now() - INTERVAL 14 DAY
GROUP BY distinct_id
ORDER BY pageviews DESC
LIMIT 10
sql

…then query-validate (catches typos / unknown columns), then query-run. Note the absence of any project_id clause — the server injects it.

#Error envelope

query-validate returns { valid: false, error: { code, message, position?, hint? } } when the compiler rejects the SQL — the tool call itself succeeded; read error.code and error.hint to fix the query.

query-run uses two shapes:

  • Compile failure (bad syntax, unknown table/column, permission denied at compile time) — { ok: false, error: { … } } in the normal tool result, same error object as above. MCP clients surface this in structuredContent instead of collapsing it to "Error occurred during tool execution".
  • Runtime failure (query compiled but ClickHouse rejected it) — { error: { code: "invalid_arguments", message, next, details: { vql: { code: "execution_error", … } } } } on isError: true.
{
  "ok": false,
  "error": {
    "code": "unknown_table",
    "message": "Unknown table: secret_internal_table.",
    "hint": "Call schema-get for the catalog of virtual tables your credential can access."
  }
}
json

Stable VQL error.code values: parse_error, unsupported_statement, unsupported_construct, unknown_table, unknown_function, permission_denied, execution_error, plus embed-specific codes (embed_invalid_argument, embed_text_too_long, embed_disabled) described on AI intelligence. Agents pattern-match on code to self-correct (e.g. parse_error → re-read the syntax; unknown_table → call schema-get; permission_denied → ask a project admin or pick a different table).

Every other tool follows the same idea with a slightly richer envelope so agents can recover in one turn:

{
  "error": {
    "code": "project_not_found",
    "message": "No accessible project matches \"markting site\".",
    "next": "projects-list, switch-project",
    "details": { "suggestions": ["Marketing site", "Marketing app"] }
  }
}
json

code is one of no_active_project, project_not_found, project_ambiguous, permission_denied, feature_disabled, resource_not_found, invalid_arguments, internal_error. next names the tool(s) to call to recover, and details carries actionable structure (closest project names, the missing permission, the disabled feature). The agent reads next and details and retries without asking you.

#Searching the docs from your agent

The docs-search tool gives your AI client access to the same documentation corpus that powers /docs, /llms.txt, and /llms-full.txt. Use it when you'd otherwise paste a doc link into the conversation — the agent grabs the relevant snippet itself and can keep working without context-switching.

Input: { query: string, limit?: number (default 5, max 15) }. Output: { results: Array<{ title, slug, url, description, snippet, score }>, total_matched: number }.

{
  "query": "how do I configure event forwarding to GA4?",
  "limit": 3
}
json

Returns matched pages with body excerpts and absolute URLs (https://www.vtilt.com/docs/event-forwarding). The tool runs in-process — no third-party search service — so results are deterministic and self-host installations get the same surface for free.

"Where do I put my project token in a Next.js app? Show me the exact code."
text

…will typically call docs-search with the question, then weave the matched code samples into the response.

#Writing data

vTilt MCP is mostly read-only by design — the curated readers and the VQL query layer cover almost every "summarise / analyse / explore" prompt without touching tenant state. Phase 3 added the first write tool, project-update, with three-layer safety:

  1. Scoped credentials. Read-only personal API keys (the default when you create a key) and OAuth tokens without mcp:write never see write tools in tools/list. Mint a new key with Read + write access from Account → Personal API keys, or include mcp:write in your OAuth scope= parameter (paired with offline_access if your client refreshes tokens).
  2. Permission gate. project-update declares requires: project:update. The MCP layer hides it from tools/list when you lack the permission and re-checks per call (so a stale tools/list cache cannot smuggle the tool past the auth wrapper).
  3. Audit log. Every successful or failed call lands a row in org.mcp_audit_logs with userId, projectId, traceId, the tool name, the credential's scope at call time, a SHA-256 hash of the arguments, a one-line summary (e.g. name → New Name), the result, and the wall-clock duration. The args themselves are never stored — only the hash, mirroring the wide log's privacy convention. Future write tools (person-create, campaign-send, …) will use the same audit channel.

Warning

Warning: Write-capable keys can mutate project state. Treat them like a database password — don't paste them into shared chats, don't commit them to git, and don't leave them in dotfiles backed up to a public service. Most agent workflows want read-only keys; reach for write only when you actively need the agent to change project state on your behalf.

The current write surface is intentionally tiny (one tool, one mutation: rename the active project). It's a demonstrator for the pattern more than a feature. The next slices add the rest of the write surface (campaign-send, person-create, …) on the same audit + scope foundation.

#Limits and errors

LimitDefault
Requests per minute (per personal key)60 (sliding window)
Maximum rows per tool callevents-recent 200, event-types-list 200, persons-list 200, recordings-list 100, campaigns-list 100, query-run 1000, kpis-get n/a
VQL query timeout30s execution, 50M-row scan cap
VQL result cell truncation4 KB per string cell (overage replaced with …[truncated]); truncated: true flag returned in response
Result payload sizeNo hard cap; agents typically truncate themselves

When you hit the rate limit the server returns a JSON-RPC error with code -32099 and error.data.retryAfter (seconds). Standard MCP clients surface this to the agent; well-behaved agents back off automatically.

#Filtering tools by feature

If your client supports query parameters on the MCP URL, you can narrow the tool list per connection:

https://www.vtilt.com/api/mcp?features=analytics,persons
https://www.vtilt.com/api/mcp?tools=kpis-get,events-recent
text

Both filters apply with union semantics — a tool survives if it matches either. Use this to keep the agent focused on a subset (e.g. only analytics tools when summarising marketing performance).

The available feature names are analytics, persons, events, recordings, campaign, event-destinations, project, navigation, query, and docs. Unknown feature names are silently ignored (a malformed URL won't break a connection).

#Next steps

Guides

  • Authentication — personal API keys, headers, troubleshooting.
  • OAuth — browser sign-in flow for Claude, ChatGPT, Cursor, VS Code, …
  • Agent skills (prompts) — /vtilt:… slash commands bundled with the server.
  • AI intelligence — AI memory tables, :embed('text'), memory-ask, person-memory-get.

Client setup

  • Cursor · Claude Desktop · VS Code · Codex

SDK

  • Identify & alias — make sure your dashboard data has high-quality persons before asking the agent to reason about them.
PreviousEvent forwardingNextAuthentication

On this page

  • Documentation map
  • Supported tools
  • What you can ask
  • How it works
  • Connect a client
  • Pinning a project
  • Switching projects conversationally
  • Parallel chats on one MCP config
  • Querying with VQL
  • What VQL is
  • Tools
  • $autocapture clicks in tool responses
  • Permissions
  • Example: pageviews per day, last 14 days
  • Error envelope
  • Searching the docs from your agent
  • Writing data
  • Limits and errors
  • Filtering tools by feature
  • Next steps