# Share OpenAI Agents SDK Output — A Tool or an MCP Server

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

> Runner.run returns final_output and nothing anyone can open. Add a @function_tool or attach Comma's MCP server so the agent ends a run with a URL.

# Share what an Agents SDK run produced

`Runner.run` comes back. `result.final_output` holds the report. You print it,
the terminal wraps four thousand lines, and the person who asked for the
analysis is on Slack asking whether it is done.

## Two ways in

**A function tool** is the shortest path:

```python
from agents import Agent, Runner, function_tool
import httpx, os

@function_tool
def publish_report(title: str, html: str) -> str:
    """Publish an HTML report and return its shareable URL."""
    r = httpx.post(
        "https://commareports.com/api/v1/reports",
        headers={"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
        json={"title": title, "html": html},
    )
    r.raise_for_status()
    return r.json()["url"]

agent = Agent(
    name="analyst",
    instructions=(
        "Do the analysis, render it as a self-contained HTML report, "
        "publish it with publish_report, and return only the URL."
    ),
    tools=[publish_report],
)
```

**An MCP server** is the better path if you also use Comma from an editor,
because it is the same endpoint and the same token:

```python
from agents.mcp import MCPServerStreamableHttp

async with MCPServerStreamableHttp(
    params={
        "url": "https://commareports.com/api/mcp",
        "headers": {"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
    },
) as comma:
    agent = Agent(name="analyst", instructions=..., mcp_servers=[comma])
```

The tool list is discovered, so publishing, updating and reading comments all
arrive without you writing wrappers for each. Setup shape is in
[MCP setup](/mcp).

## Make the URL part of the type

The SDK's structured outputs are the clean way to stop HTML from sloshing
around your program:

```python
class Analysis(BaseModel):
    summary: str
    report_url: str

agent = Agent(name="analyst", output_type=Analysis, tools=[publish_report])
```

Now `final_output` is a short object with an address in it. The document lives
at the URL, the program handles a string, and nothing downstream has to decide
what to do with four thousand lines of markup — the point of
[context budgeting for AI reports](/agents/context-budgeting-ai-reports).

## Handoffs: publish at the end, not everywhere

In a triage → research → writer chain, only the writer should hold the publish
tool. Giving it to all three invites the triage agent to publish an
introduction, and you end up with three reports where you wanted one.

## Worth knowing

- **Guardrails run first.** Anything that checks the output for policy should
  trip before the publish tool, not after.
- **Sessions make revisions natural.** Keep the report id in session state and
  update it on the next turn, so one conversation produces one evolving report.
- **`reports:write`, plus `comments:read` for the review loop** — 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 Claude Agent SDK output](/agents/share-claude-agent-sdk-output) ·
  [Share a PydanticAI agent's output](/agents/share-pydantic-ai-output)
- [The API](/docs/api) · [MCP setup](/mcp)
