[KongchangAI]
· 3 min read· 1,807 words

How a Paid SaaS Built Its MCP Server: OAuth, Cost Previews, and Tool Trade-offs

How a Paid SaaS Built Its MCP Server: OAuth, Cost Previews, and Tool Trade-offs

Traffic Creator shares the architecture, tool design, authentication, and security decisions behind building an MCP server for a paid SaaS.

The Traffic Creator team built an MCP server for their paid SaaS product so Claude and ChatGPT users can manage ad campaigns in natural language. The article covers four key decisions: a thin proxy architecture that centralizes permissions, rate limits, and logging in the underlying API; 34 tools organized by how users ask questions rather than by database structure; READ/SPEND/CHANGE/STOP/MESSAGE annotations that mark risk levels and let AI clients decide when to request confirmation; and `settings_version` optimistic locking to keep the assistant from overwriting dashboard edits. Authentication uses OAuth (PKCE, token rotation) for Claude and ChatGPT, keeps API keys for terminal tools, and offers a read-only mode. The team deliberately omitted high-risk tools like delete, clone, and buy credits, and distilled four lessons for building MCP servers for paid products.

When an AI assistant can help users create campaigns, it can also spend their money. The Traffic Creator team recently built an MCP server for their paid SaaS product, letting Claude and ChatGPT users query and manage ad campaigns through natural language. This article shares their key decisions around OAuth authentication, cost control, and tool design.

Building an MCP server for a paid SaaS

Thin proxy architecture: a deliberate design choice

Traffic Creator's MCP server has no database access and holds no state beyond a short-lived credential cache. Each tool is just a small function that calls an /api/v1/account route using the caller's own credentials and returns JSON.

This seemingly boring decision turned out to be the single most important architectural choice of the entire project. Ownership checks, balance checks, rate limits (60 reads and 10 mutations per minute), read-only scopes, and activity logging all live at the API layer. This means a tool can't do anything beyond what the customer's API key permits. When a rule in the API is fixed, every AI assistant gets the fix at the same time, with no separate deployment required.

Another benefit of deploying the API and MCP server independently: the server can hide tools whose routes aren't live yet, rather than exposing an endpoint that errors out.

MCP (Model Context Protocol) is an open protocol introduced by Anthropic in late 2024 that lets AI assistants (such as Claude and ChatGPT) connect to external tools and data sources in a standardized way. Think of it as a "USB-C port for the AI world": the client initiates the conversation and renders the output, while the server exposes a set of named tools that the model invokes through JSON-RPC-style requests. The MCP server itself carries no business logic—business rules should stay in the underlying API, which is exactly where the thin proxy architecture comes from. Tokens are the credentials a user grants to a client, commonly in the form of opaque random strings or JWTs, with the former making immediate revocation easier.

Tool design follows user questions, not database schemas

The initial idea was to map MCP tools one-to-one with API routes, but the resulting experience felt like reading a database schema. Customers don't ask "give me campaign analytics with a date range"—they ask "which campaign is falling behind" or "how does last week compare to the week before."

The team eventually grouped 34 tools by how users actually ask questions: reading data, rankings, single-campaign analysis, pricing and cost previews, creation, modify/pause/resume, credits and billing, support tickets, and account management.

Every tool description was written specifically for the model: what it returns, what units it uses, the time zone (UTC), and when a different tool should be used instead. Since AI assistants display tool names while working, those names also need to be intuitive for customers—things like get_campaign_stats, preview_campaign, and pause_campaign.

Annotations determine when the app asks for confirmation

MCP lets servers add hint annotations to each tool: read-only, destructive, idempotent, and open-world. The team used five combinations:

  • READ: read-only, non-destructive, idempotent
  • SPEND: mutation, destructive, non-idempotent (e.g., creating a campaign)
  • CHANGE: mutation, destructive, idempotent (e.g., resuming and updating a campaign)
  • STOP: mutation, non-destructive, idempotent (e.g., pausing a campaign)
  • MESSAGE: for creating or replying to support tickets

Creating a campaign is marked SPEND because it genuinely spends money. Resuming and updating are classified as CHANGE—running them twice causes no additional harm, but they can make a campaign consume credits again, so they're also marked destructive. Claude, ChatGPT, Claude Code, and Codex use these hints to decide whether to ask the user before invoking a tool.

Pausing is marked STOP: it does change state, but only makes the account cheaper, so the team didn't want the app to make users feel the action is dangerous.

Annotations are requests to the client, not guarantees. So the server instructions also spell things out in plain language: before creating a campaign, show the user the result of preview_campaign and get their consent. The preview returns which wallet will be charged, how many credits the campaign needs, and whether the balance is sufficient. Creating a campaign itself costs nothing—credits are billed on actual delivered visits or verified clicks, and never exceed the balance.

Idempotency means that performing an operation once vs. multiple times leaves the system in the same final state. For example, "pause the campaign" is idempotent because calling it twice yields the same result, whereas "create campaign" is non-idempotent because each call creates a new campaign and consumes credits. In MCP annotations, "destructive" and "idempotent" are two independent dimensions: destructive describes whether an operation could cause hard-to-undo consequences (like spending the balance), and idempotent describes whether repeated execution is safe. Understanding how these two dimensions combine is what lets you assess what a given tool will do if the model calls it by mistake, and it also determines whether the client needs to show a confirmation dialog before invoking it.

