Skip to content
Harjot Singh Rana

Full-Stack & AI Product Engineer

All writing

Locum: Giving Grok Bot a Stand-In

Grok Bot can burn a week of quota in an hour. Locum is a small MCP server that hands the work to the Claude Code or Codex CLI already logged in on your machine, through documented extension points, without ever touching a credential.

I burned 20% of a weekly Grok Bot allowance in thirty minutes. Seven or eight bots running in parallel, all doing real work, all metering hard. The product is genuinely good; that was the frustrating part. The bottleneck wasn't capability. It was inference budget.

I already pay for Claude and for ChatGPT. Those subscriptions have their own limits, and they were sitting completely idle while Grok Bot ground through its quota.

So I built Locum, a provider adapter that lets Grok Bot delegate coding work to the agent CLIs already authenticated on my own machine. A locum is a qualified professional who temporarily does someone else's job, which is exactly the arrangement.

This post is about how it works, and more importantly about the one architectural decision that separates it from the version of this idea that gets you banned.

The constraint that decides everything

Grok Bot doesn't run on your laptop. Each bot gets a persistent computer in xAI's cloud, which is what makes it useful: it keeps working after you close the lid. It also means the bot cannot see localhost. There is no local plugin surface to hook into.

What it does have is documented: custom MCP connectors. You point it at a public HTTPS endpoint speaking the Model Context Protocol, it discovers your tools, and it can call them. xAI even publishes a guide for tunnelling a server running on your own machine.

That single fact fixes the shape of the whole thing. Locum has to be a remote MCP server that reaches back to your machine, not a patched binary and not an intercepted protocol.

The line I would not cross

There are two ways to build "use your Claude subscription somewhere else," and they are not close to each other.

The tempting one: read the OAuth token that Claude Code stores, and call Anthropic's API yourself with it. It works. It is also explicitly prohibited. Anthropic's own Claude Code legal page says OAuth is "intended exclusively" for ordinary use of Claude Code and other native Anthropic applications, and that they do not permit third-party developers to route requests through Free, Pro, or Max plan credentials on behalf of their users. People have shipped tools on the other side of that line and watched them get shut off.

The other way: don't handle credentials at all. Spawn the official binary.

no path: Locum never calls the vendor APILocumthe MCP serverspawnclaudeofficial binary, your loginits own authAnthropicapi.anthropic.comnever reads tokensnever touches the keychainnever opens ~/.claude

The credential boundary. Locum is a launcher, not a proxy.

Locum runs claude -p "<prompt>" as a subprocess. That's it. The CLI you already logged into does the authenticating, exactly as it does when you type the command yourself. Locum never opens ~/.claude, never touches the keychain, never sees a token, and never makes a request to api.anthropic.com.

Four invariants are written at the top of the source file, and they're the reason the project is publishable:

  1. Never read credential files, keychains, or OAuth tokens.
  2. Never call api.anthropic.com or api.openai.com directly.
  3. Only ever spawn the official claude / codex binaries.
  4. Single-operator. One token, one allowlist of workspace roots.

One more thing it deliberately does not do: bypass Grok Bot's own entitlement. You still need Grok Bot access for a connector to exist at all. Locum changes where the inference comes from. It does not get you Grok Bot for free, and I'd have no interest in shipping the version that did.

The shape of it

Grok Botcloud computerMCPCloudflare tunnellocum.example.comxAI cloudyour machineLocum127.0.0.1:8791spawnsclaude -pyour Claude plancodex execyour ChatGPT plan

Five tools cross that boundary: delegate_to_claude, resume_claude, delegate_to_codex, check_job, cancel_job.

About 640 lines of Python, one file, no framework beyond an MCP server library.

Async is the entire trick

This is the part that decides whether the integration works at all, and it's easy to get wrong.

MCP tool calls are request/response with a timeout measured in tens of seconds. Real coding tasks take minutes. If delegate_to_claude tried to run the task and return the answer, every useful call would time out, and worse, it would time out after burning the work, with the result stranded in a dead subprocess.

synchronous: what does not workMCP timeoutresult discardedasynchronous: what Locum doesjob_id returnedcheck_job every ~30sturns, recent tool activitystatus: done

Build this in from the start. Retrofitting it means rewriting the server.

So delegate_to_claude starts the job and returns a handle immediately. The bot polls check_job until the status changes.

The polling is more useful than it sounds. Locum runs the CLI with --output-format stream-json and parses the event stream as it arrives, so a poll returns not just "still running" but the turn count and the last few tool calls:

{
  "job_id": "dacb5836aa66",
  "status": "running",
  "elapsed_seconds": 74.2,
  "turns": 9,
  "recent_activity": ["Read src/auth.ts", "Edit src/auth.ts", "Bash npm test"]
}

The bot can narrate real progress instead of staring at a black box. When the job finishes, the result comes back along with a session_id, which matters, because resume_claude reuses it. A cold delegation re-pays about 18k tokens of CLAUDE.md and system prompt setup; resuming hits the prompt cache instead. Follow-ups should always resume.

Grok speaks OAuth, and only OAuth

I built the first version with a bearer token. Simple, adequate for a single-operator tool, easy to test with curl.

