BambooHR MCP

BambooHR MCP

Enables AI assistants to interact with BambooHR for employee management, hiring, time-off, documents, and reporting, with read-only tools by default and optional write capabilities.

Category
Visit Server

README

BambooHR MCP (Admin)

An administrative Model Context Protocol server for BambooHR, in Node.js and TypeScript.

It runs in two modes from the same codebase:

  • Streamable HTTP — a remote server you host, for Microsoft Copilot Studio and any other remote MCP client.
  • stdio — a local process, for Claude Desktop, VS Code, and other desktop MCP clients.

35 tools cover the employee directory, employee records and their historical tables, hiring and the applicant pipeline, employee and company documents, time-off requests and balances, reports, BambooHR user accounts, and time tracking. Record-changing tools are off by default.

BambooHR MCP


Quick start

git clone https://github.com/encoreshao/bamboohr-mcp.git
cd bamboohr-mcp
npm install
cp .env.example .env    # fill in BAMBOOHR_TOKEN and BAMBOOHR_COMPANY_DOMAIN
npm run build
npm start               # HTTP on http://0.0.0.0:3000/mcp

Check it is alive:

curl http://localhost:3000/health

For a local desktop client instead:

npm run start:stdio

Configuration

Everything is environment-driven. See .env.example for the annotated list.

Variable Required Default Purpose
BAMBOOHR_TOKEN yes API token. Carries exactly the permissions of the user who created it.
BAMBOOHR_COMPANY_DOMAIN yes Subdomain of your BambooHR URL (acme in acme.bamboohr.com).
BAMBOOHR_EMPLOYEE_ID no Default employee for self-service tools.
MCP_API_KEY for public deploys Shared secret required as X-API-Key. Unset means no authentication.
BAMBOOHR_ENABLE_WRITES no false Master switch for every record-changing tool.
BAMBOOHR_ALLOWED_TOOLS no all Comma-separated allowlist of tool names.
BAMBOOHR_ALLOW_TOKEN_HEADER no false Let callers pass their own token via X-BambooHR-Token.
BAMBOOHR_MAX_RESPONSE_BYTES no 350000 Cap on a single tool response.
MCP_TRANSPORT no http http or stdio. --stdio on the command line does the same.
PORT / HOST no 3000 / 0.0.0.0 HTTP bind address.

Creating the API token

  1. Log in to BambooHR and open your profile menu (bottom-left).
  2. Choose API KeysAdd New Key.
  3. Name it (e.g. "Copilot Studio agent") and click Generate Key.
  4. Copy it immediately — BambooHR shows it once.

The key inherits the access level of the user who created it. A key made by a full admin can read compensation and terminate employees. Create it as a user whose permissions match what the agent should be able to do — that BambooHR user is your real permission boundary, not this server.


Connecting to Copilot Studio

Copilot Studio reaches MCP servers over Streamable HTTP only — it cannot launch a local stdio process. So the server has to be hosted somewhere with a public HTTPS address.

1. Deploy

CI/CD to Azure App Service is wired up — see Deployment below. To run it locally instead, a Dockerfile is included:

docker build -t bamboohr-mcp .
docker run -p 3000:3000 --env-file .env bamboohr-mcp

Set the environment variables as secrets wherever you host it — never bake the token into an image.

2. Create the custom connector

connector/copilot-studio-connector.yaml is a ready Swagger 2.0 definition. Replace the host line with your deployed hostname, then:

  1. Go to your agent's Tools page → Add a toolNew toolCustom connector.
  2. In Power Apps, choose New custom connectorImport OpenAPI file and select the YAML.
  3. On the Security step, the definition declares an API key in the X-API-Key header. Supply the value of your MCP_API_KEY when you create the connection.
  4. Create connector, then add it to your agent.

The critical line is x-ms-agentic-protocol: mcp-streamable-1.0 on the POST /mcp operation — that is what tells Copilot Studio to speak MCP rather than treat the endpoint as a plain REST action.

Copilot Studio's MCP onboarding wizard is the alternative route and takes the same URL and header.


Connecting a local MCP client

{
  "mcpServers": {
    "bamboohr": {
      "command": "node",
      "args": ["/absolute/path/to/bamboohr-mcp/dist/index.js", "--stdio"],
      "env": {
        "BAMBOOHR_TOKEN": "your_api_token_here",
        "BAMBOOHR_COMPANY_DOMAIN": "yourcompany",
        "BAMBOOHR_ENABLE_WRITES": "false"
      }
    }
  }
}

Tools

Read-only tools (always available):

