> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Tools Reference

> Complete list of tools the Alex MCP server exposes, their inputs, and example outputs.

The Alex MCP server is read-only and exposes 16 tools. All tools accept an optional `companyId` argument; if omitted, the call is scoped to the user's primary company. See [Multi-company access](/api-reference/mcp/getting-started#multi-company-access) for when to pass it.

## Discovery

### `listCompanies`

List the Alex companies this token has access to. The LLM should call this first whenever a user has multiple companies, or whenever it needs to translate a company name into a `companyId`.

**Inputs**

* `query` *(string, optional)* — substring to filter company names. Only used by Alex platform admins (who can list across all companies).

**Returns** human-readable list of companies with their IDs.

**Example prompt:** *"Which Alex tenants do I have access to?"*

## Search

### `searchCandidates`

Natural-language candidate search across the talent pool. Backed by the same search infrastructure as Talent Match in `app.alex.com`.

**Inputs**

* `query` *(string, required)* — natural-language description: role, skills, location, experience, education, prior employers, work preferences.
* `limit` *(number, default 10)* — max results.
* `radius` *(number, optional)* — location radius in miles when a location is in the query.
* `recency` *(number, optional)* — only return candidates updated within this many days.
* `status` *(string\[], optional)* + `statusOperator` *(`"includes" | "excludes"`)* — filter by candidate status.
* `companyId` *(string, optional)*.

**Example prompt:** *"Find React developers in Atlanta with 3+ years of experience, updated in the last 30 days."*

### `searchDocumentation`

Searches Alex's product documentation and returns a synthesized answer with citation links. Useful when users ask "how do I...?" rather than asking about their data.

**Inputs**

* `query` *(string, required)* — the user's question, in natural language.

**Example prompt:** *"How do I integrate with Bullhorn?"*

## Jobs and pipeline

### `getJobDetails`

Fetch a single job's configuration plus current pipeline counts and recent sessions.

**Inputs (any one of)**

* `jobId` *(string)* — internal Alex position UUID.
* `externalId` *(string)* — ATS job ID.
* `jobName` *(string)* — fuzzy match on role title.
* `sessionLimit` *(number, default 10)* — recent sessions to include.
* `sessionStatus` *(string, optional)* — filter sessions by status (e.g. `"COMPLETED"`).
* `companyId` *(string, optional)*.

**Example prompt:** *"Tell me about job 73605, including the interview link."*

### `getJobsSummary`

Summary of all jobs (or one user's jobs) with completion rates, average scores, and decision breakdowns.

**Inputs**

* `userId` *(string, optional)* — scope to one user's created jobs.
* `userName` *(string, optional)* — scope by recruiter name (resolved server-side).
* `sinceDate` / `untilDate` *(ISO date strings, optional)* — filter by job creation date.
* `includeClosedJobs` *(boolean, default true)*.
* `status` *(string, optional)* — filter by job status (e.g. `"ACTIVE"`, `"PAUSED"`).
* `limit` *(number, default 50, max 200)* — max jobs returned per call.
* `offset` *(number, default 0)* — pagination offset; combine with `limit` to page through large companies.
* `companyId` *(string, optional)*.

The aggregate counts (`totalJobs`, `openJobs`, `closedJobs`, `activeJobs`, `pausedJobs`, etc.) are always over the full filtered set regardless of `limit`/`offset`. The `jobs` array is the page, and `hasMore` indicates whether more pages exist.

Each job in the `jobs` array now carries both `isOpen` (open vs closed for applications) and `status` (`"ACTIVE"`, `"PAUSED"`, etc.).

**ATS coverage** — output also includes an `atsCoverage` block aggregated from `ats_job_coverage_weekly` and filtered by the same date window:

* `supported` — `true` only when the company's ATS has weekly coverage rows. Bullhorn-only today; non-Bullhorn customers get `supported: false` and null fields.
* `atsType` — the ATS type for the rolled-up rows.
* `atsJobsCreated` — total active jobs created in the customer's ATS during the window.
* `alexJobsImported` — how many of those landed in Alex.
* `alexJobsWithInterviews` — Alex jobs that actually ran at least one interview.
* `coveragePercent` — `alexJobsImported / atsJobsCreated`.
* `interviewPercent` — `alexJobsWithInterviews / atsJobsCreated`.
* `weeksCovered` — number of weekly rows the rollup spans.

**Example prompts**

* *"Give me a summary of Sarah's jobs from this quarter."*
* *"Show me the next 50 active jobs."* (paginates with `limit: 50`, `status: "ACTIVE"`)
* *"How many of Acme's ATS jobs are running through Alex this year?"*

### `getCandidatePipelineStats`

Candidate counts grouped by interview status, decision, or as a funnel view.

**Inputs**

* `metric` *(`"byStatus" | "byDecision" | "funnel"`, required)*.
* `positionId` *(string, optional)* — scope to one job.
* `sinceDate` / `untilDate` *(optional)*.
* `companyId` *(string, optional)*.

**Example prompt:** *"Show me the candidate funnel for the Senior PM role."*

### `getPositionStats`

Position-level metrics: open positions count, total positions, candidates per position.

**Inputs**

* `metric` *(`"openPositions" | "totalPositions" | "candidatesPerPosition"`, required)*.
* `sinceDate` / `untilDate` *(optional)*.
* `status` *(string, optional)* — filter by position status (e.g. `"ACTIVE"`).
* `companyId` *(string, optional)*.

**Example prompt:** *"How many open positions do we have right now?"*

## Interviews

### `getInterviewGuideDetails`

The interview guide (questions, criteria, intro/outro, learning objectives) for a position. Useful for asking "how should I improve my questions?" or "what's in my guide?"

**Inputs** — none required (uses the current job context if available, otherwise pass `companyId` and the LLM may need a position context first).

**Example prompt:** *"Are there gaps in our interview questions for backend engineers?"*

### `getInterviewStats`

Interview-volume metrics: completed interviews, completed candidates, average duration, breakdowns by interviewer, by status, or by medium type (video vs phone vs SMS), top candidates.

**Inputs**

* `metric` *(`"completedCandidates" | "completedInterviews" | "averageDuration" | "byInterviewer" | "byStatus" | "byMediumType" | "topCandidates"`, required)*.
* `positionId` *(string, optional)* — auto-scopes to current job context if available.
* `companyWide` *(boolean, optional)* — explicitly query across all jobs.
* `sinceDate` / `untilDate` *(optional)*.
* `limit` *(number, optional, max 100)* — for `topCandidates`.
* `decision` *(`("NONE" | "ACCEPTED" | "REJECTED" | "SUBMITTED" | "PLACED" | "HIRED")[]`, optional)* — for `topCandidates`.
* `mediumType` *(`("VIDEO" | "PHONE" | "EXTERNAL" | "BOTH" | "WHATSAPP" | "PHONE_SMS" | "SMS" | "NONE")[]`, optional)* — filter any metric by interview medium. For a full breakdown, use `metric: "byMediumType"` instead.
* `companyId` *(string, optional)*.

**Example prompts:**

* *"What's the top 10 highest-scoring candidates for the Atlanta Sales Lead role?"*
* *"How many phone vs video interviews did we run last quarter?"* (`metric: "byMediumType"`)
* *"How many phone interviews has AlexAI completed this month?"* (`metric: "completedInterviews"`, `mediumType: ["PHONE"]`)

### `getInterviewTimeOfDayBreakdown`

Hour-of-day distribution of completed interviews plus a headline after-hours coverage % — answers "what share of interviews happen outside business hours?".

**Inputs**

* `sinceDate` / `untilDate` *(ISO date, optional)* — defaults to the last 30 days.
* `timezone` *(IANA name, optional, default `America/New_York`)* — buckets are computed in this zone; Postgres handles DST automatically.
* `businessHoursStart` / `businessHoursEnd` *(0–23 / 1–24, optional, defaults `9` and `18`, end-exclusive)*.
* `positionId` *(string, optional)* — auto-scopes to current job context if available.
* `companyWide` *(boolean, optional)*.
* `companyId` *(string, optional)*.

**Returns** 24 hour buckets plus `inHoursCount` / `afterHoursCount` / `afterHoursPercent`.

**Example prompt:** *"What share of Acme's interviews happened after 6pm last quarter?"*

### `getCandidateReport`

Look up a candidate's interview report by name, email, or position. Returns one or more candidate records with deep links into Alex, plus per-session ratings, reviews, decision, integrity flags, and invite-to-completion timing.

**Inputs (one of name or email required)**

* `name` *(string, optional)* — partial match, order-agnostic (`"liu chao"` matches `"Chao Liu"`).
* `email` *(string, optional)* — exact match.
* `jobName` *(string, optional)* — narrow to a specific job title.
* `limit` *(number, default 5)* — max sessions per candidate.
* `status` *(string, optional)* — filter sessions by status.
* `companyId` *(string, optional)*.

**Per-session output fields**

* `decision` — `Decision` enum (`NONE | ACCEPTED | REJECTED | SUBMITTED | PLACED | HIRED`).
* `recruiterRating`, `alexRating` — `Session.feedbackScore` and `Session.alex_feedback_score` (numeric, nullable).
* `recruiterReview`, `alexReview` — `Session.feedback` and `Session.alex_feedback` text (nullable).
* `notes` — up to 10 most-recent recruiter notes attached to the report (`report_note.content`, ordered desc).
* `inviteToCompletionSeconds` — time between invite (`Session.dateCreated`) and completion (`Session.time_completed`); `null` if the session isn't completed.
* `fraudHighRisk` — `true` when the candidate's `overall_verification_score` is below 50 (mirrors `getFraudVerificationStats`); `null` when `fraud_detection_enabled` is off.
* `fraudVerificationScore` — raw `overall_verification_score` (0–100); `null` when fraud detection is off.
* `cheatingFlagged` — `true` when `base_evaluation.cheating_score >= Company.cheat_threshold`; `null` when `gemini_cheat_detection_enabled` is off.
* `cheatingScore` — raw `cheating_score`; `null` when cheating detection is off.

**Example prompts**

* *"Show me Anthony's interview reports."*
* *"Pull last quarter's hires for Acme — include their ratings and reviews so I can highlight a few in the renewal recap."*

## Integrity (cheating, fraud, identity)

Three distinct concepts in Alex, each with their own tool. Don't confuse them:

| Concept                   | What it answers                                        | Backed by                                                                  | Tool                           |
| ------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------- | ------------------------------ |
| **Cheating**              | Was the candidate cheating *during* the interview?     | `base_evaluation` (eye/head, voice, transcript, multi-person)              | `getCheatingDetectionStats`    |
| **Fraud**                 | Are the candidate's *identity signals* legit?          | `candidate_verification` (SEON: email/phone/IP/LinkedIn/digital footprint) | `getFraudVerificationStats`    |
| **Identity verification** | Did the candidate verify a real govt ID with a selfie? | `identity_verification` (Stripe document + liveness)                       | `getIdentityVerificationStats` |

For a single candidate, `getCandidateVerification` returns all three at once.

### `getCheatingDetectionStats`

Cheating-detection metrics — flagged candidates, flagged interviews, percentages, per-job rates, and individual flagged sessions with full sub-score breakdown (eye/head tracking, voice analysis, transcript analysis, multi-person detection).

**Inputs**

* `metric` *(`"flaggedCandidates" | "flaggedInterviews" | "flaggedPercentage" | "byJob" | "flaggedDetails"`, required)*.
* `positionId` *(string, optional)*.
* `companyWide` *(boolean, optional)*.
* `sortByJobMetric` *(`"rate" | "count"`, optional, for `byJob`)*.
* `sinceDate` / `untilDate` *(optional)*.
* `companyId` *(string, optional)*.

Requires `fraud_detection_enabled` or `gemini_cheat_detection_enabled` on the company.

**Example prompt:** *"Which jobs have the highest cheating rate this month?"*

### `getFraudVerificationStats`

Identity-signal metrics from SEON — disposable emails, VOIP phones, VPN/proxy IPs, thin LinkedIn, digital-footprint anomalies. Aggregates over `candidate_verification`.

**Inputs**

* `metric` *(`"flaggedCandidates" | "riskBreakdown" | "flaggedDetails"`, required)*.
* `threshold` *(number, optional, default 50)* — `overall_verification_score` below this counts as flagged.
* `sinceDate` / `untilDate` *(optional)*.
* `limit` *(number, optional, max 50)* — for `flaggedDetails`. Default 20.
* `companyId` *(string, optional)*.

Requires `fraud_detection_enabled` on the company.

**Example prompt:** *"How many candidates this month had VPN-flagged IPs or disposable emails?"*

### `getIdentityVerificationStats`

Stripe document + selfie verification metrics. Aggregates over `identity_verification`.

**Inputs**

* `metric` *(`"statusBreakdown" | "verificationRate" | "failureReasons" | "failedDetails"`, required)*.
* `sinceDate` / `untilDate` *(optional)*.
* `limit` *(number, optional, max 100)* — for `failedDetails`. Default 20.
* `companyId` *(string, optional)*.

Requires `enable_identity_verification` on the relevant jobs.

**Example prompt:** *"What's our ID-verification completion rate this month, and why are candidates failing?"*

### `getCandidateVerification`

The full integrity picture for ONE candidate (or session): cheating sub-scores, fraud signals, identity-verification status. The right tool for "is this candidate legit?" or "why was X flagged?"

**Inputs (any one of)**

* `sessionId` *(string)* — most precise.
* `candidateId` *(string)* — returns the most recent session's verification.
* `email` *(string)* — exact match.
* `name` *(string)* — partial / order-agnostic.
* `companyId` *(string, optional)*.

Returns nullable blocks for each integrity surface depending on what data exists for that candidate (e.g. cheating block is null if no `base_evaluation` row exists; identity block is null if Stripe ID verification wasn't run for that job).

**Example prompt:** *"Show me everything we know about [jane@acme.com](mailto:jane@acme.com)'s identity."*

## Account

### `getCompanyFeatures`

Which Alex features are enabled for the current company (fraud detection, resume fit, talent match, coordinator, WhatsApp, custom logo, etc.). Useful for the LLM to know whether to suggest a feature.

**Inputs**

* `featureName` *(string, optional)* — check one specific feature; otherwise returns all.
* `companyId` *(string, optional)*.

**Example prompt:** *"Is fraud detection enabled for our company?"*

### `getAtsConnection`

Inspect how the company's ATS integration is configured: which ATS is set, whether credentials are populated (field-presence only — values are never returned), Kombo aggregator status, the auto job-import flag, and whether decision-stage sync mappings are configured.

Use this for "is this customer connected to *X*?", "what ATS does *Y* use?", "why isn't sync working?", "is auto job import on?".

**Inputs**

* `companyId` *(string, optional)*.

**Returns** structured fields:

* `atsType` + `atsLabel` (e.g. `"BULLHORN"` / `"Bullhorn"`).
* `connected` *(boolean)* — at least one expected credential field for the configured ATS is populated.
* `expectedCredentialFields` / `configuredCredentialFields` / `missingCredentialFields` — credential field **names** only.
* `kombo` — `{ integrationId, status, configured }` for the Kombo aggregator (used for Avature, SAP SuccessFactors, SmartRecruiters, and as an alt path for some other ATSes).
* `autoJobImportEnabled` *(boolean)*.
* `syncFieldsConfigured` *(boolean)* — at least one decision-stage sync field+value pair (hired/placed/rejected/shortlisted/submitted/no-decision) is set.

The tool is read-only and never returns API keys, tokens, passwords, or any other secret values.

**Example prompt:** *"Is Acme connected to Bullhorn, and is auto job import on?"*

### `listTemplates`

Lists templates the company has defined and reports adoption per type — "% of active jobs using a template". Covers two template surfaces: resume-fit (drives resume scoring) and interview-guide (structured guides). Each entry carries an `adoptionMethod` field describing how the number was computed, and the two differ in how much they can be trusted:

* **`resumeFit` is exact.** The template id survives on the job (`Interviewer.resume_fit_template_id`), so both the adoption rate and the per-template job counts are real.
* **`interviewGuide` is a proxy.** A guide template copies its content into the job and drops the template id, leaving only a `source='template'` mark on the copied questions and grading criteria — so adoption is counted as "the job's guide has at least one template-marked question **or** grading criterion". The bucket-level rate is meaningful; per-template `jobCount` is always 0 and is not usage. The mark is also copied when a guide is duplicated, so it can be present on a job that nobody created from a template. Treat it as company-level adoption, not as a per-job fact.

<Note>
  This previously reported two separate interview-guide numbers — one for grading criteria, one for question text. They have been merged into a single `interviewGuide` figure: the split implied a precision the underlying data doesn't have, the two barely overlapped, and each half alone undercut real adoption. The merged number reads higher than the old criteria-only one; that is the same population counted correctly, not growth.
</Note>

**Inputs**

* `templateType` *(`"resumeFit" | "interviewGuide"`, optional)* — drill into a single type; otherwise returns both.
* `topN` *(number, optional, default 10, max 50)* — top templates per type to include.
* `includeUnused` *(boolean, optional, default false)* — only meaningful for `resumeFit`; `interviewGuide` always lists templates alphabetically since per-template usage is unattributable.
* `companyId` *(string, optional)*.

**Returns** per type: `totalTemplates`, `jobsUsingType`, `adoptionPercent`, `adoptionMethod`, and a `topTemplates` array. Active job = `Interviewer.status='ACTIVE' AND is_deleted=false` (matches `getJobsSummary` and `getPositionStats`).

**Example prompts**

* *"What % of Acme's active jobs use a resume-fit template?"*
* *"Which interview-guide templates does Acme have set up?"*

## Calling a tool directly

If you're building a non-LLM client and want to call a tool over JSON-RPC:

```bash theme={null}
curl -X POST https://mcp.alex.com/mcp \
  -H "Authorization: Bearer YOUR_ALEX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "searchCandidates",
      "arguments": { "query": "Senior backend engineer in Boston", "limit": 5 }
    }
  }'
```

The response is a JSON-RPC envelope; tool output is in `result.content[0].text` as a human-readable string. (Internally each tool returns structured data; the server formats it for LLM and human consumption.)