Then I opened Grok's custom connector dialog and found no field for a header. It had probed my server, got a bare 401, and fallen back to asking me for OAuth app credentials: client ID, authorization endpoint, token endpoint, PKCE method.

That's the MCP specification's authorization flow, and it isn't optional. A remote MCP server that wants a real client has to be an OAuth 2.1 authorization server.

So Locum became one. It's less code than it sounds:

Grok BotLocumPOST /mcp, no credentials401 + WWW-Authenticate, pointing at discoveryPOST /register (RFC 7591), self-registers/authorize, the consent screenoperator types the passphrasePOST /token: code + PKCE verifiertools/call, authenticated

Discovery, dynamic registration, PKCE, refresh. No third-party OAuth app to create.

The consent screen is the load-bearing part, and it's worth being explicit about why. /authorize sits on a public tunnel. Without a gate, anyone who learned the URL could walk through the flow, mint themselves a token, and get shell access to my machine. So the consent page demands a passphrase and shows the redirect target before you approve. If that destination isn't the one you expect, you're looking at someone else's authorization attempt.

Codes are single-use and expire in 120 seconds. S256 is required; plain is refused. Every comparison is constant-time. There's a test file that exercises all of it: discovery, registration, the consent gate, PKCE enforcement, replay rejection, refresh. Security code that isn't tested is decoration.

Three bugs worth your time

The architecture took an afternoon. These took longer.

The 404 that was a port collision

Everything started returning 404. Every path, including /health, which is an unconditional route. The tunnel reported healthy, ingress validated, the routing rule matched.

The response was content-length: 10. Exactly the length of Not found., and that string wasn't in my server.

cloudflaredto http://localhost:8787resolved first[::1]:8787the Grok Bot desktop app, answering “Not found.”127.0.0.1:8787Locum, never reached

The Grok Bot app itself listens on port 8787. macOS prefers ::1, so the tunnel talked to it instead.

The Grok Bot desktop app listens on [::1]:8787. Locum was on 127.0.0.1:8787. Same port, different address families, so there was no "address already in use" error, no warning, nothing. macOS resolves localhost to ::1 before 127.0.0.1, so every tunnelled request reached the wrong process.

Two things found it. lsof -nP -iTCP:8787 -sTCP:LISTEN showed both listeners, one line each. And cloudflared --loglevel debug logs ingressRule= and originService= per request, which separates "cloudflared's catch-all matched" from "the origin returned this". I'd assumed the former and was wrong.

Two fixes, because either alone leaves a trap: move to port 8791, and target 127.0.0.1 explicitly rather than localhost.

The service daemon that installed itself broken

sudo cloudflared service install writes a launch daemon whose ProgramArguments is just the binary path. No tunnel run. No --config. It runs as root, whose $HOME is /var/root, so ~/.cloudflared/config.yml is invisible to it, and it crash-loops on a 5-second KeepAlive.

The failure hides itself perfectly: the tunnel keeps working, because your user-level cloudflared tunnel run is still serving. You end up with a crash-looping root daemon and a working user process registered as two connectors for one tunnel. Adding a connector while that's in flux is how you get "Connection failed" from a server that answers every external probe.

The fix is to put the config where root can read it, /etc/cloudflared, and rewrite credentials-file to match.

The nested-session auth failure

Every delegated job died with Failed to authenticate: OAuth session expired and could not be refreshed.

Claude Code exports session plumbing into every child process it spawns: CLAUDECODE, a family of CLAUDE_CODE_* variables, and ANTHROPIC_BASE_URL. A claude that inherits those believes it's a nested child session and tries to delegate authentication to a host socket that isn't listening.

Locum now strips them before spawning, but only when it detects it's nested, so a deliberately-set ANTHROPIC_BASE_URL still works in a normal terminal.

The lesson generalises past this project: when you spawn a CLI from a server, you inherit an environment you did not design. Decide what crosses that boundary.

What it actually saves

Honesty matters more than the pitch here, so:

It reduces Grok Bot usage. It does not eliminate it. The bot's own orchestration turns still meter. The win is collapsing fifty bot steps into one tool call and a few polls, moving the long agentic grind onto the subscription you already pay for.

Your machine has to be awake with the tunnel up. It's a personal tool, not a service.

Those subscriptions have limits too. This changes where the inference budget comes from. Nobody found an infinite-token glitch.

The skill file matters more than the tools. Without an instruction telling the bot to prefer delegating, it has the tools available and keeps grinding through its own loop anyway. That one markdown file is the difference between the integration working and merely existing.

Running it

It's open source. You need claude or codex already signed in, a domain on Cloudflare for a stable hostname, and Grok Bot access.

git clone https://github.com/HarjjotSinghh/locum
cd locum && cp .env.example .env    # set a token and your workspace roots
set -a && source .env && set +a
uv run server.py

Then a tunnel, a custom connector pointed at https://your-host/mcp, and the skill file pasted into a Grok Bot Skill.

The invariants at the top of server.py aren't decoration. If a change would break one of them, it's the wrong change, and that is what keeps this a tool worth using rather than a liability worth avoiding.

Not affiliated with or endorsed by xAI, Anysphere, OpenAI, or Anthropic.

More in writing, or back home.