Skip to main content
Glama
luthersystems

Buildium MCP Server

README.md
# Buildium MCP Server

**Experimental** Model Context Protocol (MCP) server for Buildium Property Management API, built with Python + FastMCP. Uses API key authentication for server-to-server communication.

## āš ļø Status & Disclaimers

* **Experimental**: Not production-ready; no SLA; APIs and behavior may change.
* **No affiliation with Buildium**: This is a community integration. Buildium is a trademark of Buildium, LLC.
* **Security**: Do **not** commit secrets. Treat client IDs and secrets as sensitive; use a secrets manager. **Use at your own risk.**

## Features

* šŸ” **API Key Authentication** - Secure server-to-server authentication via headers
* šŸ˜ļø **81 Tools Across 12 Categories** - Comprehensive property management coverage
* šŸ“‹ **Selective Tool Loading** - Enable only the categories you need
* šŸ¢ **Multi-Property Types** - Rentals, associations, and units
* šŸ”Œ **MCP Protocol** - Compatible with Claude Desktop, Cursor, and other MCP clients

## Requirements

* Python 3.11+
* `uv` package manager (or `pip`)
* Buildium API credentials (client ID and client secret)

## Installation

### Using `uv` (Recommended)

```bash
# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install the package
uv pip install -e "git+https://github.com/luthersystems/mcp-server-buildium.git"
```

### Using `pip`

```bash
pip install git+https://github.com/luthersystems/mcp-server-buildium.git
```

## Configuration

Configure the server using environment variables:

```bash
# API Base URL (no /v1 suffix - SDK adds it automatically)
BUILDIUM_BASE_URL=https://api.buildium.com  # Production
# BUILDIUM_BASE_URL=https://apisandbox.buildium.com  # Sandbox

# API Key Credentials
BUILDIUM_CLIENT_ID=your-client-id
BUILDIUM_CLIENT_SECRET=your-client-secret

# Optional: Selective Tool Categories (comma-separated)
# If not specified, all categories are enabled
BUILDIUM_CATEGORIES=associations,leases,rentals
```

`BUILDIUM_BASE_URL` must be an `https://` URL without credentials, a query or a
fragment, because every request carries the client secret in a header. The
server refuses to start otherwise. An empty value means the production URL.

### Tool Categories

Control which tool categories are enabled using the `BUILDIUM_CATEGORIES` environment variable:

| Category | Tools | Description |
|----------|-------|-------------|
| `associations` | 6 | Homeowner association management |
| `leases` | 5 | Lease agreements and transactions |
| `rentals` | 5 | Rental properties and listings |
| `applicants` | 10 | Rental applicants and applications |
| `tenants` | 7 | Tenant management (rental & association) |
| `owners` | 8 | Property owner management |
| `units` | 7 | Individual unit management |
| `vendors` | 7 | Vendor and service provider management |
| `tasks` | 5 | Task and to-do management |
| `bills` | 7 | Bill and payment management |
| `files` | 8 | Document and file management |
| `bank_accounts` | 6 | Bank account and transaction management |

**Total: 81 tools**

If `BUILDIUM_CATEGORIES` is not set, all 81 tools across all 12 categories are enabled.

### Environment File

Create a `.env` file (copy from `.env.example`):

```bash
cp .env.example .env
# Edit .env with your credentials
```

## Usage

### Running the Server

```bash
# With uv
uv run mcp-server-buildium

# Or with Python
python -m mcp_server_buildium.server
```

### Using with Claude Desktop

Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "buildium": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/luthersystems/mcp-server-buildium",
        "mcp-server-buildium"
      ],
      "env": {
        "BUILDIUM_BASE_URL": "https://apisandbox.buildium.com/",
        "BUILDIUM_CLIENT_ID": "your-client-id",
        "BUILDIUM_CLIENT_SECRET": "your-client-secret"
      }
    }
  }
}
```

## Available Tools (81 total)

### Associations (6 tools)
* `list_associations` - List all associations
* `get_association` - Get association details by ID
* `create_association` - Create a new association
* `update_association` - Update an existing association
* `list_association_board_members` - List board members for an association
* `list_association_ownership_accounts` - List ownership accounts for an association

### Leases (5 tools)
* `list_leases` - List leases with optional filters
* `get_lease` - Get lease details by ID
* `create_lease` - Create a new lease
* `update_lease` - Update an existing lease
* `list_lease_transactions` - List transactions for a lease

### Rentals (5 tools)
* `list_rentals` - List rental properties
* `get_rental` - Get rental property details by ID
* `create_rental` - Create a new rental property
* `update_rental` - Update an existing rental property
* `list_unit_listings` - List unit listings for rentals

### Applicants (10 tools)
* `list_applicants` - List rental applicants
* `get_applicant` - Get applicant details by ID
* `create_applicant` - Create a new applicant
* `update_applicant` - Update an existing applicant
* `list_applicant_applications` - List applications for an applicant
* `get_application` - Get application details by ID
* `update_application` - Update an application
* `list_applicant_groups` - List applicant groups
* `create_applicant_group` - Create a new applicant group
* `update_applicant_group` - Update an applicant group

### Tenants (7 tools)
* `list_rental_tenants` - List rental tenants
* `get_rental_tenant` - Get rental tenant details by ID
* `create_rental_tenant` - Create a new rental tenant
* `update_rental_tenant` - Update a rental tenant
* `list_association_tenants` - List association tenants
* `create_association_tenant` - Create a new association tenant
* `update_association_tenant` - Update an association tenant

### Owners (8 tools)
* `list_rental_owners` - List rental property owners
* `get_rental_owner` - Get rental owner details by ID
* `create_rental_owner` - Create a new rental owner
* `update_rental_owner` - Update a rental owner
* `list_association_owners` - List association owners
* `get_association_owner` - Get association owner details by ID
* `create_association_owner` - Create a new association owner
* `update_association_owner` - Update an association owner

### Units (7 tools)
* `list_rental_units` - List rental units
* `get_rental_unit` - Get rental unit details by ID
* `create_rental_unit` - Create a new rental unit
* `update_rental_unit` - Update a rental unit
* `list_association_units` - List association units
* `create_association_unit` - Create a new association unit
* `update_association_unit` - Update an association unit

### Vendors (7 tools)
* `list_vendors` - List vendors
* `get_vendor` - Get vendor details by ID
* `create_vendor` - Create a new vendor
* `update_vendor` - Update an existing vendor
* `list_vendor_categories` - List vendor categories
* `create_vendor_category` - Create a new vendor category
* `update_vendor_category` - Update a vendor category

### Tasks (5 tools)
* `list_tasks` - List tasks
* `get_task` - Get task details by ID
* `list_task_categories` - List task categories
* `create_task_category` - Create a new task category
* `update_task_category` - Update a task category

### Bills (7 tools)
* `list_bills` - List bills
* `get_bill` - Get bill details by ID
* `create_bill` - Create a new bill
* `update_bill` - Update an existing bill
* `list_bill_payments` - List payments for bills
* `get_bill_payment` - Get bill payment details by ID
* `create_bill_payment` - Create a new bill payment

### Files (8 tools)
* `list_files` - List files
* `get_file` - Get file details by ID
* `update_file` - Update file metadata
* `create_file_upload_request` - Create a file upload request
* `create_file_download_request` - Create a file download request
* `list_file_categories` - List file categories
* `create_file_category` - Create a new file category
* `update_file_category` - Update a file category

### Bank Accounts (6 tools)
* `list_bank_accounts` - List bank accounts
* `get_bank_account` - Get bank account details by ID
* `create_bank_account` - Create a new bank account
* `update_bank_account` - Update a bank account
* `list_bank_account_transactions` - List transactions for a bank account in a date range (`start_date`, `end_date`; default: the last 30 days)
* `get_bank_account_transaction` - Get bank account transaction details by ID

## Tool Request/Response Examples

This section provides detailed schemas and examples for key MCP tools.

### Example: List Leases

Query leases with optional filters (property, unit, status).

**Parameters:**
- `property_id` (int, optional): Filter by property ID
- `unit_id` (int, optional): Filter by unit ID
- `lease_status` (str, optional): Filter by status (e.g., "Active", "Future", "Past", "Expired")
- `limit` (int, optional): Maximum results (default: 100)
- `offset` (int, optional): Pagination offset (default: 0)

**Example Request (no filters):**
```json
{
  "name": "list_leases",
  "arguments": {}
}
```

**Example Request (with filters):**
```json
{
  "name": "list_leases",
  "arguments": {
    "property_id": 123,
    "lease_status": "Active",
    "limit": 50
  }
}
```

List tools return `{"<items>": [...], "count": n}`. Records use Buildium's
field names, as in its API reference; the examples below show only some fields.

**Example Response:**
```json
{
  "leases": [
    {
      "Id": 12345,
      "PropertyId": 123,
      "UnitId": 456,
      "LeaseType": "Fixed",
      "LeaseFromDate": "2024-01-01",
      "LeaseToDate": "2024-12-31",
      "LeaseStatus": "Active",
      "CurrentTenants": [
        {
          "Id": 789,
          "FirstName": "John",
          "LastName": "Doe"
        }
      ]
    }
  ],
  "count": 1
}
```

### Example: Get Lease

Retrieve details of a specific lease by ID.

**Parameters:**
- `lease_id` (int, required): The lease ID

**Example Request:**
```json
{
  "name": "get_lease",
  "arguments": {
    "lease_id": 12345
  }
}
```

**Example Response:**
```json
{
  "Id": 12345,
  "PropertyId": 123,
  "UnitId": 456,
  "LeaseType": "Fixed",
  "LeaseFromDate": "2024-01-01",
  "LeaseToDate": "2024-12-31",
  "LeaseStatus": "Active",
  "CurrentTenants": [
    {
      "Id": 789,
      "FirstName": "John",
      "LastName": "Doe",
      "Email": "john.doe@example.com"
    }
  ],
  "CreatedDateTime": "2024-01-01T10:00:00Z",
  "LastUpdatedDateTime": "2024-01-01T10:00:00Z"
}
```

### Example: Create Lease

Create a new lease agreement.

**Required Parameters:**
- `lease_type` (str): Lease type - `"AtWill"` (month-to-month), `"Fixed"` (specific dates), or `"FixedWithRollover"`
- `unit_id` (int): Unit ID for the lease
- `lease_from_date` (str): Start date (format: `YYYY-MM-DD`)
- `send_welcome_email` (bool): Whether to send welcome email to tenants

**Optional Parameters:**
- `lease_to_date` (str): End date (required for Fixed/FixedWithRollover leases)
- `tenant_ids` (list[int]): Existing tenant IDs to add (max 5)
- `tenants` (list[object]): New tenants to create (max 5)
- `applicant_ids` (list[int]): Approved applicant IDs to convert to tenants (max 5)
- `rent` (object): Rent configuration with cycle and charges
- `security_deposit` (object): Security deposit details
- `prorated_first_month_rent` (float): Prorated first month rent
- `prorated_last_month_rent` (float): Prorated last month rent
- `cosigners` (list[object]): Cosigner details

**Example Request (Minimal - Using Existing Tenant):**
```json
{
  "name": "create_lease",
  "arguments": {
    "lease_data": {
      "lease_type": "Fixed",
      "unit_id": 456,
      "lease_from_date": "2025-01-01",
      "lease_to_date": "2025-12-31",
      "send_welcome_email": true,
      "tenant_ids": [789]
    }
  }
}
```

**Example Request (Complete with New Tenant and Rent):**
```json
{
  "name": "create_lease",
  "arguments": {
    "lease_data": {
      "lease_type": "Fixed",
      "unit_id": 456,
      "lease_from_date": "2025-01-01",
      "lease_to_date": "2025-12-31",
      "send_welcome_email": true,
      "tenants": [
        {
          "first_name": "Jane",
          "last_name": "Smith",
          "email": "jane.smith@example.com",
          "phone_numbers": {
            "home": "555-0200"
          },
          "address": {
            "address_line1": "123 Main St",
            "city": "Springfield",
            "state": "IL",
            "postal_code": "62701",
            "country": "US"
          }
        }
      ],
      "rent": {
        "cycle": "Monthly",
        "charges": [
          {
            "gl_account_id": 1001,
            "amount": 2000.00
          }
        ]
      },
      "security_deposit": {
        "due_date": "2025-01-01",
        "amount": 2000.00
      }
    }
  }
}
```

**Example Response:**
```json
{
  "Id": 12346,
  "PropertyId": 123,
  "UnitId": 456,
  "LeaseType": "Fixed",
  "LeaseFromDate": "2025-01-01",
  "LeaseToDate": "2025-12-31",
  "LeaseStatus": "Active",
  "CurrentTenants": [
    {
      "Id": 790,
      "FirstName": "Jane",
      "LastName": "Smith",
      "Email": "jane.smith@example.com"
    }
  ],
  "CreatedDateTime": "2024-11-07T00:00:00Z"
}
```

**Field Descriptions:**

**Lease Types:**
- `AtWill`: Month-to-month lease with no end date. Automatic transactions continue until manually ended.
- `Fixed`: Lease with specific start/end dates. Moves to expired on end date, stops automatic transactions.
- `FixedWithRollover`: Lease that converts to AtWill status at end date, continuing automatic transactions.

**Rent Configuration:**
- `cycle`: Rent frequency - `"Monthly"`, `"Weekly"`, `"Every2Weeks"`, `"Quarterly"`, `"Yearly"`, `"Every2Months"`, `"Daily"`, `"Every6Months"`, `"OneTime"`
- `charges`: Array of rent charges with GL account ID and amount

**Tenant Creation:**
- Can provide up to 5 tenants via `tenant_ids` (existing), `tenants` (new), or `applicant_ids` (approved applicants)
- Required tenant fields: `first_name`, `last_name`, `address`
- Optional: `email`, `phone_numbers`, `date_of_birth`, `emergency_contact`, etc.

## Example Usage

### Example Prompts

> "List all rental properties in Buildium"

> "Show me lease #12345"

> "Create a new lease for property 100, unit 205"

> "List all associations"

## Development

### Setting Up Development Environment

```bash
# Clone the repository
git clone https://github.com/luthersystems/mcp-server-buildium.git
cd mcp-server-buildium