Optimistic locking: preventing the assistant from overwriting dashboard changes

Users operate the same campaign from both the dashboard and the AI assistant at the same time. If the assistant read the campaign ten minutes ago and the customer has since made a change in the dashboard, a blind update would silently undo the customer's edit.

Every campaign carries a settings_version. update_campaign must pass back the version it read, and if the campaign has been modified, the API rejects the change. The assistant has to re-read the campaign before trying again.

This is standard optimistic locking, but it matters even more in scenarios involving a model, because models are very confident about the state they read some time ago.

Tools that were deliberately left out

The team intentionally excluded several capabilities:

  • Deleting and cloning campaigns—a single misinterpreted sentence shouldn't make a campaign disappear, and reopening the dashboard to do it again takes only seconds
  • Buying credits—no card or bank details ever pass through the MCP server
  • Billing, payment details, profile, or password operations—billing tools can only read purchase history, refunds, and subscription information

Read-only access is an option on the consent screen. Once selected, the assistant can report but cannot change or spend anything.

Dual-track authentication: OAuth for Claude and ChatGPT, keys for the terminal

Claude and ChatGPT connect via OAuth—customers log in with their normal account and never need to copy a key. The flow is as follows:

  1. A token-less request to /mcp receives a 401 response with a resource_metadata pointer to the authorization server
  2. The client registers via dynamic client registration or a client ID metadata document
  3. PKCE is mandatory, with S256 only
  4. The customer lands on the consent page in the dashboard and chooses full access or read-only
  5. Behind the scenes, this maps to four scopes: campaigns:read, campaigns:create, campaigns:update, campaigns:control

Tokens are opaque and stored only as SHA-256 digests. Access tokens last one hour; refresh tokens last thirty days and rotate on every use. If a rotated refresh token shows up again, the entire token family is revoked (RFC 9700 Section 4.14.2).

Local tools like Claude Code, Codex, and Cursor can log in the same way, or use an account API key as a bearer token. Changing the password or revoking the app on the API & MCP page immediately terminates that access.

PKCE (Proof Key for Code Exchange, RFC 7636) is an extension to the OAuth 2.0 authorization code flow that prevents an intercepted authorization code from being abused. The client generates a random code_verifier when starting authorization and sends its hash, the code_challenge; when exchanging the code for a token, it sends back the original verifier, and the authorization server checks whether the hashes match. Since the verifier never travels through the browser redirect, an attacker can't redeem the token even if the authorization code leaks. S256 means hashing with SHA-256. For AI applications like MCP that can't securely store a client secret, PKCE is essentially a necessity, because in dynamic client registration scenarios it's hard to pre-share a secret.

Token rotation and family revocation is a mechanism in OAuth 2.0 security best practices (RFC 9700): a refresh token is invalidated immediately after each use and a new refresh token is issued. If an old refresh token appears again, it may have been stolen and replayed by a third party, so the authorization server treats the entire "token family" as compromised, revokes all of it, and requires the user to log in again. Combined with SHA-256 digest storage (the server never keeps the raw token), even a database leak won't let an attacker use these tokens directly. The one-hour access token and thirty-day refresh token lifetimes are a common trade-off between security and user experience.

Production monitoring

Every tool call logs the tool name, result, and credential type, but not parameters or account data. A small monitor connects like a client every five minutes, checks whether the number of tools returned by tools/list matches expectations, and calls a read tool. If a deployment causes tools to go missing or authentication to fail, the team finds out before customers do.

Key takeaways

The Traffic Creator team offered four recommendations:

  • Build on top of your public API, not a parallel one beside it
  • Write tool descriptions for readers who have never seen your product
  • Treat every tool that spends money as something the user must see before it runs
  • Leave irreversible operations in the UI

These lessons are valuable to any team thinking about building an MCP server for a paid product. The core insight is this: when AI assistants gain the ability to act, designing safety boundaries matters more than the features themselves. The thin proxy architecture, cost previews, optimistic locking, and tool trade-offs all exist to keep the model's capabilities operating within controllable limits.

One-line Summary

Traffic Creator shares the architecture, tool design, authentication, and security decisions behind building an MCP server for a paid SaaS.

Paragraph Summary

The Traffic Creator team built an MCP server for their paid SaaS product so Claude and ChatGPT users can manage ad campaigns in natural language. The article shares four key decisions: a thin proxy architecture that centralizes permissions, rate limits, and logging in the underlying API; 34 tools organized by how users ask questions rather than by database structure; READ/SPEND/CHANGE/STOP/MESSAGE annotations that mark risk levels and let AI clients decide when to ask for confirmation; and settings_version optimistic locking to keep the assistant from overwriting dashboard edits. For authentication, they use OAuth (PKCE, token rotation) for Claude and ChatGPT, keep API keys for terminal tools, and offer a read-only mode. The team also deliberately omitted high-risk tools like delete, clone, and buy credits, and distilled four lessons for anyone building an MCP server for a paid product.

#MCP server#OAuth#paid SaaS#AI assistant#tool annotations#PKCE#optimistic locking#cost preview
Share:

Related articles