Skip to main content
Glama
README.md
# 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.

```bash
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](https://docs.astral.sh/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](https://github.com/r28ai/recruiting-ops-mcp/releases/latest). 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:

```bash
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](https://docs.r28.ai/charter/auth/setup/google)). | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` |
| Granola | Your own key ([get one](https://docs.granola.ai/help-center/sharing/integrations/granola-api)), entered once. In the Granola app: Settings → Connectors → API keys. Business plan or above. | `GRANOLA_API_KEY` |
| Notion | Your own key ([get one](https://www.notion.so/profile/integrations)), entered once. Then share the pages it should see with the integration. | `NOTION_API_KEY` |
| Slack | Your own key ([get one](https://docs.r28.ai/charter/auth/setup/slack)), 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](https://github.com/settings/tokens/new?description=Charter&scopes=repo,read:user)), entered once. | `GITHUB_TOKEN` |
| Linear | Your own key ([get one](https://linear.app/settings/account/security)), entered once. | `LINEAR_API_KEY` |
| Tavily | Your own key ([get one](https://app.tavily.com/home)), entered once. | `TAVILY_API_KEY` |
| Firecrawl | Your own key ([get one](https://www.firecrawl.dev/app/api-keys)), entered once. | `FIRECRAWL_API_KEY` |

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

## Workflows

| Workflow | What you get | Apps |
|---|---|---|
| **Applications inbox → candidate sheet** <br>`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** <br>`interview_scheduling` | Panels booked around everyone's calendars with the candidate confirmed. | Google Sheets, Google Calendar, Gmail |
| **Interview debrief** <br>`interview_debrief` | Each interviewer's notes summarised onto the candidate page, posted to the hiring channel. | Granola, Notion, Slack |
| **Engineering candidate's public work** <br>`engineering_candidate_s_public_work` | What they've built in public, summarised before the technical interview. | GitHub, Notion |
| **New hire day one** <br>`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** <br>`pulse_survey` | Survey sent where people are, results summarised for leadership. | Google Forms, Slack, Google Docs |
| **Offer letter** <br>`offer_letter` | Template copied, filled from the candidate page and drafted to send. | Notion, Google Drive, Google Docs, Gmail |
| **Job post from the market** <br>`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](https://docs.astral.sh/uv/getting-started/installation/) if you have not, since Claude Desktop starts the server with it, then open the `.mcpb` from the [latest release](https://github.com/r28ai/recruiting-ops-mcp/releases/latest). 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`.

```json
{
  "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:

```json
{
  "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:

```toml
[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](https://github.com/r28ai/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:

```python
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](https://docs.r28.ai/charter/start/coding-agents): your coding agent writes the declarations, and Charter's conformance suite checks them.

<details>
<summary>All 27 tools</summary>

- **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`

</details>

## License

Apache 2.0.

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

ActivityMaintained
ResponsivenessNo issues