Skip to main content
The quickstart wires a Mastra agent end to end. This page is the rest.

The definition is static, the caller is not

One Mastra agent definition serves every user, so vendoMastraTools takes no principal. Each call reads one off Mastra’s request context, and a vendo_* call that finds none fails closed.
app/api/chat/route.ts
resolvePrincipal is the lib/vendo.ts export from the quickstart, unchanged here. A context you build and never pass is a context the tools never see. Skip the params.requestContext assignment and every vendo_* call fails, which reads at first glance like a broken install. Set VENDO_SESSION_KEY on the same context to carry your own session id into the audit trail. Leave it out and each call mints one.
That subject has to match what your wire route’s principal resolves. If they disagree, the person never sees what the agent makes, and the app embed polls forever while the agent reports success.Resolve both from the same session, and never take a principal from the client. See Auth.
vendoMastraTools returns a Promise, which is why the agent takes the tools-as-function form.
src/mastra/agents/your-agent.ts

Warm the store

Every vendo_* call runs the schema check before it executes, so a guarded tool that fires before any wire request still works. Call ensureSchema once at module scope to move that one-time cost off the first turn.
src/mastra/index.ts

Render the parts

Mastra streams tool calls as dynamic-tool or as tool-<name>, depending on how the tool was declared. Match both. Where that match lives is yours. The path below is a name, not a contract — only your own chat imports it, so a components/ directory you do not have is a directory you do not need. examples/mastra-agent inlines the same branch in src/app/page.tsx instead.
components/vendo-part.tsx
Everything downstream of that is the same as any other framework. Full contract: Embeds in your chat.
The Mastra example chat with the vendo_send_trip_report tool pill above an approval card carrying the report, the recipient, and Approve and Deny buttons

Open input schemas ride a bridge

Extraction emits an open object schema for a route whose body shape it cannot type. Mastra’s provider compat layers hard-close every object node, so an open schema reaches the model as “this tool takes no arguments”, and the model then calls it with {}. The shim routes those tools through one declared args property instead, a JSON object or that object as a JSON string, and unwraps it before the guard. Nothing on your side changes.

The model pin

Multi-turn tool use with GPT-5 reasoning models breaks on history replay. Mastra’s memory drops the Responses API’s reasoning items and the second turn errors. mastra-ai/mastra#9005 is closed, but it still reproduces on @mastra/core 1.51.0, retested 2026-07-20, so the example pins openai/gpt-4.1-mini. Your agent’s model is your call. Vendo’s own turns take a seat of their own, filled through the Cloud gateway on your VENDO_API_KEY: Model credentials.

The full example

examples/mastra-agent is the stock create-mastra weather starter, fronted with Next.js per Mastra’s guide, with this diff applied. Every added line sits between --- vendo and --- /vendo markers, about sixty of them in total.