Integrate new provider
Skill Flowxtra/career-agent/.agents/skills/integrate-new-provider
Use when adding a new job-listings provider to openings-mcp — a new ATS platform (like Workday, Greenhouse, Lever, Ashby, SmartRecruiters) or a dedicated job board or careers site (like 104, Cake, Google, NVIDIA, TSMC) — or when finishing a stalled integration by wiring an existing provider package into the MCP server, e.g. its client, tests, and debug CLI all work but its companies aren't reachable through the MCP tools.From its SKILL.md
npx -y skills add Flowxtra/career-agent --skill integrate-new-providerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 14 days oldThe repository was created 14 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
11.6 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
Integrate a New Provider
Overview
Every provider follows the same pipeline: recon (find and rank the site's real data surface) → capture fixtures → build a client that matches that surface → provider package with fixture-replaying tests → debug CLI → MCP surface (ATS adapter or dedicated tools, wired into the server).
For JSON REST, the client path is: minimal OpenAPI spec → ogen-generated client. For GraphQL, HTML, SSR-embedded state, RSS/Atom, or JSON Feed, write a hand-rolled client instead — do not force ogen. Work the stages in order; each builds on the previous one's verified output. The integration is done when the provider is reachable through the MCP server, not when the debug CLI works — if a session stops before the surface stage, hand off the remaining stages explicitly.
SmartRecruiters (internal/provider/smartrecruiters, cmd/smartrecruiters)
is the most recent ogen/JSON worked example. Spec-less hand-written clients
in-tree: LinkedIn, jobindex, join, iCIMS, SuccessFactors, UltiPro (HTML /
GraphQL / __NEXT_DATA__).
Pick the MCP Surface
- Multi-company ATS (one API, many tenants/boards): implement
internal/ats.Adapterso companies join the unifiedsearch_jobs_by_companytools. Examples: workday, greenhouse, lever, ashby. - Single site or job board: dedicated
<name>_search_jobs/<name>_get_job_detailMCP tools ininternal/openingsmcp/<name>.go. Examples: job104, cake, google, nvidia, tsmc, linkedin.
RSS/Atom and most niche boards land on dedicated tools unless each tenant has a stable, roster-able feed URL and multi-company routing is worth it.
Pipeline
-
Spec hunt — WebSearch for an official OpenAPI/Swagger spec or developer API docs before reverse-engineering anything (e.g. "<provider> API openapi spec", "<provider> developer docs"). An official spec beats a hand-derived one: fewer wrong guesses about types, nullability, and pagination. If one exists, trim it down to the endpoints you need rather than writing from scratch. Official or not, the spec still gets verified against captured traffic in the next step — vendor specs drift from what the public endpoints actually return. No official docs does not mean no integration; it means recon (step 2) owns the surface choice.
-
Recon + fixtures — Recon means reconnaissance: before writing a client, discover how the site actually exposes listings, confirm the calls work outside a browser, and pick the best surface. Do not assume JSON REST.
Surface ranking (prefer higher when it is public, stable, and replayable without login or a browser):
- Public JSON REST (OpenAPI + ogen path)
- Public GraphQL (hand-written client; see Indeed)
- JSON Feed / structured feed (e.g. Teamtailor
/jobs.json) - RSS / Atom (list dump; only when the quality bar below holds)
- SSR-embedded state (
__NEXT_DATA__,var Stash = {...}, JSON smuggled in HTML — join, jobindex, UltiPro detail) - Pure HTML / JSON-LD scrape (LinkedIn, iCIMS)
- Browser automation (Playwright) — openings-mcp avoids this unless a later decision explicitly allows it
When the endpoint isn't guessable or refuses direct requests, drive a real browser with your browser-automation tool: load the careers page, perform a search or open a posting, then read the network requests it fired — URL, query params, and required headers (in Claude Code: the Browser pane's
navigate/computerplusread_network_requests). Also check for<link rel="alternate" type="application/rss+xml">, Atom, orapplication/feed+jsonif no JSON API appears. Replay the recovered request outside the browser to confirm it works standalone.Capture each operation as a hurl request + response pair in
internal/provider/<name>/testdata/(happy path, filtered search when applicable, not-found, unknown company). Fixtures are real captures: JSON, XML (RSS/Atom), or HTML — never hand-written bodies.make hurl-testreplays them live;make hurl-fmtbefore committing.RSS / Atom adoption bar (all should hold; otherwise keep looking or treat the feed as a list-only MVP with an explicit detail gap):
- Stable per-item id (
guid, or a durable link used as id) - Enough fields for search (at least title + link); short descriptions need a replayable detail path (second request or linked HTML)
- Fixed or constructible feed URL, no login
- Prefer a higher-ranked surface when the same site also exposes one
RSS is a legitimate surface for niche boards. It is usually a full dump + client-side filter shape, not server-side search. Do not choose RSS to save time when a stable public JSON API already exists.
-
Client — first classify the chosen surface's search shape (this decides adapter behavior regardless of transport):
- Server-side search: list/search with query params and pagination, plus detail-by-id (workday, smartrecruiters).
- Full dump: one response returns the whole board; search happens
in our code via
searchDump(internal/ats/filter.go) for ATS adapters (greenhouse, lever, ashby). Dump-style boards/feeds use the same idea locally. A separate detail endpoint may still exist, or the dump may already carry full descriptions.
Then build the client for the surface:
- JSON REST — minimal
internal/provider/<name>/openapi.yamlcovering only the endpoints you use (trimmed official spec or written from captures). Mark fields nullable per real responses (see docs/superpowers/plans/2026-07-11-provider-schema-nullable-sweep.md). Addgen.gowith the ogengo:generateline, rungo generate ./internal/provider/<name>, add the spec toOPENAPI_SPECSin the Makefile, runmake validate-openapi. - GraphQL, HTML, SSR blob, RSS/Atom, JSON Feed — hand-written
client (
client.go/parse.go). Document the surface and quirks indoc.goand, when reverse-engineering is non-obvious,API.md. For RSS/Atom, default togithub.com/mmcdole/gofeed(one API for RSS and Atom, including common real-world feed variations). Reserve directencoding/xmlfor unsupported vendor extensions or provider-specific fields gofeed does not surface. Use goquery for HTML /__NEXT_DATA__. Skip ogen andOPENAPI_SPECSunless you later gain a true REST OpenAPI surface.
-
Provider package —
mocksrv.goreplays the testdata fixtures;client_test.goexercises the client against it. Roster-based providers addcompanies.yaml+companies.go(embedded viago:embed, validated at init, sorted by name). Seed the initial roster with 3–5 companies: WebSearch for well-known companies hosted on this ATS (e.g. "site:<careers host> ..." or "<ATS> customers"), then confirm each against the live API before adding it — a real request must return 200 with jobs present and a matching company name (the smartrecruiters roster documents this bar). Bulk expansion comes later in step 7; the seed roster just has to prove the pipeline end-to-end. -
Debug CLI —
cmd/<name>/main.gousing ff/v4 withsearch,detail, andcompaniessubcommands for live manual checks. Validate pagination flags and reject stray positional args (mirrorcmd/smartrecruiters). Do not addcmd/<name>/doc.go; package-level documentation belongs only ininternal/provider/<name>/doc.go. -
MCP surface
- ATS adapter:
internal/ats/<name>.goimplementingAdapter(Name, Roster, ParseCareersURL, Search, Filters, Detail) + tests. Register it innewATSRegistry(cmd/openings-mcp/main.go), add its careers-URL host pattern tocareersHostPatternsByAdapter(internal/ats/registry.go), and add it toproviderOrder(cmd/verify-companies/main.go). - Dedicated tools:
internal/openingsmcp/<name>.gowithRegister<Name>+ tests; wire the client innewServer.
Finish the stage with a live smoke test through the real MCP path: sample 3–5 companies from the provider's
companies.yamland send actual MCP requests into the server — every sampled company must return live listings viasearch_jobs_by_company, andget_job_detail_by_companymust work on at least one returned job_id. Over stdio that is initialize → notifications/initialized → tools/call, keeping stdin open (trailingsleep) so the server doesn't EOF before answering:(echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' echo '{"jsonrpc":"2.0","method":"notifications/initialized"}' echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_jobs_by_company","arguments":{"company":"<roster company>"}}}' sleep 15) | go run ./cmd/openings-mcpDedicated-tool providers have no roster: run the same requests against the new
<name>_search_jobs/<name>_get_job_detailtools with a few real queries instead. - ATS adapter:
-
Roster curation — bulk-discovered candidates go in
unverified/<name>.yaml; verify entries withcmd/verify-companies(runs the real adapter path) before promoting them into the curatedcompanies.yaml. Follow the roster commit convention in CLAUDE.md. -
Docs — update the README provider list and, if tool-selection guidance changes, the server instructions in
cmd/openings-mcp.
Conventions
- Brainstorm and plan each stage under
docs/superpowers/{plans,specs}; the ashby documents there are the template (openapi → provider → cli). - Never hand-edit
oas_*_gen.go; changeopenapi.yamland regenerate. - Document provider-specific quirks (e.g. opaque params, odd value
domains, soft filters) in
openapi.yaml(then regenerate) when the provider has a spec, otherwise in the package'sdoc.go. A quirk scoped to a single operation in a spec-less provider may go in that method's godoc instead ofdoc.go. - Fixtures are captured real responses, never hand-written JSON, XML, or HTML.
Common Mistakes
- Stopping after the debug CLI (step 5): users only reach the provider
through the MCP server, so a provider package that isn't registered in
cmd/openings-mcpis invisible no matter how complete its client, tests, and CLI are. - Forgetting to add a new REST
openapi.yamltoOPENAPI_SPECS, somake validate-openapisilently skips it. - Forcing ogen / OpenAPI onto GraphQL, HTML, RSS, or SSR-embedded blobs — those stay hand-written clients.
- Hand-rolling RSS/Atom with
encoding/xmlwhengofeedalready parses the feed; only drop to raw XML for fields gofeed cannot expose. - Picking RSS (or HTML scrape) when a stable public JSON API exists on the same site.
- Adopting an RSS feed without stable item ids, or with title+link only and no plan for detail.
- Roster slug or display-name collisions across adapters:
ats.NewRegistryfails at startup by design — check the othercompanies.yamlfiles before adding entries.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most mcp tooling skills give in ~2.8k tokens
Counted across 638 of the 750 authors here whose files we hold, read 2026-08-07
- Create ten complex or independent read-only evaluation questionsin 69 of 638, across 15 files
- Test servers using MCP Inspectorin 61 of 638, across 19 files
- Provide actionable error messages with specific next stepsin 54 of 638, across 12 files
- Prioritize comprehensive API coverage over specific workflows or workflow toolsin 54 of 638, across 12 files
- Use TypeScript and Streamable HTTP for remote servers or clientsin 54 of 638, across 8 files
- Define structured output schemas where possiblein 50 of 638, across 8 files
- Use Zod or Pydantic for input schemasin 47 of 638, across 5 files
- Fetch MCP specification pages with markdown suffixin 46 of 638, across 4 files
- Load framework documentation using WebFetchin 45 of 638, across 3 files
- Verify each evaluation answer independentlyin 45 of 638, across 3 files
- Implement API client with authentication and paginationin 45 of 638, across 3 files
- Define input schemas with validationin 27 of 638, across 9 files
Said here and by no other author read
- search for an official api spec first
- discover and rank available data surfaces
- capture real request and response fixture pairs
- use ogen for json rest surfaces
- write a hand-rolled client for non-rest surfaces
- build a debug cli with search and detail subcommands
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.