MCP Server

Evidal exposes every hiring-manager action as a tool over the Model Context Protocol. Point Claude Code, Cursor, or any MCP-aware client at the server URL and your agent can list roles, review candidates, and record decisions directly. This server requires a Bearer API key and an existing company.

Connect

Server URL: https://app.evidal.ai/api/mcp/{company}/hiring-manager

claude mcp add \
  --transport http \
  evidal-<company> \
  https://app.evidal.ai/api/mcp/<company>/hiring-manager \
  --header "Authorization: Bearer $EVIDAL_API_KEY"

The company-scoped skill.md ( /api/v1/company/{slug}/hiring-manager-skill.md) has the same snippet in machine-readable form.

Tools (29)

list_roles

List roles for the company (proxies GET /api/v1/company/[slug]/roles). Returns public role fields only; no internal weight_profile details.

Parameters

include_inactive ?
boolean

* required, ? optional

get_role

Fetch a single role by UUID or slug (proxies GET /api/v1/company/[slug]/roles/[roleId]).

Parameters

role_id_or_slug *
string

* required, ? optional

create_role

destructive

Create a new role for the company. IMPORTANT: roles are created as DRAFTS by default — they are NOT visible to candidates until you call publish_role. Before calling this tool, present the role details (title, level, description, location, compensation) to the user and get their explicit confirmation. After creation, show the user the draft role and ask "Ready to publish this role?" before calling publish_role. DESTRUCTIVE: creates a persistent row in the roles table; use idempotency_key to safely retry on network errors. EVI-366: `seniority_level` accepts one of L1_Assisted_Contributor | L2_Independent_Contributor | L3_End_to_End_Owner | L4_System_Domain_Lead | L5_Strategic_Principal — invalid values are rejected. `requirements[]` accepts entries shaped like { name, importance, type|nature, notes? } where importance is "Core"|"Important"|"Informational" (or lowercase) and type/nature is "Foundational"|"Emerging"|"Contextual" (or lowercase) — recommendation-schema shapes from `recommend_role` pipe directly in. `archetype_slug` must be one of the six canonical slugs (call `list_archetypes` to discover them); unknown slugs are rejected. `weight_adjustments` keys must be a subset of [grounding, causal, tradeoff, ownership, robustness]. EVI-386: `ai_assistance_check` accepts one of "auto" | "required" | "skip" — controls whether the onboarding bot coaches Step 3B (AI Assistance Evidence). Default "auto" coaches when the JD or candidate tools mention an AI keyword.

Parameters

title *
string
level *
string
ai_native_required *
boolean
archetype_slug ?
string
seniority_level ?
enum("L1_Assisted_Contributor" | "L2_Independent_Contributor" | "L3_End_to_End_Owner" | "L4_System_Domain_Lead" | "L5_Strategic_Principal")
description ?
string
location ?
string
compensation ?
string
must_haves ?
array<string>
nice_to_haves ?
array<string>
requirements ?
array<object>
custom_notes ?
string
weight_adjustments ?
object
ai_assistance_check ?
enum("auto" | "required" | "skip")
idempotency_key ?
string

* required, ? optional

update_role

destructive

Patch role fields (proxies PATCH /api/v1/company/[slug]/roles/[roleId]). Accepted fields: title, description, level, seniority_level, ai_native_required, archetype_slug, weight_adjustments, must_haves, nice_to_haves, requirements, custom_notes, location, compensation, ai_assistance_check. DESTRUCTIVE: mutates a persistent row; input shape is { role_id_or_slug, patch: {...} } — patch fields are merged, omitted fields are left unchanged. EVI-366: same enum and normalization rules as `create_role` apply to `seniority_level`, `requirements`, `archetype_slug`, and `weight_adjustments` keys. EVI-386: `ai_assistance_check` accepts one of "auto" | "required" | "skip" — see `create_role`.

Parameters

role_id_or_slug *
string
patch *
object

* required, ? optional

