- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Documented (README)
- !No description
/plugin marketplace add connorlagana/watchtower
/plugin install watchtowerResumen de Plugins
# Watchtower
**Tech job monitoring for AI agents. Say what you are looking for once and get only the new postings that match, from the job boards of tech companies and startups, as structured JSON.**
Agents are bad at waiting for a job to be posted. A session lasts minutes, careers pages are heavy to re-read, and "has anyone posted an iOS role in Austin yet?" turns into re-running the same search every day. With Watchtower the agent creates a watch once, in plain language:
```json
{ "query": "iOS jobs in Austin making at least 150k a year with a maximum of 6 years of experience" }
```
Watchtower checks the job boards of tech companies and startups on a schedule and remembers which jobs were open. `get_changes` then returns only the new postings that match, and a webhook can wake the agent when one arrives. An agent can also watch one company's board by URL and get `JOB_ADDED`, `JOB_REMOVED` and `JOB_UPDATED` events.
- **No URL needed.** A watch created from a `query` covers every board Watchtower monitors: a built-in directory of tech company and startup boards plus every board anyone has watched by URL.
- **Pay and experience.** Jobs carry `salary` and `experience_years` when the posting states them, and watches filter on `min_salary` and `max_experience_years`.
- **Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Workday and iCIMS** are read through each platform's own endpoints: no bot walls. Any other careers page works if it publishes schema.org `JobPosting` markup.
- **Filters on the watch**, so `get_changes` only returns what matters: `keywords`, `all_keywords`, `exclude_keywords`, `locations`, `seniority`, `remote_only`, `min_salary` and `max_experience_years`. Every job carries derived `remote` and `seniority` fields.
- **Many companies in one call**: pass `urls` instead of `url`.
- **MCP** (Streamable HTTP) and **REST**, backed by the same service layer.
- Free and anonymous: a client gets a token and up to 50 watches (a search across all boards is one watch).
- TypeScript, Node.js 22, Fastify 5, PostgreSQL, the official MCP TypeScript SDK. No LLM or third-party API keys required.
## Use the hosted service
Watchtower runs at **[watchtower.lat](https://watchtower.lat)**, free, with no sign-up. MCP endpoint: `https://watchtower.lat/mcp`.
- **Claude Code**: `claude mcp add --transport http watchtower https://watchtower.lat/mcp`, or install the plugin, which adds a skill that tells Claude when to use it:
```
/plugin marketplace add connorlagana/watchtower
/plugin install watchtower@watchtower
```
- **Claude.ai / Claude Desktop**: Settings → Connectors → Add custom connector → `https://watchtower.lat/mcp`.
- **Cursor, VS Code**: one-click buttons at [watchtower.lat/#install](https://watchtower.lat/#install).
- **Anything else**: `{ "mcpServers": { "watchtower": { "type": "http", "url": "https://watchtower.lat/mcp" } } }`.
Listed in the [MCP Registry](https://registry.modelcontextprotocol.io) as `lat.watchtower/watchtower` ([server.json](server.json)).
## Quick start
```bash
cp .env.example .env
docker compose up --build # app on http://localhost:3000, Postgres alongside
docker compose --profile demo run --rm demo # the end-to-end demo below
```
Without Docker (Node 22 and a running Postgres):
```bash
npm install
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/watchtower
npm run migrate
npm run dev # or: npm run build && npm start
```
## Demo
`npm run demo` (with `DATABASE_URL` set) runs the core loop end to end against a local fixture careers page:
1. create client → 2. create a job watch with keyword `ios` (takes the initial snapshot) → 3. a second agent watches the same board and reuses the same resource → 4. a third agent names no board and creates a search watch from "remote iOS jobs paying at least 150k with a maximum of 6 years of experience" → 5. a re-check where only the page around the jobs changed (session token, "N minutes ago") reports no change → 6. the source adds an iOS job and an Android job → 7. Watchtower checks and detects the change → 8. an MCP client calls `get_changes`:
```json
{
"changes": [
{ "type": "JOB_ADDED",
"summary": "New job: Senior iOS Engineer (Remote - US)",
"data": { "job": { "title": "Senior iOS Engineer", "location": "Remote - US", "company": "Acme Robotics", "url": "…/careers/ios-303" } } }
],
"cursor": 2, "has_more": false
}
```
The Android job is filtered out by the keyword. 9. A second `get_changes` call returns `[]`. 10. The second agent, with no keyword, sees both new roles. 11. The third agent gets the iOS role with the salary and experience read from the posting.
The demo runs its fixture on 127.0.0.1, so it turns on `ALLOW_PRIVATE_NETWORKS` for its own process only.
## Connecting an agent (MCP)
```json
{ "mcpServers": { "watchtower": { "type": "http", "url": "http://localhost:3000/mcp" } } }
```
| Tool | What it does |
|---|---|
| `watch_jobs` | With `query` and no URL: watch every monitored board for new postings that match a plain-language request. The result shows the reading (`interpreted`), the matching jobs open now (`current_jobs`) and the boards covered (`coverage`). With `url`, or `urls` (up to 25; returns `watches` and per-URL `errors`): watch specific boards. Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Workday and iCIMS use their own endpoints; other careers pages use schema.org `JobPosting`. A careers page that only links to a supported board is watched through that board (`resolved_from`), and a page with neither is rejected with `NO_JOB_DATA`. Board watches emit `JOB_ADDED` / `JOB_REMOVED` / `JOB_UPDATED`; search watches emit `JOB_ADDED`. Explicit filters override the query: `keywords`, `all_keywords`, `exclude_keywords`, `locations`, `seniority`, `remote_only`, `min_salary`, `salary_currency`, `max_experience_years`, `include_unknown`. |
| `get_changes` | Changes since your last call (the cursor advances). `peek`, `since` (replay), `watch_id`, `limit`. |
| `ack_changes` | Acknowledge a cursor after `get_changes(peek=true)`, for at-least-once processing. |
| `list_watches` | Your watches, with health, expiry and pending-change counts. |
| `get_watch` | One watch plus the jobs currently open that match its filters (across all boards for a search watch). |
| `delete_watch` | Stop monitoring and free a slot. |
`watch_jobs` also accepts `webhook_url` for push delivery (see below).
The tool descriptions and server `instructions` tell agents to prefer Watchtower over re-running job searches or re-checking careers pages.
## Search watches
A watch with no `url` is a search watch. It is its filters, and it reads the new postings of every monitored board.
```bash
curl -s -X POST localhost:3000/v1/watches -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
-d '{"query":"iOS jobs in Austin making at least 150k a year with a maximum of 6 years of experience"}'
```
```json
{
"scope": "all_boards",
"interpreted": { "filters": { "keywords": ["ios"], "locations": ["austin"], "min_salary": 150000, "max_experience_years": 6, "include_unknown": true }, "notes": [] },
"coverage": { "boards": 1061 },
"matching_jobs_count": 3,
"current_jobs": [ { "title": "Senior iOS Engineer", "company": "Acme", "location": "Austin, TX", "salary": { "min": 165000, "max": 210000, "currency": "USD", "period": "year", "annual_min": 165000, "annual_max": 210000 }, "experience_years": 5, "url": "…" } ]
}
```
- **The query is read by rules, not a model.** Watchtower still needs no LLM or API key. The reading comes back as `interpreted`, with `notes` for anything it could not use, so the calling agent can check it. Explicit filters always win over the query.
- Role words become `keywords` (any of, for "iOS or Android") or `all_keywords` (all of, for "data scientist"). Generic words such as "engineer" and "developer" are dropped when a more specific word is present.
- "in Austin", "in Austin, TX", "in New York or remote" become `locations`. A bare "remote" becomes `remote_only`.
- "at least 150k", "$180,000+", "$45/hr" become `min_salary`, converted to a yearly figure.
- "a maximum of 6 years of experience", "3-5 years", "I have 4 years of experience" become `max_experience_years`.
- "senior", "staff", "entry level" and the other levels become `seniority`. "no managers" becomes `exclude_keywords`.
- **Coverage is tech companies and startups, not the whole internet.** The built-in directory (`src/search/boards.ts`) lists 1,061 company boards on Greenhouse, Lever, Ashby and Workable, from large public tech companies down to seed-stage startups. Each was confirmed against its platform's API with open jobs. Every role those companies post is covered, not only engineering. On top of that, every board any client watches by URL is covered, and stays covered (see [Growing the directory](#growing-the-directory)).
- **Pay and experience come from the posting.** Lever, Ashby, Greenhouse, Recruitee and JSON-LD pay fields are read directly; otherwise the posting text is parsed ("$150,000 - $200,000/yr", "5+ years of experience"). A job passes `min_salary` when the top of its range reaches it, and `max_experience_years` when it asks for no more than that.
- **Postings that state neither are still reported**, without a `salary` or `experience_years` field, because many postings state no pay. Pass `include_unknown: false` to report only postings that state a qualifying value.
- **Only new postings are reported** (`JOB_ADDED`), and only those that appear after the watch was created. `current_jobs` on creation and on `get_watch` is the baseline of what is open now.
- SmartRecruiters, Workday and iCIMS listings carry no posting text, so their jobs never have `salary` or `experience_years`.
### Growing the directory
The directory grows in three ways.
- **Agents grow it by using it.** When a client watches a platform board bLo que la gente pregunta sobre watchtower
¿Qué es connorlagana/watchtower?
+
connorlagana/watchtower es plugins para el ecosistema de Claude AI con 0 estrellas en GitHub.
¿Cómo se instala watchtower?
+
Puedes instalar watchtower clonando el repositorio (https://github.com/connorlagana/watchtower) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar connorlagana/watchtower?
+
Nuestro agente de seguridad ha analizado connorlagana/watchtower y le ha asignado un Trust Score de 77/100 (tier: Trusted). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene connorlagana/watchtower?
+
connorlagana/watchtower es mantenido por connorlagana. La última actividad registrada en GitHub es del 2026-10-03, con 0 issues abiertos.
¿Hay alternativas a watchtower?
+
Sí. En ClaudeWave puedes explorar plugins similares en /categories/plugins, ordenados por popularidad o actividad reciente.
Despliega watchtower en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](https://claudewave.com/repo/connorlagana-watchtower)<a href="https://claudewave.com/repo/connorlagana-watchtower"><img src="https://claudewave.com/api/badge/connorlagana-watchtower" alt="Featured on ClaudeWave: connorlagana/watchtower" width="320" height="64" /></a>Más Plugins
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
AI agent skill that researches any topic across Reddit, X, YouTube, HN, Polymarket, and the web - then synthesizes a grounded summary
Write HTML. Render video. Built for agents.
Agent skill that removes signs of AI-generated writing from text
Academic Research Skills for Claude Code: research → write → review → revise → finalize
Create beautiful slides on the web using a coding agent's frontend skills