
Build Mcp App
Harden authless StreamableHTTP MCP servers against abuse on compute, upstream API quota, and egress before claude.ai or public internet exposure.
Overview
build-mcp-app is an agent skill most often used in Ship (also Build integrations, Operate infra) that documents abuse protection patterns for authless hosted MCP StreamableHTTP servers.
Install
npx skills add https://github.com/anthropics/claude-plugins-official --skill build-mcp-appWhat is this skill?
- Tiered token-bucket limits for Anthropic egress CIDRs versus per-IP for direct clients
- Documents authless mode limits: no per-user identity on claude.ai proxied traffic
- 429 plus Retry-After on bucket exhaust as a per-replica backstop
- Calls out edge enforcement (Cloudflare, Cloud Armor) for cross-replica limits
- Explains when OAuth is the path to true per-user rate limits
- Documents two Anthropic egress CIDR blocks for shared-pool rate limiting
- Example tier constants: anthropic pool capacity 600 refill 100/s; other per-IP capacity 30 refill 2/s
Adoption & trust: 2.6k installs on skills.sh; 29.6k GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your public authless MCP server can be drained by anyone on the internet with no per-user identity on claude.ai traffic.
Who is it for?
Indie builders deploying authless hosted MCP tools who need rate limits without adding OAuth yet.
Skip if: Teams that already issue per-user OAuth tokens and enforce limits at an identity-aware gateway only.
When should I use this skill?
You are building or operating an authless hosted MCP app exposed on StreamableHTTP and need abuse and rate-limit guidance.
What do I get? / Deliverables
You implement CIDR-tiered token buckets, correct trust proxy settings, and a clear path to OAuth when per-user limits matter.
- Tiered token-bucket configuration aligned to Anthropic versus direct client IPs
- Documented decision on OAuth when per-user limits are required
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Hosted MCP exposure is a ship-time concern: you protect production endpoints before users hit them. Token buckets, CIDR tiering, and trust-proxy guidance are application security patterns for live MCP hosts.
Where it fits
Choose StreamableHTTP hosting and plan quota limits before wiring tools to paid upstream APIs.
Apply Anthropic CIDR tiers and per-IP buckets before opening the server to claude.ai users.
Align trust proxy with your load balancer and move cross-replica limits to Cloudflare or Cloud Armor.
How it compares
Operational security playbook for hosted MCP, not a skill that scaffolds MCP tool handlers from scratch.
Common Questions / FAQ
Who is build-mcp-app for?
Solo and indie builders shipping StreamableHTTP MCP servers that are reachable without auth, especially before claude.ai or wide public exposure.
When should I use build-mcp-app?
During Ship security when hardening hosted MCP; in Build integrations when choosing authless versus OAuth; in Operate infra when tuning trust proxy and edge rate limits alongside per-replica buckets.
Is build-mcp-app safe to install?
Treat it as documentation-style procedural knowledge; review the Security Audits panel on this Prism page before trusting the plugin source in production.
SKILL.md
READMESKILL.md - Build Mcp App
# Abuse protection for authless hosted servers An authless StreamableHTTP server is reachable by anything on the internet. There are three resources to protect: your compute, any upstream API quota your tools consume, and egress bandwidth for large `callServerTool` payloads. ## You don't get a per-user identity In authless mode there is no token and stateless transport gives no session ID. Traffic from claude.ai is proxied through Anthropic's egress — every web user arrives from the same small set of IPs: ``` 160.79.104.0/21 2607:6bc0::/48 ``` (See https://platform.claude.com/docs/en/api/ip-addresses.) Claude Desktop, Claude Code, and other hosts connect **directly from the user's machine**, so those *do* have distinct per-user IPs. Per-IP limiting therefore works for direct-connect clients; for claude.ai you can only limit the aggregate Anthropic pool. If true per-user limits matter, that's the trigger to add OAuth. ## Tiered token-bucket (per-replica backstop) ```ts const ANTHROPIC_CIDRS = ["160.79.104.0/21", "2607:6bc0::/48"]; const TIERS = { anthropic: { capacity: 600, refillPerSec: 100 }, // shared pool other: { capacity: 30, refillPerSec: 2 }, // per-IP }; ``` Match `req.ip` against the CIDRs, pick a bucket (`"anthropic"` or `"ip:<addr>"`), 429 + `Retry-After` on exhaust. This is a per-replica backstop — cross-replica enforcement belongs at the edge (Cloudflare, Cloud Armor), which keeps the containers stateless. ## `trust proxy` must match your topology `req.ip` only honours `X-Forwarded-For` if `app.set('trust proxy', N)` is set. `true` trusts every hop, which lets a direct client send `X-Forwarded-For: 160.79.108.42` and claim the Anthropic tier. Set it to the exact number of trusted hops (e.g. `1` behind a single LB, `2` behind Cloudflare → origin LB) and **never `true` in production**. ## Hard-allowlisting Anthropic IPs is a product decision Blocking everything outside `160.79.104.0/21` locks out Desktop, Claude Code, and every other MCP host. Use the CIDRs to **tier** rate limits, not to gate access, unless claude.ai-only is an explicit goal. ## Cache upstream responses For tools that wrap a third-party API, an in-process LRU keyed on the normalized query (TTL hours, no secrets in the key) is the primary cost control — repeat queries become free and absorb thundering-herd. Rate limits are the safety net, not the first line. # ext-apps messaging — widget ↔ host ↔ server The `@modelcontextprotocol/ext-apps` package provides the `App` class (browser side) and `registerAppTool`/`registerAppResource` helpers (server side). Messaging is bidirectional and persistent. ## Construction ```js const app = new App( { name: "MyWidget", version: "1.0.0" }, {}, // capabilities { autoResize: true }, // options ); ``` `autoResize: true` wires a `ResizeObserver` that emits `ui/notifications/size-changed` so the host iframe height tracks your rendered content. Without it the frame is fixed-height and tall renders get clipped — set it for any widget whose height depends on data. --- ## Widget → Host ### `app.sendMessage({ role, content })` Inject a visible message into the conversation. This is how user actions become conversation turns. ```js app.sendMessage({ role: "user", content: [{ type: "text", text: "User selected order #1234" }], }); ``` The message appears in chat and Claude responds to it. Use `role: "user"` — the widget speaks on the user's behalf. ### `app.updateModelContext({ content })` Update Claude's context **silently** — no visible message. Use for state that informs but doesn't warrant a chat bubble. ```js app.updateModelContext({ content: [{ type: "text", text: "Currently viewing: orders from last 30 days" }], }); ``` ### `app.callServerTool({ name, arguments })` Call a tool on your MCP server directly, bypassing Claude. Returns the tool result. ```js const result = await app.callServerTool({ name: "fetch_order_details", arguments: