Skip to main content
Glama

lendlord-mcp

npm CI

An MCP server that lets AI agents (Claude Code, Claude Desktop, Claude.ai connectors, or any MCP client) manage a Lendlord landlord account: properties, documents, tenants, maintenance, tasks, rent, transactions, compliance, and filing emails against properties.

Lendlord has no public API. This server wraps the private JSON API used by the Lendlord web app, reverse-engineered from browser traffic and the public JS bundle (docs/api.md). Every write tool has passed a live round-trip test against a real account (test/live-write.test.ts): it creates a throwaway property, exercises the tool, reads the result back, and cleans up.

Disclaimer: Unofficial and not affiliated with Lendlord. It uses undocumented endpoints that may change without notice. Use it only with your own account, at your own risk, and check Lendlord's terms of service.

Quick start

Local (Claude Code / Claude Desktop / any stdio client)

{
  "mcpServers": {
    "lendlord": {
      "command": "npx",
      "args": ["-y", "lendlord-mcp"],
      "env": {
        "LENDLORD_EMAIL": "you@example.com",
        "LENDLORD_PASSWORD": "your-password"
      }
    }
  }
}

Claude Code CLI:

claude mcp add lendlord -e LENDLORD_EMAIL=you@example.com -e LENDLORD_PASSWORD=... -- npx -y lendlord-mcp

Cursor