deactivate_role

destructive

Set role.active = false (proxies POST /api/v1/company/[slug]/roles/[roleId]/deactivate). DESTRUCTIVE: the role stops accepting applications immediately.

Parameters

role_id_or_slug *
string

* required, ? optional

publish_role

destructive

Publish a draft role so it becomes visible to candidates. Only call this AFTER the user has reviewed and confirmed the role details. Roles created via create_role start as drafts (active: false) — this tool flips them to active.

Parameters

role_id_or_slug *
string

* required, ? optional

list_candidates

Paginated candidate list with scores (proxies GET /api/v1/company/[slug]/candidates). Each row includes `lead_unlocked: boolean` indicating whether the candidate has been lead-unlocked for this company. Pass `unlocked_only: true` to restrict results to unlocked candidates. Never returns contact_email or display_name — PII is gated behind the Stripe unlock-lead flow and not exposed via MCP.

Parameters

limit ?
integer
offset ?
integer
unlocked_only ?
boolean

* required, ? optional

get_candidate

Fetch a single candidate (proxies GET /api/v1/company/[slug]/candidates/[id]). Response includes `lead_unlocked: boolean` and `lead_unlocked_at: string|null` (ISO timestamp of the $100 lead-unlock payment, or null if not yet unlocked). PII gate (EVI-198): when `lead_unlocked=true`, the response ALSO includes `display_name` and `contact_email`. When not unlocked, those keys are ABSENT from the response. One `candidate.pii_read` audit row is emitted per PII-returning response. The `score` object (null until the v4 evaluation completes) carries: `total` (0-100), `normalized_percentile`, `dimensions` (the five named quality dimensions, each 0-100: `grounding`, `causal_reasoning`, `tradeoff_awareness`, `ownership_integrity`, `robustness`), `dimension_levels` (per-dimension object: `final` is the authoritative guardrail-reconciled band weak|moderate|strong, `level` is the un-capped natural band, `numeric_0_to_1` the 0-1 basis), `signal_scores` (per-lens evidence map the dimensions roll up from), `guardrail_overrides` (which dimensions a guardrail capped down and why — present only when a cap fired), `flagged` (boolean) + `flag_reasons` (string[] — the human-readable caps/concerns), `score_narrative` (plain-English summary; on a capped row it is prefixed with a "Final Evidal score: N/100." reconciliation preamble that names the fired caps), and `scored_at`. These score sub-fields are NOT candidate PII — they are company-visible regardless of lead-unlock state.

Parameters

candidate_agent_id *
string (uuid)

* required, ? optional

record_decision

destructive

Record a hiring decision for a candidate (proxies POST /api/v1/company/[slug]/candidates/[id]/decision). Allowed values: advance, reject, hold. The decision is timestamped server-side. DESTRUCTIVE: persists a decision row that drives downstream notifications (Slack, Lever sync); confirm with the user before invoking.

Parameters

candidate_agent_id *
string (uuid)
decision *
enum("advance" | "reject" | "hold")

* required, ? optional

configure_slack

destructive

Configure Slack notifications (proxies PATCH /api/v1/company/[slug]/slack/configure). Requires Slack to already be connected via the dashboard OAuth flow. DESTRUCTIVE: overwrites the company-wide notification channel and threshold; PATCH semantics apply only to the fields you send.

Parameters

channel_id *
string
channel_name *
string
notify_mode *
enum("all" | "threshold")
score_threshold ?
number

* required, ? optional

rotate_api_key

destructive

Immediately rotate the company API key (proxies POST /api/v1/company/[slug]/api-key/rotate). DESTRUCTIVE: invalidates the current API key; this MCP session will need to re-authenticate with the new key on subsequent calls. Response contains the new raw key EXACTLY ONCE — the client must store it immediately.

No parameters.

check_dns

