Skip to main content
Glama
AI Interview Agents MCP Server

AI Interview Agents MCP Server

by dpkdhingra91

AI Interview Agents — MCP Server

aiia-mcp-server MCP server

The official Model Context Protocol server for AI Interview Agents, a voice-AI hiring platform. It lets any MCP client (Claude, Cursor, custom agents) drive the recruiting workflow in natural language: create roles, screen CVs, schedule and manage candidate interviews, and read back scored reports with transcripts.

The live voice interview itself is out of scope here (that runs on a separate real-time service). This server triggers interviews and reads back the results.

What it exposes

  • Roles — list your roles, or create one from a job description.

  • Candidates & CVs — parse dropped CV/PDF files, normalize messy pasted candidate lists (names, emails, phone numbers, experience, skills), and reassign candidates between roles.

  • Interviews — schedule interviews (with a dry_run mode that normalizes and previews without sending anything or consuming quota), reschedule, cancel, and send reminders.

  • Reports — read the candidate pipeline and pull back scored interview reports with transcripts.

Related MCP server: Spec-Driven IntelliMatch / iSuite MCP

The server runs remotely with OAuth sign-in. Point any Streamable-HTTP MCP client at:

{
  "mcpServers": {
    "ai-interview-agents": {
      "type": "streamable-http",
      "url": "https://mcp.aiinterviewagents.com/mcp"
    }
  }
}

Your client discovers the auth flow automatically via /.well-known/oauth-protected-resource and signs you in with your AI Interview Agents account. It is also listed in the official MCP Registry as com.aiinterviewagents/interviews.

Run it locally (stdio)

pip install -e .
export AIIA_FIREBASE_TOKEN="<your token>"   # or: aiia-mcp login
aiia-mcp

AIIA_BACKEND_URL overrides the API base if you are pointing at your own deployment.

How auth works

The server holds no account secrets. Each request carries the user's own Firebase ID token, forwarded to the backend as a bearer token — the same trust path as the web app. For the hosted transport, an OAuth layer (dynamic client registration + PKCE) bridges MCP clients to Firebase sign-in.

Development

pip install -e ".[dev]"
pytest
ruff check .

License

MIT — see LICENSE.

Available Tools

18 tools
add_screening_candidatesA

Attach parsed CVs to a screening as candidates, ready to be scored.

    Pass the rows returned by parse_cvs straight through — each
    {"identity": {...}, "details": {...}} row is reshaped automatically into
    what the backend needs. Already-flat dicts ({firstName, lastName, email,
    summary, parsedResumeData, number, location}) also work.

    Email is NOT required here — screening scores a CV, it doesn't message
    anyone. CVs with no email are kept and given an unroutable placeholder so
    they still rank; the response's 'withoutEmail' count tells you how many.
    Those rows CANNOT be scheduled later until a real email is added — surface
    that number to the user.

    Returns {savedCount, skippedEmpty (CVs with no readable content),
    withoutEmail, candidates}. After this, call run_screening(role_id).
    

Inputs: role_id (required), candidates (required).

cancel_interviewA
Destructive

Cancel a scheduled interview. Destructive — meeting and candidate rows are deleted.

    ASK BEFORE CALLING if the user said "that interview" / "Priya's"
    / anything ambiguous and you don't have a specific meeting_id from
    a prior list_meetings result. Call list_meetings first to surface
    candidates, then confirm with the user which meeting they mean.
    Cancelling the wrong meeting cannot be undone in-band — the
    candidate has to be rescheduled from scratch.
    

Inputs: meeting_id (required).

create_screening_roleA

Create a Screening — a role to rank CVs against a JD before spending any interview credit. Returns {"roleId": , ...}; keep that roleId for the rest of the flow.

    THE SCREENING FLOW (call these tools in order):
    1. create_screening_role  ← you are here
    2. parse_cvs(files=...)    — extract candidates from resume files
    3. add_screening_candidates(role_id, candidates=<parse_cvs rows>)
    4. run_screening(role_id)  — starts async CV-vs-JD scoring
    5. get_screening_results(role_id) — poll until scored, read the ranking
    6. schedule_screened_candidates(role_id, candidate_ids) — invite the good ones

    FIELDS:
    - name: the screening / role title (required, e.g. "Senior Backend Engineer").
    - job_description: paste the full JD text if you have it — it drives the
      score most. Optional but strongly recommended.
    - must_have_skills vs nice_to_have_skills: skills the role weighs heavily
      vs would-be-nice. Neither AUTO-REJECTS anyone — they shape the LLM
      score and the matched/related/missing breakdown.
    - experience: free text, e.g. "5+ years".
    - location + work_mode: work_mode is 'remote', 'onsite', or 'hybrid'.
      Location is a soft signal only (a remote role ignores it); nobody is
      dropped for being in the wrong city.
    - additional_requirements: free text the model folds into scoring, e.g.
      "valid work visa", "bachelor's degree", "AWS certified".
    - language: 'en', 'hi', or 'ar'. company_name: optional.

    This creates a role record (screening_enabled). If the user already has a
    screening for this opening, call list_screening_roles first and reuse its
    roleId instead of making a duplicate.
    

Inputs: name (required), language, location, work_mode, +6 more.

generate_questionsA
Read-only

Generate interview questions using AI. Read-only — does not create a role or schedule anything. Use to preview what the bot will ask before committing to schedule_interview.

    interview_type: 'screening' or 'technical'.
    language: 'en', 'hi', 'ar'.
    

Inputs: position (required), interview_type (required), required_skills (required), duration, +4 more.

get_meetingA
Read-only

Full details of a single meeting including transcript and feedback.

    meeting_id accepts the UUID or the integer primary key.
    

Inputs: meeting_id (required).

get_pipelineA
Read-only

Pipeline view (invited / scheduled / completed / no-show) for a role.

Use to answer 'how is the hiring going for this role'.

Inputs: role_id (required).

get_reportA
Read-only

Scored interview report: analysisSummary, strengths, weaknesses, recommendations, averagePercentage, nextSteps.

    Returns status=processing if the report hasn't generated yet.
    

Inputs: meeting_id (required).

get_screening_resultsA
Read-only

Read a screening's ranked candidates (the poll for run_screening).

    Returns {roleId, count, role:{name, mustHaveSkills, niceToHaveSkills,
    location, workMode, additionalRequirements}, candidates:[...]}, candidates
    sorted best-first by screeningScore. Each candidate:
    {id, name, email, hasEmail, phone, position, experienceYears, location,
    locationFit ('remote'|'in_region'|'other'|null), screeningScore (0-100),
    screeningVerdict ('strong'|'possible'|'weak'|'reject'), screeningStatus,
    reason, matchedSkills, relatedSkills, missingSkills, status, screenedAt}.

    DONE-SIGNALS (scoring is async): a candidate is finished when
    screeningStatus is 'scored', 'hard_filtered', 'overridden', or 'error',
    and still pending at 'not_screened'. The whole screening is done when
    every row has left 'not_screened' (or screenedCount == candidateCount in
    list_screening_roles). If rows are still 'not_screened', wait and call again.

    To schedule, take the 'id' of each candidate you want and pass them to
    schedule_screened_candidates. Skip rows where hasEmail is false — they
    need a real email first.

    Results are capped at `limit` (default 100, top by score) and each
    'reason' is truncated to keep the response small; raise limit if you
    need more rows.
    

Inputs: role_id (required), limit.

list_meetingsA
Read-only

List interviews for the authenticated HR user.

    Optional filters:
      - role_id, candidate_id — narrow to a specific role / candidate
      - status — one of: Scheduled, Completed, Cancelled, Expired
      - scheduled_after — ISO datetime or YYYY-MM-DD; only meetings
        scheduled on/after this point. Use for "last week" / "this
        month" queries (compute the cutoff client-side).

    Returns paginated meeting records with totals for completed and
    cancelled. The 'role_name' field on each row is the easiest way to
    surface 'recent roles' when the user hasn't named one yet.

    Rows are summaries: transcripts and full report payloads are
    stripped to keep responses small — use get_meeting / get_report
    for one meeting's full detail.
    

Inputs: page, size, status, role_id, +2 more.

list_rolesA
Read-only

List the authenticated HR user's roles (job postings / openings).

    Use this whenever the user hasn't named a role and you need to ask
    "which role should I schedule against?" — surface 2-3 recent ones
    from the response by 'name' / 'position'. Also use for queries like
    "what roles do I have" or "what openings am I hiring for".

    Returns: {"data": [{roleId, name, position, experience, skills,
    evaluationFocus, interviewType, jobDescription, language, ...}]}.
    

Inputs: no named parameters.

list_screening_rolesA
Read-only

List the authenticated user's screenings (CV-screening roles).

    Use this to find an existing screening's roleId (so you don't create a
    duplicate), or to report progress. Each row carries:
    {roleId, name, position, location, workMode, candidateCount,
    screenedCount, topMatches (score >= 60), scheduledCount, createdAt}.

    screenedCount == candidateCount means scoring has finished for that
    screening (the role-level done-signal for run_screening). topMatches is
    the count worth scheduling.
    

Inputs: no named parameters.

parse_cvsA

Extract candidate identity (name, email, phone) from CV/resume files (PDF or DOCX, up to 20 per call). Runs OCR fallback for image-based PDFs. Returns deduplicated candidate rows ready to pass to schedule_interview.

    Each entry in 'files' must have:
      - 'name': filename including extension
      - 'bytes_base64': base64-encoded file contents

    If role_id is provided, dedups against candidates already attached to
    that role. Files exceeding 20 are rejected — batch across multiple calls.

    After parsing, ALWAYS show the user the extracted name/email/phone for
    each file before scheduling, then call schedule_interview with the
    returned 'candidates' list.
    

Inputs: files (required), role_id.

reassign_candidateA

Move a candidate from their current role to a different one. Existing meetings cascade to the new role. Records the reassignment in reassignmentAuditLogs with the optional reason.

    ASK BEFORE CALLING if either candidate_id or to_role_id is not
    unambiguous from the user's message. Reassignment affects all the
    candidate's open meetings — confirm before moving.
    

Inputs: to_role_id (required), candidate_id (required), reason.

reschedule_interviewA

Reschedule a meeting. Emails the candidate a new invite link.

    ASK BEFORE CALLING if the meeting isn't unambiguously identified
    by the user's message. Same rule as cancel_interview: a
    rescheduled-wrong-meeting message is awkward to clean up.
    

Inputs: meeting_id (required).

run_screeningA
Idempotent

Start CV-vs-JD scoring for a screening's candidates. ASYNC: this returns immediately with {"status": "queued"} and NO scores — the LLM scoring runs in the background.

    To read results, poll get_screening_results(role_id) a few seconds later
    (and again until candidates leave the 'not_screened' status). Do not
    expect scores in this tool's response.

    - Scoring is idempotent: candidates whose CV+JD are unchanged since the
      last run are skipped. Pass force=True to re-score everything (e.g.
      after you have not changed the JD but want a fresh pass).
    - limit: optionally cap how many candidates get scored this run.
    - Uses a cheap model; safe to run on a large roster.
    

Inputs: role_id (required), force, limit.

schedule_interviewA

Schedule AI-driven interviews. Creates meetings and sends invite emails.

    ASK BEFORE CALLING — DO NOT PICK A ROLE ON YOUR OWN:
    - A role is MANDATORY and now ENFORCED: if you pass neither role_id
      nor a position, this tool schedules NOTHING and returns
      {"status": "role_required", "existingRoles": [...]}. When you get
      that, ask the user which role to use, suggesting the returned
      existingRoles, and only call again once they pick one.
    - If the user did not name a role AND did not give enough info to
      create a new one (position + JD/skills), STOP and ask first.
    - Wrong role = wrong invite goes out. Treat role selection as
      mandatory clarifying input; never default.

    DRY RUN FIRST IF YOU'RE AUTO-FILLING ANYTHING:
    - If you are supplying any field the user did not explicitly state —
      interview_type, position, job_description, evaluation_focus,
      duration, language, required_skills, OR candidates extracted from
      a paste/CV — CALL WITH dry_run=True FIRST.
    - The response returns the normalized candidates + the role payload
      that WOULD be created or used, including the evaluation focus
      split. Persists nothing, sends no email, consumes no quota.
    - Read it back to the user in chat as a brief "here's what I'd send"
      summary (4–6 bullet lines covering candidates, role, interview
      type, focus split, JD if auto-generated). Get explicit go-ahead.
    - THEN call again with dry_run=False to actually send invites.
    - Only skip dry_run when EVERY field came from the user verbatim
      (e.g. they named the role, the interview type, and pasted clean
      structured candidates with no extraction).

    EVALUATION FOCUS HANDLING:
    - If user didn't specify, leave evaluation_focus out — backend
      reuses the existing role's stored split, or applies an
      interview-type default for new roles (screening:
      role-fit/comms/experience/motivation; technical:
      depth/problem-solving/comms/system-design; hr:
      comms/culture/motivation/leadership).
    - The dry-run response surfaces what the resolved split will be so
      the user can override before invites go out.

    CANDIDATE FIELDS:
    - Each candidate dict requires 'firstName' and 'email'. Optional:
      'lastName', 'phoneNumber', 'experience', 'summary'.
    - Email must be well-formed; phone numbers auto-normalise to 10
      digits (Indian format).

    TIMING — IMPORTANT:
    - This tool sends an invite EMAIL. The candidate opens the link
      when they're ready; THEY pick the moment to start the interview.
      You cannot pin an interview to a clock time via this tool.
    - If a user says "schedule Priya for 3pm Tuesday" — explain that
      AIIA invites are candidate-self-served, then offer to send the
      invite now (so Priya has it in her inbox) or note the desired
      window in the candidate summary so HR can chase if she hasn't
      joined by then.

    OTHER:
    - interview_type: 'screening', 'technical', or 'hr'.
    - duration: seconds per interview (default 900 = 15 min).
    - language: 'en', 'hi', or 'ar'.
    - If role_id is provided, position/jobDescription are ignored.

    Returns per-candidate status. Some rows may succeed while others
    fail (invalid email, quota exhausted, duplicate within role). Never
    assume the whole batch succeeded.
    

Inputs: candidates (required), interview_type (required), dry_run, role_id, +9 more.

schedule_screened_candidatesA
Destructive

Schedule AI interviews for chosen candidates of a screening. Sends a real invite EMAIL to each and consumes interview quota.

    CONFIRM BEFORE CALLING:
    - Read back to the user which candidates (by name) you're about to invite
      and get explicit go-ahead. This spends money and emails real people —
      inviting the wrong candidates is not undoable in-band.
    - Pull candidate_ids from get_screening_results 'id' fields. Only include
      rows with hasEmail=true; rows with a placeholder email are REJECTED
      (the whole call 400s) until a real email is added.

    The interview reuses the screening role's stored config (JD, questions,
    evaluation focus) — same as the web 'Schedule these N' action. The
    candidate self-serves the interview when they open the link; you cannot
    pin a clock time. duration is seconds per interview (default 900 = 15 min).

    Returns {scheduledCount, scheduled_candidates:[{candidateId,
    candidate_email, status ('scheduled'|'needs_email'|'skipped_existing'|
    'failed'), meetingId, meetingUrl, reason}]}. Some rows may succeed while
    others fail or are skipped (already scheduled) — never assume the whole
    batch went out.
    

Inputs: role_id (required), candidate_ids (required), duration.

send_remindersA
Idempotent

Send reminder emails for one or more pending interviews. Skips already-completed meetings. Pass the same idempotency_key on retry to avoid duplicate sends.

    ASK BEFORE CALLING if the user said "remind everyone" / "the
    no-shows" / anything that resolves to >1 meeting and you don't
    have an explicit list. Call list_meetings (filter by status) first,
    show the user the meetings you'd remind, and confirm before sending.
    Reminder emails to the wrong candidates are an embarrassing leak.

    Cap: 100 meeting_ids per call. For more, batch across multiple calls.
    

Inputs: meeting_ids (required), idempotency_key.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 18 tools

    Disambiguation4/5

    Tools have distinct purposes with detailed descriptions, but there is slight overlap between schedule_interview and schedule_screened_candidates (both schedule interviews, one for unscreened and one for screened candidates). Overall, the differences are clear from the descriptions.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern in snake_case (e.g., list_meetings, create_screening_role, run_screening). No mixing of conventions like camelCase or irregular verbs.

    Tool Count5/5

    With 18 tools, the set covers the full interview management lifecycle without being overwhelming. Each tool serves a specific function, and the count is appropriate for the domain.

    Completeness4/5

    The tool surface covers the screening pipeline (from role creation to scheduling screened candidates) and interview management (schedule, cancel, reschedule, list, get details, report, reminders). Minor gaps include no tool for updating candidate email or deleting roles, but core workflows are well-supported.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      This MCP server provides hiring automation tools for recruitment processes. It enables users to generate job descriptions, score CVs, create interview questions, benchmark salaries, and draft offer letters through natural language interactions.
      16 npm
      31 PyPI
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      Enables interaction with IntelliMatch recruiting and iSuite insurance systems through spec-driven MCP tools, allowing natural language queries for candidate search, policy management, claims, and workflows.
      28
      -
    • F
      license
      Not graded
      quality
      F
      maintenance
      Exposes HR recruitment actions as MCP tools over stdio, enabling AI clients to work with openings, candidates, interviews, and feedback through natural language.
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables natural language management of job applications through MCP-compatible clients, including searching real job offers, creating and deleting applications, generating CVs or cover letters, and retrieving application metrics.
      ISC