Intent Capture
Capture why the AI agent calls each tool — the user goal behind every invocation
Tool-call counts tell you what agents do with your MCP server. Intent capture tells you why: for every tool call, the calling model states what the user is trying to achieve. Captured intents appear on the tool detail page in your dashboard.
Intent capture is off by default. Enable it with one line:
withYavio(server, {
apiKey: "yav_...",
intent: true,
});How it works
When enabled, the SDK advertises an extra required string parameter named context on every tool, described to the model as "Why are you calling this tool? Briefly describe the user's goal." The calling model (ChatGPT, Claude, Cursor, …) fills it in with each call — for example:
"User wants a compact rental car in Munich for a weekend trip"
The description is deliberately one open question, and the example above is deliberately not a template. Prescribing a length, a grammatical person or a sample sentence makes models converge on that exact shape with the nouns swapped — tidy, and close to useless for telling one call apart from another. Expect real intents to vary in length and phrasing. If you want a house style anyway, set description yourself — see below.
The SDK reads and removes the context value at the protocol layer before your tool's input validation runs. This means:
- Your handler never sees it. Tool code and input schemas stay untouched — strict schemas included.
- A call without
contextnever fails. The parameter is required only in the advertised schema; the server tolerates its absence. - Tools that define their own
contextparameter are left completely alone — no injection, no capture, no stripping. - Widget-invoked tools get
contextas optional, automatically. A widget iframe calls its tools with only their real arguments — it cannot know to sendcontext— and hosts validate widget calls against the advertised schema, so a requiredcontextwould make them refuse every such call. The failure is delayed (hosts cache connector schemas until a refresh), i.e. the widget would break silently some time after you enable intent capture. Three_metashapes mark a tool as widget-invoked:"openai/widgetAccessible": true; an MCP Appsui.visibilitycontaining"app"; and — the broadest rule — aui.resourceUriwith no declared visibility, because MCP Apps defaults omitted visibility to["model", "app"]. That last rule covers every view-owning tool of every MCP App. If your widget never calls its view-owning tool, declareui.visibility: ["model"]on it to keepcontextrequired there (maximum capture). The SDK logs one[yavio] Intent context advertised as OPTIONAL…line per affected tool so you can see exactly which tools were downgraded. Capture still works whenever a model call fills the optional field — but note that thefallbackoption is not invoked for context-less calls on these tools: they are presumptively the widget's own machine traffic, and inferring an intent per 3-second refresh would drown the real ones. Everytool_callevent records howcontextwas advertised for its tool inintent_context(required,optionalornone). The dashboard computes Call Coverage over required-context calls and shows optional-context calls as a separate share, so a widget-heavy tool no longer drags the headline down. That second share mixes the widget's own calls, which never carry an intent, with model calls that skipped the optional parameter: the two look identical to the server, so the SDK does not guess which is which. If the share is low and your widget rarely calls the tool, declareui.visibility: ["model"]to makecontextrequired there. Two things to expect around a change:intent_contextis what your server advertises now, and hosts keep showing models a cached schema until they refresh, so coverage can read off for a while after you change these settings; and a date range that spans your upgrade to 0.4.0 still contains older events, which count toward Call Coverage the old way until the range rolls past the upgrade.
Captured intents are stored on the tool_call event (intent_signals), passed through the SDK's PII stripper, and capped at 500 characters. Read what that does not cover before enabling this on a server that handles sensitive data.
Options
withYavio(server, {
apiKey: "yav_...",
intent: {
// Advertise context as optional instead of required (lower capture
// rate, but immune to clients that hard-validate required params)
required: false,
// Replace the model-facing parameter description
description: "Explain the user's goal for this call in one sentence.",
// Derive an intent for calls that arrive without context
// (captured with source "inferred" instead of "context_parameter").
// Runs before your handler on every context-less call — keep it fast
// and synchronous; never call an LLM or network service here.
fallback: (toolName, args) => `Called ${toolName}`,
},
});YAVIO_INTENT=true (environment) and "intent": true (.yaviorc.json) work as well; code options take precedence.
Dashboard status
The SDK reports its intent setting with the first tool call of each session, so the dashboard's User Intents panel always shows an honest state: enabled, disabled, or SDK too old (intent capture requires @yavio/sdk 0.2.0+). If capture is enabled but no intents arrive, check two causes before suspecting a bug: some MCP clients do not fill unknown parameters, and on widget-invoked tools context is optional by design (see above) — a tool that is mostly called by the app's own widget will naturally show few or no intents, because widget calls carry no conversation to describe. The SDK logs one [yavio] line per tool it downgrades, so the server log tells you which tools are affected.
Privacy and app-store review
Intent text is derived from your end user's conversation. Treat it as user data: disclose the collection in your privacy policy, or leave intent capture disabled.
If you plan to submit your server to the ChatGPT App Store or the Claude Directory, review their data-collection policies first. OpenAI's guidelines permit an intent field only when it meaningfully improves tool execution, and Anthropic's directory policy prohibits collecting extraneous conversation data. For a listed app, the safe default is intent: false — or explicit disclosure in your submission.
Intent capture can also stay on for the server while individual tools are excluded: a booking or support-message tool, where the model's sentence tends to contain the user's name, gets intent: false as a per-tool override. The context parameter is then neither advertised nor captured for that tool.
Two token-related costs are worth knowing: the parameter description (~11 words) is added to every tool's schema in the model's context, and the model writes roughly a sentence per tool call.
What intent capture does not protect you from
Intent is free text written by a model from your end user's conversation, so treat it as user-generated content rather than a controlled field.
Two things happen to it unconditionally, before the event leaves your process: it is passed through the SDK's PII stripper, and it is clamped to 500 characters. Neither makes it safe to store arbitrary conversation text. The stripper matches four specific shapes — email addresses, Luhn-valid card numbers, US Social Security numbers and US-format phone numbers — and the ingestion API adds ITIN and US street addresses. That is a backstop for the most obvious identifiers, not a general-purpose redactor.
Everything outside those shapes is stored verbatim. In practice that includes:
- financial details a user states in conversation — income, balances, investment amounts
- names, employers, job titles, and non-US addresses and phone formats
- health, legal or other sensitive context that made it into the user's question
- credentials or keys a user pastes, unless they happen to match one of the patterns above
Do not treat the parameter description as a control either. It is an instruction the model may reinterpret or ignore: an earlier default asked it never to include personal data, and captured intents still contained users' stated monthly incomes. If a category of data must not be stored, keep intent capture off for that server rather than asking the model nicely.
If you handle regulated data, the safe position is intent: false. If you enable it, disclose it in your privacy policy, and read a real sample of your own captured intents before assuming what they contain.