feat: add Flowxtra board-wide jobs provider, debug CLI, and MCP tools - #245
Conversation
Flowxtra (https://flowxtra.com) is a multi-tenant ATS whose public central endpoint lists live postings from every company on the platform in one cross-tenant feed — no slug, no token, no auth. Search narrowing (title, location, company, workplace) is server-side. - internal/provider/flowxtra: ogen client from a minimal openapi.yaml written against live captures (2026-07-26); fixtures + mock server + tests. Nullability verified across all 116 live rows; the upstream employment_type param is a live no-op and deliberately unmodeled. - cmd/flowxtra: ff/v4 debug CLI (search, detail). - internal/openingsmcp: flowxtra_search_jobs / flowxtra_get_job_detail dedicated tools (board-wide source, so no ats.Adapter/roster), wired into cmd/openings-mcp with server-instruction mentions. - docs: README job-board list; Makefile OPENAPI_SPECS entry. Verified: go test ./... green, hurl fixtures replay live 4/4, and a stdio MCP smoke test (initialize -> tools/call on both tools) returns live listings and a full detail through the real server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@9mtm Want to merge this. One thing first,
That describes the endpoint. A description should tell a client when to pick the tool. The other providers name a market: 104 "Taiwan's largest job board", Jobindex "(Denmark job board)", Mynavi "a major Japanese job board for mid-career hires. Listings are in Japanese." Flowxtra isn't the largest board in any single market, so a client can't route on the name. It skips the tool, or fires on unrelated queries and burns user tokens on rows that miss. Your server eats the request either way. Pulling the board today ( I'd rather you write that line than me. Name what Flowxtra covers, so a client fires the tool on questions it can answer and skips the rest. If the snapshot above misses where you're headed, write to that instead. Longer term: a per-company endpoint keyed by slug or career domain would put Flowxtra behind |
| HasID string `json:"has_id" jsonschema:"Public hashed job id; pass to flowxtra_get_job_detail's has_id param."` | ||
| URL string `json:"url" jsonschema:"Public apply URL for the posting."` | ||
| } | ||
|
|
||
| type flowxtraDetailInput struct { | ||
| HasID string `json:"has_id" jsonschema:"Public hashed job id (has_id from flowxtra_search_jobs results, e.g. M88PB)."` |
There was a problem hiding this comment.
Rename this to job_id on the outward-facing MCP schema. Two reasons.
has_* reads as a boolean prefix. A client meeting has_id can parse it as "does this have an id" rather than as a value to carry into the next call.
Eight providers here already use job_id, including mynavi_get_job_detail, whose ids look like 348855-1-29-1. The three that don't (path, job_code, job_key) each carry a term the caller meets elsewhere. This one carries upstream's misspelling of hash_id.
| HasID string `json:"has_id" jsonschema:"Public hashed job id; pass to flowxtra_get_job_detail's has_id param."` | |
| URL string `json:"url" jsonschema:"Public apply URL for the posting."` | |
| } | |
| type flowxtraDetailInput struct { | |
| HasID string `json:"has_id" jsonschema:"Public hashed job id (has_id from flowxtra_search_jobs results, e.g. M88PB)."` | |
| JobID string `json:"job_id" jsonschema:"Public job id; pass to flowxtra_get_job_detail's job_id param."` | |
| URL string `json:"url" jsonschema:"Public apply URL for the posting."` | |
| } | |
| type flowxtraDetailInput struct { | |
| JobID string `json:"job_id" jsonschema:"Public job id (job_id from flowxtra_search_jobs results, e.g. M88PB)."` |
Keep has_id in the provider package and openapi.yaml. That layer should stay faithful to the wire. Line 173, the tool description on 236, and the error string on 240 need the same rename.
Per @amikai's review on amikai#245: - Drop implementation mechanism (server-side / client-side / board-wide) from tool and parameter descriptions — a client behaves no differently for it; descriptions now say what to search, not how the backend runs. - Rename the outward-facing id field has_id -> job_id on the MCP schema (search output + detail input, plus the tool description and error string). `has_*` reads as a boolean prefix, and eight other providers already expose the id as job_id. The provider package and openapi.yaml keep has_id, staying faithful to the wire. go build + go test ./... green (63 packages). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for the review — both make sense. Pushed:
|
Adds Flowxtra (https://flowxtra.com) as a job-board provider, built following
.agents/skills/integrate-new-provider/SKILL.mdend to end (recon → fixtures → ogen client → provider package → debug CLI → MCP surface → docs).Why dedicated tools, not an ats.Adapter
Flowxtra is a multi-tenant ATS, but its public central endpoint (
GET https://app.flowxtra.com/api/central/jobs) is one cross-tenant, board-wide feed — no per-company slug or board to roster, no token, no auth. Per the SKILL.md surface-choice rule that makes it structurally like Indeed/LinkedIn/104, so it ships asflowxtra_search_jobs/flowxtra_get_job_detaildedicated tools rather than anats.Adapter.Surface notes (all verified against live captures, 2026-07-26)
search-key(title),location(city/state/country),company_name,workplace(exactOn-site/Hybrid/Remote) all reducetotal. Nullability inopenapi.yamlwas swept across every row live at capture time (116 jobs), not guessed.employment_typeparameter is a live no-op (the backing column stores objects, the filter compares strings — every value returns zero rows), so it is deliberately unmodeled, with the deviation documented inopenapi.yamlremotive-style.GET /api/candidate/jobs/{has_id}) 404s cleanly with a JSON envelope for unknown ids;date_shareserializes as null on detail (posted-at comes from the list); list rows carry aflowxtra.com/apply/…URL while detail carries the company's own career-page apply URL.What's included
internal/provider/flowxtra— minimalopenapi.yaml+ ogen client, 4 captured hurl fixtures, mock server, client testscmd/flowxtra— ff/v4 debug CLI (search,detail)internal/openingsmcp/flowxtra.go— the two tools + E2E tests (mock-backed in-memory MCP sessions), wired intocmd/openings-mcpwith serverInstructions mentionsOPENAPI_SPECSentryVerification
go test ./...— all packages greenhurlfmt --checkclean;hurl --testreplays all 4 fixtures against the live API successfullytools/call flowxtra_search_jobs(query=sales, workplace=Remote → 4 live listings with formatted salaries) →tools/call flowxtra_get_job_detail(full plain-text description + career-page apply URL), bothisError: falsemake validate-openapinot run locally (no docker on this machine) — relying on CIDisclosure: I work at Flowxtra. The endpoint is public and unauthenticated; this integration is read-only search/detail like every other provider, no apply flow.