Skip to main content
Glama

Recruiting and Onboarding

Applications to a candidate sheet, interview scheduling, debriefs and a new hire's day one.

An MCP server with 8 workflows across Gmail, Google Drive, Google Sheets, Google Calendar, Granola, Notion, Slack, GitHub, Linear, Google Forms, Google Docs, Tavily and Firecrawl. Each workflow is a prompt your agent runs as a slash command, over the 27 tools it needs and no others.

uv tool install https://github.com/r28ai/recruiting-ops-mcp/releases/download/v0.1.0/recruiting_ops_mcp-0.1.0-py3-none-any.whl
claude mcp add hiring -- recruiting-ops-mcp

It installs with uv from this repository's release, with no git and nothing to build; nothing but Charter and the libraries it uses comes from PyPI. To update, run the install line from the latest release. If a desktop app cannot find recruiting-ops-mcp, give it the full path from which recruiting-ops-mcp (where recruiting-ops-mcp on Windows).

Then ask your agent to connect your apps, or run /mcp__hiring__setup.

Connect your apps

Ask the agent to connect one ("connect Linear"). It tells you where to get that app's key and the command that stores it, and the next call works, with no restart. The agent never asks for a key in the chat.

Or connect everything this server uses from a terminal:

recruiting-ops-mcp login            # each app in turn
recruiting-ops-mcp login granola    # just one
recruiting-ops-mcp status           # what is connected

Tokens and keys go to your operating system's keychain (macOS Keychain, Windows Credential Manager, the Secret Service on Linux), and are checked with one read-only call to the app's own API before they are kept. Every key, token and OAuth client is yours: we register no app with any of these services, and nothing passes through a server of ours, because there isn't one.

App

How it connects

Or set

Google

Browser sign-in, over your own OAuth client (make one).

GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET

Granola

Your own key (get one), entered once. In the Granola app: Settings → Connectors → API keys. Business plan or above.

GRANOLA_API_KEY

Notion

Your own key (get one), entered once. Then share the pages it should see with the integration.

NOTION_API_KEY

Slack

Your own key (get one), entered once. A bot token from your own Slack app, which the guide sets up in about three minutes.

SLACK_BOT_TOKEN

GitHub

Your own key (get one), entered once.

GITHUB_TOKEN

Linear

Your own key (get one), entered once.

LINEAR_API_KEY

Tavily

Your own key (get one), entered once.

TAVILY_API_KEY

Firecrawl

Your own key (get one), entered once.

FIRECRAWL_API_KEY

A variable set in your client's config always wins over the keychain.

Related MCP server: Lever MCP Server

Workflows

Workflow

What you get

Apps

Applications inbox → candidate sheet applications_inbox_to_candidate_sheet

Every application filed with its CV and a row, no ATS needed at ten hires a year.

Gmail, Google Drive, Google Sheets

Interview scheduling interview_scheduling

Panels booked around everyone's calendars with the candidate confirmed.

Google Sheets, Google Calendar, Gmail

Interview debrief interview_debrief

Each interviewer's notes summarised onto the candidate page, posted to the hiring channel.

Granola, Notion, Slack

Engineering candidate's public work engineering_candidate_s_public_work

What they've built in public, summarised before the technical interview.

GitHub, Notion

New hire day one new_hire_day_one

Week-one meetings, channels, team, folders and checklist, from one name and start date.

Google Calendar, Slack, Linear, Google Drive, Notion

Pulse survey pulse_survey

Survey sent where people are, results summarised for leadership.

Google Forms, Slack, Google Docs

Offer letter offer_letter

Template copied, filled from the candidate page and drafted to send.

Notion, Google Drive, Google Docs, Gmail

Job post from the market job_post_from_the_market

A job description benchmarked against how others describe and pay the role.

Tavily, Firecrawl, Google Docs

Every prompt takes one optional argument, details: the repo, team, channel, customer or date range you mean, so the agent does not have to ask. In Claude Code, put it in quotes, or only its first word arrives:

/mcp__hiring__applications_inbox_to_candidate_sheet "the jobs@ inbox, sheet 'Candidates 2026'"

Reads run without asking. Before anything that creates, sends, changes or deletes, the prompt tells the agent to show you the call and wait.

1 of the 8 workflows need no Google or Granola credential.

