protogrid

Search / io.github.work0r-ai/workorai

workorai

R0· no sign-in

WorkorAI talent marketplace MCP: candidate job search and employer hiring with explainable matching

v0.4.3 · active · repository · descriptor JSON · versions

An agent can connect right now, no human step.Remote endpoint, no authentication, reachable on the last probe.
Quality76good
Trust87of 100
Uptime, 30 days100%
Latency p50405 ms

Connect

8 clients · secrets stay placeholders
{
  "mcpServers": {
    "workorai": {
      "type": "http",
      "url": "https://workorai.com/mcp"
    }
  }
}
claude mcp add --transport http workorai https://workorai.com/mcp
[mcp_servers.workorai]
url = "https://workorai.com/mcp"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "workorai": {
      "type": "remote",
      "url": "https://workorai.com/mcp",
      "enabled": true
    }
  }
}
{
  "mcpServers": {
    "workorai": {
      "type": "http",
      "url": "https://workorai.com/mcp"
    }
  },
  "deeplink": "cursor://anysphere.cursor-deeplink/mcp/install?name=workorai&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vd29ya29yYWkuY29tL21jcCJ9"
}
{
  "servers": {
    "workorai": {
      "type": "http",
      "url": "https://workorai.com/mcp"
    }
  }
}
{
  "mcpServers": {
    "workorai": {
      "httpUrl": "https://workorai.com/mcp"
    }
  }
}
extensions:
  "workorai":
    enabled: true
    name: "workorai"
    type: streamable_http
    uri: "https://workorai.com/mcp"
    timeout: 300

Quality 76 / 100good

protocol75 · weight 25
  • warn
    protocol.modernnewest supported version is 2025-11-25; 2026-07-28 not supported, older versions are deprecated until 2027-07-28
  • n/a
    protocol.statelessonly applies to 2026-07-28 servers
  • pass
    protocol.transportstreamable HTTP
  • n/a
    protocol.list_ttlonly applies to 2026-07-28 servers
authorizationn/a · weight 15
  • n/a
    auth.prm, auth.as_metadata, auth.cimdno remote uses OAuth
  • n/a
    auth.secret_in_urlno templated URL
tool hygiene64 · weight 30
  • pass
    tools.descriptionsevery tool has a description
  • warn
    tools.description_length1 tools have descriptions over 1,024 characters, which crowds the context
  • pass
    tools.schemasevery tool has an object input schema
  • fail
    tools.annotationsno tool declares readOnlyHint or destructiveHint, so clients cannot tell safe calls from risky ones
  • fail
    tools.directory_hintsno tool declares all four of readOnlyHint, destructiveHint, idempotentHint and openWorldHint; missing: candidate.get_saved_jobs (readOnlyHint, destructiveHint, idempotentHint, openWorldHint); candidate.decline_invitation (readOnlyHint, destructiveHint, idempotentHint, openWorldHint); candidate.withdraw_application (readOnlyHint, destructiveHint, idempotentHint, openWorldHint); …
  • pass
    tools.token_costabout 4,677 tokens to load every tool
  • pass
    tools.api_dumptools are not a one-to-one API dump
stability100 · weight 15
  • pass
    stability.changesno tool changes in 30 days
  • pass
    stability.rug_pullno tool changed its meaning under the same name
dependenciesn/a · weight 15
  • n/a
    deps.known_vulns, deps.mcp_sdk_version, deps.resolvableno npm or PyPI package

29 tools, about 4,677 tokens to load them all · tool set 854380d3b565 · checked 2026-10-20 10:03 UTC

Tool changes

No tool definition changes recorded. A server's first observation is its baseline; later probes record what changes.

Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed. How it is computed.

Trust 87 / 100

provenance80
liveness100
freshness65
hygiene100

+repository +namespace-matches-repo +reachable +uptime-100% ~updated-110d-ago

Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed. How it is computed.

Endpoints

remoteauthreachableuptime 30dp50protocollast ok
https://workorai.com/mcp streamable-httpnoneyes100%405 ms2025-11-252026-10-20

Tools (29)

