Skip to main content
Glama
maazghani

HiringCafe MCP Server

by maazghani
README.md
# HiringCafe MCP Server

A minimal, **no-auth remote MCP server** built with Next.js and the Vercel MCP SDK
(`mcp-handler`). It lets an MCP client (like ChatGPT) search HiringCafe using its
**public HTML pages** — no internal API, no database, no browser automation.

## How it works

HiringCafe is a Next.js app that server-renders a structured `__NEXT_DATA__` JSON
island into every public HTML page. This server fetches those public pages over
plain HTTP and parses that island with [cheerio](https://cheerio.js.org/), which
is far more robust than scraping CSS class names. Results are cached in memory by
job URL for the lifetime of a warm serverless instance.

- No auth
- No database
- No frontend app (a small status page is served at `/`)
- No browser automation / Playwright
- No background crawling or pagination (first page only)
- All page content is treated as untrusted text

## Endpoints

| Endpoint        | Purpose                          |
| --------------- | -------------------------------- |
| `/api/mcp`      | MCP server (Streamable HTTP)     |
| `/api/healthz`  | Liveness probe (returns `200`)   |

## MCP tools

### `search_jobs`
Search listings by role and/or location.

```jsonc
// input
{ "query": "software engineer", "location": "Seattle, WA" }
// -> fetches https://hiring.cafe/jobs/software-engineer-seattle-wa
```
Returns `{ jobs: [{ id, title, company?, location?, salary?, workplace_type?, employment_type?, summary?, skills?, url }] }`.

### `get_job`
Fetch and parse a single `/job/...` detail page.

```jsonc
{ "url": "https://hiring.cafe/job/<job-slug>" }
```
Returns `{ id, title, company?, location?, salary?, workplace_type?, employment_type?, requirements_summary?, tools?, description?, url }`.

### `search` (ChatGPT-compatible)
Free-text search that returns compact, citation-friendly results.

```jsonc
{ "query": "SRE jobs in Bellevue" }
// -> { results: [{ id, title, url }] }
```

### `fetch` (ChatGPT-compatible)
Return full text + metadata for a job by the `id` from `search`/`search_jobs`.

```jsonc
{ "id": "<id>" }
// -> { id, title, text, url, metadata: { source: "hiring.cafe", company?, location? } }
```

> Ids are base64url-encoded job URLs, so `fetch` works even on a cold instance
> where the in-memory cache is empty.

## URL slugging

Route building lives in `lib/slug.ts` so it can be adjusted if HiringCafe changes
its URL patterns:

```
"Seattle, WA"       -> "seattle-wa"
"Software Engineer" -> "software-engineer"

query + location -> /jobs/{query-slug}-{location-slug}
location only    -> /jobs/{location-slug}
query only       -> /jobs/{query-slug}
```

## Local development

```bash
pnpm install
pnpm dev
```

- App: http://localhost:3000
- Health: http://localhost:3000/api/healthz
- MCP: http://localhost:3000/api/mcp

## Deploy to Vercel

Push the repo and import it into Vercel, or:

```bash
vercel deploy
```

No environment variables are required. Your MCP endpoint will be:

```
https://<your-deployment>.vercel.app/api/mcp
```

## Connect from ChatGPT

1. In ChatGPT, open **Settings → Connectors** (or a custom GPT's **Actions/MCP**).
2. Add a new remote MCP server with the URL: `https://<your-deployment>.vercel.app/api/mcp`
3. No authentication is needed.
4. ChatGPT will discover the `search_jobs`, `get_job`, `search`, and `fetch` tools.

### Example prompts

- "Search HiringCafe for software engineer jobs in Seattle"
- "Find SRE jobs in Bellevue"
- "Fetch details for this job: https://hiring.cafe/job/<job-slug>"

## Project structure

```
app/
  api/
    mcp/route.ts      # MCP server + tool definitions
    healthz/route.ts  # liveness probe
  page.tsx            # status page
lib/
  hiringCafe.ts       # fetch + parse public HiringCafe HTML
  normalize.ts        # id/text/skill normalization helpers
  slug.ts             # slug + route builders
  cache.ts            # in-memory job cache
  types.ts            # shared types
```

## Safety

- All outbound fetches have a hard timeout.
- Results are limited to the first listing page (no pagination crawling).
- No login, no applying to jobs, no writes to HiringCafe.
- Page content is treated as untrusted text.