Runs a live CNAME lookup and writes dns_verified=true to the company record on success (proxies GET /api/v1/company/[slug]/dns-status). Call this after adding your CNAME record to confirm it propagated. Returns { dns_verified, cname_found, points_to_evidal, expected_cname, last_checked } and emits an audit event on each call.

No parameters.

update_company

destructive

Patch company-level fields (proxies PATCH /api/v1/company/[slug]). Allowed fields: name, notification_email, domain, logo_url. Slug and secrets cannot be updated via this path. logo_url must be https and point to an image (SVG not allowed). After changing domain, dns_verified resets to false — call check_dns once your new CNAME has propagated. DESTRUCTIVE: mutates the company row; input shape is { patch: {...} } — patch fields are merged, omitted fields are left unchanged.

Parameters

patch *
object

* required, ? optional

start_checkout

destructive

Start a Stripe Checkout session for a role plan (proxies POST /api/v1/payment/stripe/checkout). Returns { checkout_url, session_id }. The checkout_url must be opened in a browser — it cannot be completed programmatically. Plans: full_access = $500 unlimited evals, pay_per_eval = $10/eval, lead_unlock = $100 per-candidate PII unlock. DESTRUCTIVE: opens a billable Stripe session; pass an idempotency_key to safely retry on network errors.

Parameters

plan *
enum("full_access" | "pay_per_eval" | "lead_unlock")full_access = $500 unlimited evals | pay_per_eval = $10/eval | lead_unlock = $100 per-candidate PII unlock
role_id ?
string (uuid)Required for full_access and pay_per_eval
candidate_agent_id ?
string (uuid)Required for lead_unlock
idempotency_key ?
string

* required, ? optional

get_billing_status

Return per-role billing state + lead-unlock summary + active company-wide subscription (proxies GET /api/v1/company/[slug]/billing/status). Response: { roles: [{ role_id, slug, title, role_status, eval_count, trial_remaining, needs_payment, subscription_covered, subscription_over_cap }], lead_unlocks_count: number, subscription: { plan: "starter"|"growth", status, current_period_end, role_cap, trial_end, cancel_at_period_end } | null }. `subscription` is non-null when the company is on a monthly plan; `subscription_covered` is true for roles within `role_cap` (oldest-first), false otherwise. `lead_unlocks_count` is the total number of $100 candidate lead unlocks ever paid for this company (not paginated). Active, non-archived roles only.

No parameters.

activate_role

destructive

Activate a role by charging the stored payment method directly (no browser redirect). Returns { status: 'activated' } on success or { status: 'requires_setup', checkout_url } if no payment method is on file or 3DS is required — open the checkout_url in a browser to save a card first. Plans: full_access = $500 unlimited evals, pay_per_eval = $10/eval. DESTRUCTIVE: bills the company's stored card on success; pass an idempotency_key to safely retry on network errors.

Parameters

role_id_or_slug *
string
plan *
enum("full_access" | "pay_per_eval")
idempotency_key ?
string

* required, ? optional

invite_member

destructive

Invite a teammate to the company (proxies POST /api/v1/company/[slug]/members/invite). Sends an invite email with a secure token. `role` = owner|admin|viewer. API-key callers have implicit owner scope and may invite at any role. Idempotent: if the email already has a pending invite or active membership, returns ok without re-sending. DESTRUCTIVE: grants persistent access to company data on accept; confirm with the user before invoking.

Parameters

email *
string (email)
role *
enum("owner" | "admin" | "viewer")

* required, ? optional

list_members

Paginated list of company hiring-team members + pending invites (proxies GET /api/v1/company/[slug]/members). Returns `{ members: [{ id, user_id, email, role, status, invited_at, accepted_at }], total, limit, offset }`. `status` is `active` (accepted) or `pending` (invite outstanding). NOTE: `email` here is the hiring-team teammate email — NOT candidate PII. Treat the returned roster as confidential company-internal data. Default limit 50, max 100.

Parameters

limit ?
integer
offset ?
integer

* required, ? optional

update_member_role

