> ## Documentation Index
> Fetch the complete documentation index at: https://vendo-mintlify-54d109e7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Mastra

> Spread Vendo's guarded tool pack into a Mastra agent you already run, and render what it returns in your own chat.

Keep your agent. Vendo adds the guarded tools and renders what they return.

<Steps>
  <Step title="Install and run init">
    <CodeGroup>
      ```bash npm theme={null}
      npm install @vendoai/vendo
      npx vendo init
      ```

      ```bash pnpm theme={null}
      pnpm add @vendoai/vendo
      pnpm exec vendo init
      ```
    </CodeGroup>

    Answer the first question with **Through my own agent loop (AI SDK / Mastra)**, and take **Vendo Cloud** on the model one. Init reads your repo, writes the wire route, and lands a `VENDO_API_KEY` in `.env.local`.

    Init wrote `lib/vendo.ts` for you (`src/lib/vendo.ts` when your app lives under `src/`) — the `createVendo` call, and the caller resolver beside it, so your chat route, your wire route, and the pack all share one instance.

    ```ts lib/vendo.ts focus={6} theme={null}
    import { authJs } from "@vendoai/vendo/auth/auth-js";
    import { createVendo } from "@vendoai/vendo/server";

    const auth = authJs();
    export const vendo = createVendo({ auth });
    export const resolvePrincipal = (req: Request) => auth.principal(req);
    ```

    Init wires whichever provider it detects — swap `authJs()` for another preset any time ([Auth](/production/auth) lists all five).

    With no provider yet, init wrote a demo principal in place of a preset, and the resolver beside it hands your chat route that same subject:

    ```ts lib/vendo.ts theme={null}
    import { createVendo } from "@vendoai/vendo/server";

    // init writes both — swap them for
    // your real session lookup.
    const principal = async () => ({
      kind: "user" as const,
      subject: "demo-user",
    });
    export const vendo = createVendo({ principal });
    export const resolvePrincipal = (_req: Request) => principal();
    ```

    Either way, your agent and your wire route have to resolve the same subject — a mismatch has no error; the embed just polls a screen it will never be shown ([Auth](/production/auth)).
  </Step>

  <Step title="Spread the tools into your agent">
    One Mastra definition serves every user, so `vendoMastraTools` takes no principal — it returns a Promise, which is why the agent takes the tools-as-function form. Every tool lands under a `vendo_` prefix — one per registered host action, plus `vendo_make` (a live view) and `vendo_delegate` (a whole task for Vendo's own agent); trim the pack with `include` or `exclude` by final tool name.

    ```ts src/mastra/agents/your-agent.ts focus={10} theme={null}
    import { Agent } from "@mastra/core/agent";
    import { vendoMastraTools } from "@vendoai/vendo/mastra";
    import { vendo } from "@/lib/vendo";

    export const yourAgent = new Agent({
      id: "your-agent",
      name: "your-agent",
      instructions: "…your system prompt as it is",
      model: "openai/gpt-4.1-mini", // your model, unchanged
      tools: async () => ({ ...yourTools, ...(await vendoMastraTools(vendo)) }),
    });
    ```
  </Step>

  <Step title="Hand the caller to the route">
    ```ts app/api/chat/route.ts theme={null}
    import { RequestContext } from "@mastra/core/request-context";
    import { VENDO_PRINCIPAL_KEY } from "@vendoai/vendo/mastra";
    import { resolvePrincipal } from "@/lib/vendo";

    // in your existing POST handler, before you invoke the agent:
    const caller = await resolvePrincipal(req);
    if (!caller) return new Response("Unauthorized", { status: 401 });
    const requestContext = new RequestContext();
    requestContext.set(VENDO_PRINCIPAL_KEY, caller);
    params.requestContext = requestContext;
    ```

    `RequestContext` is Mastra's own per-invocation carrier, and Vendo's tools read the principal off it on every call. Set `VENDO_SESSION_KEY` on the same context to carry your own session id into the audit trail.
  </Step>

  <Step title="Render the embeds">
    Mastra streams tool calls as `dynamic-tool` or as `tool-<name>`, depending on how the tool was declared, so match both.

    ```tsx components/vendo-part.tsx focus={5-8} theme={null}
    import { getToolName, isToolUIPart, type UIMessage } from "ai";
    import { VendoToolResult } from "@vendoai/vendo/react";

    export function VendoPart({ part }: { part: UIMessage["parts"][number] }) {
      if (!isToolUIPart(part)) return null; // true for both shapes
      return part.state === "output-available"
        ? <VendoToolResult output={part.output} />
        : <span>Running {getToolName(part)}…</span>;
    }
    ```

    Nothing to wrap: the embed finds the wire itself and rides your host session cookie, and that one component covers plain data, app refs, and approval refs. Full contract: [Embeds in your chat](/existing-agent/embeds).
  </Step>

  <Step title="See it live">
    Run your own chat and ask for something behind your API. The answer comes back as a working card, inside your own bubble.

    Init wrote `VENDO_BASE_URL` into `.env.local`; deployments set the same variable to the public URL, path prefix included ([environment variables](/reference/environment-variables)).

    <Frame>
      <img src="https://mintcdn.com/vendo-mintlify-54d109e7/vvld4364KM70aFr6/images/existing-agents/mastra-approval.png?fit=max&auto=format&n=vvld4364KM70aFr6&q=85&s=2059dc5c70b49556df63aa6228d76ca8" alt="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" width="672" height="605" data-path="images/existing-agents/mastra-approval.png" />
    </Frame>
  </Step>
</Steps>

## Apps in your product

Your agent can build apps now. Where they land in your product, and how to teach your model when to build one, is one more quickstart: [Apps in your product](/generated/quickstart).

## The full example

[`examples/mastra-agent`](https://github.com/runvendo/vendo/tree/main/examples/mastra-agent) is the stock [`create-mastra`](https://mastra.ai/docs) 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.