Tool What it does
bamboohr_search_employees Search the directory by name, email, title, department, or location. Paged.
bamboohr_get_employee One employee's record, with an optional explicit field list.
bamboohr_list_employee_fields Every field id in the account — discover before reading or writing.
bamboohr_list_field_options Allowed values for list fields (department, division, location…).
bamboohr_list_tables Historical tables available on employee records.
bamboohr_get_employee_table Rows of one table: job history, compensation, employment status.
bamboohr_list_users BambooHR user accounts and access levels — for access reviews.
bamboohr_get_changed_employees Records inserted/updated/deleted since a timestamp.
bamboohr_whos_out Who is out over a date range, company-wide.
bamboohr_list_time_off_requests Requests filtered by range, status, employee, type — the approval queue.
bamboohr_get_time_off_balances Projected balances for an employee as of a date.
bamboohr_list_time_off_types Configured time-off types.
bamboohr_list_time_off_policies Accrual policies.
bamboohr_run_report Run a saved company report by ID.
bamboohr_run_custom_report Ad-hoc report over any field list.
bamboohr_list_projects Time-tracking projects and tasks.
bamboohr_get_timesheet_entries Timesheet entries over a date range, one or many employees.
bamboohr_list_job_openings Job openings with applicant counts.
bamboohr_list_applications The candidate pipeline, filtered by job, status, or search.
bamboohr_get_application Full detail of one application, including answers and status history.
bamboohr_list_applicant_statuses Configured applicant statuses and their IDs.
bamboohr_list_employee_files An employee's document categories and file metadata.
bamboohr_list_company_files Company-wide document categories and files.
bamboohr_download_employee_file Download a document, base64-encoded. Small files only.
bamboohr_server_info How this server is configured and which tools it exposes.

Write tools (only registered when BAMBOOHR_ENABLE_WRITES=true):

Tool What it does
bamboohr_create_employee Create an employee record.
bamboohr_update_employee Update fields on an existing employee.
bamboohr_add_employee_table_row Append a promotion, raise, or status change to a historical table.
bamboohr_set_time_off_request_status Approve, deny, or cancel a time-off request.
bamboohr_submit_work_hours Log hours against a project and task.
bamboohr_set_application_status Advance, reject, or hire a candidate.
bamboohr_add_application_comment Record interview feedback against an application.
bamboohr_upload_employee_file Attach a document to an employee, supplied base64-encoded.
bamboohr_update_employee_file Rename, recategorise, or reshare a document.
bamboohr_delete_employee_file Permanently delete a document. Not recoverable.

Each write tool takes a required confirm argument that must be true. Field maps can be passed either as a fields object or as a fieldsJson string — use the string form from Copilot Studio, whose connector layer handles free-form objects poorly.

Compensation and offboarding

There are no dedicated tools for these; BambooHR stores both as historical tables on the employee record, so they go through the generic table tools. Read salary history with bamboohr_get_employee_table(employeeId, "compensation") and record a raise with bamboohr_add_employee_table_row. Terminations are a row in employmentStatus. Both are permission-gated by the API key's user.

Not available: Global Employment

BambooHR's Global Employment is an embedded EOR service delivered with Remote — hiring and onboarding begin in BambooHR, but payroll and benefits live in Remote's platform. It exposes no endpoints in the BambooHR v1 API, so there is nothing to build tools against. Integrating it would mean going to Remote's API as a separate service.


Security model

  • The server holds the BambooHR token. Callers authenticate to the server with MCP_API_KEY; they never see or supply the HR credential. Per-request tokens are possible but opt-in via BAMBOOHR_ALLOW_TOKEN_HEADER.
  • Read-only by default. Write tools are withheld from tools/list entirely when writes are disabled — the model is never told a capability exists that the server will refuse.
  • Nothing is shared between requests. Each HTTP request builds its own context, client, and MCP server instance, so one caller's credentials and employee context can never bleed into another's. There is no mutable global config.
  • Responses are capped. Anything over BAMBOOHR_MAX_RESPONSE_BYTES returns an actionable "narrow your query" error rather than a 500 KB payload that Copilot Studio would reject with an opaque HTTP 400.
  • Least privilege lives in BambooHR. BAMBOOHR_ALLOWED_TOOLS narrows the surface, but the token's own access level is the boundary that actually matters.

Set MCP_API_KEY before exposing the server publicly. It logs a warning at startup if you have not.


Deployment

Two GitHub Actions workflows:

  • .github/workflows/ci.yml — typecheck, build, and test on Node 20 and 22 for every push and pull request, plus a guard asserting the default build still gates its write tools.
  • .github/workflows/deploy.yml — on every push to main, builds a production zip, deploys it to Azure App Service, then fails the run if the app does not come up or if it comes up missing its BambooHR credentials or its API key. A deploy that lands but cannot serve traffic is reported as a failure, not a pass.

The deploy workflow targets the Web App BambooHRMCP (https://bamboohrmcp.azurewebsites.net) by default. Override it with an AZURE_WEBAPP_NAME repository variable.

1. Create the Web App

Skip this if the app already exists — creating it through the Portal's Deployment Center does the same thing.

az group create --name bamboohr-mcp-rg --location eastus

az appservice plan create \
  --name bamboohr-mcp-plan --resource-group bamboohr-mcp-rg \
  --is-linux --sku B1

az webapp create \
  --name BambooHRMCP --resource-group bamboohr-mcp-rg \
  --plan bamboohr-mcp-plan --runtime "NODE:20-lts"

az webapp config set \
  --name BambooHRMCP --resource-group bamboohr-mcp-rg \
  --startup-file "node dist/index.js"

2. Set app settings

These live in Azure, never in the repo. .env is for local development only and is gitignored.

az webapp config appsettings set \
  --name BambooHRMCP --resource-group bamboohr-mcp-rg \
  --settings \
    BAMBOOHR_TOKEN="<your-bamboohr-token>" \
    BAMBOOHR_COMPANY_DOMAIN="<your-subdomain>" \
    MCP_API_KEY="<your-long-random-key>" \
    BAMBOOHR_ENABLE_WRITES="false" \
    SCM_DO_BUILD_DURING_DEPLOYMENT="false" \
    WEBSITE_RUN_FROM_PACKAGE="1"

SCM_DO_BUILD_DURING_DEPLOYMENT=false matters: the zip already contains dist/ and production node_modules, and letting Oryx rebuild on the server would only introduce drift.

3. Authentication

The workflow deploys with the publish profile that Azure's Deployment Center stored in the repository as AZUREAPPSERVICE_PUBLISHPROFILE_F89A515B4E6341C788E87EFCEC7A991B. Nothing further to configure — connecting the app through the Portal already did it.

If you ever regenerate the publish profile, or wire up a different Web App, update that secret under Settings → Secrets and variables → Actions and change the name in deploy.yml to match.

Two optional repository variables:

Name Purpose
AZURE_WEBAPP_NAME Target Web App. Defaults to BambooHRMCP.
AZURE_WEBAPP_SLOT Deployment slot. Defaults to production.

Also create an environment named production under Settings → Environments. Adding required reviewers there turns every deploy into an approval gate, which is worth doing for a server holding an HR admin token.

<details> <summary><strong>Moving to OIDC instead</strong> — short-lived tokens, no long-lived credential stored in GitHub</summary>

A publish profile is a long-lived credential with deploy rights sitting in GitHub. OIDC replaces it with a token minted per run. Create an Entra app registration, grant it Contributor on the Web App, and add federated credentials:

az ad app create --display-name bamboohr-mcp-deploy
# note the appId, then:
az ad sp create --id <APP-ID>

az role assignment create \
  --assignee <APP-ID> --role Contributor \
  --scope /subscriptions/<SUB-ID>/resourceGroups/bamboohr-mcp-rg

az ad app federated-credential create --id <APP-ID> --parameters '{
  "name": "github-main",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:aakarsh1t/BambooHR-MCP:ref:refs/heads/main",
  "audiences": ["api://AzureADTokenExchange"]
}'

The subject must match exactly how the workflow runs. Because the deploy job uses a GitHub Environment, add a second credential with subject repo:aakarsh1t/BambooHR-MCP:environment:production.

Then add secrets AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID — none is a credential on its own — and in deploy.yml restore id-token: write to the deploy job's permissions, drop the publish-profile line, and add before the deploy step:

      - name: Sign in to Azure
        uses: azure/login@v3
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

</details>

4. Point Copilot Studio at it

connector/copilot-studio-connector.yaml already points at bamboohrmcp.azurewebsites.net. Change host: if you deploy elsewhere, then import it as described above.


Development

npm run dev          # HTTP, ts-node, no build step
npm run dev:stdio    # stdio, ts-node
npm run typecheck    # tsc --noEmit
npm run build        # emit dist/
npm test             # run test/ against the built output
npm run verify       # typecheck + build + test, same as CI

Layout:

src/
  index.ts          entry point and transport selection
  http.ts           express app, auth, stateless /mcp endpoint
  server.ts         builds an McpServer for one request context
  config.ts         environment loading and per-request context
  tools/index.ts    tool definitions, schemas, and gating
  apis/bamboohr.ts  typed BambooHR v1 client
  utils/            response shaping and models
test/
  smoke.test.js     transport, auth, gating, and error-shaping tests

The tests run against dist/, so build first — npm run verify does both in order. They use throwaway credentials and need no BambooHR account: everything asserted is about transport, gating, and error shaping.

Adding a tool means one define(...) call in src/tools/index.ts and a method on BambooHRClient in src/apis/bamboohr.ts. Give it readOnlyHint: true only if it genuinely does not change anything — that flag is what decides whether writes gating applies.

Two notes for anyone extending this:

  • In stdio mode nothing may be written to stdout — it is the JSON-RPC channel and a stray console.log corrupts the stream. Diagnostics go to console.error.
  • Zero-argument tools go through the four-argument server.tool() form. SDK 1.11 decides whether an argument is a Zod shape by checking for a ZodType value, so an empty {} schema gets mistaken for the annotations object and the real annotations get called as the handler. registerTool() in src/tools/index.ts handles this.

License

MIT. See LICENSE.

Contributors

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured