# How AI Agents Publish to the Web: MCP, CLI, SDK, or API

> MCP server, CLI, SDK, or raw REST — compare the four ways AI agents publish content to the web, with the auth models, setup costs, and failure modes of each.

Published: 2026-06-12  
Author: Damjan Malis (Founder, dropthis)  
Canonical: https://dropthis.app/blog/how-ai-agents-publish-to-the-web/

## Key takeaways

- MCP is the default publish surface for chat-based agents (Claude, ChatGPT, Cursor): tools are discovered at runtime and OAuth handles auth without pasting keys into a conversation.
- A CLI wins inside terminal agents like Claude Code: deterministic flags, exit codes, and nothing standing between the agent and the publish call.
- SDKs and the REST API are for pipelines, not conversations — cron jobs, CI steps, and products that publish on a schedule.
- Auth is the real differentiator between surfaces: OAuth 2.1 with scopes on remote MCP, environment API keys everywhere else.
- The most common production failure is the ghost drop: an agent that re-publishes instead of updating. Keep the id from the first publish and use an explicit update verb.

An AI agent that can write HTML but can't put it anywhere is a typewriter without paper. The moment agents started generating reports, dashboards, games, and landing pages, "how do AI agents publish to the web" stopped being a hypothetical and became an architecture decision. There are four real answers — an MCP server, a CLI, an SDK, or a raw REST API — and we ship all four at [dropthis](https://dropthis.app), so this comparison comes from running them in production, not from reading other people's docs.

## What are the four ways an AI agent can publish to the web?

An agent can publish through an MCP server (tool calls inside a chat client), a CLI (shell commands inside a terminal agent), an SDK (typed library calls inside a codebase), or a REST API (direct HTTP from anywhere). All four can produce the same result — a live URL — but they differ sharply in auth, setup, and who the natural caller is.

| Surface | Natural caller | Auth model | Setup cost | Best for |
| --- | --- | --- | --- | --- |
| MCP server | Claude, ChatGPT, Cursor, Windsurf | OAuth 2.1 (remote) or env key (stdio) | One connector entry | Conversational publishing |
| CLI | Claude Code, Codex, terminal agents | API key in env | One `npm install -g` | Repo and file workflows |
| SDK | Your application code | API key in env | One dependency | Products and pipelines |
| REST API | Any language, any runtime | Bearer key | Zero dependencies | Everything else |

The split matters because agents don't choose tools the way developers do. A chat agent picks whatever tools its client exposes; a terminal agent reaches for shell commands; a cron job calls whatever its language can import. You don't pick the best surface in the abstract — you pick the one that meets the agent where it already runs.

## When should an agent publish over MCP?

