# Share a PydanticAI Agent's Output — Typed Result, Real URL

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

> PydanticAI gives you a validated output type. Make the report a URL in that type: add an @agent.tool that publishes, or attach Comma's MCP server.

# Share what a PydanticAI agent produced

PydanticAI's whole argument is that an agent's output should be a validated
object rather than a hopeful string. That argument extends one step further
than most people take it: if the deliverable is a report, the validated object
should contain the report's *address*, not the report.

## A tool, from a function signature

```python
from pydantic_ai import Agent, ModelRetry
import httpx, os

agent = Agent("claude-sonnet-5", output_type=Analysis)

@agent.tool_plain
def publish_report(title: str, html: str) -> str:
    """Publish an HTML report and return its shareable URL."""
    if "<body" not in html.lower():
        raise ModelRetry("html must be a complete document with a <body>.")
    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"]
```

The `ModelRetry` is the part worth copying. A model that hands you a fragment
instead of a document gets told why and tries again — far better than
publishing a page that renders as three orphaned `<div>`s and finding out from
the reader.

## Put the URL in the type

```python
from pydantic import BaseModel, HttpUrl

class Analysis(BaseModel):
    headline: str
    confidence: float
    report_url: HttpUrl
```

Now "did this run actually produce something shareable?" is answered by
validation rather than by inspection. An agent that skipped the tool cannot
satisfy the type, and the failure happens in your process instead of in
someone's inbox.

It is also the cheap version. The document lives at the URL; your program
handles a short object — the argument in
[context budgeting for AI reports](/agents/context-budgeting-ai-reports).

## Or attach the MCP server

```python
from pydantic_ai.mcp import MCPServerStreamableHTTP

comma = MCPServerStreamableHTTP(
    "https://commareports.com/api/mcp",
    headers={"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
)
agent = Agent("claude-sonnet-5", toolsets=[comma], output_type=Analysis)
```

Publishing, updating and comment reading all arrive at once, from the same
endpoint your editor uses — see [MCP setup](/mcp).

## Worth knowing

- **Dependencies carry the token.** If you use `deps_type`, put the API token
  there rather than reading the environment inside the tool — it makes the
  agent testable without a live token.
- **One report id per recurring job.** Store it and update, so a nightly agent
  keeps one address and one comment history.
- **`reports:write`, plus `comments:read` for revisions** — 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.

**[Read the API reference →](/docs/api)**

### 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 LlamaIndex workflow's output](/agents/share-llamaindex-output)
- [The API](/docs/api) · [MCP setup](/mcp)
