Skip to main content
Projects

Outloud

A remote MCP server that turns what you tell your coding agent about your work into LinkedIn and X drafts in your voice. Nothing posts until you review it.

Status
Live
Role
Full Stack Developer
Team
Solo
Outloud screenshot

The problem

Developers are told to build in public, but turning what they just built into a post takes real effort. The reasoning about what was built and why already happened inside their coding-agent session, and a git webhook or a web form never sees it.

Who it is for

Developers who work with coding agents such as Claude Code and Cursor and want to post about what they ship on LinkedIn and X.

What I built

A remote MCP server and web app. Mid-session, the developer asks their agent to draft a post. Outloud takes the raw conversation context, generates a voice-matched LinkedIn or X draft, and puts it in a review inbox. It has per-platform voice profiles, learns from the edits the user makes, and publishes to LinkedIn and X. Nothing auto-posts by default. Auto-publish is Pro only, opt-in, per platform, with a countdown you can cancel.

Built with

Frontend
  • React 19
  • TanStack Start
  • TanStack Router
  • TanStack Query
  • Vite
Backend
  • Node.js
  • Hono
  • Better Auth
  • Drizzle
  • Zod
Data
  • Neon Postgres
AI & Agents
  • MCP (Streamable HTTP)
  • OAuth 2.1
  • OpenRouter
Integrations
  • LinkedIn API
  • X API
  • Dodo Payments
Infra
  • Railway
  • Cloudflare Workers
  • pnpm workspaces

Key decision

Capture the live reasoning through remote MCP rather than a git webhook or web form, and keep a human review step before publishing.

Proof

A production remote-MCP product with OAuth 2.1 dynamic client registration, atomic LLM usage accounting, and human review guaranteed before anything goes live.

In detail

Shipped to draft
<30s
Agent connect, zero keys
1 URL
Platforms, one inbox
2
Pro, BYOK on free
$10/mo

How it works

  1. Connect your agent over remote MCP

    Claude Code, Cursor, or any MCP client connects to one URL. The agent discovers Outloud's OAuth metadata, registers itself, and runs a standard authorization-code flow. No API keys to generate or paste.

  2. Ask mid-session

    "I just shipped X, write about it." The agent calls the draft_post(context, platform) tool with the raw conversation excerpt. No client-side summarization: voice matching benefits from the actual reasoning trail.

  3. Voice-matched generation

    Outloud assembles a prompt from the per-platform voice profile plus the agent context, generates through OpenRouter, and returns the draft inline to the agent along with a link into the web app.

  4. Human review

    Edit, regenerate, or discard in the draft inbox. Regeneration never burns the monthly cap, only fresh context does. Every edit is silently recorded for voice learning.

  5. Publish

    Copy-paste on the free tier, or direct API publishing to LinkedIn and X on Pro. Auto-publish is opt-in, per platform, with a cancelable countdown.

Key features

Remote MCP server

Streamable HTTP transport per the current MCP spec, so Claude Code, Claude Desktop, and Cursor all connect via a single URL rather than a locally-run stdio process. The same mechanism used by remote connectors like Notion and Linear.

OAuth 2.1, token per user

Dynamic client registration with every access token mapping 1:1 to an Outloud account, so the server always knows whose voice profile, cap, and drafts to operate on.

Per-platform voice profiles

Tone dials (technical depth, humor, formality) set separately for LinkedIn and X, plus pasted sample posts. Outloud does not scrape LinkedIn; pasted samples are the permanent approach.

Silent voice-learning loop

Edits the user makes to a draft are logged as diffs and periodically folded back into that platform's voice profile as reference examples, so drafts converge on the user's real voice over time.

One draft inbox

List, edit inline, regenerate, publish, discard, across both platforms in one place. In-house LinkedIn and X OAuth publish integrations give full control of publish UX and clean Pro gating.

Atomic usage caps

A generation slot is reserved before any LLM spend and released if generation fails, because check-then-increment was racy across LLM latency. Caps are per calendar month (UTC) with an implicit reset.

Graceful cap handling

At the cap with no BYOK key, draft_post still returns successfully with capped: true instead of erroring, so the agent surfaces it as a normal message rather than a stack trace.

BYOK on the free tier