destructive

Change a teammate's role (proxies POST /api/v1/company/[slug]/members/[user_id]/role). `role` = owner|admin|viewer. Guarded: cannot demote the last owner (returns 400). DESTRUCTIVE: changes a teammate's access scope; confirm with the user before invoking.

Parameters

user_id *
string (uuid)
role *
enum("owner" | "admin" | "viewer")

* required, ? optional

remove_member

destructive

Remove a teammate from the company (proxies DELETE /api/v1/company/[slug]/members/[user_id]). DESTRUCTIVE: revokes the member immediately. Guarded: cannot remove the last owner (returns 400).

Parameters

user_id *
string (uuid)

* required, ? optional

list_unlocks

Paginated list of $100 lead unlocks for the company (proxies GET /api/v1/company/[slug]/unlocks). Admin+ only for session callers (financial data); API-key callers have implicit owner scope. Returns { unlocks: [{ candidate_agent_id, candidate_agent_id_short, amount_cents, unlocked_at, stripe_payment_intent_id }], pagination: { limit, offset, total } }. The stripe_payment_intent_id is the opaque Stripe identifier (non-secret; cannot be acted on without the platform secret key).

Parameters

limit ?
integer
offset ?
integer

* required, ? optional

import_from_lever

Import a role from a Lever posting URL (Tier 1, no API key needed). Accepts a full `https://jobs.lever.co/acme/abc123` URL or a bare `acme/abc123` pair. Returns a pre-filled role object matching the `create_role` input schema. IMPORTANT: do NOT call `create_role` automatically — present the extracted fields to the user for review and get explicit confirmation before creating the role.

Parameters

lever_url *
string

* required, ? optional

connect_lever

Connect the company to Lever by storing an API key (proxies POST /lever/connect). The key is stored encrypted and never echoed back. Requires `lever_api_key` and `lever_subdomain`.

Parameters

lever_api_key *
string
lever_subdomain *
string

* required, ? optional

disconnect_lever

destructive

Disconnect Lever integration (proxies POST /lever/disconnect). Deletes the stored API key and clears connection metadata. DESTRUCTIVE.

No parameters.

import_lever_postings

List open Lever postings for the connected company (proxies GET /lever/postings). Returns posting ID, title, department, location, commitment, and team. Requires Lever to be connected first via `connect_lever`.

No parameters.

sync_candidate_to_lever

destructive

Push an Evidal candidate (email, name, score narrative, score breakdown) into Lever as an opportunity + note on the role's linked posting (proxies POST /api/v1/company/[slug]/lever/sync/[candidateAgentId]). DESTRUCTIVE: writes an opportunity and a note to your Lever account. Dedupes by email, so a second call writes a second note on the same opportunity rather than duplicating the candidate. Requires: Lever connected, the candidate's role linked via `link_role_to_lever_posting`, and the candidate has a `contact_email` on file. Owner/admin only.

Parameters

candidate_agent_id *
string (uuid)

* required, ? optional

recommend_role

Generate an AI role recommendation given a title and description (proxies POST /api/v1/company/[slug]/roles/recommend). Returns { archetype: { recommended, confidence, alternatives, explanation }, seniority: { recommended, confidence, explanation }, dimension_weights: { grounding, causal, tradeoff, ownership, robustness }, requirements: [{ name, importance, nature, reason, usage_context }] }. The output is designed to be piped directly into `create_role` after letting the user confirm — `create_role` accepts both `nature` (returned here) and `type` (DB-canonical) and both titlecase and lowercase importance values. Rate limit: 20 calls per company per 5-minute window (shared with the dashboard).

Parameters

role_title *
string
role_description *
string
location ?
string
compensation ?
string

* required, ? optional

list_archetypes

List the six canonical role archetypes (slug, name, description, default_weights). Returns the static catalog from `lib/archetypes.ts` — call this before `create_role` to discover the valid `archetype_slug` values. Read-only.

No parameters.