Other clients

Claude Desktop: install uv if you have not, since Claude Desktop starts the server with it, then open the .mcpb from the latest release. Claude asks for any keys in its own settings and keeps them in your keychain. The first start takes a few seconds longer, while uv installs it.

VS Code (.vscode/mcp.json): VS Code asks for each key the first time the server starts and stores it securely. Leave out any you stored with login.

{
  "inputs": [
    {
      "type": "promptString",
      "id": "google-client-secret",
      "description": "Google: OAuth client secret",
      "password": true
    },
    {
      "type": "promptString",
      "id": "granola-api-key",
      "description": "Granola: API key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "notion-api-key",
      "description": "Notion: Integration secret (ntn_\u2026)",
      "password": true
    },
    {
      "type": "promptString",
      "id": "slack-bot-token",
      "description": "Slack: Bot token (xoxb-\u2026)",
      "password": true
    },
    {
      "type": "promptString",
      "id": "github-token",
      "description": "GitHub: Personal access token",
      "password": true
    },
    {
      "type": "promptString",
      "id": "linear-api-key",
      "description": "Linear: Personal API key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "tavily-api-key",
      "description": "Tavily: API key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "firecrawl-api-key",
      "description": "Firecrawl: API key",
      "password": true
    }
  ],
  "servers": {
    "hiring": {
      "type": "stdio",
      "command": "recruiting-ops-mcp",
      "env": {
        "GOOGLE_CLIENT_SECRET": "${input:google-client-secret}",
        "GRANOLA_API_KEY": "${input:granola-api-key}",
        "NOTION_API_KEY": "${input:notion-api-key}",
        "SLACK_BOT_TOKEN": "${input:slack-bot-token}",
        "GITHUB_TOKEN": "${input:github-token}",
        "LINEAR_API_KEY": "${input:linear-api-key}",
        "TAVILY_API_KEY": "${input:tavily-api-key}",
        "FIRECRAWL_API_KEY": "${input:firecrawl-api-key}",
        "GOOGLE_CLIENT_ID": ""
      }
    }
  }
}

Cursor (.cursor/mcp.json) starts it the same way:

{
  "mcpServers": {
    "hiring": {
      "command": "recruiting-ops-mcp"
    }
  }
}

Codex (~/.codex/config.toml) starts a turn without waiting for a server unless it is required, and then the agent has none of its tools. required = true makes the session wait for it, and startup_readiness = "catalog" waits for its tool list rather than just its connection:

[mcp_servers.hiring]
command = "recruiting-ops-mcp"
required = true
startup_readiness = "catalog"
startup_timeout_sec = 30

Name the server hiring. A host builds each tool's name from that key, and a longer one can push a tool past the 64 characters a function name allows.

Built with Charter

Every tool here is a Charter declaration: a Pydantic schema saying where each field goes on the wire. Charter's runtime builds the request, attaches and refreshes the credential, and trims the response before the model reads it. It runs in your process, with no proxy and no telemetry.

The 27 tool schemas come to 46,924 tokens.

The same tools work in your own agent, without MCP:

from charter.adapters.openai import to_openai_tools
from charter_packs_mcp import FAMILIES

tools = FAMILIES["people"].tools()
definitions = to_openai_tools(tools)   # or charter.adapters.langchain

Need an API that isn't here? Write a pack: your coding agent writes the declarations, and Charter's conformance suite checks them.

  • Gmail: gmail_threads_list, gmail_messages_attachments_get, gmail_messages_send, gmail_drafts_create

  • Google Drive: gdrive_files_create, gdrive_permissions_create, gdrive_files_copy

  • Google Sheets: gsheets_spreadsheets_values_append, gsheets_spreadsheets_values_get

  • Google Calendar: gcalendar_events_list, gcalendar_events_insert

  • Granola: granola_notes_get

  • Notion: notion_pages_update, notion_pages_create, notion_pages_retrieve

  • Slack: slack_chat_post_message, slack_conversations_invite

  • GitHub: github_search_repositories, github_repos_list_languages, github_search_commits

  • Linear: linear_team_membership_create

  • Google Forms: gforms_forms_create, gforms_forms_responses_list

  • Google Docs: gdocs_documents_create, gdocs_documents_batch_update

  • Tavily: tavily_search

  • Firecrawl: firecrawl_scrape