Use MCP when the agent lives in a chat client. The [Model Context Protocol](https://modelcontextprotocol.io/specification/2025-06-18), open-sourced by [Anthropic in November 2024](https://www.anthropic.com/news/model-context-protocol) and adopted by [OpenAI's Agents SDK in March 2025](https://openai.github.io/openai-agents-python/mcp/), lets clients discover a server's tools at runtime — so a publish capability appears to the model as a native tool, with typed inputs and structured results.

Two properties make MCP the right default for conversational agents:

**Runtime discovery.** The client fetches tool definitions when it connects. When a server adds a verb — say, `update_settings` next to `publish` — every connected agent gets it without a software update. The tool description itself teaches the model when to use it, which is why well-written descriptions measurably reduce misuse.

**Delegated auth.** Remote MCP servers authenticate with OAuth, so the user clicks "approve" once and no credential ever appears in the conversation. A scoped token (for example `drops:write`) limits what a hijacked session can do. The trade-off is operational: someone has to run that OAuth server, handle dynamic client registration, and enforce scopes on every dispatch — we learned each of those the hard way building `mcp.dropthis.app`.

MCP's weakness is the terminal. A coding agent mid-task doesn't want a connector handshake; it wants a command that exits 0.

## When does a CLI beat an MCP server?

A CLI wins whenever the agent already has shell access and the content already lives on disk. Terminal agents like Claude Code work in repositories — they build a static site, then run one command to put it online. Flags are deterministic, output is parseable, exit codes signal failure, and there's no server hop between the agent and the publish call.

The pattern that makes CLIs agent-friendly is strict non-interactivity: every option a human would be prompted for must be a flag, because an agent can't answer an interactive prompt it can't see. Our own [CLI contract](https://github.com/dropthis-dev/dropthis-cli#readme) treats any prompt in non-TTY mode as a bug.

CLIs also compose with the rest of the shell. An agent that just generated 40 HTML reports can glob, loop, and publish them in one pass — something a chat tool-call loop does slowly and expensively.

## Where do SDKs and the REST API fit?

SDKs and REST are the pipeline surfaces. When publishing happens on a schedule or inside a product — a nightly report generator, a CI artifact step, an app that gives every user a shareable page — there is no chat client and no terminal, just code. An SDK gives you types and retries; raw REST gives you zero dependencies in any language.

The practical difference between them is small enough that the decision usually reduces to: use the SDK if it exists for your language, use REST if it doesn't. Either way the contract should be boring — one call in, one URL out:

```ts
import { Dropthis } from "@dropthis/node";

const client = new Dropthis(); // reads DROPTHIS_API_KEY from the environment
const drop = await client.drops.publish({
  content: html,
  title: "Q2 revenue report",
});
console.log(drop.url); // live URL, ~1s later
```

One number worth knowing when you wire this into an agent pipeline: payload limits. On dropthis, a free drop carries up to 5 MB and expires after 7 days; Pro raises that to 100 MB and permanent URLs. Agents that generate media-heavy pages hit size ceilings far sooner than humans do, so check limits programmatically instead of letting a 413 surprise your pipeline.

## How does authentication differ across the four surfaces?

Remote MCP uses OAuth 2.1 with PKCE — the user approves once in a browser and the client holds a scoped token. Every other surface uses an API key read from the environment. The rule of thumb: interactive runtimes get OAuth so users never handle keys; non-interactive runtimes get keys because there is no user present to click "approve."

| Surface | Credential | Where it lives | Revocation story |
| --- | --- | --- | --- |
| Remote MCP | OAuth 2.1 token, scoped | Chat client's token store | Disconnect the connector |
| Local (stdio) MCP | API key | MCP config env block | Rotate the key |
| CLI | API key | Shell environment / keychain | Rotate the key |
| SDK / REST | API key | Secret manager, CI secrets | Rotate the key |

The [OAuth 2.1 draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/) matters here because it made PKCE mandatory — which is what makes it safe for chat clients to register dynamically and connect without a pre-shared secret. If you're evaluating any publish-capable MCP server, the two questions to ask: does it enforce scopes on every tool dispatch (not just at token mint), and can a token downscope but never upscope? Both have been real vulnerabilities in shipping MCP servers — including, briefly, ours.

## The failure mode nobody designs for: ghost drops

The most common bug in agent publishing isn't auth or payload size. It's identity: an agent generates an artifact, publishes it, loses the returned id, then "fixes a typo" by publishing again. Result: five URLs for one document, four of them stale. We call these ghost drops, and before we redesigned our verbs around the problem, they were the dominant pattern in our production data.

The fix is a vocabulary constraint, not a feature. `publish` never takes an id and always creates; `update_content` always takes an id and never creates. The publish response carries `next` hints telling the agent exactly which verb to use for follow-ups. Since shipping that split across [all our surfaces](/blog/pillars/agent-publishing/), repeat-publish loops dropped to near zero. If you're building any publish tool for agents, make creation and mutation impossible to confuse — agents follow the grammar you give them.

## Which surface should you pick?

Match the surface to the runtime, not to taste:

1. **Agent in a chat client** (Claude, ChatGPT, Cursor) → remote MCP with OAuth. Zero key handling for users.
2. **Agent in a terminal** (Claude Code, Codex) → CLI. Deterministic flags, shell composition.
3. **Scheduled or product code** → SDK in supported languages, REST anywhere else.
4. **Unsure** → start with REST; it's the substrate the other three are built on.

The deeper point: these aren't competing options, they're one capability that has to show up in four runtimes. An agent that publishes a report over MCP today may regenerate it from CI tomorrow — and if your publish layer doesn't keep ids, URLs, and verbs identical across surfaces, the handoff breaks. That consistency is the actual product. The transport is just plumbing.

## Frequently asked questions

### What is the easiest way for an AI agent to publish content to the web?

Use an MCP server if the agent runs in a chat client, or a CLI if it runs in a terminal. Both turn generated content into a live URL in one call. dropthis exposes the same publish verb across MCP, CLI, SDK, and REST, so the choice is about runtime, not capability.

### Can ChatGPT or Claude publish a web page directly?

Yes, through MCP connectors. Claude, ChatGPT, Cursor, and Windsurf can call a remote MCP server such as mcp.dropthis.app, authenticate with OAuth, and publish generated HTML to a permanent URL mid-conversation. The page URL comes back as the tool result — no copy-paste, no separate deploy step.

### Do AI agents need OAuth to publish content?

Only on remote MCP connections, where OAuth 2.1 with PKCE is the standard and keys never touch the chat client. Terminal and pipeline surfaces — CLI, SDK, REST — authenticate with an API key from an environment variable instead, which is simpler to rotate and audit in CI.

### How does an agent update a page instead of creating a new one?

Keep the id returned by the first publish call and pass it to an explicit update verb, like update_content in the dropthis MCP server. Publishing again without the id creates a second URL — the ghost-drop problem — so agents should store ids next to the artifacts they generate.

## Sources

- [Model Context Protocol specification (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18), Model Context Protocol
- [Introducing the Model Context Protocol](https://www.anthropic.com/news/model-context-protocol), Anthropic
- [OpenAI Agents SDK: Model Context Protocol](https://openai.github.io/openai-agents-python/mcp/), OpenAI
- [The OAuth 2.1 Authorization Framework (draft)](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/), IETF
- [dropthis OpenAPI specification](https://api.dropthis.app/openapi.json), dropthis