Are you the author? Sign in to claim
A Model Context Protocol (MCP) server that wraps AppFolio's REST API so Claude can call it natively in any conversation
A Model Context Protocol (MCP) server that wraps AppFolio's REST API so Claude can call it natively in any conversation — the same way Quo works. 38 tools covering portfolio structure, leasing, financials, maintenance, and admin.
When this is deployed and connected, you can say things like:
And Claude calls the AppFolio API directly.
AppFolio's public API does NOT expose a /budgets endpoint. I can pull actuals from the GL all day (that's what build_pl_summary and build_cash_flow do), but the budget side has to come from somewhere else:
Once budgets are available in the conversation, Claude can do budget-vs-actual comparisons against the actuals this server returns. If AppFolio adds a budgets endpoint to a newer API version, the pattern to add it is identical to the existing tools — let me know and I'll wire it up.
What this server gives you on the financial side:
build_pl_summary) — income / expense / NOI by GL account for a property and date rangebuild_cash_flow) — monthly trend of cash in / cash out / netget_gl_details, get_gl_accounts, get_journal_entries, get_bank_accounts)For the actual official month-end reports you sign off on, keep using AppFolio's UI — this server is for the day-to-day questions you'd otherwise have to log in to find.
No server to rent, no domain to buy. Cloudflare gives you a free *.workers.dev URL.
npm install
npx wrangler login
npx wrangler secret put APPFOLIO_CLIENT_ID
npx wrangler secret put APPFOLIO_CLIENT_SECRET
npx wrangler secret put APPFOLIO_DEVELOPER_ID
Paste each value when prompted.
npm run deploy
You'll get a URL like https://appfolio-mcp.<your-subdomain>.workers.dev. Test it in a browser — root should return a JSON health check showing 38 tools.
AppFoliohttps://appfolio-mcp.<your-subdomain>.workers.dev/sseClaude discovers all 38 tools. Done.
| Tool | Purpose |
|---|---|
get_properties | List properties; filter by type or date |
get_property_groups | Property groupings (e.g. by city or manager) |
get_portfolios | Top-level portfolios |
get_units | Units across portfolio |
get_unit_types | Unit type / floor-plan definitions |
get_owners | Property owners |
get_owner_groups | Ownership groups + percentages |
| Tool | Purpose |
|---|---|
get_tenants | Tenants; filter by status (Current/Past/Future/Notice) |
get_rent_roll | Composite — units + current tenants joined |
get_leases | Lease records |
get_lease_renewal_pricings | Approved renewal pricing options |
get_leads | Leasing leads / guest cards |
get_rental_applications | Applications by property/unit/date |
get_showings | Tour appointments |
get_listings | Active marketing listings |
| Tool | Purpose |
|---|---|
get_charges | Open charges (good for duplicate detection) |
get_delinquent_charges | Past-due charges |
get_recurring_charges | Monthly/recurring billings |
get_tenant_ledgers | Charges + payments + credits by occupancy |
| Tool | Purpose |
|---|---|
get_bills | Accounts payable |
get_payables | Outgoing payments / checks |
| Tool | Purpose |
|---|---|
get_gl_accounts | Chart of accounts |
get_gl_details | GL transaction detail (raw) |
get_journal_entries | Manual journal entries |
get_bank_accounts | Bank accounts |
build_pl_summary | Composite — P&L by GL account for property + date range |
build_cash_flow | Composite — monthly cash in/out/net by month |
| Tool | Purpose |
|---|---|
get_work_orders | Work orders; filter by status |
get_work_order_attachments | Photos/PDFs on work orders |
get_open_work_orders | Composite — open WOs with vendor + age |
get_work_order_aging | Composite — WOs bucketed by 0-7/8-30/31-60/61-90/90+ days |
get_vendors | Vendor list |
get_vendor_compliance | Composite — expired/expiring insurance/contracts/workers comp |
get_inventories | Maintenance parts/supplies inventory |
get_inventory_locations | Where inventory is stored |
| Tool | Purpose |
|---|---|
get_users | AppFolio Property Manager users |
get_custom_fields | Custom field definitions |
get_jobs | Status of bulk async jobs (from bulk-creates) |
All list-type tools auto-paginate up to 3 pages by default (300 records). Override with max_pages (cap 10).
Rent roll
"Pull the rent roll for property
<id>and tell me what % is occupied vs vacant."
P&L summary
"Build a Cash P&L for Greensborough (
<id>) from 2026-01-01 to 2026-03-31. Show NOI."
Cash flow trend
"Run a cash flow report for Russellville for the last 12 months. Highlight the months below $5K net."
Open work orders by age
"Show me work orders open more than 60 days, grouped by property."
Vendor compliance
"Which vendors have insurance expiring in the next 30 days?"
cp .dev.vars.example .dev.vars
# Fill in real credentials in .dev.vars (gitignored).
npm run dev
Local URL: http://localhost:8787/sse.
npm run logs
Or Cloudflare dashboard → Workers & Pages → appfolio-mcp → Logs.
Common issues:
wrangler secret put commands.get_gl_details returning 400: It requires date_from + date_to AND property_id unless the window is ≤2 days. The composite tools (build_pl_summary, build_cash_flow) handle this for you.This version is read-only on purpose. Writes (creating bills, updating leads, posting charges) touch money and legal documents. Before adding any create_* or update_* tool:
When you're ready, ask me to add a specific write tool with these safeguards.
┌─────────────┐ MCP/SSE ┌──────────────────┐ HTTPS+Basic ┌──────────────┐
│ Claude UI │ ◄────────────────►│ Cloudflare │ ◄────────────────►│ AppFolio │
│ │ │ Worker (this) │ │ REST API │
└─────────────┘ └──────────────────┘ └──────────────┘
│
▼
Cloudflare Secrets
(CLIENT_ID, SECRET,
DEVELOPER_ID)
Composite tools (build_pl_summary, build_cash_flow, get_rent_roll, get_open_work_orders, get_work_order_aging, get_vendor_compliance) make multiple AppFolio API calls server-side and stitch the results together before returning. This keeps Claude's context clean and respects rate limits.
Cost: well within Cloudflare's free tier for personal/small-team use.
appfolio-mcp/
├── src/
│ ├── index.ts # Worker entrypoint — wires up tool modules, /sse + /mcp routing
│ ├── client.ts # AppFolio REST client — auth, pagination, 429 retry
│ ├── util.ts # Shared helpers + zod schemas (dateFromSchema, maxPagesSchema, ...)
│ ├── list-tool.ts # Factory for the common "list entities" tool shape
│ └── tools/
│ ├── portfolio.ts # Portfolio & structure (7)
│ ├── leasing.ts # Leasing & occupancy (8), incl. rent roll composite
│ ├── financial.ts # Receivables, payables, GL & reporting (12), incl. P&L/cash flow composites
│ ├── maintenance.ts # Work orders, vendors, inventory (8), incl. composites
│ └── admin.ts # Admin (3)
├── package.json # Dependencies + npm scripts
├── tsconfig.json # TypeScript config
├── wrangler.toml # Cloudflare Worker config
├── .dev.vars.example # Template for local dev secrets
├── .gitignore
└── README.md # You are here
Most simple list-style tools (properties, units, owners, vendors, etc.) are registered via registerListTool in list-tool.ts to avoid repeating the same id/property_id/last-updated/pagination boilerplate ~20 times. Tools with required fields, "one of X is required" validation, or multi-call composite logic (build_pl_summary, get_rent_roll, get_vendor_compliance, etc.) are hand-written in their category file.
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows