The engine, not just the app.
The search half of Umbodi is reachable from your own code: a typed client, an HTTP service, an MCP server, or a licensed self-hosted build running on your own provider keys. The AI-answer half is hosted-only today. The tables below say exactly which is which, because a developer surface that overstates its reach costs you an afternoon before you find out.
Six ways to ship it.
createSeoAgent()
One facade over the cores. Trusted/untrusted split — the browser never holds keys.
HTTP service
One Request→Response router, Bearer-licensed, secrets server-side.
MCP server
Zero-dep stdio server, seven tools — six read, one opens a pull request — for Claude and other MCP clients.
Claude skill
Drop-in ascent-seo skill — MCP-preferred, script fallback.
Browser widgets
Pure build/mount widgets — rank badge, potential gauge, result card.
Admin cockpit
Operator panels — proposals, keywords, rankings — writes via your onAction.
Three ways it runs — and where each one stops.
The six surfaces above are HOW you call it. These are how much it is allowed to do on its own. Each mode has a different honest ceiling, and the one you pick decides who presses the last button.
No repository access
Umbodi prepares the change and a person applies it — copy, paste, send. It then re-crawls the live page and proves the change is really there. Complete, and needs no trust.
Opens a pull request
One atomic commit on a namespaced branch, never the default branch. A person reviews and merges. Retry-safe: running twice updates the same pull request instead of opening a second.
Self-heal
Embedded through the API or SDK, the loop closes with nobody in it: merge on green, then verify. Merging is a separate explicit opt-in, never inherited from an autonomy dial.
What the engine gives you, and what is hosted-only.
Measured against the engine source on 30 September 2026, not described from memory. If you are evaluating Umbodi for the AI-answer half, read this before you start.
- In the engine — six actions.
analyze·generate·rankings·competitors·content·status. Every surface — SDK, HTTP service, MCP, the Claude skill, the widgets — is a different way of reaching those same six. All six are search-side. The engine ships its own suite, reported by its ownnpm test— a different figure from the platform total above, and one this repository cannot re-derive for you. - Hosted-only — AI-answer visibility. The five answer engines, citation checking and answer-engine reporting are not in the licensable engine and not reachable by SDK or MCP. They run in the hosted product. Saying so is cheaper for you than discovering it after a licence.
- Hosted-only — the write channel. Opening a pull request, merging it on green and verifying the deploy live are hosted capabilities; the engine itself researches and drafts and never writes to your repository. A license key reaches the first of those through the hosted API —
applyopens a pull request with the changes you approved. It cannot merge one, ever. - Bounded on purpose. Whatever the mode, the agent writes content and discoverability files only — it may never modify the code that decides what it is allowed to change. That bound is enforced immediately before any irreversible act, not just when the change is proposed.
@ascent/seo-agent (its MCP server is the seo-agent-mcp bin), and the environment variables are still ASCENT_SERVICE_URL, ASCENT_LICENSE and ASCENT_LOCAL. Those are real identifiers, pinned until a major version — copy them exactly as written. The package is not on a public registry: licensed customers receive it from Umbodi. The hosted API at https://umbodi.ai/v1 needs no package at all.Architecture & integration brief
The full developer document — runtime profile, the six surfaces, the action lifecycle, a safe adoption path and the production checklist.
Analyze a site in about a minute.
With a license key, call the hosted API — nothing to install, and analyze is free. With the licensed engine, run the same audit in-process with the sitemap content adapter.
# hosted — your license key, nothing to install
curl -s -X POST https://umbodi.ai/v1/analyze \
-H "authorization: Bearer $ASCENT_LICENSE" -H 'content-type: application/json' \
-d '{"url":"https://acme.com"}' # → { issues: [...] }
// licensed engine, in-process (delivered to licensed customers)
import { createSeoAgent, sitemapAdapter } from '@ascent/seo-agent'
// the sitemap adapter is the no-code source of pages to audit
const { listIndexableEntities } = sitemapAdapter({ origin: 'https://acme.com' })
const seo = createSeoAgent(
{ site: { domain: 'acme.com' } },
{ listIndexableEntities },
)
const { issues } = await seo.analyze()
console.log(issues)
generate() (AI content proposals) and run() (the job loop) take the provider and storage seams — see the SDK section.
The SDK — one facade, four profiles.
createSeoAgent(config, deps) wraps the pure cores and returns { analyze, generate, run }. A runtime layer detects Node / Deno / edge / browser and splits trusted (server, holds keys) from untrusted (browser, proxies secret calls to the service). An offline ES256 license gate reuses your entitlements.
import { createSeoAgent, sitemapAdapter, memoryStore } from '@ascent/seo-agent'
const seo = createSeoAgent(
{ site: { domain: 'acme.com' }, serviceUrl: 'https://your-service' },
{
listIndexableEntities: sitemapAdapter({ origin: 'https://acme.com' }).listIndexableEntities,
store: memoryStore(),
// callClaude, db, auth … wire the seams you need for generate()/run()
},
)
const { issues } = await seo.analyze()
const draft = await seo.generate() // human-approved content proposals
@ascent/seo-agent/license—verifyLicense(token, {publicJwk, now}), offline.@ascent/seo-agent/config·/runtime— resolve config + the runtime/trust split.- Four
initprofiles: headless · api · frontend · admin.
One handler. N routes. Server-side secrets.
A single Web-standard handleRequest(Request) → Response router over every backend shell, with unified Authorization: Bearer license auth, CORS, and fail-soft responses. This is the endpoint the browser SDK proxies to — so keys stay on the server.
https://umbodi.ai/v1: every action is POST /v1/{action} with your license key, paid actions are charged in credits to the key’s account, and a site must be one of your own projects. GET /v1/status lists the actions.import { createSeoService, defaultHandlers, toNodeHandler } from '@ascent/seo-agent/service'
const service = createSeoService({
handlers: defaultHandlers(ctx), // ctx = your provider + storage seams
publicJwk, // verifies license keys offline
corsOrigins: ['https://your.site'],
})
// Web-standard (edge / Deno / Bun):
export default (request) => service.handleRequest(request)
// or adapt to a Node http handler:
export const nodeHandler = toNodeHandler(service)
# call it with a license
curl -X POST https://your-service/analyze \
-H "authorization: Bearer $ASCENT_LICENSE" \
-H "content-type: application/json" \
-d '{"site":{"domain":"acme.com"}}'
Seven tools, and one of them acts.
A hand-rolled, zero-dependency Model Context Protocol server over stdio, exposing analyze · generate · rankings · competitors · content · status · apply. A license key alone uses Umbodi’s hosted API (umbodi.ai/v1); point it at your own service with ASCENT_SERVICE_URL, or run a local in-process SDK.
apply asks the service to open a pull request — reviewable, revertible, visible before it is anything else — and it can never merge one. A license key is a string that ends up in client configs, CI variables and screenshots, which a GitHub App installation never does, so no license-authenticated action can merge: the hosted API has no merge action, and the permission check refuses a merge before it examines the key. Merging stays with the project owner. There is still no run or job/worker tool, transport is stdio only, and the JSON-RPC layer is a hand-rolled subset, not the official SDK.// add to your MCP client config (e.g. Claude Desktop)
{
"mcpServers": {
"ascent-seo": {
"command": "node",
"args": ["/path/to/seo-agent/mcp/server.mjs"],
"env": { "ASCENT_LICENSE": "your-license-key" }
}
}
}
Prefer to import it? @ascent/seo-agent/mcp exposes runServer, defineTools, and resolveBackend.
A drop-in Claude skill.
The ascent-seo skill lets Claude run Umbodi tasks for you. It prefers the MCP server when it’s connected and falls back to a zero-dep CLI script otherwise — so it works whether or not MCP is wired up. An honesty reference keeps measured and projected results clearly separated in what Claude reports back.
- Ships as
skill/ascent-seo/—SKILL.md+ honesty reference + fallback CLI. - MCP-preferred; the script path needs only
ASCENT_SERVICE_URL+ASCENT_LICENSE. - Pairs naturally with the MCP server above.
Browser widgets — pure build, thin mount.
Zero-dependency, XSS-escaped browser widgets: a meta injector, a rank badge (measured), a potential gauge (projected), and an honest result card. Each is a pure buildX(data) → html plus a thin mountX(el, data), with a base theme.css.
import { mountRankBadge } from '@ascent/seo-agent/widgets'
import '@ascent/seo-agent/widgets/theme.css'
mountRankBadge(document.querySelector('#rank'), {
keyword: 'best crm',
position: 5, // measured on Google — not a projection
})
makeAscentClient() builds a browser client that talks to your service; only the token is sent, never secrets.
An operator cockpit you control.
Six actionable panels — proposals, keywords, rankings, competitors, autonomy, status — built on a small panel framework. Panels render the data you pass in and write through an onAction callback you wire, so there’s no hidden write endpoint pretending to exist.
import { mountProposals } from '@ascent/seo-agent/admin'
mountProposals(document.querySelector('#proposals'), proposals, {
onAction: (action) => approve(action), // you own the write path
})
Two ways to pay for paid ops.
Paid operations (SERP + AI) cost money to run. How they’re billed depends on where the engine runs — on your infrastructure, or on Umbodi’s hosted API.
Self-hosted / licensed → your keys, no Umbodi credits
Run the engine on your own infrastructure with your own Serper/Anthropic keys. You pay those providers directly — Umbodi credits don’t apply and there’s nothing to top up. Your cost is your provider bill plus the annual license. Generate a key from any 🔒 Generate license button.
Hosted API / Service → Umbodi credits
Call Umbodi’s hosted API at https://umbodi.ai/v1 with your license key (Authorization: Bearer). Umbodi runs the ops on its keys — or yours, if you connected them — and charges Umbodi credits to the key’s account: the same wallet and the same prices as the app. A call that fails at the provider is refunded; an empty wallet returns 402 insufficient_credits, and a project at its monthly cap 402 project_cap_reached. A content topic about one of your tracked competitors is refused with 422 topic_refused before anything is charged.
// a paid call: charged to the key's account, refused if the site is not yours
POST https://umbodi.ai/v1/rankings
Authorization: Bearer <license key>
{ "site": { "domain": "example.com" } } // → { results: [{ keyword, position, top }] }
// top up from the app, signed in — the account comes from your session
POST /api/payments/mips/create-payment
{ "kind": "credit_pack", "packId": "pack_5k" } // 401 signed out · 503 payments off
Bring your own keys (BYOK): connect your Serper/Claude keys in Account settings and hosted ops run on your keys at near-zero credits — the natural fit for agencies and high-volume dispatch.
The operator may pause metering — hosted calls then run free (usage is still recorded).
Grab the free components.
The open pieces are yours to download and use now. The licensed engine — SDK core, service/API, MCP server, admin cockpit — is available on request.
🔒 Licensed · SDK core · service/API · MCP · admin
The engine itself is a commercial, licensed product — self-host or white-label. It isn’t a public download — request developer access and we’ll set you up.