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

# Mastra AI Twitter MCP agent guide for TypeScript

> Build a Mastra Twitter agent for tweet search, followers, monitors, and approved X actions. Add typed MCP workflows, pagination, errors, and resumable handoffs.

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

Build a Mastra AI Twitter MCP agent through Xquik's remote MCP server. This
Mastra Twitter agent searches tweets, profiles, replies, followers, and
monitors. It keeps every tweet ID, profile ID, cursor, and job ID.

This Mastra AI tutorial combines agent tools, one typed workflow, and a strict handoff.
Xquik provides the Twitter MCP server and documented API endpoints.
Mastra controls the AI model, tool call, approval, and agent workflows.

## Why use the Mastra AI agent framework with a Twitter API?

Mastra is an open-source TypeScript framework for building AI applications.
The Mastra AI agent framework adds tools, routing, memory, and workflows.
Use an X API agent when a model must choose related operations. Use REST for
fixed routes, scheduled exports, or predictable latency.

| Boundary | Mastra control | Twitter agent result |
| - | - | - |
| Remote MCP | `MCPClient` | Connect tweet, profile, follower, and monitor tools |
| Final response | Zod `structuredOutput` | Reject malformed rows and cursor state |
| Per-call tools | `listToolsets()` | Isolate each user's Xquik key |
| X actions | `requireToolApproval` | Pause before the `execute` tool runs |
| Tool errors | `onToolError: "throw"` | Keep failed MCP results |
| Cleanup | `disconnect()` | Close remote sessions |

## Mastra Twitter API prerequisites

* Node.js 22.13 or later
* An [Xquik API key](/x-api-quickstart) beginning with `xq_`
* A model-provider key supported by Mastra
* A connected X account for private reads or writes

Public reads need no X Developer credentials. Authenticate through Xquik.

## Install the Mastra AI SDK and MCP support

Install the tested Mastra AI SDK packages and Zod.

```bash theme={null}
npm install "@mastra/core@^1.55" "@mastra/mcp@^1.15" zod
```

Store secrets outside source control.

```bash .env theme={null}
XQUIK_API_KEY=xq_YOUR_KEY_HERE
OPENAI_API_KEY=YOUR_OPENAI_KEY
```

```text .gitignore theme={null}
.env
xquik-*-handoff.json
```

Run `npm run dev` in a generated Mastra project. Then inspect agent calls in
Mastra Studio.

## Build a Mastra AI agent example for tweet search

Define the final handoff before creating the agent.

```typescript theme={null}
import { writeFile } from "node:fs/promises";
import { Agent } from "@mastra/core/agent";
import { createStep, createWorkflow } from "@mastra/core/workflows";
import { MCPClient } from "@mastra/mcp";
import { z } from "zod";

const tweetRowSchema = z.object({
  tweet_id: z.string(),
  text: z.string(),
  author_username: z.string().nullable(),
  created: z.number().int().nullable(),
  url: z.string().url().nullable(),
});

const handoffSchema = z.object({
  query: z.string(),
  route_used: z.literal("GET /api/v1/x/tweets/search"),
  tweets: z.array(tweetRowSchema),
  has_more: z.boolean(),
  next_cursor: z.string().nullable(),
  pages_fetched: z.number().int().positive(),
  stop_reason: z.enum(["complete", "requested_limit", "cursor_stalled"]),
});

const mcp = new MCPClient({
  servers: {
    xquik: {
      url: new URL("https://xquik.com/mcp"),
      requestInit: {
        headers: {
          "x-api-key": process.env.XQUIK_API_KEY!,
        },
      },
      onToolError: "throw",
    },
  },
  timeout: 60_000,
});

try {
  const agent = new Agent({
    id: "xquik-tweet-search-agent",
    name: "Xquik Tweet Search Agent",
    instructions: [
      "Use Xquik for Twitter API requests.",
      "Use GET /api/v1/x/tweets/search.",
      "Preserve exact tweet IDs, created timestamps, and cursors.",
      "Never invent missing tweet fields.",
      "Stop when the requested limit is reached.",
      "Stop if the server repeats a cursor.",
    ].join(" "),
    model: "openai/gpt-5",
    tools: await mcp.listTools(),
  });

  const searchStep = createStep({
    id: "search-twitter",
    inputSchema: z.object({ query: z.string().min(1) }),
    outputSchema: handoffSchema,
    execute: async ({ inputData }) => {
      const result = await agent.generate(`Search 50 latest tweets for: ${inputData.query}`, {
        structuredOutput: { schema: handoffSchema },
      });

      return handoffSchema.parse(result.object);
    },
  });

  const tweetResearchWorkflow = createWorkflow({
    id: "xquik-tweet-research",
    inputSchema: z.object({ query: z.string().min(1) }),
    outputSchema: handoffSchema,
  })
    .then(searchStep)
    .commit();

  const run = await tweetResearchWorkflow.createRun();
  const workflowResult = await run.start({
    inputData: { query: "Mastra AI MCP" },
  });

  if (workflowResult.status !== "success") {
    throw new Error(`Tweet workflow ended with ${workflowResult.status}.`);
  }

  await writeFile(
    "xquik-mastra-handoff.json",
    JSON.stringify(workflowResult.result, null, 2),
    "utf8",
  );
} finally {
  await mcp.disconnect();
}
```

`listTools()` suits static agent construction. `listToolsets()` groups tools by server for each call.
The client tries Streamable HTTP for URL servers.

The MCP runtime returns normalized snake\_case fields through
`xquik.request()`. It normalizes `createdAt` to the Unix-second field
`created`. Keep the Zod schema aligned with that contract.

## Run a Mastra AI workflow for tweet research

The `inputSchema: z.object(...)` declaration validates every query. The
`outputSchema` protects the final handoff.
Mastra core workflows chain steps with `.then()` and finish with `.commit()`.

`createRun()` creates isolated state. Store each successful handoff immediately.
A registered agent can also run the workflow through a Mastra instance.

## Search tweets with precise operators

Keep the exact `q` in every checkpoint.

| Search intent | Example `q` |
| - | - |
| Framework posts | `"Mastra" MCP` |
| One timeline | `from:mastra_ai since:2026-07-01 until:2026-08-01` |
| Popular TypeScript posts | `"Twitter API" TypeScript lang:en min_faves:25` |
| Questions | `"tweet search agent" ? -filter:retweets` |

Use `queryType=Latest` for time-ordered research. Pass `next_cursor` unchanged.
Stop when `has_more` becomes false. Also stop when a cursor repeats.

## Require approval for Twitter actions

Xquik exposes one `execute` tool. It can run authorized reads and
writes. Require human-in-the-loop approval when the key permits writes.

```typescript theme={null}
const approvedMcp = new MCPClient({
  servers: {
    xquik: {
      url: new URL("https://xquik.com/mcp"),
      requestInit: {
        headers: { "x-api-key": process.env.XQUIK_API_KEY! },
      },
      requireToolApproval: ({ toolName }) => toolName === "execute",
      onToolError: "throw",
    },
  },
});
```

Show the route, method, arguments, and X account before approval.
Never treat MCP annotations as an authorization boundary. They are server-provided hints.

<Warning>
  `@mastra/core` 1.55.0 predates the declined-call fix in
  [PR #20487](https://github.com/mastra-ai/mastra/pull/20487). Do not enable
  writes on that release. Upgrade after a stable fix. Then prove declined
  calls never execute.
</Warning>

Use a guest `paid_reads` key for a firm read-only boundary. See [guest
wallets](/guides/guest-wallets) for its exact scope.

## Expose discovery without execution

Filter `listTools()` when an agent should only inspect endpoint schemas.

```typescript theme={null}
const tools = await mcp.listTools();
const discoveryTools = Object.fromEntries(
  Object.entries(tools).filter(([name]) => name.endsWith("_explore")),
);

const discoveryAgent = new Agent({
  id: "xquik-route-discovery-agent",
  name: "Xquik Route Discovery Agent",
  instructions: "Inspect Twitter API routes. Never execute an operation.",
  model: "openai/gpt-5",
  tools: discoveryTools,
});
```

The `search` tool returns routes, methods, parameters, and response fields.
It does not call X. Adding `execute` enables every key-authorized operation.

## Use a Mastra AI MCP client for each user

Create one client after resolving the tenant identity.

```typescript theme={null}
import { Agent } from "@mastra/core/agent";
import { MCPClient } from "@mastra/mcp";

async function runTenantRequest(prompt: string, userApiKey: string) {
  const tenantMcp = new MCPClient({
    id: crypto.randomUUID(),
    servers: {
      xquik: {
        url: new URL("https://xquik.com/mcp"),
        requestInit: {
          headers: { "x-api-key": userApiKey },
        },
        requireToolApproval: true,
        onToolError: "throw",
      },
    },
  });

  const agent = new Agent({
    id: "tenant-twitter-agent",
    name: "Tenant Twitter Agent",
    instructions: "Preserve tweet IDs, profile IDs, cursors, and job IDs.",
    model: "openai/gpt-5",
  });

  try {
    return await agent.generate(prompt, {
      toolsets: await tenantMcp.listToolsets(),
    });
  } finally {
    await tenantMcp.disconnect();
  }
}
```

Never share keys across tenants. Keep keys outside prompts, traces, and
handoffs. Supply a unique client `id` for otherwise identical settings.

## Forward dynamic headers safely

Use custom `fetch` when request context selects a tenant key.

```typescript theme={null}
const mcp = new MCPClient({
  servers: {
    xquik: {
      url: new URL("https://xquik.com/mcp"),
      fetch: async (url, init) => {
        const headers = new Headers(init?.headers);
        headers.set("x-api-key", await secretStore.getXquikKey());
        return fetch(url, { ...init, headers });
      },
      onToolError: "throw",
    },
  },
});
```

The `secretStore` object represents your existing secret manager. Never send
its key to the model. Keep `forwardInstructions` disabled for untrusted
servers.

## Store a resumable Twitter agent handoff

Store validated identifiers outside the model transcript.

<CardGroup cols={2}>
  <Card title="Tweet search rows" icon="search">
    Store `tweet_id`, `text`, `author_username`, `created`, `url`, `has_more`, `next_cursor`, and the original `q`.
  </Card>

  <Card title="Follower exports" icon="users">
    A Twitter follower scraper API handoff keeps `user_id`, `username`, `followers`, `has_more`, and `next_cursor`.
  </Card>

  <Card title="Monitor events" icon="radio">
    Store `monitor_id`, `event_id`, `type`, `occurred_at`, and replay cursors.
  </Card>

  <Card title="Extraction jobs" icon="database">
    Store `extraction_id`, `status`, `poll`, and `export_after_complete`.
  </Card>

  <Card title="Approved X actions" icon="send">
    Store `tweet_id`, `write_action_id`, `status`, and `poll`. Never resend pending writes.
  </Card>

  <Card title="Webhook deliveries" icon="webhook">
    Store `webhook_id`, `delivery_id`, and `stream_event_id`. Protect the secret.
  </Card>
</CardGroup>

Store the validated `result.object`, not free-form `result.text`.

## Handle Twitter API errors and rate limits

Match each documented status before retrying.

| Status | Meaning | Agent action |
| - | - | - |
| `400` | The search query is missing or invalid | Fix `q` |
| `401` | Guest authentication cannot complete this request | Provide a key or connect X |
| `402` | The account lacks credits | Request account action |
| `424` | The upstream X dependency failed | Apply bounded backoff |
| `429` | The Twitter API rate limit applies | Keep the cursor and wait |
| `502` | The X dependency returned an invalid response | Retry later |

Keep `onToolError: "throw"`. It throws on MCP `isError` results. Never restart
pagination after `429`. Read each route before designing retry logic.

## Verify Mastra package compatibility

Xquik checked these stable versions on August 3, 2026.

| Package | Checked version | Requirement |
| - | - | - |
| `@mastra/core` | 1.55.0 | Node.js 22.13 or later |
| `@mastra/mcp` | 1.15.0 | `@mastra/core` below 2.0 |
| `@modelcontextprotocol/sdk` | 1.30.0 | Installed through Mastra |
| `zod` | 4.4.3 | `^3.25` or `^4` |

`@mastra/mcp` 1.15.0 predates the concurrent reconnect fix in
[PR #20530](https://github.com/mastra-ai/mastra/pull/20530). Avoid parallel
recovery through one client. Test reconnects before every upgrade.

## Mastra AI MCP frequently asked questions

### What are the main Mastra AI MCP features?

The integration combines typed agent tools, workflows, approval, and MCP. It
supports tweets, profiles, followers, monitors, webhooks, and writes.

### How does Mastra AI MCP compare with other agent frameworks?

Choose Mastra for TypeScript-first agent workflows and Zod contracts. Choose
direct REST for fixed routes. Xquik supports both paths.

### What must a Twitter agent system enforce?

Require bounded queries, typed outputs, exact IDs, cursor checkpoints, and
rate limiting. Isolate credentials and approve every write.

### Which Mastra AI examples does this tutorial include?

It covers MCP discovery, tweet search, workflows, approval, errors, tenant
isolation, and durable handoffs.

### What is a Mastra AI MCP client?

The Mastra AI MCP client uses Xquik's remote tools. A Mastra AI MCP server
publishes local tools.

### How do I search tweets with TypeScript?

Call `GET /api/v1/x/tweets/search`. Supply `q`, `queryType`, and `limit`.
Keep `tweet_id`, `created`, and `next_cursor`.

### Can a Mastra agent export Twitter followers?

The Twitter follower scraper API returns profiles and cursors. Use the
[followers API](/api-reference/x/followers) for bounded pages. Use extraction
jobs for CSV, JSON, or XLSX exports.

### Can Mastra monitor tweets in real time?

Monitors store matching events between agent calls. Replay events by cursor.
Use signed webhooks when the receiver needs immediate delivery.

### Can a Mastra AI agent post tweets and replies?

Yes, after connecting X. Do not enable writes on affected Mastra releases.
Test approval rejection before production.

### How should a Mastra agent handle Twitter rate limits?

Treat `429` separately from dependency failures. Save the cursor and completed
tweet IDs. Resume after reset guidance.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.