{
  "markdown": "# JobMojito MCP Server\n\n## Welcome\n\n[JobMojito](https://jobmojito.com) is an AI interview platform for hiring teams:\n**let AI interview every candidate — you make the call.** Lifelike avatar and\nvoice agents run structured, multilingual interviews at any scale, score them in\nreal time, and hand recruiters the transcripts, summaries and reports they need\nto make the actual decision. Everything the platform produces is decision\nsupport for a qualified human reviewer — never the hiring decision itself.\n\nWhat the platform offers:\n\n- **AI avatar & voice interviews** — real-time conversational interviews with\n  configurable avatars, generated from a job description or built from your own\n  question set, with a fully branded candidate experience.\n- **Scoring, transcripts & reports** — consistent structured assessment with\n  real-time scoring, plus shareable interview reports for the hiring team.\n- **Candidate management & invitations** — register candidates in bulk, send\n  each one a personal interview link, grant extra attempts, track results.\n- **Role-play & coaching personas** — avatars that play a defined role, for\n  screening scenarios or candidate practice on the coaching portal.\n- **Knowledge base** — upload company documents so interviews and evaluations\n  reflect your organisation's context.\n- **Workspaces & analytics** — merchants and sub-merchants, credit usage, and\n  hiring analytics across positions.\n\nAround the product: the admin console at [app.jobmojito.com](https://app.jobmojito.com),\ndeveloper/API docs at [developer.jobmojito.com](https://developer.jobmojito.com),\nthe help center at [help.jobmojito.com](https://help.jobmojito.com), and the\nhosted MCP endpoint this repo powers at `https://mcp.jobmojito.com/mcp`.\n\nThis repository is the MCP server that puts all of that into AI assistants like\nClaude and ChatGPT.\n\n---\n\nAn [MCP](https://modelcontextprotocol.io) server for the **JobMojito API**, built\nwith [FastMCP](https://gofastmcp.com) and designed to deploy on\n[Prefect Horizon](https://horizon.prefect.io) (the MCP platform from the FastMCP\nteam — `app.prefect.cloud` → Horizon).\n\nIt exposes:\n\n- **25 API tools** auto-generated from JobMojito's live OpenAPI spec (interviews\n  and role-play personas, the coaching catalogue, candidates and results,\n  knowledge base, merchant lists/analytics). All endpoints — including the `GET`\n  lists — are surfaced as tools with clean, curated names, each with a title and\n  read-only/destructive annotations.\n- **2 documentation tools** (`search_documentation`, `get_documentation`) that read\n  the developer docs and help center **live** — docs stay single-source on their\n  existing platforms; nothing is copied into this repo.\n- **Supabase OAuth** so end users can log in directly from Claude, ChatGPT, etc.\n  The signed-in user's Supabase JWT is forwarded to the JobMojito API on every call.\n\n---\n\n## How it fits together\n\n```\nMCP client (Claude / ChatGPT)\n        │  OAuth login (Supabase OAuth Server, DCR)\n        ▼\nJobMojito MCP server (this repo, on Horizon)\n        │  forwards the user's Supabase JWT\n        ▼\nJobMojito API  (https://cool.jobmojito.com/functions/v1)\n```\n\n| Concern            | Approach |\n|--------------------|----------|\n| API → tools        | `FastMCP.from_openapi(...)`, all routes mapped to **Tools**, curated names via injected `operationId`s |\n| Spec freshness     | Fetched **live at startup**; a runtime cache + committed snapshot are fallbacks |\n| Auth               | `SupabaseProvider` (Remote OAuth); per-request JWT forwarding to the upstream API |\n| Docs               | Built-in tools reading `developer.jobmojito.com/llms.txt` + `.md` and the `help.jobmojito.com` (Featurebase) help center |\n\n### Files\n\n| File | Purpose |\n|------|---------|\n| `server.py` | Entry point — builds and exposes `mcp` (point Horizon at `server.py:mcp`) |\n| `config.py` | Environment-driven settings |\n| `openapi_loader.py` | Live fetch + cache/snapshot fallback + operationId injection |\n| `naming.py` | Curated tool names, titles, hints and annotations per endpoint |\n| `upstream.py` | httpx client that forwards the user's Supabase JWT to the API |\n| `middleware.py` | Call logging, upstream-error rewriting, result-size guard, annotation backfill |\n| `merchants.py` | Merchant picker (MCP App UI) + `list_my_merchants` text fallback |\n| `docs_tools.py` | `search_documentation` / `get_documentation` |\n| `featurebase.py` | Featurebase REST client (help-center articles) |\n| `mintlify.py` | Mintlify developer-docs MCP proxy + client-credentials auth |\n| `tests/` | `test_smoke.py` (inventory, schema relaxation, docs) + `test_listing_readiness.py` (directory contract) |\n| `docs/` | Developer docs: architecture, development, deployment |\n| `scripts/update_snapshot.py` | Refresh the committed fallback spec |\n| `scripts/try_docs.py` | Local smoke test for the documentation tools |\n| `data/openapi.snapshot.json` | Offline fallback spec (regenerate from your machine) |\n| `lazy_auth.py` | Unauthenticated capability discovery + `scope=` on the 401 challenge |\n| `wellknown.py` | `/healthz`, OpenAI domain challenge, Smithery server card |\n| `server.json` | Official MCP Registry entry |\n| `.github/workflows/publish-registry.yml` | Publishes `server.json` to the MCP Registry when a push to `main` changes `server.py:SERVER_VERSION` (or via manual dispatch) |\n\n---\n\n## Local development & testing (macOS)\n\n```bash\n# 1. Set up a virtualenv and install deps\npython3 -m venv .venv && source .venv/bin/activate\npip install -r requirements.txt\n\n# 2. Configure secrets in .env (auto-loaded; gitignored)\ncp .env.example .env        # then fill in your keys/credentials\n\n# 3. Static check — see exactly what Horizon will deploy\nfastmcp inspect server.py:mcp\n\n# 4. Run the unit tests (offline; uses the snapshot fallback)\nENABLE_AUTH=false pytest -q --asyncio-mode=auto\n\n# 5. Smoke-test the docs tools live (Featurebase + Mintlify federation)\nENABLE_AUTH=false python scripts/try_docs.py \"how do I create an interview\"\n\n# 6. Run the server and connect a client\nENABLE_AUTH=false python server.py        # → http://localhost:8000/mcp\n#   then, in another terminal, open the MCP Inspector:\nnpx @modelcontextprotocol/inspector\n#   transport: \"Streamable HTTP\", URL: http://localhost:8000/mcp\n```\n\nTesting notes:\n\n- **Docs tools** (`search_documentation`, and the mounted Mintlify\n  `search_*_developer_*` / `query_docs_filesystem_*`) work with `ENABLE_AUTH=false`\n  — no Supabase login needed. `scripts/try_docs.py` is the fastest check.\n- **API tools** (`create_interview`, `list_candidates`, …) call the real\n  JobMojito API and need a Supabase user token. For local testing, set\n  `JOBMOJITO_DEV_BEARER_TOKEN` in `.env` to a valid Supabase access token; it's\n  forwarded when `ENABLE_AUTH=false`. (Never set this in production — there the\n  token comes from the user's OAuth session.)\n- **Full OAuth locally** is rarely needed; if you want it, run with\n  `ENABLE_AUTH=true`, set `BASE_URL=http://localhost:8000`, and connect a client\n  that supports OAuth (it will bounce you through Supabase).\n\n> **Refresh the fallback snapshot** from a network that can reach the API:\n> `python scripts/update_snapshot.py` (the committed snapshot in this repo is a\n> thin structural fallback; this replaces it with the full live spec).\n\n---\n\n## Supabase OAuth setup\n\nThis server uses Supabase's **OAuth Server** feature (which supports Dynamic\nClient Registration, so MCP clients self-register). The MCP server is the OAuth\n**resource server** — it validates tokens; Supabase issues them.\n\n**Project:** `https://momsbvnltsydezmoesqt.supabase.co`\n`SupabaseProvider` derives every endpoint from this project URL:\n\n| Endpoint | URL |\n|----------|-----|\n| Authorize | `…/auth/v1/oauth/authorize` |\n| Token | `…/auth/v1/oauth/token` |\n| JWKS | `…/auth/v1/.well-known/jwks.json` |\n| OIDC discovery | `…/auth/v1/.well-known/openid-configuration` |\n\nSteps:\n\n1. **Supabase Dashboard → Authentication → OAuth Server**\n   - Enable the **OAuth Server** and **Allow Dynamic OAuth Apps**\n   - **Site URL:** `https://app.jobmojito.com`\n   - **Authorization Path:** `/oauth/consent`\n2. Env: `SUPABASE_PROJECT_URL=https://momsbvnltsydezmoesqt.supabase.co`,\n   `BASE_URL=https://mcp.jobmojito.com` (public base, no `/mcp`),\n   `SUPABASE_JWT_ALGORITHM=ES256` (switch to `RS256` if your JWKS shows RSA keys).\n3. If the JobMojito Edge Functions require an `apikey` header, set `SUPABASE_ANON_KEY`.\n\n**Consent is handled by Supabase / your app, not this server.** Because the Site\nURL is `app.jobmojito.com`, Supabase serves the approve/deny screen at\n`https://app.jobmojito.com/oauth/consent`, where the user already has a Supabase\nsession. This MCP server is only the OAuth resource server — it does not serve a\nconsent page. `SITE_URL` and `OAUTH_CONSENT_PATH` are informational and should\nmatch your Supabase OAuth Server settings.\n\n> Token note: `SupabaseProvider` cannot validate token *audience* (Supabase Auth\n> doesn't implement RFC 8707 resource indicators yet). This is expected and\n> logged at startup.\n\n---\n\n## Authentication model\n\n**Every tool *call* requires the user to be signed in via Supabase OAuth** —\nincluding `search_documentation` and `get_documentation`. Keep `ENABLE_AUTH=true`;\nthe signed-in user's token is forwarded to the JobMojito API so calls respect that\nuser's permissions.\n\nThe one deliberate exception is **capability discovery**: with `ENABLE_LAZY_AUTH`\n(default `true`), `initialize`, `ping` and the `*/list` methods answer without a\ntoken so directory crawlers can render the tool list. No data is reachable that\nway — every `tools/call`, resource read and prompt still needs a verified JWT.\n\n## Deploy to Prefect Horizon\n\n1. Push this repo to GitHub.\n2. At [horizon.prefect.io](https://horizon.prefect.io), sign in with GitHub and\n   select this repo.\n3. Configure:\n   - **Entrypoint:** `server.py:mcp`\n   - **Dependencies:** auto-detected from `requirements.txt`\n   - **Horizon Authentication:** **off** — this server provides its own Supabase\n     OAuth, so let it be the auth layer.\n4. Set environment variables / secrets:\n   - `ENABLE_AUTH=true`\n   - `BASE_URL=https://mcp.jobmojito.com` — **critical.** This must be the\n     server's **public base URL, with no `/mcp` and no trailing slash**. It's what\n     `SupabaseProvider` advertises as the OAuth resource; if it's left at the\n     `http://localhost:8000` default, clients get `401 invalid_token`. The startup\n     log prints `base_url=…` and warns if it's localhost.\n   - `SUPABASE_PROJECT_URL=https://momsbvnltsydezmoesqt.supabase.co`\n   - `SUPABASE_ANON_KEY` if the Edge Functions need the `apikey` header.\n   - `FEATUREBASE_API_KEY` for help-center docs.\n5. **Deploy.** Your MCP endpoint is `https://mcp.jobmojito.com/mcp`. Horizon\n   redeploys on every push to `main`.\n\n### Testing the login\n\nA `401` on `initialize` is the **normal first step** of MCP OAuth — the client is\nmeant to read the discovery metadata and run the Supabase login. Horizon's\n**Inspector / ChatMCP do not perform that login**, so they'll show a bare 401.\nTest by adding `https://mcp.jobmojito.com/mcp` as a **custom connector in\nClaude.ai**, which runs the full OAuth flow and prompts the Supabase login.\n\nMake sure the Supabase side is configured (or the flow 401s regardless): OAuth\nServer enabled + Dynamic Client Registration on; Site URL `https://app.jobmojito.com`\n+ Authorization Path `/oauth/consent`; and that consent page hosted on the app.\n\nBecause the OpenAPI spec is fetched live at startup, each redeploy picks up the\nlatest JobMojito API automatically.\n\n---\n\n## Keeping documentation single-source\n\nDocs are **not** duplicated here. `search_documentation` builds a live index and\n`get_documentation` fetches a page on demand, both reading the source platforms\ndirectly. Edit docs there and the MCP reflects changes within the cache TTL\n(`DOCS_CACHE_TTL_MINUTES`, default 60).\n\n| Source | How it's read | Credentials |\n|--------|---------------|-------------|\n| `developer.jobmojito.com` (Mintlify) | **Mintlify MCP** mounted into this server (semantic `search` + `query_docs_filesystem`, incl. the imported OpenAPI) when client credentials are set; else public `llms.txt`/`.md` via the built-in tools | MCP client id/secret (to federate) |\n| `help.jobmojito.com` (Featurebase) | **REST API** if `FEATUREBASE_API_KEY` is set (structured articles incl. body), else public-HTML scraping | API key (optional) |\n\n### Why this server *and* Mintlify\n\nMintlify imports the OpenAPI, but its MCP is **read-only documentation** — it lets\nan agent search and read the API reference. It cannot *call* the API. This server\nturns the same OpenAPI into **authenticated, callable tools** (acting as the\nsigned-in Supabase user). So: Mintlify answers \"how does it work?\"; this server\n\"does the thing.\" The federation mounts Mintlify's doc-search tools alongside the\naction tools so end users get both from one connector.\n\n### Developer docs: Mintlify federation\n\n`developer.jobmojito.com` is a Mintlify site whose MCP exposes `search` +\n`query_docs_filesystem` tools (including the imported OpenAPI). This server mounts\nit as a proxy. When federated, the built-in developer-`llms.txt` source is\ndisabled to avoid duplication.\n\n**Public by default (no credentials).** Mintlify serves a public MCP at `/mcp`.\nBecause the developer docs are public, `DEVELOPER_DOCS_MCP_URL` defaults to\n`https://developer.jobmojito.com/mcp` and is mounted with no auth — it works out\nof the box.\n\n**Authenticated endpoint (optional).** Mintlify also offers an `/authed/mcp`\nendpoint with **OAuth client-credentials**, for group-restricted content. It is\n**not enabled by default** — the site's `/.well-known/mcp` only advertises a\n`public` server until you enable it. To use it:\n\n1. Mintlify dashboard → **MCP server page → Enable MCP Server** (otherwise\n   `/authed/mcp/oauth/token` returns 404).\n2. Create a client credential there (*MCP client credentials*, **not** Mintlify\n   API keys) → set `DEVELOPER_DOCS_MCP_CLIENT_ID` / `_CLIENT_SECRET`.\n3. Set `DEVELOPER_DOCS_MCP_URL=https://developer.jobmojito.com/authed/mcp`.\n\nAuth is attached only when the URL contains `/authed` **and** both credentials\nare set; the server then exchanges them at `{url}/oauth/token` and\ncaches/refreshes the token.\n\n### Featurebase: which credential is which\n\nFeaturebase exposes docs three ways — the credential shape tells you which:\n\n- **REST API** → a single **API key** (sent as `Authorization: Bearer <key>` and\n  `X-API-Key`), base `https://do.featurebase.app`. Create it in the Featurebase\n  dashboard: log in via [auth.featurebase.app/choose-org](https://auth.featurebase.app/choose-org)\n  → **Settings → Advanced → API** → copy the key. (The direct `/settings/api` URL\n  404s — you must enter through your org first.) This is what this server uses for\n  the help center (set `FEATUREBASE_API_KEY`).\n- **Hosted MCP** (`mcp-read.featurebase.app`, the *Reader* connector) →\n  **interactive OAuth (authorization code + PKCE, no client secret)**. Designed\n  for a human to click \"Connect\" in Claude/ChatGPT. Best added **directly** as a\n  connector in your AI client, not proxied from this server (there's no machine\n  token / client-credentials grant).\n- **Public web** → `llms.txt` / `.md` (developer docs), no auth.\n\n> A **Client ID + Client secret** pair is *not* a Featurebase docs credential —\n> neither the REST API (API key) nor the MCP (PKCE, no secret) uses that shape.\n> If you have one, it's likely a Featurebase SSO/identity OAuth app or from a\n> different system. For docs, use the REST **API key** above.\n\n### Featurebase Reader MCP (not federated)\n\nUnlike Mintlify, Featurebase's Reader MCP (`mcp-read.featurebase.app`) only\nsupports interactive OAuth (no client-credentials), so it isn't mounted here. If\nyou want its native search, add it **directly** as a connector in your AI client.\n`HELP_DOCS_MCP_URL` is kept in config for reference only.\n\n\n---\n\n## Directory listings & discoverability\n\nThe server is built to satisfy the Anthropic connector directory, the OpenAI\nplugin directory (ChatGPT + Codex) and the official MCP Registry without further\ncode changes. What that means in practice:\n\n| Requirement | Where it lives |\n|---|---|\n| Every tool has a `title` + `readOnlyHint`/`destructiveHint`, with a written justification | `naming.py::TOOL_META`, applied by `server.py::_customize_component`, backfilled by `middleware.ToolMetadataBackfillMiddleware` |\n| Read and write are separate tools (no catch-all) | `naming.py` — asserted in `tests/test_listing_readiness.py` |\n| `tools/list` works without credentials, so crawlers can show the tool list | `lazy_auth.py` |\n| `scopes_supported` in the protected-resource metadata, `scope=` on the 401 | `server.py::_build_auth` + `lazy_auth.WWWAuthenticateScopeMiddleware` |\n| Actionable errors instead of bare 4xx/5xx | `middleware.UpstreamErrorMiddleware` |\n| Results stay under the client's size ceiling | `middleware.ResultSizeGuardMiddleware` |\n| Health probe for uptime monitoring | `GET /healthz` |\n| OpenAI domain verification | `GET /.well-known/openai-apps-challenge` (set `OPENAI_APPS_CHALLENGE_TOKEN`) |\n| MCP Registry entry | `server.json` + the publish workflow |\n\nGenerate the annotation justifications OpenAI asks for at submission:\n\n```bash\npython -c \"import naming,json;print(json.dumps(naming.annotation_justifications(),indent=2))\"\n```\n\nVerify the public identity after any deploy or hostname change — this is the\nsingle most common way an otherwise-healthy server 401s every client:\n\n```bash\ncurl -s https://mcp.jobmojito.com/.well-known/oauth-protected-resource/mcp\n# \"resource\" MUST equal \"https://mcp.jobmojito.com/mcp\"\ncurl -s https://mcp.jobmojito.com/healthz\n```\n\n### Still to do outside this repo\n\nThe code is ready; these are not code problems:\n\n1. **A Claude Team or Enterprise plan.** The Anthropic submission portal lives\n   under `claude.ai/admin-settings/` and is unavailable on individual plans.\n2. **A public \"Connect JobMojito to Claude & ChatGPT\" docs page** — required by\n   Anthropic (documentation URL) and OpenAI (support URL).\n3. **A populated reviewer test account** with no MFA and no email confirmation\n   step; OpenAI rejects submissions whose test account requires either.\n4. **DNS TXT record on `jobmojito.com`** for the `com.jobmojito/*` registry\n   namespace, and the `MCP_REGISTRY_PRIVATE_KEY` repo secret — see the header\n   comment in `.github/workflows/publish-registry.yml`.\n5. **Allowlist Anthropic's egress range `160.79.104.0/21`** on the MCP host and\n   anything in front of Supabase. A blocking WAF is Anthropic's most common\n   documented failure mode.\n",
  "bytes": 18694,
  "sha": "0150af7aac35bdcc63a1ef9a52d2a913718b9e0eca11f04f410d7d6b3b8e1d48",
  "repo_slug": "jobmojito/mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_jobmojito_jobmojito_89ae67d1/readme"
}