Bring your own API key to lift the 15/month cap and the model restriction. Pro adds unlimited generations, direct publish, auto-publish, and deeper voice analysis. Billing via Dodo Payments.

Architecture

A pnpm monorepo with one backend deploy and one frontend deploy:

apps/api          Hono on Node (Railway): one process, two entry prefixes
                    /api  REST for the web app (Better Auth cookie sessions)
                    /mcp  MCP Streamable HTTP for agents (OAuth 2.1 bearer)
apps/web          TanStack Start + React 19 on Cloudflare Workers
packages/core     Business logic + platform clients (LLM, LinkedIn, X, Dodo)
packages/schemas  Shared Zod schemas: the backend/frontend contract

Decision

ADR-13 ยท Single deployable unit

The MCP handler and the REST handlers need identical business logic: build a draft, check caps, publish, update a voice profile. Both are thin entry points into one internal service layer, one Hono app on two route prefixes rather than two separately deployed services. For a small team's first version, operational simplicity matters more than splitting services early.

Core services follow ports and adapters. Every core service module ships ports.ts (the interfaces it needs) and service.ts (a factory taking those deps), and depends only on ports. The API app supplies Drizzle repos and real HTTP clients; tests supply in-memory fakes. Nothing in core opens a database connection or reads env vars.

Boot fails fast. loadEnv() validates every env var up front, then logger, Neon pg pool wrapped by Drizzle, Better Auth with the MCP OAuth-provider plugin, then all core services. A bad cipher key or missing OpenRouter key fails the deploy, not the first request. SIGINT/SIGTERM stop background jobs, close the server, then drain the pool.

Transport is hardened in a few ways. Body limits sit in front of JSON parsing (256 KiB on /api/*, 1 MiB on /mcp) because per-field Zod caps do not bound the parse itself. Credentialed CORS restricted to the web app origin, plus a CSRF guard since CORS only limits who can read responses.

The frontend holds no business logic. TanStack Start with file-based routing, React Query, and typed fetch wrappers that validate every response against @outloud/schemas, so contract drift fails loudly at the boundary.

Background jobs run in-process: publish-token refresh, auto-publish countdowns, and folding edit diffs back into voice profiles.

Note

The wedge

The input mechanism is the differentiator. Generation quality and publishing infrastructure are both commoditizing fast. Outloud captures the reasoning rather than the diff: a git webhook knows what changed, and the agent conversation knows why it mattered.

Outcome

Outloud is a production remote-MCP product: a spec-compliant Streamable HTTP MCP server with OAuth 2.1 dynamic client registration, a transport-agnostic core shared between agent and web entry points, atomic usage accounting around LLM spend, and a voice-learning loop driven by real user edits. It captures the developer's live reasoning instead of a commit diff, and a human reviews every draft before it goes live.

Challenges

  • Remote MCP over Streamable HTTP with OAuth 2.1 dynamic client registration, so Claude Code and Cursor connect via a single URL with no pasted API keys
  • One Hono process serving two entry prefixes into the same core: /api REST with cookie sessions and /mcp with OAuth bearer tokens
  • Atomic generation-cap reservation before any LLM spend, since check-then-increment was racy across LLM latency
  • Ports-and-adapters core package that is fully transport-agnostic: no Hono, HTTP, or MCP framing leaks into business logic
  • Silent voice-learning loop: user edits to drafts are recorded as diffs and periodically folded back into the per-platform voice profile
  • Fail-fast boot sequence: env validation, cipher keys, and service construction all fail the deploy, never the first request

What I learned

  • MCP authorization spec in practice: agent discovers OAuth metadata from the server URL, registers dynamically, and every tool call maps 1:1 to a user account
  • Shared core service layer consumed by both MCP and REST handlers avoids duplicating generation, usage, and publish logic across transports
  • Per-field Zod caps do not bound JSON parse cost, so body limits (256 KiB REST, 1 MiB MCP) sit in front of parsing
  • Monthly usage caps keyed by calendar month make resets implicit: the month key changes, no cron reset needed
  • Shared Zod schema package as the backend/frontend contract, so drift fails loudly at the boundary instead of rendering garbage
  • Capped-but-successful tool responses: at the generation cap the MCP tool returns capped: true instead of an error, so agents surface it as a normal message

Have a role in mind?

Send me a message on X with the role and a link to the posting. My resume has the rest.