Rentvine MCP
Rentvine MCP (Rentor fork)
MCP server for Rentvine — gives Claude (and any MCP client such as Voice Agents) live access to your property management data.
Rentvine stopped maintaining their MCP Server on Apr 25, 2026 leaving Property Management Companies to fork it and continue maintaing it themselves.
Fork notice. This is Rentor's fork of the upstream
rentvine-mcpnpm package (v1.3.2, MIT, by Base Homes). Upstream is deprecated on npm and its GitHub repo has been deleted, so this repo is now the maintained line. TypeScript sources here were reconstructed from the publisheddist/— the original package shipped compiled JS only.Do not
npm install rentvine-mcp— that pulls the dead upstream. Build from this repo instead.
Available Tools
Tool | Description |
| All properties with address, type, and active status |
| Units for a named property with vacancy and rent |
| All leases with tenant, rent, dates, and status |
| Rental applications with applicant and status |
| Maintenance inspections with date and inspector |
| Work orders with status and priority |
| Create a new maintenance work order |
| Update status, priority, cost, or scheduling on a work order |
| All tenants with contact details and status; |
| Ledger balance for a named tenant |
| All property owners |
| All vendors with full contact, insurance, billing, and audit fields (45 fields) |
| Single vendor's full detail record — includes |
| Vendors within N miles of a property, sorted by distance (ZIP-centroid approximation) |
| All portfolios |
| All bills |
| Create a new bill |
| Search accounting transactions by date, amount, or keyword |
| Chart of accounts |
| Rentvine object type IDs (for file attachment) |
| Upload a file and attach it to a property, unit, lease, or work order |
| Files attached to any Rentvine object (by object ID + type) |
| Images and files attached to a specific work order |
| Metadata for a single file (name, size, mime type) — no download |
| Download a file as base64 (images, PDFs, up to ~375 KB) |
Install
This is a hosted service, not a local tool. Deploy it once on a Linux server under pm2, front it with nginx over HTTPS, and point every client at that URL. Claude, ChatGPT, voice agents, and teammates all connect to the same endpoint with a bearer token.
The server holds your Rentvine API credentials. Clients hold only the endpoint
URL and MCP_AUTH_TOKEN — no keys are distributed to laptops.
Run one process per Rentvine account, each on its own port:
Environment | Public (nginx) | Internal (node) |
prod |
|
|
dev |
|
|
Prerequisites: Ubuntu (or similar), Node.js 18+, nginx, and a TLS certificate for your host (certbot is fine). Plus your Rentvine API credentials from Settings → Users, Roles & API.
1. Clone and build
cd /opt # or wherever you keep services
git clone https://github.com/Rentor-CA/Rentvine-MCP.git
cd Rentvine-MCP
npm install # do NOT set NODE_ENV=production here
npm run buildDo not set
NODE_ENV=productionfor the install. npm skips devDependencies, TypeScript never installs, andnpm run builddies withtsc: not found. Install normally, build, then setNODE_ENVwhen you run the server. To slim the install afterwards:npm prune --omit=dev.
Use the HTTPS clone URL — the repo is public, so it needs no credentials.
The SSH form (git@github.com:…) requires a key on the machine regardless of
repo visibility, so save it for boxes where you intend to push.
This runs from the clone and installs nothing globally, so it won't disturb an
existing rentvine-mcp (the legacy upstream package) already on the box — run
both on different ports while you migrate, then retire the old one.
2. Create the launcher
cp start-mcp.sh.example start-mcp.sh
chmod +x start-mcp.sh
$EDITOR start-mcp.sh # fill in keys, subdomains, and MCP_AUTH_TOKENGenerate a token per environment with openssl rand -hex 32. start-mcp.sh is
gitignored — it holds live credentials and must never be committed.
The script resolves dist/http.js relative to itself, validates that every
credential is set, and execs node so pm2 supervises the server directly
instead of a wrapper shell.
3. Start under pm2
npx pm2 start ./start-mcp.sh --name rentvine-prod -- prod
npx pm2 start ./start-mcp.sh --name rentvine-dev -- dev
npx pm2 save # persist the process list
npx pm2 startup # prints a command to run — restarts pm2 on bootThe -- prod after the script name is what gets passed to the script as $1,
selecting the credential block. Everything before -- is a pm2 flag.
pm2 save plus pm2 startup are what make this survive a reboot. save writes
the current process list to ~/.pm2/dump.pm2; startup prints a sudo command
you must actually run to install the systemd unit. Run both, or the processes
are gone after a restart. Re-run pm2 save any time you add or rename a
process.
pm2 command reference
Inspect
npx pm2 list # status table: name, pid, uptime, restarts, cpu, memory
npx pm2 describe rentvine-dev # full details for one process: script path, args, log paths, env
npx pm2 monit # live dashboard (cpu/mem/logs); q to quit
npx pm2 jlist # same as list but JSON — for scripting/monitoring
npx pm2 prettylist # JSON, human-formatteddescribe is the one to reach for when a process behaves unexpectedly — it
shows the resolved script path, the -- prod/-- dev argument it started with,
its restart count, and where its logs live.
Logs
npx pm2 logs # tail all processes, interleaved
npx pm2 logs rentvine-prod # tail one
npx pm2 logs rentvine-prod --lines 200 # last 200 lines then follow
npx pm2 logs --err # stderr only — startup failures land here
npx pm2 flush # truncate all log filesLogs are written to ~/.pm2/logs/<name>-out.log and <name>-error.log. They
are not rotated by default; install pm2-logrotate to avoid filling the disk:
npx pm2 install pm2-logrotateLifecycle
npx pm2 restart rentvine-prod # restart one
npx pm2 restart all # restart everything
npx pm2 stop rentvine-dev # stop but keep it in the list
npx pm2 start rentvine-dev # start a stopped process by name
npx pm2 delete rentvine-dev # remove from pm2 entirely (then pm2 save)restart fully replaces the process, so it re-reads start-mcp.sh and picks up
credential changes. Editing the script alone changes nothing until you restart.
pm2 reload (zero-downtime) does not help here — it is for clustered Node
apps, and these are forked shell scripts.
Persistence
npx pm2 save # snapshot current process list
npx pm2 resurrect # restore from the snapshot
npx pm2 startup # print the boot-persistence install command
npx pm2 unstartup # undo itUpgrading to a new version
cd /opt/Rentvine-MCP
git pull
npm install
npm run build
npx pm2 restart all
npx pm2 logs --lines 20 # confirm both came back clean4. Expose it over HTTPS
The Node server listens on loopback only — reachable from the server itself, not from the internet. nginx holds the public TLS port and forwards inward. One public port per environment:
internet ──▶ your-host:8003 (nginx, TLS) ──▶ 127.0.0.1:18003 (node) prod
internet ──▶ your-host:8004 (nginx, TLS) ──▶ 127.0.0.1:18004 (node) dev
public loopback, unreachable
from outsideCertificates and TLS termination live in nginx; Node never faces raw internet traffic. The endpoint is still fully public — the proxy is what makes it so.
Responses are Server-Sent Events, so proxy buffering must be off. With
default buffering the connection appears to hang and clients time out. The four
lines that matter are proxy_http_version 1.1, proxy_set_header Connection "",
proxy_buffering off, and a long proxy_read_timeout.
# ---- prod : https://your-host.example.com:8003/mcp ----
server {
listen YOUR.SERVER.IP:8003 ssl;
server_name your-host.example.com;
ssl_certificate /etc/letsencrypt/live/your-host.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-host.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
access_log /var/log/nginx/mcp-prod.access.log;
error_log /var/log/nginx/mcp-prod.error.log;
location / {
proxy_pass http://127.0.0.1:18003;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Connection "";
# SSE: buffering off, or streamed responses stall.
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
# ---- dev : https://your-host.example.com:8004/mcp ----
# Same block with 8004 -> 127.0.0.1:18004 and its own log files.Enable and reload:
sudo ln -s /etc/nginx/sites-available/rentvine-mcp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxlocation / proxies everything, so /health and /mcp both pass through.
listen is pinned to a specific IP here; plain listen 8003 ssl; binds all
interfaces. Open the ports if a firewall is active: sudo ufw allow 8003/tcp.
nginx does not authenticate anything here. It forwards every request straight through, so
MCP_AUTH_TOKENin the Node process is the only access control on the endpoint. See the warning below.
5. Verify
HOST=https://your-host.example.com:8003
INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
# 1. Health — public by design, returns only {"ok":true}
curl -sS $HOST/health
# 2. No token — MUST be 401
curl -sS -o /dev/null -w '%{http_code}\n' -X POST $HOST/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' -d "$INIT"
# 3. With token — expect the initialize result
curl -sS -X POST $HOST/mcp \
-H "Authorization: Bearer $MCP_AUTH_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' -d "$INIT"
# event: message
# data: {"result":{...,"serverInfo":{"name":"rentvine",...}},...}If step 2 returns anything other than 401, stop and fix it — your Rentvine account is exposed to the internet. See the warning below.
⚠️
MCP_AUTH_TOKENis mandatory hereThe server refuses to start without a token only when binding to a non-loopback address. This deployment binds to
127.0.0.1, which is exempt from that check — so an emptyMCP_AUTH_TOKENstarts happily and the reverse proxy then publishes an unauthenticated/mcpto the world. The built-in guard cannot save you behind a proxy.Anyone reaching that endpoint gets full read/write access to your Rentvine account: tenant PII, ledgers, and the ability to create work orders and bills.
start-mcp.sh.examplerefuses to start on an empty or placeholder token for this reason — keep that check.
/healthis intentionally unauthenticated and returns only{"ok":true}.
6. Connect clients
Every client uses the same two values — the URL and the bearer token. No Rentvine credentials ever leave the server.
URL |
|
Transport | Streamable HTTP |
Auth |
|
Claude Code / Claude Desktop / Cursor / Windsurf / VS Code
{
"mcpServers": {
"rentvine": {
"type": "http",
"url": "https://your-host.example.com:8003/mcp",
"headers": { "Authorization": "Bearer your_mcp_auth_token" }
}
}
}Config locations — Claude Code: ~/.claude.json or project .mcp.json ·
Cursor: Settings → MCP → Add new server · Windsurf:
~/.codeium/windsurf/mcp_config.json · VS Code (Copilot): .vscode/mcp.json.
Restart the client; you should see rentvine with all 25 tools.
Vapi / voice agents
Add as a custom MCP tool provider with the URL above and an Authorization
header of Bearer <MCP_AUTH_TOKEN>. Voice agents call tools with no human
reviewing arguments first, so give them a token you can rotate independently
and consider a read-only Rentvine API key — create_work_order, create_bill,
update_work_order, and upload_file all write to live data.
ChatGPT (Business / Enterprise / Edu)
An admin enables Workspace Settings → Permissions & Roles → Developer Mode,
then: Settings → Connectors → Advanced → Add custom MCP server, URL as
above, Auth Bearer → the MCP_AUTH_TOKEN. Pick Developer mode from the
Plus menu in a new chat and select the rentvine connector. Not available on
Plus or Free.
Anything else
Any MCP client supporting Streamable HTTP works — point it at the URL with the bearer header.
Sessions live in memory, keyed by the
mcp-session-idheader. Apm2 restartdrops active sessions and clients reinitialize on their next call. If you ever run more than one replica behind the proxy, you need sticky routing on that header.
Environment Variables
Variable | Description |
| Your Rentvine API key |
| Your Rentvine API secret |
| Your subdomain (e.g. |
| Bearer token clients must present on |
| HTTP server port (default: |
| Bind address (default: |
All of these are set in start-mcp.sh, one block per environment.
Testing
After install, ask Claude things like:
List all my properties.
How many units are vacant across all properties?
Which leases expire in the next 60 days?
Show me all open work orders sorted by priority.
What is the balance for tenant [name]?
Create a work order for the leaking roof at 123 Main St, high priority.
Upload this invoice and attach it to work order #1042.
Show me all unpaid bills.
Which vendors have liability insurance expiring in the next 60 days?
Find vendors within 25 miles of property [ID].
Show me all photos attached to work order [ID].
Download the inspection report file [ID].Development
SSH clone here, since contributors push:
git clone git@github.com:Rentor-CA/Rentvine-MCP.git
cd Rentvine-MCP
npm install
npm run build # tsc → dist/
npm run typecheck # tsc --noEmit, no outputSmoke-test the HTTP transport the same way it runs in production:
HOST=127.0.0.1 PORT=18009 MCP_AUTH_TOKEN=dev-token \
RENTVINE_API_KEY=... RENTVINE_API_SECRET=... RENTVINE_COMPANY=... \
node dist/http.js
# another shell
curl -sS localhost:18009/health
curl -sS -X POST localhost:18009/mcp -H 'Authorization: Bearer dev-token' \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Deploy by pushing, then on the server: git pull && npm install && npm run build && npx pm2 restart all.
Layout
src/
index.ts stdio entrypoint
http.ts Streamable-HTTP entrypoint (Express, in-memory sessions)
createServer.ts tool registry — 24 registerTool() calls + zod schemas
tools.ts projections: Rentvine camelCase → our snake_case contract
client.ts HTTP layer: Basic auth, envelope unwrapping, timeouts
apiDocs.ts Rentvine API reference, served as rentvine://api-docs
types/ ambient .d.ts for untyped deps (zipcodes)Adding a tool
Three edits, in order:
src/client.ts— add afetchX()that calls the endpoint and returns the raw rows.src/tools.ts— add an exported function projecting raw → snake_case.src/createServer.ts—server.registerTool("x", { description, inputSchema }, handler).
Endpoints documented in src/apiDocs.ts but not yet wrapped include the eleven
/accounting/diagnostics/* routes, /maintenance/vendor-trades,
/leases/{leaseID}, /properties/units/export, and
/accounting/transactions/entries/search.
Known issues
Work-order enum maps are unverified.
WO_STATUS/WO_PRIORITY/LEASE_STATUSinsrc/tools.tsdisagree with the enum tables insrc/apiDocs.ts(which document status as 1=Pending, 2=Open, 3=Closed, 4=On Hold and priority as 1–3 only, with noemergency). If the docs are right,update_work_order(status: "cancelled")sets the work order to On Hold. Verify againstGET /maintenance/work-order/statusesbefore trusting either.No pagination on list tools.
list_properties,list_leases,list_work_orders,list_bills,list_vendors, andlist_accountssend nopage/pageSize, and Rentvine defaults to 15–25 rows. They will silently truncate as the portfolio grows. Onlysearch_transactionsexposes paging.unwrap()returns[]on unrecognized shapes (src/client.ts), so an API contract change reads as "no results" rather than an error.Fuzzy lookups take first match.
list_unitsandget_tenant_balancesubstring-match names and silently pick the first hit.list_tenantspaging is unverified.page/page_sizeare passed through toGET /tenants, but that endpoint's paging behavior is unconfirmed. If Rentvine ignores them, results are silently capped at its default page size.searchandactive_onlyare applied client-side, after the fetch, so they filter only what came back on that page.
Handling tenant PII
Rentvine serves tenants, vendors, and owners from one shared contact schema, so
every tenant record carries birthDate, identificationNumber,
identificationTypeID, and achAccountNumberTruncated — real PII on a consumer.
list_tenants therefore splits its projection: identity, contact, status, and
audit fields by default; date of birth, government ID, tax/payee, and
payout/ACH fields only when include_sensitive=true. Without that split, a bare
"list the tenants" request would put every tenant's DOB and bank details into
the model's context window.
If you'd rather have the full record by default, drop the include_sensitive
branch at the end of listTenants() in src/tools.ts and always spread
projectTenantSensitive(c).
Troubleshooting
{"error":"unauthorized"} from /mcp — the client's bearer token doesn't
match MCP_AUTH_TOKEN. This is the server's own 401, not Rentvine's.
Rentvine 401 Unauthorized inside a tool result — wrong RENTVINE_API_KEY,
RENTVINE_API_SECRET, or RENTVINE_COMPANY. Verify in Rentvine → Settings →
Users, Roles & API.
Client connects but hangs on the first call — nginx is buffering. Confirm
proxy_buffering off, proxy_http_version 1.1, and proxy_set_header Connection ""
are in the location block; responses are SSE and stall without them.
502 Bad Gateway — node isn't running or is on a different port than
proxy_pass. Check with npx pm2 list and:
sudo ss -tlnp | grep -E ':(8003|8004|18003|18004)'
npx pm2 logs rentvine-prod --lines 50Server won't start — npx pm2 logs <name>. FATAL: MCP_AUTH_TOKEN must be set… means HOST isn't loopback and no token is set. FATAL: <VAR> is unset or still CHANGE_ME is the launcher's own check. tsc: not found during build means
NODE_ENV=production was set during npm install.
Gone after a reboot — npx pm2 save and npx pm2 startup were never run.
Confirm the token is actually set in the live process (prints 1 or 0,
never the secret):
sudo tr '\0' '\n' < /proc/$(pgrep -f 'dist/http.js' | head -1)/environ \
| grep -c '^MCP_AUTH_TOKEN=.\+'Empty results — your Rentvine account may have no data in that category, the
list endpoint truncated at Rentvine's default page size, or the API returned an
unexpected envelope (see unwrap() under Known issues).