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

# Your own agent

> Wire the door into an agent you already run: one stock MCP client, one token from tokenFor, one session per conversation.

Your agent imports nothing from Vendo. The door speaks MCP over streamable
HTTP, so the client SDK you already have reaches it. The only Vendo-shaped part
is the bearer, and that is one method call.

## Get the token

Ask your composition. Pass a request and it reads who is signed in from the
session cookie; pass a user id and it acts as that person.

```ts agent.ts focus={4} theme={null}
import { vendo } from "@/lib/vendo";

export async function runForRequest(request: Request) {
  const accessToken = await vendo.tokenFor(request);
  // …
}
```

```ts jobs/nightly.ts focus={3} theme={null}
import { vendo } from "@/lib/vendo";

const accessToken = await vendo.tokenFor("user_1904");
```

The id is one of *your* user ids, spelled the way your product spells it —
whatever your own `principal()` resolves for that person. Everything the agent
does with the token is attributed to them.

A subject that is not a real id — blank, `null`, `undefined`, or not a string at
all — is refused here rather than minting a token that dies on the first tool
call.

## Connect

The client is the stock MCP SDK, which is your install, not Vendo's:

<CodeGroup>
  ```bash npm theme={null}
  npm install @modelcontextprotocol/sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @modelcontextprotocol/sdk
  ```
</CodeGroup>

One URL and one header.

```ts agent.ts focus={9} theme={null}
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

export async function connect(accessToken: string): Promise<Client> {
  const client = new Client({ name: "acme-agent", version: "1.0.0" });
  await client.connect(new StreamableHTTPClientTransport(
    new URL(`${process.env.VENDO_BASE_URL}/api/vendo/mcp`),
    { requestInit: { headers: { authorization: `Bearer ${accessToken}` } } },
  ));
  return client;
}
```

The path is fixed at `/api/vendo/mcp` under your own origin. It never points at
a broker, so switching sign-in posture later invalidates nothing here.

## One session per conversation

Connect once and keep the client for the whole conversation. The door pins each
MCP session to the subject and client id that opened it, and answers
`404 Session not found` if either moves.

This matters most for approvals. A parked call resumes only when the retry lands
on the same session, so an agent that reconnects per call parks forever.

## Read the result

The door never fails a tool call as a protocol error — a retry on a protocol
error would re-execute a write that already happened. Everything arrives
in-band.

```ts focus={3-6} theme={null}
const result = await client.callTool({ name: "host_setCardLimit", arguments: args });

if (result.isError) {
  // "This action needs approval. Approval apr_… is waiting in Maple's
  // Vendo approvals queue — resolve it there, then retry."
  return result.content[0].text;
}
return result.structuredContent;
```

Hand that text back to your model as the tool result. It already says what
happened and what the next move is, which is enough for the model to retry or to
tell the person.

Successful calls carry the output twice: as text for the model, and as
`structuredContent` for your own code.

## Long calls need your opt-in

Your client abandons a `tools/call` after 60 seconds by default, and generating
a screen with `vendo_make` routinely runs longer. The door beats
`notifications/progress` every 15 seconds for any call that carries a progress
token, but the SDK extends the deadline only when you asked it to — so a client
that receives every frame still gives up at 60 seconds.

Ask for both. `onprogress` is what puts the progress token on the request, and
`resetTimeoutOnProgress` is what makes the frames count.

```js focus={4} theme={null}
const result = await client.callTool(
  { name: "vendo_make", arguments: args },
  undefined,
  { onprogress: () => {}, resetTimeoutOnProgress: true },
);
```

## When ten minutes runs out

A token lasts ten minutes and has no refresh path. An expired one answers `401`
with a `WWW-Authenticate` challenge on the next request. Call `tokenFor` again
and reconnect — for work that runs longer than one token, mint per unit of work
rather than holding one open.

## The whole loop

```ts agent.ts focus={1} theme={null}
const accessToken = await vendo.tokenFor("user_1904");
const client = await connect(accessToken);

const { tools } = await client.listTools();
const step = await model.decide({ task, tools });

const result = await client.callTool({
  name: step.name,
  arguments: step.arguments,
});
task.push(result.isError ? result.content[0].text : result.structuredContent);
```

What the agent may call, and what happens to a call on the way to your API, is
in [How the door works](/outside-agents/how-the-door-works).
