Part 2 of 3 - Part 1: Building a job-hunting MCP server · Part 3: Teaching it what it can do
TL;DR: In part one I built a personal MCP server that scores job fits and tracks my pipeline. It only ran on my laptop. I've now turned it into a remote server with a public URL, so I can use it from the Claude and ChatGPT apps on my phone. Most of the work was getting personal data out of the code, finding free hosting, and fixing an error that only happened in ChatGPT.
Why go remote
The original version connected to Claude Desktop over stdio, and to ChatGPT through a local Express server exposed with a Cloudflare tunnel. It only worked while my laptop was on, and I couldn't paste a job ad into the app on my phone and get a score back.
With a remote MCP server, you add a public HTTPS endpoint once as a custom connector and it works on every device.
Keeping both transports
I didn't want to break the desktop setup while building the remote one. That was easy because the tool logic was already separate from the transport. All the work (the prompts, the Sheets and Docs calls) lives in one function. The entrypoint picks a transport from an environment variable: stdio by default for the desktop app, or an HTTP server for the remote version. Both use the same tools.
Auth
A public URL means anyone who finds it could call my tools, which read and write my Google Sheet. I'm the only user, so I didn't need multi-user OAuth, just a way to keep other people out.
A single shared token does that. The server won't start without one, and rejects any request that doesn't include it. One detail: the custom-connector dialogs are built around OAuth and don't always have a field for a plain token. So the server accepts the token two ways, as an Authorization header or in the URL path. One of them works with whatever client you're using.
Cleaning up before going public
I planned to make the repo public, so I went looking for anything that shouldn't be shared. The credentials were fine, the service-account key and config had never been committed. But personal details had ended up in the source: notes and preferences that should have come from my private config were written into the tool prompts. They weren't secrets, but I didn't want them in a public repo.
I moved all of that into my gitignored config, and went through the whole project before making it public.
Gitignore only helps from the moment you add it, so it's easier to keep personal data out of the source from the start.
Hosting it for free
My first choice was Google Cloud Run, since my Google project was already there. It's free at my usage, but it requires a billing account on file before you can deploy. I didn't want to add a card for a personal project, so I used Render's free tier instead, which doesn't need one.
The downside of free hosting is that the service sleeps after a period of inactivity, and the first request after that takes a while to wake it. To avoid that, I set up a scheduled job that pings a health endpoint every few minutes and keeps the service awake.
The error that only happened in ChatGPT
The server worked in my own tests. Then I connected it to ChatGPT, asked for my applications, and got a 502.
At the same time, my own request to the same URL worked and returned all my data. The server was fine, but ChatGPT couldn't talk to it.
The cause was the response format. The MCP HTTP transport replies with a streaming text/event-stream by default. Command-line tools handle that fine, and it had worked locally through the Cloudflare tunnel. But ChatGPT's client, behind the free host's proxy, didn't read the stream cleanly and returned a gateway error. The fix was to make the transport reply with plain JSON instead. It doesn't need streaming here, and JSON goes through proxies without trouble.
Marking which tools are safe
By default the assistant treated every tool as potentially destructive and asked permission before running anything, including reads. MCP lets you annotate each tool: read-only, writes, or deletes. After labelling them, the read-only tools stopped prompting for permission, and only the tool that deletes a row still asks for confirmation.
Tidying the context
Part one ended with the model losing context between and within conversations. Going remote made me deal with a related problem. My context had grown into several overlapping files, plus a section of config the tool wasn't even reading anymore. The same facts were written in a few places and had started to disagree with each other.
I merged them. Structured settings stay in config, and the rest (background, project write-ups, interview notes) lives in one knowledge base file, with one command that bundles it for the hosted server. One file is easier to keep accurate and cheaper to send to the model on each call. It doesn't stop the model losing context, but the facts no longer disagree.
Where it is now
It's the same tool from part one, just not tied to my laptop. I can score a role from my phone, and the data still lives in my own Google Sheet behind my own service account. With everything personal and every secret moved out of the code and into the host's environment, I was comfortable making it public.
The code is on GitHub at altany/job-hunter; the README covers how to set up your own. If you have questions, get in touch.
Part 1: Building a job-hunting MCP server · Part 3: Teaching my job-hunting MCP server what it can do
