oll.am · Research + Build Spec · Plan & Reflection · 2026-07-06

The Agent Seam

Extending oll.am for the job-application layer via MCP — the least-effort path from building blocks to a stranger's dollar. Plan the MCP server first, as a build spec, before any code.
Question: how does Sam's "OpenCLAW" agent runtime drive oll.am's capabilities? Answer: MCP — one capability seam, any runtime. Status: plan only · no code written

What's inside

  1. The thesis — one seam, two payoffs
  2. The honest split — done vs hard
  3. The shape — runtime → seam → capabilities
  4. The MCP server build spec
  5. The auth model — the real design decision
  6. The one genuinely-new extension
  7. Sequencing — respecting ship-vs-build
  8. Risks, open questions & decisions
  9. Detailed build plan (v1) — thin server, local-test-first

The thesis lead with this

oll.am should stay the frozen capability layer. The "job agent" is not a new backend service — it's an external agent runtime (Sam's OpenCLAW, an agentic browser / computer-use runner) that is a thin, disposable client. The correct seam between them is MCP: build the capability surface once and any runtime can drive it — OpenCLAW today, Claude Desktop tomorrow (already on the roadmap as the Phase-3 "oll.am as a Claude Desktop plugin"). One build, two payoffs.

Capabilities are frozen infrastructure. Agents are cheap and replaceable. MCP is the wall between them — so a new runtime never touches the capabilities, and a new capability never touches the runtime. — the same philosophy that froze Core, applied to agents

This matches the frozen-Core discipline exactly. We do not bolt a bespoke "job-application backend" onto the platform; we expose what we already have through a standard protocol, and we let the volatile, fashion-driven, replaceable part — the agent — live outside the wall where it can be thrown away and rebuilt without risk to the money-earning core.

The honest split two halves, wildly different difficulty

The job-application pipeline is two halves, and being honest about which is which is the whole plan. One half we have already built and it is low-risk. The other half is un-built, hostile, and where every seductive "apply to 100 jobs in an hour" pitch secretly lives.

Already done · low risk · frozen

Content generation

  • oll-write already exposes clarify / draft / rewrite / expand / compress / simplify / tldr / bullets / brainstorm / improve — all Core-JWT-gated with live plan checks.
  • foto does guest-checkout headshots via Replicate.
  • oll-model is the provider-agnostic LLM gateway underneath.
  • Core owns auth / billing / email.
  • Tailored CV, cover letter, headshot, interview-prep answers — this is prose and pixels, which the platform is built for.
Un-built · high risk · hostile

Application execution

  • Find jobs → match → fill heterogeneous forms → submit across LinkedIn / Indeed / company ATS.
  • Anti-bot & captchas on every real portal.
  • ToS bans — LinkedIn actively bans automation, and it's the user's account that gets burned.
  • Per-user credentials + isolated browser sessions to hold safely.
  • This is browsers and adversaries, not prose — a different and much harder engineering problem.
The honesty point: "apply to 100 jobs in an hour" lives entirely in the hard, hostile half. And 100 tailored applications an hour is a contradiction — mass-generic applications get spam-filtered and flag the applicant. The real value is tailored-at-speed: ten sharp applications beat a hundred generic ones, and they don't torch accounts. We sell the good half; we defer the hostile half until a stranger has paid.

The shape runtime → seam → capabilities

The agent runtime lives outside the wall. It reaches oll.am's frozen capabilities through exactly one thing — the MCP server — which is itself a thin adapter over the HTTP endpoints that already exist and already earn.

OpenCLAW
external agent runtime · browser / computer-use
Sam's agentic runner. Disposable. Replaceable tomorrow by Claude Desktop with zero change below.
MCP · stdio · JSON-RPC
the one reusable seam
oll.am MCP server
thin stdio server · tools namespaced ollam_*
Transport + tool registration only. A thin service layer adapts each tool to an existing oll.am HTTP call. Holds the user's Core JWT.
HTTPS · Bearer <Core JWT>
frozen
Core
core.oll.am LIVE
auth · billing · email · /me plan
frozen
oll-write
write.oll.am LIVE
clarify · draft · rewrite · +2 new verbs
frozen
foto
services/foto
guest-checkout headshots · Replicate
Dashed = external, replaceable. Bold outline = frozen infrastructure. The MCP server is the only new component, and it is thin.

The MCP server build spec the core of this doc

Plan the server before any code. It follows the house mcp-server-build pattern: the entry file is transport + tool registration only; all real work lives in a thin service layer that is an http-service-adapter over the existing oll.am endpoints (core.oll.am, write.oll.am, foto). No business logic is re-implemented in the MCP server — it translates tool calls into HTTP calls and shapes the results back into content blocks.

Every tool obeys the same six rules