# Install uv if needed
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install dependencies (from uv.lock)
uv sync --extra dev
```

### Running Tests

```bash
make check       # everything CI runs, except the integration tests
make test        # offline unit tests (no credentials, no network)
make lint        # ruff: style, bugbear and bandit security rules
make typecheck   # mypy --strict
make audit       # pip-audit over uv.lock
make format      # apply ruff's formatting and fixes
```

The unit tests replace the SDK's HTTP client, so they check the method, path
and query each tool sends without calling Buildium. The type checker matters
here: it is what proves each tool calls an SDK method, with keyword arguments
and a request model, that the generated SDK actually has.

### CI

`.github/workflows/ci.yml` runs lint, mypy, the unit tests on Python 3.11 to
3.13, a package build with a wheel smoke test, and, on `main`, weekly and by
hand, the integration tests against the live Buildium API.
`.github/workflows/vulnerability-scan.yml` runs pip-audit on every pull
request, on `main` and daily. Dependabot keeps the Python dependencies and the
SHA-pinned GitHub Actions current.

When pip-audit reports a vulnerable dependency, upgrade it
(`uv lock --upgrade-package NAME`) and, if the vulnerable version still meets
the floor in `pyproject.toml`, raise the floor too: connectorhub installs this
server with `uvx --from git+...`, which resolves from `pyproject.toml`, not
`uv.lock`.

### Integration Tests (Optional)

Integration tests validate real Buildium API authentication. They are **skipped by default** and only run when you provide real credentials.

**To enable integration tests:**

1. Copy `.env.example` to `.env`:
   ```bash
   cp .env.example .env
   ```

2. Fill in your Buildium credentials in `.env`:
   ```bash
   BUILDIUM_BASE_URL=https://apisandbox.buildium.com/
   BUILDIUM_CLIENT_ID=your-client-id
   BUILDIUM_CLIENT_SECRET=your-client-secret
   ```

3. Run integration tests:
   ```bash
   uv run pytest tests/test_integration.py -v
   ```

### CI/CD Setup (GitHub Actions)

To run integration tests in GitHub Actions:

1. **Add GitHub repository secrets:**
   * Go to your repo → **Settings** → **Secrets and variables** → **Actions**
   * Add the following secrets:
     * `BUILDIUM_BASE_URL`: `https://apisandbox.buildium.com/`
     * `BUILDIUM_CLIENT_ID`: Your client ID
     * `BUILDIUM_CLIENT_SECRET`: Your client secret