License

Apache 2.0.

Available Tools

29 tools
connectA

Connect one app this server uses. For an app that issues keys, says where to get one and the terminal command that stores it. For Google, once the user's own OAuth client is set, starts the browser sign-in and returns at once: the user approves in the browser and the next call works. To see which apps are connected, call connection_status. Never ask the user for a key in the chat.

Inputs: app (required).

connection_statusA
Read-only

See which apps this server is connected to, and how to connect each one that is not. Changes nothing.

Inputs: no named parameters.

firecrawl_scrapeB

Scrape a single URL and optionally extract information. Use when the user wants to read or summarize a specific webpage. Supports markdown, HTML, screenshots, and structured JSON extraction.

Inputs: url (required), proxy, maxAge, minAge, +22 more.

gcalendar_events_insertC

Create a calendar event; returns details of the event.

Inputs: event (required), calendarId (required), sendUpdates, maxAttendees, +3 more.

gcalendar_events_listC
Read-onlyIdempotent

List events matching a given search filter.

Inputs: calendarId (required), q, iCalUID, orderBy, +14 more.

gdocs_documents_batch_updateA

Apply a list of edits to a document. Each request sets exactly one kind of edit — insert_text, replace_all_text, update_text_style, insert_table and so on. Edits apply in order against a document that shifts as they do: when inserting at several indices, order the requests back-to-front so earlier insertions do not move the positions later ones refer to.

Inputs: body (required), documentId (required).

gdocs_documents_createA

Create a blank document with a title. Only the title is honoured — the document is created empty. To add content, call this and then documents_batch_update with the returned documentId.

Inputs: body.

gdrive_files_copyA

Create a copy of a file and apply any requested updates with patch semantics. Typically send a new name and optionally parents.

Inputs: file (required), fileId (required), ocrLanguage, copyComments, +5 more.

gdrive_files_createA

Create a file's metadata: a folder (mimeType application/vnd.google-apps.folder), a Google Doc / Sheet / Slide, or an empty blob. Media upload is not expressed here. parents takes at most one folder ID.

Inputs: file (required), ocrLanguage, includeLabels, supportsAllDrives, +4 more.

gdrive_permissions_createA

Create a permission for a file or shared drive. type is user, group, domain or anyone; role is owner, organizer, fileOrganizer, writer, commenter or reader. For user or group, send emailAddress. Concurrent permission writes on the same file are not supported.

Inputs: fileId (required), permission (required), emailMessage, supportsAllDrives, +4 more.

gforms_forms_createA

Create a new form from a title. Only the title and the document title are honoured: the form is created with no description, no items and default settings. To add questions, call this and then forms_batch_update with the returned formId. Pass unpublished=true to create a form that does not yet accept responses.

Inputs: body (required), unpublished.

gforms_forms_responses_listA
Read-onlyIdempotent

List a form's submitted responses, newest page first, up to 5000 per page. The only supported filter is on submission time: pass filter='timestamp >= 2026-01-01T00:00:00Z' to read what has arrived since a point in time. Answers come back keyed by questionId.

Inputs: formId (required), filter, pageSize, pageToken.

github_repos_list_languagesA
Read-onlyIdempotent

List a repository's languages with bytes of code each — the quickest way to find out what a repository is written in before reading any of it. Answers with an object keyed by language, so there is nothing to page.

Inputs: repo (required), owner (required).

github_search_commitsA
Read-onlyIdempotent

Search commits by message, author or date across GitHub — 'repo:owner/name fix flaky test'. The way to find where a change was introduced. Rate limited to 30 requests per minute.

Inputs: q (required), page, sort, order, +1 more.

github_search_repositoriesA
Read-onlyIdempotent

Search repositories with qualifiers, e.g. 'topic:cli language:go stars:>500'. Rate limited to 30 requests per minute.

Inputs: q (required), page, sort, order, +1 more.

gmail_drafts_createC

Save an email draft to Gmail.

Inputs: body (required), userId.

gmail_messages_attachments_getA
Read-onlyIdempotent

Read one attachment, by the attachmentId messages_get or threads_get lists. A text file comes back as text; a binary one is reported, not decoded.

Inputs: id (required), messageId (required), userId.

gmail_messages_sendC

Send an email via the Gmail API.

Inputs: body (required), userId.

gmail_threads_listC
Read-onlyIdempotent

List Gmail threads.

Inputs: q, userId, labelIds, pageToken, +2 more.

granola_notes_getA
Read-onlyIdempotent

Read one meeting note: its AI summary, the people who attended, the calendar event it was taken against, and the folders it belongs to. Pass include='transcript' to get the transcript inline as well. A transcript too large to inline answers 413 TRANSCRIPT_TOO_LARGE. Read it with notes_transcript_get instead.

Inputs: noteId (required), include.

gsheets_spreadsheets_values_appendC

Appends values to a spreadsheet.

Inputs: range (required), valueRange (required), spreadsheetId (required), valueInputOption (required), +4 more.

gsheets_spreadsheets_values_getC
Read-onlyIdempotent

Returns a range of values from a spreadsheet.

Inputs: range (required), spreadsheetId (required), majorDimension, valueRenderOption, +1 more.

linear_team_membership_createC

Add a member to a team.

Inputs: variables (required).

notion_pages_createA

Create a page — as a subpage of another page, or as a row of a database by giving its data_source_id as the parent. Content comes as a markdown string Notion parses into blocks, or from a template: one or the other, never both.

Inputs: body (required), filterProperties.

notion_pages_retrieveA
Read-onlyIdempotent

Get a page's properties. This does not return the page's content — the blocks are read with blocks_children_list, or all at once as Markdown with pages_retrieve_markdown. A property holding more than 25 entries comes back truncated; read it in full with pages_retrieve_property_item.

Inputs: pageId (required), filterProperties.

notion_pages_updateA

Update a page's property values, icon, cover, or trash state. Properties not named are left alone, and a value set to null is cleared. This cannot move a page — use pages_move.

Inputs: body (required), pageId (required), filterProperties.

slack_chat_post_messageA

Send a message to a Slack channel, private group, or DM. Provide text for a plain message; set thread_ts to reply inside an existing thread.

Inputs: channel (required), text, parse, blocks, +13 more.

slack_conversations_inviteA

Add people to a channel. With force set, Slack adds everyone it can and reports the rest alongside a success, so read the result.

Inputs: users (required), channel (required), force.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 29 tools

    Disambiguation5/5

    Each tool has a distinct service prefix and action, e.g., gmail_threads_list vs gmail_messages_attachments_get, making purposes clear. No two tools appear to do the same thing; overlapping actions are differentiated by resource and service.

    Naming Consistency4/5

    Most tools follow a service_entity_action snake_case pattern (e.g., gdrive_files_create, notion_pages_update). Minor deviations like connect, connection_status, tavily_search, and slack_chat_post_message break the pattern but remain readable.

    Tool Count2/5

    29 tools across 11+ services is excessive for a focused recruiting ops server; many services have only one or two operations. The set feels like a broad integration grab bag rather than a well-scoped toolkit.

    Completeness2/5

    Core CRUD is missing for most services: Gmail lacks message reading, GDrive lacks list/get, GSheets lacks spreadsheet creation, and GitHub/Linear have only search or membership creation. References to non-existent tools (e.g., blocks_children_list) indicate an incomplete surface.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to pull live job listings from major ATS platforms (Greenhouse, Lever, Ashby, Workable), Hacker News hiring threads, and detect hiring signals on company career pages.
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to manage Lever.co candidates and job requisitions, and send themed emails via Gmail API with OAuth 2.0.
      -
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables Claude to securely search, read, and update your ATS data — candidates, mandates, pipelines, tasks, and interviews — through natural language, with configurable write permissions and confirmation for actions like scheduling and emailing.
      -
    • A
      license
      B
      quality
      B
      maintenance
      Enables freelancers and agencies to run eight back-office workflows across Stripe, Google Drive, Linear, Google Calendar, Gmail, GitHub, Google Docs, Granola, Google Sheets and Firecrawl, covering client onboarding, invoices from calendar and commits, status reports, scope-creep detection, site audits and overdue invoice chasers. Reads run freely, while anything that creates, sends, changes or deletes is shown for approval first, with credentials kept in your own OS keychain and no proxying through any third-party server.
      28
      Apache 2.0