RuleWhy
Stable snake_case name, namespaced ollam_*The name is an API. Namespacing avoids collisions when the runtime loads many servers.
Description written for the modelSay what it returns and when to call it, so the agent picks the right tool without a human in the loop.
Typed input schema, every param described + example'dThe schema is the contract the model fills in; a description + example on each param cuts malformed calls.
Handler never throwsErrors return isError text / a fallback, not exceptions — the runtime should recover, not crash.
Content-block returnsStructured returns the model can read and chain.
No stdout noisestdio is the protocol channel; all logging goes to stderr or the protocol breaks.

The tool surface

Six tools. Note the last column: only two require new backend code — everything else is a call to something that already runs.

ToolWhen the agent calls itUpstream callNotes
ollam_tailor_cvTurn a base profile + a job description into a role-tailored CVoll-write · NEW verbReturns a markdown CV.
ollam_cover_letterGenerate a cover letter targeting a specific JDoll-write · NEW verbReturns letter text.
ollam_clarifyTighten / humanize any application textoll-write /clarifyExisting. Text in / out.
ollam_rewriteRestyle or improve a passageoll-write /rewriteExisting. Text in / out.
ollam_draftBraindump → polished prose (answers to application questions)oll-write /draftExisting.
ollam_headshot_orderStart a professional-headshot orderfoto /checkout (guest)Returns a Stripe checkout_url + job_id. Payment-gated — the agent hands the URL to the human; it cannot silently buy.
ollam_account_statusResolve identity + live plan before spending gated callsCore /api/auth/meSo the agent knows the user's tier first.
Design tell: the money tool (ollam_headshot_order) returns a checkout URL, never a purchase. The agent is a drafting and orchestration assistant; every irreversible or paid step routes back through a human click. That is both the safe design and the honest one.

The auth model the real design decision

Every oll-write verb requires Authorization: Bearer <Core JWT> — magic-link identity, a 72h HS256 token, with the plan resolved live from /me. So the MCP server's real job, beyond translating calls, is to obtain and hold a Core JWT on behalf of the user. This is where the skill's secrets/auth discipline applies.

ConcernDecision
Resolve auth in priority orderStored session file first, env-var fallback second. The server never blocks on interactive login mid-run.
Persist the tokenWrite to ~/.ollam-mcp/auth.json with an atomic write + 0o600 perms. Never echo it.
Login is a separate stepAn ollam auth CLI subcommand runs the magic-link login before the server starts.
Core stays untouchedThe MCP server is just another JWT-bearing client — the jwt-bearer-spine pattern. oll.am's frozen auth spine does not change at all.
# the login step runs once, out of band — never inside a tool call
$ ollam auth                    # magic-link → verify → token
  → wrote ~/.ollam-mcp/auth.json  (0600)

# then the runtime launches the server, which reads that token
$ ollam serve                   # stdio MCP server, ready for OpenCLAW

The consequence worth naming: the MCP server introduces no new trust boundary inside oll.am. It holds a token the user already could have held; Core validates it the same way it validates specview's or ollwrite's. The platform's security surface is unchanged.

The one genuinely-new extension least-effort

Everything above, except two tools, already exists. The only new backend code is two JD-aware verbs added to the oll-write service: tailor_cv(profile, job_description) and cover_letter(profile, job_description). Each follows the exact existing pattern_authorize_and_gate_run_model with a named prompt — differing only in the prompt template. Additive. No change to auth, billing, or frozen Core. Roughly one small PR to oll-write.

The platform extension is a prompt, not an architecture. — the whole "career layer" backend addition, in one line

This is the leverage: a job-application product that looks like a big new build is, at the backend, two prompt templates dropped into an engine that already authorizes, gates, and calls a model. The rest is the agent — which lives outside the wall — and the MCP seam, which is thin.

Sequencing respecting the ship-vs-build guardrail

Four steps, ordered so the cheap, safe, revenue-adjacent work comes first and the seductive-but-hard net-new build is explicitly last.

Build the thin oll.am MCP servernow · the one reusable seam

The single seam that serves OpenCLAW now and Claude Desktop later. Small, bounded, and it doesn't touch a frozen service — it wraps them.

Sam dogfoods OpenCLAW locallynext · his machine, his accounts, his cron

Apply to real jobs — including the AlpineAI application (the meta-play: "I built an agent that applied to jobs for me"). Zero product burden, no ToS liability transferred to strangers, and it produces the build-in-public story that sells the product.

Sell the assisted pack — the oll.in MVPthen · chargeable in days

User pastes a job URL / JD → gets a tailored CV + cover letter + optional headshot, Swiss-formatted, ready to submit by hand. ~90% existing blocks. No browser automation, no stored credentials, no ToS risk. CHF 99.

Defer the autonomous per-user cloud swipe agentonly after a stranger pays