Add the server to ~/.cursor/mcp.json (or the project's .cursor/mcp.json):

{
  "mcpServers": {
    "lendlord": {
      "command": "npx",
      "args": ["-y", "lendlord-mcp@latest"],
      "env": {
        "LENDLORD_EMAIL": "you@example.com",
        "LENDLORD_PASSWORD": "your-password"
      }
    }
  }
}

Cursor starts the configured command as its MCP server. To pick up a newly published tool or version, use lendlord-mcp@latest as above, then restart the server from Cursor's MCP settings (or restart Cursor). If the tool list is still stale, remove and re-add the server so Cursor starts a fresh npx process.

If several MCP clients start at once, install the package once (npm install --global lendlord-mcp) and configure each client to run lendlord-mcp directly. This avoids having every client invoke npx to resolve or install the package at startup. You can confirm the installed version with lendlord-mcp --version.

Remote (online / hosted agents)

Run it as a Streamable HTTP server behind HTTPS, then point the agent at https://<host>/mcp with the bearer token.

docker build -t lendlord-mcp .
docker run -p 8080:8080 \
  -e MCP_AUTH_TOKEN=$(openssl rand -hex 32) \
  -e LENDLORD_EMAIL=you@example.com -e LENDLORD_PASSWORD=... \
  lendlord-mcp

Without Docker: MCP_TRANSPORT=http MCP_AUTH_TOKEN=... npx -y lendlord-mcp. GET /health is the health check, and every /mcp request must send Authorization: Bearer <MCP_AUTH_TOKEN>.

Related MCP server: TenantCloud MCP Server

Safety model

  • Write confirmation once per session. The first tool call that would change data is refused, with a message telling the agent to ask you. After you approve, the agent repeats the call with confirm: true, and later writes in the same session go through without asking again. A new session (a new stdio process, or a new Mcp-Session-Id over HTTP) starts locked again.

  • LENDLORD_READ_ONLY=true disables every write.

  • LENDLORD_AUTO_CONFIRM=true skips the confirmation for unattended automation.

  • Lendlord admin endpoints are blocked, including through the raw lendlord_request tool.

  • Credentials stay in your environment. Nothing is sent anywhere except app.lendlord.io.

Tools

Area

Read

Write

Account

get_account (user, owners, plan limits)

Properties

list_properties, get_property, list_property_fields, compliance_overview

add_property, update_property, update_property_images, delete_property

Documents

list_documents

upload_document (path / URL / base64, into a slot like gasCertificate or a custom key), rename_document, link_document, send_document (emails it), delete_document

Email filing

find_property

file_email

Tenants

list_tenants, get_tenant

add_tenant, update_tenant, delete_tenant

Maintenance & history

list_communications (up to 1000, filter by property), get_communication

create_maintenance_report, log_communication (back-dated email/call history), update_communication, add_communication_message, delete_communication

Tasks & alerts

list_tasks

create_task, update_task (edit, complete, reopen, delete)

Money

get_rent_collection, list_transactions (search, amount, type, property, with/without receipt), get_transaction, get_profit_and_loss

update_transaction, attach_transaction_receipt, remove_transaction_receipt

Anything else

lendlord_request (GET)

lendlord_request (POST/PUT/PATCH/DELETE)

Property fields use Lendlord's own names, for example "Property Value", "EPC Due Date" and "Gas Certificate Due Date". Call list_property_fields for the full list. Updates are partial: only the fields you pass change, and null clears a field.

Values are checked and converted before anything is sent, and the response lists any conversions:

Kind

Accepts

Stored as

Dates

2026-10-15, 15/10/2026, ISO date-time

ISO timestamp

Money / counts

150000, "£150,000", "150k", "1.2m", "£650 pcm"

number

Internal Area

73, "73 sq m", "73 square metres", "790 sq ft" (converted)

square metres

Rates, agent fee, ownership

"4.5%" or 4.5; values of 1 or less are taken as fractions

fraction (0.045)

Anything ambiguous, such as "73 m" or "about 150k", is rejected with a field-specific error before the confirmation prompt, so no write is attempted.

Tenants belong to exactly one property (propertyDetailsId). list_tenants, get_tenant and get_property show the linked property's address, and propertyMissing: true flags a link to a deleted property. To move a tenant, use update_tenant { fields: { propertyDetailsId } }.

Reliability: network drops and Lendlord's transient 500 socket hang up / 502–504 errors are retried automatically for reads and updates. Document uploads are retried only after checking that the file didn't actually land, so a retry never creates a duplicate document.

Property Photos: update_property_images uploads image files into the property's Photos gallery using Lendlord's legacy images collection. Pass files to add images, removeImageKeys to remove them, or both to replace old images after the new uploads succeed. The image keys are available under get_property → fields.images. upload_document remains for Documents such as deeds and EPCs.

Filing emails against properties

The server doesn't read any mailbox. Your agent reads mail with its own email tools (Gmail, Outlook, IMAP and so on) and uses two helpers:

  1. find_property { text } ranks properties against the email's subject and body. It scores postcode matches (spacing-insensitive), house number plus street, tenant email or name, and policy, certificate and account numbers.

  2. file_email { entityId, emlBase64 | subject/from/to/date/text/html, attachments[] } stores the email as an .eml document named YYYY-MM-DD - Subject.eml on the property (or tenant). Each attachment becomes its own document named YYYY-MM-DD - Subject - file.pdf.

  3. log_communication { propertyId, date, subject, summary, counterparties[] } adds a dated entry to the property's communication history, so threads appear in list_communications. Lendlord has no generic communication log, and landlords can't start a tenant "conversation" without a tenant. So the entry is stored as a back-dated, closed maintenance report of type "Other" that isn't shared with tenants.

A typical agent instruction: "For each new email about a rental property, call find_property with the subject and body. If the top score is clearly ahead, file_email it to that property and log_communication a one-line summary. Otherwise ask me."

Attaching invoices to payments

Bank and manual transactions (payments) keep their attachment as a receipt on the transaction, not as a normal document, so upload_document can't reach them. Use:

  1. list_transactions { startDate, endDate, search?, amount?, hasReceipt? } to find the payment, for example the £27.74 Octopus payment with no receipt yet.

  2. attach_transaction_receipt { transactionId, filePath | url | base64+fileName } to attach the invoice. A transaction holds one attachment, and attaching again replaces it.

  3. get_transaction returns the receipt name and a temporary download link, and remove_transaction_receipt removes it. update_transaction can change a transaction's property and/or category while keeping its receipt attached.

Only the receipt changes: the rest of the transaction is re-saved exactly as it was.

Configuration

Env var

Purpose

LENDLORD_EMAIL, LENDLORD_PASSWORD

Login.

LENDLORD_SESSION_COOKIE

Alternative to email/password: a Cookie header from a logged-in browser.

LENDLORD_TOTP_SECRET

Base32 TOTP secret, if 2FA is enabled.

LENDLORD_READ_ONLY

true disables writes.

LENDLORD_AUTO_CONFIRM

true skips the once-per-session write confirmation.

MCP_TRANSPORT

stdio (default) or http.

MCP_HTTP_PORT / PORT

HTTP port (default 8080).

MCP_AUTH_TOKEN

Required bearer token in HTTP mode.

LENDLORD_PAYLOAD_KEY

Override the auto-discovered payload key (normally not needed).

How it works

  • Auth: POST /api/loginUser sets a session cookie, and the client logs in again automatically when the session expires. TOTP 2FA is supported.

  • Payload encryption: the web app wraps JSON bodies as ivHex:cipherHex using AES-256-CTR with a static key that ships in its public JavaScript. The client finds that key in the live bundle at startup and caches it in ~/.cache/lendlord-mcp. The key is never stored in this repo, and the client picks up a new one if Lendlord rotates it.

  • Data model: GET /api/getUserById returns most of the account in one payload: properties, tenants, tasks and alerts ("opportunities"), owners and companies. Documents link to properties and tenants through entities.

The full protocol notes, including request formats captured from the web app, are in docs/api.md.

Development

npm install
npm run build
npm test                    # offline: unit tests + in-memory MCP tests against a fake Lendlord

Live tests run against your own account. Put LENDLORD_EMAIL and LENDLORD_PASSWORD in .env, which is git-ignored.

LENDLORD_LIVE_TESTS=1 node --env-file=.env node_modules/vitest/vitest.mjs run test/live.test.ts
LENDLORD_LIVE_WRITE_TESTS=1 LENDLORD_LIVE_UPLOAD_TESTS=1 node --env-file=.env node_modules/vitest/vitest.mjs run test/live-write.test.ts

The write test creates and deletes its own ZZ TEST lendlord-mcp … property, tenant, report, task and documents.

Adding a tool: capture the real request in the web app first, implement it, then add a step to test/live-write.test.ts that reads the result back. Never commit captures: .playwright-mcp/, captures/ and .env are git-ignored.

Before pushing, run npm run check (Biome lint and format check, typecheck, tests). npm run format fixes formatting. CI runs the same checks plus tests on Node 20, 22 and 24, a build and package-contents check, a dependency audit and a Docker smoke test.

Issues and the roadmap are tracked with Beads (bd ready).

Versioning and releases

The project uses Semantic Versioning, driven by Conventional Commits:

Commit / PR title

Release

fix: …

patch (0.1.0 → 0.1.1)

feat: …

minor (0.1.0 → 0.2.0)

feat!: … or a BREAKING CHANGE: footer

major after 1.0. Before 1.0 it bumps minor

docs:, chore:, ci:, test:, refactor:

no release on their own

release-please keeps a release PR open with the next version and CHANGELOG entries. Merging it:

  1. bumps package.json, server.json and CHANGELOG.md, and tags vX.Y.Z;

  2. creates the GitHub release;

  3. publishes ghcr.io/dniasoff/lendlord-mcp:X.Y.Z (and :latest);

  4. publishes to npm via trusted publishing (OIDC, no token) with a signed provenance statement.

To pin a version, use npx -y lendlord-mcp@0.1.0 (or github:dniasoff/lendlord-mcp#v0.1.0) or the matching Docker tag. lendlord-mcp --version and GET /health report the running version.

License

MIT

Available Tools

40 tools
add_communication_messageAdd communication messageA

Add a message to a maintenance report/conversation. kind "note" = internal note (landlord only); kind "message" = reply visible to the tenant.

Inputs: message (required), communicationId (required), kind, confirm.

add_propertyAdd propertyA

Create a property. fields uses Lendlord field names (see list_property_fields), e.g. {"Property Value": 150000, "Date of Purchase": "2024-05-01"}. Owned by the first applicant unless ownerApplicantId is given.

Inputs: address (required), city, fields, status, +3 more.

add_tenantAdd tenantB

Add a tenant to a property. Optional fields may include: phone, mobile, dob, notes, unitName, tenancyStartDate, tenancyEndDate, firstPaymentDate, rentReviewDate (dates YYYY-MM-DD), rentReviewFrequency (Quarterly|Semi-Annually|Annually|Every 2 Years|Every 3 Years|Every 5 Years), generatePaymentScheduleFor (3 months|6 months|12 months|24 months), depositScheme (TDS|DPS|Mydeposits|Zerodeposits|FlatFair|SafeDeposits|Other), depositAmount, guarantor/rollingTenancy/inventoryUndertaken/legionellaInspectionRequired (Yes|No).

Inputs: name (required), propertyId (required), email, fields, +4 more.

attach_transaction_receiptAttach receipt to a bank transactionA

Attach an invoice, receipt or other file to a bank/manual transaction (payment). A transaction holds ONE attachment: attaching again replaces it. Use this instead of upload_document, which only links files to properties and tenants. Provide exactly one of filePath (on the server's machine), url, or base64 + fileName. Accepts PDF, images, Word/Excel/PowerPoint, CSV and text. Use list_transactions (search / amount / hasReceipt) to find the transaction.

Inputs: transactionId (required), url, base64, confirm, +3 more.

compliance_overviewCompliance overviewA
Read-only

Upcoming and overdue expiry dates across the portfolio (EPC, insurance, gas safety, EICR, mortgage rate expiry, …), sorted soonest first.

Inputs: withinDays.

create_maintenance_reportCreate maintenance reportB

Log a maintenance issue on a property. maintenanceType is one of: Air-conditioning, Heating and boiler, Electricity, Internet, Lighting, Plumbing, Water or leaks, Basin, Bathroom and Toilet, Laundry, Alarms and smoke detectors, Security camera system maintenance, Intercom, Doors and locks, Pests/Vermin, Floors walls and ceilings, Structural Issues, Windows, Furniture, Kitchen, Appliances, Mold, Cleaning, Carpentry, Exterior Maintenance, Roof, Garden, Garage, Shared facilities, Bike storage, Bin storage, Chimney, Stairs, Other. Not shared with tenants unless shareWithTenants is true.

Inputs: summary (required), propertyId (required), maintenanceType (required), confirm, +3 more.

create_taskCreate taskC

Add a custom task with a due date, optionally tied to a property.

Inputs: dueDate (required), subject (required), confirm, propertyId, +1 more.

delete_communicationDelete communicationA
Destructive

Permanently delete a maintenance report/conversation (use update_communication archived:true to keep history).

Inputs: communicationId (required), confirm.

delete_documentDelete documentA
Destructive

Remove a document from the property/tenant it is linked to (deletes it if that was its only link).

Inputs: entityId (required), documentId (required), confirm, entityType.

delete_propertyDelete propertyC
Destructive

Permanently delete a property from the portfolio.

Inputs: propertyId (required), confirm.

delete_tenantDelete tenantC
Destructive

Permanently delete a tenant.

Inputs: tenantId (required), confirm.

file_emailFile email to propertyA

Store an email (and its attachments) as documents on a property or tenant. Pass either emlBase64 (raw RFC 822 message) or subject/from/to/date/text/html. Attachments are uploaded as separate documents named " - - ".

Inputs: entityId (required), to, date, from, +7 more.

find_propertyFind propertyA
Read-only

Rank properties against free text such as an email's subject/body, an address, postcode, tenant name/email, or a policy/certificate number. Use before file_email to pick the property.

Inputs: text (required), limit.

get_accountGet accountA
Read-only

The Lendlord account: user, team role, currency/country settings, applicants (property owners), and plan usage limits. Excludes property data (use list_properties).

Inputs: no named parameters.

get_communicationGet communicationC
Read-only

One maintenance report or conversation, including its messages.

Inputs: communicationId (required), type.

get_profit_and_lossGet profit and lossB
Read-only

P&L per property by month, with the category list used for transactions.

Inputs: no named parameters.

get_propertyGet propertyB
Read-only

Every stored field for one property, plus its tenants and the documents linked to it.

Inputs: propertyId (required).

get_rent_collectionGet rent collectionB
Read-only

Expected rent payments (per tenant, with due dates and matched transactions) between two dates.

Inputs: to (required), from (required).

get_tenantGet tenantB
Read-only

Every stored field for one tenant, plus documents linked to them.

Inputs: tenantId (required).

get_transactionGet transactionA
Read-only

One bank/manual transaction with all its fields. If it has a receipt, receipt holds its name and a temporary download link.

Inputs: transactionId (required).

lendlord_requestRaw Lendlord API requestA
Destructive

Call any Lendlord /api endpoint directly (encryption handled). Use for features without a dedicated tool; see docs/api.md for endpoints. GET is read-only; other methods change data and need write confirmation.

Inputs: path (required), method (required), body, query, +1 more.

list_communicationsList communicationsA
Read-only

Maintenance reports and other tenant/landlord communications, newest first. Fetches Lendlord's 100-item pages as needed up to limit (max 1000); continue with skip = nextSkip while hasMore is true.

Inputs: skip, type, limit, propertyId.

list_documentsList documentsB
Read-only

Documents stored in Lendlord, with the entities (property, tenant, …) each is linked to. Optionally filter by linked entity id or name text.

Inputs: search, entityId.

list_propertiesList propertiesB
Read-only

All properties in the portfolio with key figures (address, postcode, value, rent, mortgage). Optionally filter by free text matched against address/postcode.

Inputs: search, status.

list_property_fieldsList property fieldsA
Read-only

Field names accepted by add_property / update_property, and the document slot keys for upload_document.

Inputs: no named parameters.

list_tasksList tasks and alertsB
Read-only

Tasks (custom and Lendlord-generated, e.g. MISSING_DUE_DATES) and alerts (e.g. Payment Overdue). Defaults to active ones.

Inputs: status, category.

list_tenantsList tenantsA
Read-only

Tenants (actual and prospect), optionally for one property. Each tenant is linked to exactly one property (propertyId + propertyAddress); propertyMissing means it points at a property that no longer exists. Fix a wrong link with update_tenant { propertyDetailsId }.

Inputs: propertyId.

list_transactionsList transactionsA
Read-only

Bank/manual transactions between two dates, with optional filters to find the one you want (e.g. the payment an invoice belongs to). A transaction's file is its attached receipt (see attach_transaction_receipt).

Inputs: endDate (required), startDate (required), type, amount, +4 more.

log_communicationLog communication historyA

Record a past email thread, call or letter against a property (issue #3). Lendlord has no generic communication log and landlords cannot start a "conversation" without a tenant, so this is stored as a back-dated maintenance report (default type "Other", status closed, not shared with tenants) whose description lists the date, counterparties and summary. It then appears in list_communications/get_communication with the original date. Use file_email as well to keep the full message and attachments as documents.

Inputs: date (required), subject (required), summary (required), propertyId (required), +6 more.

remove_transaction_receiptRemove a transaction's receiptA

Remove the file attached to a bank/manual transaction. The transaction itself is not changed.

Inputs: transactionId (required), confirm.

rename_documentRename documentC

Change a document's display name.

Inputs: name (required), documentId (required), confirm.

send_documentSend document by emailA

Email a stored document to someone (Lendlord sends it with the file attached). Replies go to the account's email unless replyTo is given. This sends a real email.

Inputs: to (required), subject (required), documentId (required), cc, +3 more.

update_communicationUpdate communicationC

Change a maintenance report/conversation: status (open|closed), archived, summary, description, maintenanceType, shared.

Inputs: communicationId (required), shared, status, confirm, +4 more.

update_propertyUpdate propertyA

Change fields on a property. Only the fields you pass change; set a field to null to clear it. Field names from list_property_fields. Dates as YYYY-MM-DD (DD/MM/YYYY also accepted). Money accepts 150000, "£150,000" or "150k". "Internal Area" is square metres ("73", "73 sq m"; "790 sq ft" is converted). Percentages (rates, agent fee, ownership) are stored as fractions: send "4.5%" or 4.5 (stored as 0.045); values of 1 or less are taken as fractions already. Invalid values are rejected with a field-specific error.

Inputs: fields (required), propertyId (required), confirm.

update_property_imagesUpdate property PhotosA

Add photos to a property's Photos gallery, remove selected gallery images by key, or replace images by adding new files and removing old keys together. This is separate from upload_document, which stores files under Documents. Provide one or more image files and/or removeImageKeys. Image keys are the keys shown by get_property under fields.images.

Inputs: propertyId (required), files, confirm, removeImageKeys.

update_taskUpdate taskB

Edit a task, or change its status: completed, active (reopen) or deleted.

Inputs: taskId (required), status, confirm, dueDate, +2 more.

update_tenantUpdate tenantA

Change fields on a tenant (same field names as add_tenant). Only the fields you pass change; null clears. Move a tenant to another property with { propertyDetailsId: }.

Inputs: fields (required), tenantId (required), confirm.

update_transactionUpdate transactionA

Change the property or category linked to a bank/manual transaction. Only the fields you pass change. The existing receipt is kept; this does not upload or remove a receipt.

Inputs: transactionId (required), confirm, categoryId, propertyDetailsId.

upload_documentUpload documentA

Upload a file to Lendlord and link it to a property (or tenant). To attach a file to a bank transaction (payment) use attach_transaction_receipt instead. Provide exactly one of filePath (on the server's machine), url, or base64 + fileName. key is a document slot from list_property_fields (e.g. gasCertificate, epc, eicr, insurancePolicy); any other key creates a custom document. Uses the account's upload quota.

Inputs: key (required), entityId (required), url, name, +6 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3.3/5.0

    Scored across 40 tools

    Disambiguation4/5

    Most tools target distinct resource+action pairs, and descriptions explicitly distinguish overlapping areas such as upload_document vs. attach_transaction_receipt vs. link_document vs. file_email. However, the generic lendlord_request and the maintenance-report workaround used by log_communication still require careful reading and could invite occasional misselection.

    Naming Consistency4/5

    Nearly all tool names use snake_case with a clear verb_noun pattern, such as list_tenants, add_property, update_tenant, and delete_document. A few names break the pattern slightly (compliance_overview, lendlord_request, get_profit_and_loss), but the set remains readable and predictable.

    Tool Count2/5

    With 40 tools, the server is far beyond the typical 3–15 well-scoped range and exceeds the 25+ threshold for being too heavy. While the property-management domain is broad, this many tools increases selection burden and suggests consolidation is needed.

    Completeness4/5

    Coverage is broad: CRUD for properties, tenants, communications, tasks, and documents, plus financials, compliance, rent collection, account details, and a raw API fallback. Minor gaps remain, such as no direct create/delete transaction tool and no get-single-task tool, but lendlord_request can work around less common operations.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI assistants to operate property management systems via natural language, covering repair orders, owner info, payments, notices, and inspections. Features a full agentic workflow with human-in-the-loop and observability.
      1
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables an AI agent to control one or more remote GPIO agents through a conversation by proxying tool calls to their REST APIs. It supports API-key authentication, LAN discovery, and both stdio and Streamable HTTP transports.
      18
      49 npm
      1
      MIT