candidate.accept_invitationAccept an employer's invitation to a job (INVITED -> APPLIED). Idempotent — accepting an already-accepted invite succeeds. Returns NOT_INVITED when there is no open invitation (e.g. already withdrawn/declined), and NOT_FOUND when the job/invite is not found or the job is no longer public.
candidate.apply_to_jobApply the candidate to a PUBLISHED job, reusing their evaluated profile interview as evidence. Idempotent (re-applying succeeds; `reused` is true when an application row already existed). Requires a completed + evaluated interview — otherwise returns GATE_LOCKED / GATE_EVALUATING / GATE_FAILED. A missing or non-public job returns NOT_FOUND.
candidate.decline_invitationDecline an employer's invitation to a job (INVITED -> DECLINED). TERMINAL — a declined invite blocks any re-invite from the employer, so only decline when the candidate is sure. Idempotent (declining again succeeds). Returns NOT_INVITED when there is no open invitation, NOT_FOUND when the job/invite is not found.
candidate.get_applicationsList the candidate's own job applications (newest first) with status, dates, the originating interview score, and a job summary. Returns only the caller's applications — no jobId input, so it is not an enumeration surface.
candidate.get_jobFetch a single published job by id.
candidate.get_saved_jobsList the candidate's saved (bookmarked) jobs, newest first. Only currently PUBLISHED jobs are returned — a job saved earlier then closed/archived is omitted.
candidate.search_jobsSemantically rank published jobs against the authenticated candidate profile (embedding-based fit). Optional tier (best|good|weak) narrows to a match-quality band — start with tier:'best' for the strongest fits and cascade only if needed; omit for the full ranked list (read tierCounts for the band sizes). Each scored row carries matchExplanation (the white-box 'why': fit score, the candidate's skills that match the job's required set, and a rationale). A free-text `q`, or a candidate who has not completed an interview yet, instead browses published jobs by recency — those rows carry NO fit score (`matchScore` is `null`, no bands); treat them as a browse list, not a ranking.
candidate.set_saved_jobSet whether a PUBLISHED job is in the candidate's saved list (idempotent desired-state, NOT a toggle — pass saved:true to bookmark, saved:false to remove). Returns NOT_FOUND for a missing or non-public job.
candidate.withdraw_applicationWithdraw the candidate's active application to a job (APPLIED -> WITHDRAWN). Idempotent (withdrawing again succeeds). Returns NOT_APPLIED when there is no active application (e.g. only an open invitation, or already declined), and NOT_FOUND when no application exists for the job.
employer.archive_jobTransition a CLOSED job to ARCHIVED. Returns NOT_FOUND for missing or non-owner jobs and CONFLICT when the job is not in CLOSED.
employer.cancel_invitationCancel a pending invitation sent to a candidate. The invitation record is deleted; the employer can re-invite the same candidate later.
employer.close_jobTransition a PUBLISHED job to CLOSED. Returns NOT_FOUND for missing or non-owner jobs and CONFLICT when the job is not in PUBLISHED.
employer.create_jobParse raw text via Gemini AI and create a DRAFT job under the current employer. Synchronous: latency 5-30s. The agent can then call employer.update_job to refine fields and employer.publish_job to make it live.
employer.delete_jobPermanently delete a DRAFT job that was never published. Returns NOT_FOUND for missing or non-owner jobs and CONFLICT when the job is not in DRAFT.
employer.get_applicant_detailFull applicant bundle: resume, interview light slice (overallScore + summary + facts), GitHub analysis, LinkedIn analysis. The verbatim transcript is delivered by employer.get_applicant_transcript; the resume's contact fields are blanked unless the application is SHORTLISTED or HIRED.
employer.get_applicant_transcriptVerbatim interview transcript for one applicant. Ownership-only gate (same as the UI Download button — no SHORTLISTED/HIRED requirement). Returns an empty array when the source interview never produced turns.
employer.get_candidateFetch a discoverable candidate by user id. Returns search-entry shape plus a light interview slice (overallScore + summary + completedAt + evaluatedAt) and `existingApplications`: every JobApplication this candidate has on any of the employer's jobs (all 4 statuses, all 4 job statuses) so the agent can decide whether re-inviting will succeed. Heavy artefacts (transcript, facts, resume, github, linkedin) live behind employer.get_applicant_detail and require an application.
employer.get_candidate_evidenceFetch the interview EVIDENCE (facts proven in the interview + their Q&A, the interview summary, the résumé summary, and GitHub/LinkedIn signals) for ONE candidate AGAINST one of your published jobs — the white-box basis to explain WHY a candidate ranks where they do. Use it AFTER search_candidates_for_job: shortlist with the scorecard, then read the evidence here for the few you care about and write your own comparative review. Returns NOT_FOUND if the job is missing / not yours / not published, or the candidate is not in that job's searchable pool.
employer.get_jobFetch a single employer job record by id. Returns NOT_FOUND for missing jobs and for jobs owned by another employer (no existence leak).
employer.invite_candidateInvite a discoverable candidate to one of the employer's PUBLISHED jobs. Creates a JobApplication with status=INVITED. If a prior WITHDRAWN row exists for this (candidate, job) pair, the row is UPDATEd back to INVITED (re-invite is allowed after the candidate withdrew on their own). INVITED, APPLIED, and DECLINED rows still block with INVITE_BLOCKED: INVITE_NOT_ALLOWED. Inspect `existingApplications` on employer.get_candidate before calling to know which case applies. Returns INVITE_BLOCKED with one of several sub-reasons (JOB_NOT_FOUND, JOB_NOT_PUBLISHED, CANDIDATE_NOT_FOUND, NOT_DISCOVERABLE, INVITE_NOT_ALLOWED) when the invite cannot be created. A missing vacancy and a vacancy owned by another employer both return JOB_NOT_FOUND (you cannot tell them apart — anti-enumeration).
employer.list_applicantsList the live (APPLIED) applicants on one of the employer's jobs. The candidate showcase + interview overallScore/summary are always returned; contact fields are only included when the application is SHORTLISTED or HIRED.
employer.list_invitationsList the pending (INVITED) candidates on one of the employer's jobs — candidates who have been invited but have not yet accepted or declined.
employer.list_jobsList jobs owned by the current employer account.
employer.publish_jobTransition a DRAFT job to PUBLISHED. Returns NOT_FOUND for missing or non-owner jobs and CONFLICT when the job is not in DRAFT.
employer.search_candidates_by_queryFree-form semantic search across discoverable (interviewed) candidates with no job context. The query is embedded and candidates are ranked by semantic similarity — a preliminary search with no per-vacancy fit score (there is no vacancy to fit). For a scored ranking, use employer.search_candidates_for_job with a job id.
employer.search_candidates_for_jobSemantically rank discoverable (interviewed) candidates against one of the employer's own jobs, with a per-candidate fit score AND a white-box explanation. WORKFLOW for finding the best hire: 1) call with tier:'best' to get the strongest candidates (cover the required skills + proven in interview), cascade to tier:'good' then tier:'weak' only if you need more (read tierCounts to decide; paginate within a band via page.hasMore, not page.total); 2) each row carries matchExplanation — the white-box 'why' (the fit score, the skills the candidate PROVED in their interview, what they're missing, and a plain-English rationale) — use it to explain your shortlist on OUR data, not a black box; 3) for the few you shortlist, call employer.get_candidate_evidence(jobId, userId) for the interview facts + Q&A to write a deeper comparative review. Omit tier for the full ranked pool (back-compat). Returns NOT_FOUND when the job is missing / owned by another employer (no existence leak), or NOT_INDEXED / NO_CATEGORIES when the job is not indexed for semantic search yet (re-save / republish, then retry).
employer.set_review_statusUpdate the employer review state on an application (NEW / REVIEWING / SHORTLISTED / REJECTED / HIRED). SHORTLISTED and HIRED unlock the candidate's direct contact in subsequent list_applicants / get_applicant_detail calls. WITHDRAWN applications surface as CONFLICT.
employer.update_jobUpdate the fields of an existing employer job. Accepts a partial whitelist; rawInput and dataSource are not editable (the wrapper auto-flips dataSource to USER_EDITED on every agent update). Non-owner reads return NOT_FOUND.
request_accessUse when someone wants to find work/jobs (candidate) or hire/find candidates (employer) but the authenticated role tools are not usable yet. Explains role-specific onboarding (candidate profile interview, employer key generation), MCP key location, and next steps for both surfaces.

Schemas: list_tools.

Similar MCP servers

Official server.json
{
  "name": "io.github.work0r-ai/workorai",
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
  "remotes": [
    {
      "url": "https://workorai.com/mcp",
      "type": "streamable-http"
    }
  ],
  "version": "0.4.3",
  "repository": {
    "url": "https://github.com/work0r-ai/agent-kit",
    "source": "github"
  },
  "description": "WorkorAI talent marketplace MCP: candidate job search and employer hiring with explainable matching"
}