Stored credentials, isolated browsers, cron, LinkedIn ToS exposure — the Phase-3 vision. Real, but it is the seductive-but-hard net-new thing, and it must not jump the queue.

Ship-vs-build guardrail: oll.in is Phase 2 in Sam's own roadmap, and neither ollwrite nor ollshot has taken a stranger dollar yet. But this path reuses the same oll-write engine and scratches Sam's actual AlpineAI itch (it doubles as portfolio) — so it doesn't abandon the earlier products, it extends them. The autonomous mass-apply build is the one thing here not to let cut the line.

Risks, open questions & decisions close honestly

Risks & open questions

RiskWhere it bites / how v1 avoids it
ToS & legalAutomated submission to LinkedIn / Indeed violates ToS and risks the user's account. This is exactly why v1 is assisted, not autonomous — the human submits.
Positioning / ethicsStay an application-quality tool, not a spam cannon — the same ethics line as humaniz.me (an honest tool, never a bypass). Mass-generic apps are both ineffective and dishonest to sell.
Where the agent runsLocal personal tool (step 2, cheap) vs per-user cloud with stored credentials + isolated browsers (step 4, a big lift). This is the main future cost — name it now, defer it deliberately.
Tailoring quality vs volumeTen sharp applications beat a hundred generic ones. The product's value is quality-at-speed; volume is a trap that flags the applicant.

Decision points for Sam

  1. Confirm OpenCLAW is the intended runtime — and what it is, exactly (an agentic browser / computer-use runner). The MCP seam is runtime-agnostic, but the build spec assumes stdio + a local login step, which suits a local runner.
  2. Approve the two new oll-write verbs (tailor_cv, cover_letter) as the only new backend code — additive, prompt-template-only, one small PR, no touch to frozen Core.
  3. Is the sellable v1 the assisted pack? recommended — draft tailored CV + cover letter + optional headshot, submit by hand; autonomous submission deferred until a stranger dollar lands.
Reflection: the reason this plan is short is that most of it is already built. The job-application layer isn't a new backend — it's a prompt, a protocol, and an agent that lives outside the wall. Build the seam once, dogfood it to earn the story, sell the honest assisted pack, and let the hard autonomous version wait for proof that anyone will pay.

Detailed build plan (v1) thin server · local-test-first

Everything above is the why. This is the executable spec for the one new-code piece — the MCP seam — written to be built and tested locally before anything is wired to a runtime. Sam approved starting here.

This is the first and only new-code piece of the constellation — everything else in the platform is reuse plus Coolify deploys. It is a thin adapter with zero new backend logic, sitting over endpoints that are already live and verified 200: write.oll.am, memory.oll.am, core.oll.am. Build it once, test locally against Claude Desktop / Cursor, then wire OpenCLAW. It is a local / desktop stdio tool — not a deployed service, so it never touches prod. Sequenced behind the CiteBible franc.

A · Shape & decision

ChoiceDecision
What it isOne stdio MCP server named oll-mcp, Python + FastMCP (the house Python stack; locally testable via mcp dev → the MCP Inspector). Lives in the monorepo at services/oll-mcp/. ~250 lines, mostly the HTTP adapter.
Transportstdio for v1 (local desktop clients). Optional later: an HTTP/SSE deployment for remote clients.
PatternFollows the house mcp-server-build pattern: entry file = registration + transport only; all work in a thin service layer; every tool has a model-facing description (what it returns and when to call it) + typed args with examples; handlers never throw (return isError text / fallback); content-block returns; no stdout noise (stdio is the protocol channel — log to stderr).

B · File tree

services/oll-mcp/
  oll_mcp/
    server.py    # FastMCP() + @mcp.tool registrations + mcp.run("stdio") — no logic
    service.py   # HTTP adapter: one httpx client per upstream (core/write/memory) — the ONLY network calls
    auth.py      # Core-JWT store (~/.oll-mcp/auth.json, 0600) + magic-link login + a DEV-ONLY mint helper
    config.py    # env: OLL_CORE_BASE/OLL_WRITE_BASE/OLL_MEMORY_BASE (default to the live domains),
                 #      OLL_MEMORY_SERVICE_TOKEN, OLL_MCP_JWT
    format.py    # shape upstream JSON → clean markdown content blocks
  tests/         # unit-test the pure fns (service/format/auth) + one tool smoke test
  pyproject.toml
  README.md      # install + Claude Desktop / Cursor / OpenCLAW config snippets

C · Tool surface v1

All over existing endpoints — zero new backend. Eight tools:

ToolUpstream (LIVE)ArgsAuth
ollam_clarifyPOST write.oll.am/api/write/clarifytext, tone?Core JWT
ollam_rewritePOST /api/write/rewritetext, instructions?, strength?Core JWT
ollam_draftPOST /api/write/draftbraindump, tone?Core JWT
ollam_specPOST /api/write/specbraindumpCore JWT
ollam_memory_searchmemory.oll.am (collection from JWT)query, top_k?JWT + service token
ollam_memory_chatmemory chat (grounded, cited)messageJWT + service token
ollam_memory_ingestmemory ingesttextJWT + service token
ollam_accountGET core.oll.am/api/auth/meCore JWT
Each tool is snake_case ollam_*, with a description written for the model (what + when), typed args with a concrete example each, a handler that catches everything → returns text / isError (never an exception), and markdown content blocks (not raw JSON). Defer to v2: a foto guest-checkout tool, new tailor_cv/cover_letter verbs, an HTTP/SSE remote deploy, and rate-limits.

D · Auth & identity the crux

oll.am's write / memory endpoints require a Core JWT (72h HS256, identity-only: sub=str(user_id)+email; plan resolved live from /api/auth/me). The MCP server acts as one user — it holds one Core JWT.

ConcernDecision
Resolve orderOLL_MCP_JWT env → ~/.oll-mcp/auth.json (0600) → a clear error naming both fixes. Never log or echo the JWT / token.
Loginoll-mcp auth <email>: POST core/api/auth/magic-link → user clicks the emailed link → paste token → verify → store. (Confirm with Sam whether Core needs a PRODUCT_VERIFY_BASE_OLL_MCP context or can reuse an existing product — a small Core config detail, like CiteBible needed.)
Memory callsMirror the ollwrite BFF: derive collection mem:user:{sub} from the JWT and inject OLL_MEMORY_SERVICE_TOKEN server-side (never client-visible).
Dev ergonomics guarded · dev-onlyoll-mcp mint-jwt --user <id> signs a 72h token using AUTH_JWT_SECRET so local testing needs no email round-trip. Grounded: core/.env already has AUTH_JWT_SECRET, so this works locally today — the write tools are testable immediately; memory tools need the service token, which is Coolify-set.

E · Local test plan the headline

  1. cd services/oll-mcp && uv sync (or pip install -e .).
  2. Point config at the live spines by default (OLL_*_BASE = https://{core,write,memory}.oll.am); optionally at a local stack.
  3. Mint a local test JWT: oll-mcp mint-jwt --user <valid-live-user-id> (dev-only; uses AUTH_JWT_SECRET) → export OLL_MCP_JWT. Fallback: real magic-link. Note: the user id must exist in the live oll_core DB or /me → 401 — get a valid id from Sam / a quick DB check at build time.
  4. mcp dev oll_mcp/server.py → opens the MCP Inspector (local web UI).
  5. In the Inspector: call ollam_clarify {text:"we was going to the store yesterday"} → expect clarified prose from live write.oll.am. Call ollam_account → expect the test user's plan. (Memory tools once OLL_MEMORY_SERVICE_TOKEN is supplied.)
  6. Negative tests: malformed input → tool returns isError text (no crash); missing JWT → a clear error naming the fix.
  7. Wire a real client: add oll-mcp to Claude Desktop's claude_desktop_config.json (command runs uv run oll-mcp, env carries OLL_MCP_JWT), restart, confirm the tools appear + one call round-trips; repeat for Cursor.
  8. OpenCLAW: register the server in OpenCLAW's MCP config — but first verify OpenCLAW consumes MCP servers (unconfirmed from outside: on the VPS gateway only /health is a real endpoint, /mcp + /docs are the SPA catch-all; it's the "OpenClaw Control" agent gateway). If OpenCLAW is OpenAI-tools-only rather than MCP, add a thin OpenAI-tools bridge instead — but that's a v2 detail; Claude Desktop / Cursor prove the seam first.
Acceptance: every tool callable from the Inspector + ≥1 real client · auth stored 0600 · errors degrade to text · zero secrets in logs · pytest green on the pure fns.

F · Verified vs open

Verified tonight

Solid ground

  • write.oll.am / memory.oll.am / core.oll.am all live (200).
  • The Core JWT is locally mintableAUTH_JWT_SECRET is in core/.env.
  • The write tools map 1:1 to existing endpoints.
Open · resolve at build time

Still to pin

  • A valid live user id for the test JWT.
  • OLL_MEMORY_SERVICE_TOKEN for the memory tools.
  • OpenCLAW's client model — MCP vs OpenAI-tools.
  • Whether Core needs a product context for oll-mcp sign-in.

G · Sequence

Build order — local-test-first

scaffold services/oll-mcp local smoke test (Inspector + dev-mint JWT, write tools first) wire Claude Desktop + Cursor verify OpenCLAW's model, then wire it open a feat-branch PR (NOT deployed — local/desktop tool)
then v2: memory service token· foto tool· new CV verbs· optional HTTP/SSE deploy