# Share a Mastra Agent's Output — A Tool or an MCPClient

Canonical: https://commareports.com/agents/share-mastra-output
Published: 2026-09-14

> Mastra agents and workflows return text to whatever called them. Add a createTool that publishes, or attach Comma via MCPClient, and every run ends with a URL.

# Share what a Mastra agent produced

A Mastra agent finishes its run and returns `text`. A workflow finishes and
returns whatever the last step's output schema declared. Both end up in a
server response, and neither is something you can forward to a colleague.

If the agent built an HTML report, the best case is that the report is now a
very large string in a JSON body.

## Option one — a tool

Mastra's tools are typed, so the contract is visible to you and to the model:

```ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const publishReport = createTool({
  id: "publish-report",
  description: "Publish an HTML report and return its shareable URL.",
  inputSchema: z.object({ title: z.string(), html: z.string() }),
  outputSchema: z.object({ url: z.string() }),
  execute: async ({ context }) => {
    const res = await fetch("https://commareports.com/api/v1/reports", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.COMMA_API_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ title: context.title, html: context.html }),
    });
    if (!res.ok) throw new Error(`publish failed: ${res.status}`);
    const { url } = await res.json();
    return { url };
  },
});
```

Then say so in the agent's instructions — a tool the agent is not told to reach
for is a tool it will use sometimes:

```ts
instructions: `Do the analysis. Render it as a self-contained HTML report,
publish it with publish-report, and reply with only the URL.`,
```

## Option two — MCPClient

```ts
import { MCPClient } from "@mastra/mcp";

const mcp = new MCPClient({
  servers: {
    comma: {
      url: new URL("https://commareports.com/api/mcp"),
      requestInit: {
        headers: { Authorization: `Bearer ${process.env.COMMA_API_TOKEN}` },
      },
    },
  },
});

const agent = new Agent({ name: "analyst", tools: await mcp.getTools() });
```

Publishing, updating and reading comments arrive together, and the same
configuration works from your editor — see [MCP setup](/mcp).

## Workflows: publish as the last step

```ts
const publishStep = createStep({
  id: "publish",
  inputSchema: z.object({ title: z.string(), html: z.string() }),
  outputSchema: z.object({ url: z.string() }),
  execute: async ({ inputData }) => ({ url: await publish(inputData) }),
});
```

Put it before any `suspend`. A workflow that pauses for human approval and
hands over a raw payload is asking someone to review a document they cannot
read; a workflow that pauses after publishing hands over a page with comment
threads, and the resume value can be what the reviewer wrote — see
[commenting on HTML](/comment-on-html).

## Worth knowing

- **Deployed means no filesystem.** On Mastra Cloud or any container, writing
  a file produces nothing a reader can reach — the same problem
  [CI jobs](/ci) have.
- **Keep the URL in workflow state, not the HTML.** Large strings get carried
  through every subsequent step.
- **`reports:write` is enough** to publish — see
  [scoped tokens for AI agents](/agents/scoped-tokens-for-ai-agents).
- **5 MB per report body.**

## Try it

Free — unlimited reports, commenters and revisions.

**[See MCP setup →](/mcp)**

### Related

- [Where should my agent post?](/agents/where-should-my-agent-post) ·
  [Let an agent respond to comments](/agents/let-an-agent-respond-to-comments)
- [Share OpenAI Agents SDK output](/agents/share-openai-agents-sdk-output) ·
  [Share a LangGraph run's output](/agents/share-langgraph-output)
- [MCP setup](/mcp) · [The API](/docs/api)
