|
| 1 | +--- |
| 2 | +title: How I Built an x402-Monetized MCP Server for AI Presentation Generation |
| 3 | +published: false |
| 4 | +tags: mcp, ai, typescript, python |
| 5 | +--- |
| 6 | + |
| 7 | +# How I Built an x402-Monetized MCP Server for AI Presentation Generation |
| 8 | + |
| 9 | +AI agents can write code, search the web, query databases, and manage files. But ask one to create a PowerPoint deck and you get a code block with python-pptx boilerplate that positions shapes by pixel offset. The output looks like a 2003 corporate template, and it breaks the moment content overflows a text box. |
| 10 | + |
| 11 | +I built [DeckForge](https://github.com/Whatsonyourmind/deckforge) to fix this: an API-first presentation generation platform with an MCP server that gives AI agents real slide creation capabilities. And because I wanted autonomous agents to pay per-call without API key management, I integrated x402 -- the HTTP-native micropayment protocol that settles in USDC on Base L2. |
| 12 | + |
| 13 | +This article covers the architecture, the MCP integration, and how x402 payments work in practice. |
| 14 | + |
| 15 | +## The Problem |
| 16 | + |
| 17 | +There are three ways to generate slides programmatically today: |
| 18 | + |
| 19 | +1. **python-pptx** -- the standard library. It gives you element-level control, but you manually position every shape (x, y, width, height in EMUs). There's no layout engine, no theme system, no chart rendering. It has 530 open issues on GitHub and the last meaningful update was years ago. |
| 20 | + |
| 21 | +2. **GUI tools like Gamma and Tome** -- beautiful output, but zero API access. You can't call them from code or an agent. |
| 22 | + |
| 23 | +3. **Ask an LLM to write python-pptx code** -- this sort of works, but the LLM doesn't know the actual dimensions of rendered text, so content overflow is guaranteed. And you're generating code that generates slides, which is one abstraction too many. |
| 24 | + |
| 25 | +The gap is: send structured data, get a polished deck. That's DeckForge. |
| 26 | + |
| 27 | +## Architecture |
| 28 | + |
| 29 | +DeckForge is a FastAPI service with a JSON intermediate representation (IR) at the core: |
| 30 | + |
| 31 | +``` |
| 32 | +Client (curl / SDK / MCP agent) |
| 33 | + | |
| 34 | + v |
| 35 | +FastAPI API (/v1/render, /v1/generate) |
| 36 | + | |
| 37 | + +-- Auth middleware (Unkey API keys OR x402 payments) |
| 38 | + +-- Rate limiter (Redis token bucket) |
| 39 | + +-- Credit billing (reserve -> render -> deduct/release) |
| 40 | + | |
| 41 | + v |
| 42 | +Render Pipeline (sync < 10 slides, async via ARQ workers) |
| 43 | + | |
| 44 | + +-- Layout engine (kiwisolver constraint solver, 12-col grid) |
| 45 | + +-- Theme resolver (15 YAML themes, WCAG AA contrast) |
| 46 | + +-- Chart renderer (24 types via Plotly at 300 DPI) |
| 47 | + +-- QA pipeline (5 passes: contrast, overflow, alignment, ...) |
| 48 | + | |
| 49 | + v |
| 50 | +Output: .pptx file (or Google Slides via API) |
| 51 | +``` |
| 52 | + |
| 53 | +The IR schema uses Pydantic discriminated unions. There are 32 slide types -- 23 universal (title, bullets, chart, table, comparison, timeline, funnel, matrix, org chart, stats callout, etc.) and 9 finance-specific (DCF summary, comp table, waterfall, deal overview, capital structure, market landscape, risk matrix, investment thesis, returns analysis). |
| 54 | + |
| 55 | +The key insight: **layout is a constraint satisfaction problem, not a coordinate problem.** Instead of hardcoding positions, each slide type defines layout constraints (e.g., "title is anchored to top, content fills remaining space, metrics are evenly distributed"). Kiwisolver resolves these into coordinates at render time, and the overflow handler cascades through font reduction -> reflow -> slide splitting if content doesn't fit. |
| 56 | + |
| 57 | +## MCP Integration |
| 58 | + |
| 59 | +The [Model Context Protocol](https://modelcontextprotocol.io/) is how AI agents discover and invoke tools. DeckForge exposes 6 MCP tools via a FastMCP server: |
| 60 | + |
| 61 | +```python |
| 62 | +from mcp.server.fastmcp import FastMCP |
| 63 | + |
| 64 | +mcp = FastMCP( |
| 65 | + "DeckForge", |
| 66 | + instructions=( |
| 67 | + "DeckForge is an API for generating and rendering executive-ready " |
| 68 | + "presentations. Use these tools to create PowerPoint decks from " |
| 69 | + "natural language prompts or structured IR." |
| 70 | + ), |
| 71 | +) |
| 72 | + |
| 73 | +@mcp.tool() |
| 74 | +async def render( |
| 75 | + ir_json: str, |
| 76 | + theme: str = "corporate-blue", |
| 77 | + output_format: str = "pptx", |
| 78 | +) -> dict: |
| 79 | + """Render a Presentation IR into a PowerPoint file. |
| 80 | +
|
| 81 | + Takes a JSON string conforming to the DeckForge IR schema and |
| 82 | + produces a rendered PPTX. Returns slide count, quality score, |
| 83 | + and file size. |
| 84 | + """ |
| 85 | + return await render_presentation(ir_json, theme, output_format) |
| 86 | + |
| 87 | + |
| 88 | +@mcp.tool() |
| 89 | +async def generate( |
| 90 | + prompt: str, |
| 91 | + slide_count: int = 10, |
| 92 | + theme: str = "corporate-blue", |
| 93 | +) -> dict: |
| 94 | + """Generate a complete presentation from a natural language prompt. |
| 95 | +
|
| 96 | + Transforms a text description into a fully structured presentation |
| 97 | + with professional layouts, content, and charts. |
| 98 | + """ |
| 99 | + return await generate_presentation(prompt, slide_count, theme) |
| 100 | + |
| 101 | + |
| 102 | +@mcp.tool() |
| 103 | +async def themes() -> list[dict]: |
| 104 | + """List all 15 available themes.""" |
| 105 | + return await list_themes() |
| 106 | + |
| 107 | + |
| 108 | +@mcp.tool() |
| 109 | +async def slide_types(category: str | None = None) -> list[dict]: |
| 110 | + """List 32 slide types. Filter by 'universal' or 'finance'.""" |
| 111 | + return await list_slide_types(category) |
| 112 | + |
| 113 | + |
| 114 | +@mcp.tool() |
| 115 | +async def cost_estimate(ir_json: str) -> dict: |
| 116 | + """Estimate credit cost for rendering a presentation.""" |
| 117 | + return await estimate_cost(ir_json) |
| 118 | + |
| 119 | + |
| 120 | +@mcp.tool() |
| 121 | +async def pricing() -> dict: |
| 122 | + """Get subscription tiers and x402 per-call rates.""" |
| 123 | + return await get_pricing() |
| 124 | +``` |
| 125 | + |
| 126 | +To use DeckForge with Claude Desktop or any MCP-compatible client, add this to your config: |
| 127 | + |
| 128 | +```json |
| 129 | +{ |
| 130 | + "mcpServers": { |
| 131 | + "deckforge": { |
| 132 | + "command": "python", |
| 133 | + "args": ["-m", "deckforge.mcp.server"] |
| 134 | + } |
| 135 | + } |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +Now Claude can do things like: |
| 140 | + |
| 141 | +> "Create a 12-slide PE deal memo for a $500M healthcare LBO with a DCF summary, comp table, and returns waterfall. Use the finance-pro theme." |
| 142 | +
|
| 143 | +The agent calls `generate`, gets back a rendered PPTX, and the user downloads an actual PowerPoint file -- not a code snippet. |
| 144 | + |
| 145 | +## x402: Machine-Native Payments |
| 146 | + |
| 147 | +Here's the interesting part. Traditional API billing assumes a human signs up, enters a credit card, and manages an API key. But autonomous AI agents don't have credit cards. They have wallets. |
| 148 | + |
| 149 | +[x402](https://x402.org) is an HTTP payment protocol that brings the `402 Payment Required` status code to life. The flow: |
| 150 | + |
| 151 | +1. **Agent hits a protected endpoint without credentials.** DeckForge returns `402` with a pricing schedule: |
| 152 | + |
| 153 | +```json |
| 154 | +{ |
| 155 | + "error": "payment_required", |
| 156 | + "x402": { |
| 157 | + "version": "1.0", |
| 158 | + "currency": "USDC", |
| 159 | + "network": "eip155:8453", |
| 160 | + "facilitator": "https://x402.org/facilitator", |
| 161 | + "recipient": "0x...", |
| 162 | + "price_usd": "0.05", |
| 163 | + "all_prices": { |
| 164 | + "POST /v1/render": "0.05", |
| 165 | + "POST /v1/generate": "0.15" |
| 166 | + } |
| 167 | + } |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +2. **Agent constructs a payment proof** -- a signed USDC transfer authorization on Base L2. |
| 172 | + |
| 173 | +3. **Agent retries the request with a `PAYMENT-SIGNATURE` header.** DeckForge middleware verifies the payment via the x402 facilitator, settles it on-chain, and processes the request. |
| 174 | + |
| 175 | +The middleware implementation is dual-path: |
| 176 | + |
| 177 | +```python |
| 178 | +async def x402_or_apikey_auth( |
| 179 | + request: Request, |
| 180 | + payment_sig: str | None = Security(PAYMENT_SIG_HEADER), |
| 181 | + api_key_header: str | None = Security(API_KEY_HEADER), |
| 182 | +) -> AuthContext: |
| 183 | + """Dual auth: x402 payment OR API key. |
| 184 | +
|
| 185 | + Priority: |
| 186 | + 1. PAYMENT-SIGNATURE header -> x402 verify/settle |
| 187 | + 2. X-API-Key header -> Unkey or DB auth |
| 188 | + 3. Neither -> 402 with pricing info |
| 189 | + """ |
| 190 | + if payment_sig and settings.X402_ENABLED: |
| 191 | + price = get_x402_price(request.method, request.url.path) |
| 192 | + return await _verify_x402_payment(request, payment_sig, price) |
| 193 | + |
| 194 | + if api_key_header: |
| 195 | + return await get_api_key(db, api_key_header) |
| 196 | + |
| 197 | + raise HTTPException( |
| 198 | + status_code=402, |
| 199 | + detail=_build_402_response(request.method, request.url.path), |
| 200 | + ) |
| 201 | +``` |
| 202 | + |
| 203 | +x402-authenticated requests skip rate limiting entirely -- per-call payment is inherently self-throttling. The agent pays $0.05 per render and $0.15 per generate. No subscription, no credit card, no API key signup. |
| 204 | + |
| 205 | +This matters because the direction of AI tooling is toward autonomous agent workflows. An agent that can discover a tool via MCP and pay for it via x402 doesn't need human intervention at any step. |
| 206 | + |
| 207 | +## TypeScript SDK |
| 208 | + |
| 209 | +For human developers who prefer a typed client over raw curl, there's a TypeScript SDK on npm: |
| 210 | + |
| 211 | +```bash |
| 212 | +npm install @lukastan/deckforge |
| 213 | +``` |
| 214 | + |
| 215 | +The SDK uses an immutable builder pattern: |
| 216 | + |
| 217 | +```typescript |
| 218 | +import { DeckForge, Presentation, Slides } from "@lukastan/deckforge"; |
| 219 | + |
| 220 | +const client = new DeckForge({ apiKey: "dk_test_..." }); |
| 221 | + |
| 222 | +const deck = Presentation.create("Q4 Board Update", "corporate-blue") |
| 223 | + .addSlide( |
| 224 | + Slides.titleSlide({ |
| 225 | + title: "Q4 2026 Board Update", |
| 226 | + subtitle: "Acme Corp -- Confidential", |
| 227 | + }) |
| 228 | + ) |
| 229 | + .addSlide( |
| 230 | + Slides.statsCallout({ |
| 231 | + title: "Key Metrics", |
| 232 | + metrics: [ |
| 233 | + { value: "$4.2M", label: "ARR" }, |
| 234 | + { value: "142%", label: "YoY Growth" }, |
| 235 | + { value: "94%", label: "Retention" }, |
| 236 | + ], |
| 237 | + }) |
| 238 | + ); |
| 239 | + |
| 240 | +const pptx = await client.render(deck); |
| 241 | +// pptx is a Buffer containing the .pptx file |
| 242 | +``` |
| 243 | + |
| 244 | +The generate endpoint supports SSE streaming so you can show progress: |
| 245 | + |
| 246 | +```typescript |
| 247 | +const stream = client.generate({ |
| 248 | + prompt: "PE deal memo for a $500M LBO of a healthcare platform", |
| 249 | + theme: "finance-pro", |
| 250 | +}); |
| 251 | + |
| 252 | +for await (const event of stream) { |
| 253 | + console.log(`${event.stage}: ${event.message}`); |
| 254 | +} |
| 255 | +// intent: Analyzing prompt... |
| 256 | +// outline: Creating 12-slide deal memo... |
| 257 | +// expand: Generating slide content... |
| 258 | +// refine: Running QA pipeline (5 passes)... |
| 259 | +// complete: Deck ready for download |
| 260 | +``` |
| 261 | + |
| 262 | +Full type coverage for all 32 slide types and 24 chart types. Every `Slides.*` method is typed to its specific element schema, so you get autocomplete and compile-time validation. |
| 263 | + |
| 264 | +## The Finance Angle |
| 265 | + |
| 266 | +The finance vertical is deliberate, not accidental. PE firms, investment banks, and consulting firms generate massive volumes of standardized presentations: IC memos, teasers, CIMs, board decks, quarterly reviews. The formatting is rigid, repetitive, and time-consuming. |
| 267 | + |
| 268 | +The 9 finance slide types encode domain conventions: |
| 269 | +- **DCF summary** -- valuation ranges with assumption labels |
| 270 | +- **Comp table** -- peer comparison with conditional formatting (green/red for relative positioning) |
| 271 | +- **Returns waterfall** -- bridge chart showing entry to exit value creation |
| 272 | +- **Deal overview** -- standardized transaction summary layout |
| 273 | +- **Capital structure** -- debt/equity stack visualization |
| 274 | + |
| 275 | +These aren't generic templates -- they're structured slide types with typed fields that match how finance professionals think about the data. |
| 276 | + |
| 277 | +## Current State and What's Next |
| 278 | + |
| 279 | +This is v0.1. The API is live at `https://deckforge-api.onrender.com`, 808 tests passing, MIT licensed. Pre-revenue, sole developer. |
| 280 | + |
| 281 | +What works well: |
| 282 | +- IR-to-PPTX rendering is solid -- layout engine handles most content gracefully |
| 283 | +- Finance slide types produce output that looks close to what you'd see from a real deal team |
| 284 | +- MCP integration works with Claude Desktop |
| 285 | + |
| 286 | +What needs work: |
| 287 | +- NL-to-IR content generation quality varies by LLM provider |
| 288 | +- Google Slides output path is less polished than PPTX |
| 289 | +- x402 payment flow is implemented but untested in production with real agent wallets |
| 290 | +- Need more themes and better chart styling |
| 291 | + |
| 292 | +If you work on AI agents, MCP tooling, or generate presentations programmatically, I'd appreciate feedback on the API design and output quality. |
| 293 | + |
| 294 | +**Links:** |
| 295 | +- GitHub: [github.com/Whatsonyourmind/deckforge](https://github.com/Whatsonyourmind/deckforge) |
| 296 | +- API: [deckforge-api.onrender.com](https://deckforge-api.onrender.com) |
| 297 | +- npm SDK: [@lukastan/deckforge](https://www.npmjs.com/package/@lukastan/deckforge) |
| 298 | +- Landing page: [landing-two-beta-63.vercel.app](https://landing-two-beta-63.vercel.app) |
| 299 | +- API docs: [deckforge-api.onrender.com/docs](https://deckforge-api.onrender.com/docs) |
0 commit comments