2. **The CI workflow runs the integration tests** on pushes to `main`, every
   Monday, and when started by hand (**Actions** → **CI** → **Run workflow**,
   which also works on a pull request's branch). Without the secrets they skip.

**Note**: Pull requests do not run the integration tests, so a merge never
waits on the Buildium key. The offline unit tests check the request every tool
sends.

**What the integration tests verify:**

* āœ… API key authentication works with your credentials
* āœ… API calls work (tests `list_associations`, `list_rentals`, `list_leases`)

**Note**: Integration tests require a Buildium developer account (sandbox environment recommended).

## Generating SDK from OpenAPI Spec

If you want to generate a Python SDK from the Buildium OpenAPI spec:

```bash
# Generate SDK (runs OpenAPI Generator in Docker)
make generate-sdk

# Then let response models accept enum values the spec does not list
make patch-sdk
```

Buildium returns some enum fields as integers or as values its spec omits, and
the generated validators reject them. `make patch-sdk` relaxes the validators
of response models only; request models stay strict. `tests/test_sdk_enums.py`
fails if the SDK is regenerated without it.

See `Makefile` for more details.

## Project Structure

```
mcp-server-buildium/
ā”œā”€ā”€ src/mcp_server_buildium/
│   ā”œā”€ā”€ __init__.py
│   ā”œā”€ā”€ server.py           # Main FastMCP server
│   ā”œā”€ā”€ config.py           # Configuration management
│   ā”œā”€ā”€ buildium_client.py  # SDK client with the API key headers
│   ā”œā”€ā”€ buildium_sdk/       # Generated from specs/buildium-openapi.json; not linted
│   └── tools/              # One module per tool category
ā”œā”€ā”€ tests/
│   ā”œā”€ā”€ test_config.py
│   ā”œā”€ā”€ test_client.py
│   ā”œā”€ā”€ test_tools.py       # Offline request checks for every tool
│   └── test_integration.py # Real API; needs credentials
ā”œā”€ā”€ pyproject.toml
└── README.md
```

## Troubleshooting

### "base_url must be an https:// URL with a host"

* Set `BUILDIUM_BASE_URL` to `https://api.buildium.com` or `https://apisandbox.buildium.com`

### "401 Unauthorized"

* Check the client ID and secret are valid for that environment (sandbox keys do not work in production)
* Check the API key has access to the resource in Buildium's API settings

### "Connection timeout"

* Check internet connectivity
* Verify firewall settings
* Try sandbox environment first

## API Endpoints

* **Production**: `https://api.buildium.com/`
* **Sandbox**: `https://apisandbox.buildium.com/`

## Security Best Practices

1. **Never commit credentials** - Use `.gitignore` and environment variables
2. **Use secrets management** - Store credentials in secure vaults (AWS Secrets Manager, etc.)
3. **Rotate credentials regularly** - Generate new client secrets periodically
4. **Use least privilege** - Give the API key only the Buildium permissions and set `BUILDIUM_CATEGORIES` to the tool categories you need
5. **Monitor API usage** - Check Buildium dashboard for unusual activity
6. **Use sandbox environment** - Test with sandbox accounts before production

## Architecture

* **Language**: Python 3.11+
* **Framework**: FastMCP
* **Auth**: API key headers (`x-buildium-client-id`, `x-buildium-client-secret`)
* **Transport**: stdio (MCP protocol)
* **HTTP Client**: httpx
* **Testing**: pytest with a mocked HTTP transport; mypy --strict; ruff

## References

* [Buildium Developer Documentation](https://developer.buildium.com/)
* [Model Context Protocol](https://modelcontextprotocol.io)
* [FastMCP Framework](https://github.com/jlowin/fastmcp)

## License

MIT

## Contributing

Contributions welcome! Please:

1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request

## Support

This is an experimental community project. For Buildium API issues, consult the [Buildium Developer Center](https://developer.buildium.com/).

TDQS

C2.9/5.0

Scored across 81 tools

Disambiguation4/5

Most tools are clearly separated by resource prefixes like rental_, association_, applicant_, bill_, and bank_account_, making their targets easy to identify. A few pairs could cause hesitation, such as list_rentals vs list_rental_units vs list_unit_listings, and list_association_owners vs list_association_ownership_accounts, but the names are generally distinct enough for an agent.

Naming Consistency5/5

Tool names follow a very consistent verb_noun pattern throughout: list_, get_, create_, and update_ combine with predictable entity names. Even multi-word entities like list_association_board_members and create_file_upload_request follow the same convention, so there are no stylistic clashes or mixed casing.

Tool Count2/5

At 81 tools, this is far beyond the recommended range and creates a heavy selection surface for an agent, even though Buildium is a broad property-management platform. The repetitive CRUD patterns across many entity types suggest some consolidation or sub-tool organization would be appropriate.

Completeness3/5

The core entities such as associations, leases, rentals, applicants, vendors, bills, files, and bank accounts have solid list/get/create/update coverage. However, there are no delete operations at all, and several secondary resources like tasks, unit listings, board members, ownership accounts, and lease transactions only support listing or partial operations, leaving notable lifecycle gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues