# Separate server integration
URL: /docs/integrations/frameworks/mastra/separate-server

Run Mastra as a standalone server with assistant-ui as a separate frontend.

Run Mastra as its own service and have the assistant-ui frontend hit it over HTTP. The two halves can deploy, scale, and version independently. The tradeoff is one extra hop and CORS to configure.

For the simpler in-process variant, see [full-stack](/docs/integrations/frameworks/mastra/full-stack) instead.

## Setup \[#setup]

The setup has two halves. Steps 1 to 4 happen in the **Mastra server** project; steps 5 to 7 happen in a **separate assistant-ui frontend** project. You'll have two terminals open by the end: one for each dev server.

<Steps>
  <Step>
    ### Create the Mastra server project \[#create-the-mastra-server-project]

    In a directory separate from your frontend, scaffold a Mastra project:

    ```sh
    npx create-mastra@latest
    ```

    Follow the wizard's prompts. When it finishes, change into the new directory and add the AI SDK adapter:

    ```sh
    cd your-mastra-server
    ```

    <InstallCommand npm="[&#x22;@mastra/ai-sdk@latest&#x22;]" />

    Set the model provider keys (`OPENAI_API_KEY`, etc.) in `.env.development`. The `create-mastra` wizard prompts for some keys, but verify all the providers you plan to call are present.
  </Step>

  <Step>
    ### Define an agent \[#define-an-agent]

    ```ts title="src/mastra/agents/chefAgent.ts"
    import { Agent } from "@mastra/core/agent";

    export const chefAgent = new Agent({
      name: "chef-agent",
      instructions:
        "You are Michel, a practical and experienced home chef. " +
        "You help people cook with whatever ingredients they have available.",
      model: "openai/gpt-4o-mini",
    });
    ```
  </Step>

  <Step>
    ### Register the agent, chat route, and CORS \[#register-the-agent-chat-route-and-cors]

    Open the Mastra entry point. You will set three things in the same `Mastra` constructor: the agent, a chat route, and CORS for the frontend's origin.

    ```ts title="src/mastra/index.ts"
    import { Mastra } from "@mastra/core";
    import { chefAgent } from "./agents/chefAgent";
    import { chatRoute } from "@mastra/ai-sdk";

    export const mastra = new Mastra({
      agents: { chefAgent },
      server: {
        cors: {
          origin: process.env.FRONTEND_ORIGIN ?? "http://localhost:3000",
          credentials: true,
        },
        apiRoutes: [
          chatRoute({ path: "/chat/:agentId" }),
        ],
      },
    });
    ```

    A few things to note:

    * `:agentId` matches the **JavaScript key** in the `agents` object, not the agent's `name` field. So `chefAgent` is reachable at `/chat/chefAgent` (camelCase), not `/chat/chef-agent`.
    * Add more agents to the `agents` object and they all become callable through this single route.
    * `FRONTEND_ORIGIN` should be set to your deployed frontend's URL in production. Without CORS configured, the browser blocks the request and the chat silently fails.
  </Step>

  <Step>
    ### Run the Mastra server \[#run-the-mastra-server]

    ```bash npm2yarn
    npm run dev
    ```

    The server starts on `http://localhost:4111` by default. Leave it running for the rest of the steps.
  </Step>

  <Step>
    ### Initialize the assistant-ui frontend \[#initialize-the-assistant-ui-frontend]

    In a **different directory** from the Mastra server, scaffold the frontend:

    ```sh title="New project"
    npx assistant-ui@latest create
    ```

    ```sh title="Existing project"
    npx assistant-ui@latest init
    ```

    This creates a default chat page and a local API route at `app/api/chat/route.ts`. You won't use the local route, since the agent runs on the separate Mastra server; delete it once the next step is wired.
  </Step>

  <Step>
    ### Point the runtime at the Mastra server \[#point-the-runtime-at-the-mastra-server]

    Open the file containing `useChatRuntime` (typically `app/assistant.tsx`) and pass an explicit `api` URL:

    ```tsx title="app/assistant.tsx" {8}
    "use client";

    import { AssistantRuntimeProvider } from "@assistant-ui/react";
    import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/react-ai-sdk";
    import { Thread } from "@/components/assistant-ui/thread";

    export const Assistant = () => {
      const runtime = useChatRuntime({
        transport: new AssistantChatTransport({
          api: process.env.NEXT_PUBLIC_MASTRA_URL!,
        }),
      });

      return (
        <AssistantRuntimeProvider runtime={runtime}>
          <Thread />
        </AssistantRuntimeProvider>
      );
    };
    ```

    Set the URL in your frontend's environment:

    ```sh title=".env.local"
    NEXT_PUBLIC_MASTRA_URL=http://localhost:4111/chat/chefAgent
    ```

    `NEXT_PUBLIC_*` makes the URL available in the browser. In production, point this at your deployed Mastra server. Mastra itself uses `.env.development` for its server keys; the two projects keep separate environment files.
  </Step>

  <Step>
    ### Run and verify \[#run-and-verify]

    Make sure the Mastra server (step 4) is still running in its own terminal, then start the frontend in a new terminal:

    ```bash npm2yarn
    npm run dev
    ```

    Open `http://localhost:3000`, send a message, and confirm:

    * The response streams in token by token.
    * The network tab shows the `POST` going to `http://localhost:4111/chat/chefAgent`, not the local `/api/chat`.
    * No CORS errors in the console; if you see `blocked by CORS policy`, revisit step 3.
  </Step>
</Steps>

## Notes \[#notes]

* **Auth between frontend and Mastra**: the example above is open. In production, gate the Mastra route with a header or cookie check and forward credentials from the frontend (`AssistantChatTransport` accepts `headers` and `credentials` options).
* **Single agent vs router**: `chatRoute({ path: "/chat/:agentId" })` works for any agent registered on the `Mastra` instance. Switching agents from the frontend is a matter of pointing `NEXT_PUBLIC_MASTRA_URL` at a different agent ID.
* **Advanced Mastra features** (memory, tools, workflows, evals) live in the agent definition. The frontend sees only the resulting stream. See the [Mastra docs](https://mastra.ai/docs).

## Related \[#related]

<Cards>
  <Card icon="<MastraIcon width={20} height={20} />" title="Full-stack integration" description="Run Mastra inside your Next.js API routes for a single deploy target." href="/docs/integrations/frameworks/mastra/full-stack" />

  <Card icon="<VercelIcon width={20} height={20} />" title="AI SDK runtime" description="The runtime that handles the client side of this integration." href="/docs/runtimes/ai-sdk/v6" />
</Cards>
