Rentvine MCP
A hosted MCP server that gives Claude, ChatGPT, Cursor, and voice agents live read/write access to a Rentvine property-management account via 25 tools.
Properties & units — list all properties (address, type, active status); list units for a named property with vacancy, rent, and deposit.
Portfolios & owners — list portfolios and all property owners with contact details.
Leases & applications — list leases (tenant, rent, dates, status, bed/bath) and rental applications.
Inspections — list maintenance inspections with date, status, and inspector.
Work orders — list, create, and update work orders (status, priority, scheduling, cost, owner approval, vendor assignment).
Tenants — list/search tenants with
searchandactive_onlyfilters; sensitive PII (DOB, gov ID, ACH) withheld unlessinclude_sensitive=true. Get a tenant's ledger balance.Vendors — list all vendors (45 fields incl. insurance, billing, audit), fetch one vendor's full record, and find vendors within N miles of a property by ZIP-centroid distance.
Accounting — list bills, create bills, search transactions by date/amount/keyword, and list the chart of accounts.
Files — upload files and attach them to a property, unit, lease, or work order; list attachments on any object or work order; get file metadata; download files as base64 (~375 KB–1 MB cap).
Reference lookup — list Rentvine object type IDs for file attachment.
Access control — connect over Streamable HTTP with a bearer token; separate read-only (
/mcp/read) and write-only (/mcp/write) endpoints, plus optional attribution headers for agent platforms.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Rentvine MCPWhat are the open work orders at 123 Main St?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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) |
Related MCP server: Property MCP Server
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. Get the code onto the server
Option A — servers with no git/npm access (zip upload). Build a
self-contained zip on your laptop, upload it, unzip it. No build step, no
npm install, and no network needed on the server:
# on your laptop, in the repo
npm run package
# -> build/rentvine-mcp-<version>-<sha>.zip (~6 MB)The zip contains dist/, node_modules/ (production deps only),
package.json, start-mcp.sh.example, and a DEPLOY.txt with these same
steps. Upload it however you like (scp, or any file host + curl -LOJ), then:
sudo mkdir -p /opt/rentvine-mcp
sudo chown -R "$USER":"$USER" /opt/rentvine-mcp
unzip -o rentvine-mcp-*.zip -d /tmp
cp -r /tmp/rentvine-mcp/. /opt/rentvine-mcp/
cd /opt/rentvine-mcpRe-run npm run package after every change and repeat the upload — the
server has no way to rebuild. The filename carries the version and git SHA
(plus -dirty for uncommitted changes) so you can tell what's deployed.
Requires Node.js 18+ on the server. Nothing else.
Install to /opt/rentvine-mcp, owned by the user that will run pm2 — not
root:
sudo mkdir -p /opt/rentvine-mcp
sudo chown -R "$USER":"$USER" /opt/rentvine-mcp
git clone https://github.com/Rentor-CA/Rentvine-MCP.git /opt/rentvine-mcp
cd /opt/rentvine-mcp
npm install # do NOT set NODE_ENV=production here
npm run build/opt is root-owned by default, so cloning into it as a normal user fails with
Permission denied — hence the chown. Don't work around that with
sudo git clone: everything ends up root-owned, and then git pull,
npm install, and npm run build all need sudo too, while pm2 runs as your
login user and can't write its own build output. npm also behaves badly as root.
Keep the directory owned by the same user that runs npx pm2 — pm2 startup
installs a systemd unit bound to one specific user, and ~/.pm2 lives in that
user's home.
Do 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.
Either way, the legacy rentvine-mcp already on the box is untouched — give the
new instance its own port (e.g. 18005 internal / 8005 public) and run both
until you cut over.
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 |
|
Read and write endpoints. Besides /mcp (every tool), the server serves
the same token on two narrower paths:
Path | Tools |
| Everything that only reads — nothing changes in Rentvine |
| Only |
A client that should never change data connects to /mcp/read only; an agent
platform can connect to both and require a person's approval for every call on
/mcp/write. Both are stateless: each request gets its own server holding
only that path's tools, so there are no sessions to lose on a restart and any
replica can answer. /mcp keeps in-memory sessions as before; an unknown or
ended session there now gets 404 (the MCP spec's cue to start a new session).
upload_file's file_path reads a file from the server's disk. Over
stdio that's your machine; over HTTP it's the server, so a caller could upload
any file the server can read — its own credentials included. /mcp keeps it as
before (set RENTVINE_ALLOW_FILE_PATH=0 to turn it off — recommended once no
client relies on it); /mcp/read and /mcp/write never offer it (send
file_content_base64 + file_name).
Who a call is for. On /mcp/read and /mcp/write an agent platform can
send x-rentor-user (the person's email) and x-rentor-bot (the bot). Every
tool call — on any endpoint — writes one JSON line to stdout:
{"event":"tool_call","endpoint":"/mcp/write","tool":"create_work_order","user":"…","bot":"…","ok":true,"ms":412}
— never the arguments or the result. The headers are attribution, not access
control: any client holding the token could set them.
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 apply to
/mcponly. They live in memory, keyed by themcp-session-idheader, so apm2 restartends them. The server then answers 404, which is the MCP spec's cue for a client to start a new session — but not every client acts on it. One agent platform kept replaying the dead ID and lost every Rentvine tool until it was itself restarted./mcp/readand/mcp/writeare stateless, so there is nothing to lose on a restart; prefer them for agent platforms. Likewise, running more than one replica behind the proxy needs sticky routing on that header for/mcp— the stateless endpoints need none.
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.
Docker (optional — not how we deploy this)
This is not our deployment path. Rentor runs this under pm2 behind nginx, as described in Install — that is the supported setup, the one every other section assumes, and the one to use. The
Dockerfileis an unsupported convenience for running the server somewhere else. Nothing in our deployment builds it or depends on it, and you never need Docker to install, run, or develop this server.
docker build -t rentvine-mcp .
docker run --rm --init -p 127.0.0.1:18009:3000 \
-e RENTVINE_API_KEY -e RENTVINE_API_SECRET -e RENTVINE_COMPANY -e MCP_AUTH_TOKEN \
rentvine-mcpThe image runs dist/http.js as the unprivileged node user on 0.0.0.0:3000
(so MCP_AUTH_TOKEN is required — it refuses to start without one) with a
/health check. Put TLS in front of it as for pm2.
Nothing is pinned inside the image: PORT and HOST are defaults, so
-e PORT=18009 moves the listener and the healthcheck follows it. EXPOSE 3000
is only metadata — publish whatever you want with -p.
Use --init as shown. The server registers no SIGTERM handler, so as PID 1 it
ignores the signal and docker stop waits the full grace period before killing
it (10s, exit 137); --init supplies a real init and it stops in 1s.
Testing
npm test # scripts/test-endpoints.sh35 checks over auth, the three endpoints, which tools each exposes, session
handling, ID validation, and the call log. It builds if needed, starts its own
server on port 18099 with fake credentials, and stops it again — so it needs no
.env, reaches no Rentvine account, and touches no live data. Run it before
opening a PR.
For a quick look at a running server against a real account, use
scripts/mcp-test.sh (handshake plus one tool call) or
scripts/test.sh (the same through the MCP Inspector CLI).
Both read credentials from .env.
Continuous integration
.github/workflows/ci.yml runs on every pull
request and on pushes to main: typecheck, build, the 35 endpoint tests, and a
scan for credential-shaped literals in tracked files. It needs no secrets — the
test suite supplies its own fake ones.
A red run does not block merging on its own. GitHub reports the failure and
still offers the merge button until test is a required status check. Set it
once, after the first run has appeared:
Settings → Branches → Add branch ruleset (or Add rule on older repos) → target
main→ tick Require status checks to pass before merging → searchtestand select it → also tick Require a pull request before merging so the check cannot be bypassed by pushing straight tomain.
The check only appears in that search once it has run at least once, so open the CI PR first, then add the rule.
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 outputDeploy by pushing, then on the server: git pull && npm install && npm run build && npx pm2 restart all.
Testing locally
Nobody uses this locally — it's a hosted endpoint. But you should always verify changes locally before deploying.
1. Credentials. Point at a dev Rentvine account, never production:
create_work_order, create_bill, update_work_order, and upload_file write
to live data with no sandbox.
cp .env.example .env
$EDITOR .env # dev key, secret, subdomain
set -a && source .env && set +a.env is gitignored. There's no dotenv dependency — set -a exports
everything in the file into your shell, which is all the server needs.
2. Run it, exactly as production does (loopback, bearer token, port 18009 so it can't collide with a deployed instance):
npm run build
node dist/http.js
# rentvine-mcp HTTP listening on http://127.0.0.1:18009/mcpFor an edit loop, run npm run dev (tsc --watch) in one shell and restart
node dist/http.js after each rebuild.
3. Drive it with scripts/mcp-test.sh. It reads .env, does the handshake,
and calls a tool — one command instead of four:
./scripts/mcp-test.sh # list all 25 tool names
./scripts/mcp-test.sh list_properties # call a tool
./scripts/mcp-test.sh list_tenants '{"search":"aaron"}' # with arguments
./scripts/mcp-test.sh list_work_orders | jq '.[0]' # pipe to jq
./scripts/mcp-test.sh --raw # full tools/list JSONIt fails loudly if the server isn't running or the token doesn't match, which covers the two things that actually go wrong.
Streamable HTTP is session-based: initialize first, then send the returned
mcp-session-id header on every later request. A bare tools/list returns
400 Bad Request: no valid session ID.
$MCP_AUTH_TOKEN must be exported in your shell — set -a && source .env && set +a.
If it's empty the header becomes Authorization: Bearer and every call returns
{"error":"unauthorized"}.
MCP=http://127.0.0.1:18009/mcp
AUTH="Authorization: Bearer $MCP_AUTH_TOKEN"
JSON="Content-Type: application/json"
SSE="Accept: application/json, text/event-stream"
# health — no auth, no session
curl -sS http://127.0.0.1:18009/health
# 1. handshake, capturing the session id from the response headers
SID=$(curl -sS -D- -o /dev/null -X POST $MCP -H "$AUTH" -H "$JSON" -H "$SSE" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
| tr -d '\r' | awk -F': ' '/^mcp-session-id:/{print $2}')
echo "session: $SID"
# 2. complete the handshake (returns 202)
curl -sS -X POST $MCP -H "$AUTH" -H "$JSON" -H "$SSE" -H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. list tools — responses are SSE, so strip the "data: " prefix for jq
curl -sS -X POST $MCP -H "$AUTH" -H "$JSON" -H "$SSE" -H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| sed -n 's/^data: //p' | jq -r '.result.tools[].name'
# 4. call one
curl -sS -X POST $MCP -H "$AUTH" -H "$JSON" -H "$SSE" -H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_properties","arguments":{}}}' \
| sed -n 's/^data: //p' | jq -r '.result.content[0].text'Step 3 should print 25 tool names. Step 4 returns the tool's JSON payload, or a
Rentvine 4xx … string if the credentials are wrong — which still proves the
transport, routing, and projection path all work.
4. Or use the official MCP Inspector. Standard tooling, no repo checkout needed — works against localhost or the deployed endpoint:
export TOKEN=your_mcp_auth_token
URL=http://127.0.0.1:18009/mcp # or https://your-host.example.com:8003/mcp
# list tools
npx @modelcontextprotocol/inspector --cli $URL \
--header "Authorization: Bearer $TOKEN" --method tools/list
# call one, with arguments
npx @modelcontextprotocol/inspector --cli $URL \
--header "Authorization: Bearer $TOKEN" \
--method tools/call --tool-name list_tenants --tool-arg page_size=100
# read the API-docs resource
npx @modelcontextprotocol/inspector --cli $URL \
--header "Authorization: Bearer $TOKEN" \
--method resources/read --uri rentvine://api-docsIt also has --tui (interactive terminal browser) and --web (browser UI, good
for clicking through tools and inspecting raw JSON-RPC). The transport is
auto-detected from the URL — /mcp means Streamable HTTP.
scripts/mcp-test.sh stays useful for scripted checks: no npx download, and it
reads .env so there's no token to pass.
5. Or point a real client at it. Same config as production, just localhost:
{
"mcpServers": {
"rentvine-local": {
"type": "http",
"url": "http://127.0.0.1:18009/mcp",
"headers": { "Authorization": "Bearer dev-token" }
}
}
}This is the fastest way to test tool descriptions — whether the model picks the right tool and fills arguments correctly is not something curl can tell you.
6. Quick checks without a server. dist/index.js is the stdio transport;
it speaks JSON-RPC on stdin/stdout with no session handshake, which makes it
handy for scripted assertions:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node dist/index.js | tail -1 | jq '.result.tools | length'Run npm run typecheck before pushing.
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).
Available Tools
25 toolscreate_billA
Create a bill in Rentvine (live data, write). Requires a payee contact ID (from list_vendors or list_owners), bill date, due date, and bill type ID. Optionally link to a work order.
Inputs: due_date (required), bill_date (required), bill_type_id (required), payee_contact_id (required), +4 more.
create_work_orderA
Create a new maintenance work order in Rentvine (live data, write). Requires description, property_id, and priority. Rentvine auto-fills unitID for single-unit properties and defaults status to 'open'. Returns the new work_order_id and work_order_number.
Inputs: priority (required), description (required), property_id (required), status, +6 more.
download_fileA
Download a Rentvine file as base64 (live data, read). Returns mime_type, size_bytes, is_image, and content_base64. Supports images and other binary attachments up to ~1MB. Large files may fill the context window — prefer list_attachments first to check file_size.
Inputs: file_id (required).
get_fileA
Get metadata for a single Rentvine file by file_id (live data, read). Returns name, size, mime type, and attachment info — but not file contents. Use download_file to fetch the actual bytes.
Inputs: file_id (required).
get_tenant_balanceA
Get the current ledger balance for a tenant from Rentvine (live data). Returns balance and ledger data.
Inputs: tenant_name (required).
get_vendorA
Get a single vendor's full details from Rentvine (live data). Calls /vendors/{id} which returns ~20 fields not available via list_vendors — notably code (100-char free-text identifier used as a catch-all for metadata Rentvine's schema can't hold, such as trade/hourly-rate), website_url, name components (first/middle/last/suffix), discount tiers, QuickBooks linkage, and contact_type. Also parses the packed code field into a code_metadata object when it uses the pipe-delimited k=v convention.
Inputs: vendor_id (required).
list_accountsA
List the chart of accounts from Rentvine (live data). Returns account ID, number, name, category, and active status. Useful for identifying accountIDs when creating bill charges.
Inputs: no named parameters.
list_applicationsA
List rental applications from Rentvine (live data). Returns applicant name, property, unit, status, and application date.
Inputs: no named parameters.
list_attachmentsA
List files attached to any Rentvine object (live data, read). Returns file metadata including file_id, file_name, mime_type, size, and upload date. Use list_object_types to find the correct object_type_id.
Inputs: object_id (required), object_type_id (required).
list_billsA
List all bills from Rentvine (live data). Returns bill ID, payee, dates, voided status, and linked work order. Use this to review outstanding vendor invoices.
Inputs: no named parameters.
list_inspectionsA
List maintenance inspections from Rentvine (live data). Returns title, property, unit, scheduled date, status, and inspector.
Inputs: no named parameters.
list_leasesA
List all leases from Rentvine (live data). Returns tenant name, unit address, rent, deposit, bed/bath count, dates, and status. Use this to answer questions about lease expirations, rent amounts, or active tenants.
Inputs: no named parameters.
list_object_typesA
List Rentvine object types. Returns the full table of object_type_id values and names (e.g. 7 = Unit, 16 = Work Order, 4 = Lease). Use this to find the correct object_type_id when uploading files or attaching documents.
Inputs: no named parameters.
list_ownersA
List all property owners from Rentvine (live data). Returns owner name, contact ID, email, phone, and address. Use this to look up who owns a property or to get a contact ID for bill creation.
Inputs: no named parameters.
list_portfoliosA
List all portfolios from Rentvine (live data). Returns portfolio name, ID, active status, reserve amount, and associated owners.
Inputs: no named parameters.
list_propertiesA
List all properties from Rentvine (live data). Returns property name, address, type, and active status.
Inputs: no named parameters.
list_tenantsA
List all tenants from Rentvine (live data). Returns contact ID, full name and name components, email, phone, address, active status, linked applicant ID, and audit timestamps. Use this to look up a tenant's contact details or to get a contact_id for other calls. Filter with search (matches name, email, or phone) and active_only. By default this omits personally sensitive fields; set include_sensitive=true only when the task actually requires them — see that parameter's description.
Inputs: page, search, page_size, active_only, +1 more.
list_unitsA
List units for a property from Rentvine (live data). Returns unit address, vacancy status, rent amount, and deposit.
Inputs: property_name (required).
list_vendorsA
List all vendors from Rentvine (live data). Returns every field the /vendors/search endpoint exposes — contact details (name, email, phone, address, city, state, postal_code, country), billing/payout (tax_payer_name, payout_type_id, ach_account_number_truncated, hold_payments), full insurance coverage (liability and workers-comp policy numbers + expirations, days_until_insurance_expires), discount terms, identification documents, active status, and audit timestamps. Use to find vendors for work-order assignment, bill creation, or compliance review. Note: Rentvine does not expose trade categories or service areas via the public API. SENSITIVE: response includes PII and financial fields (birth_date, identification_number, ACH details) — treat output as confidential.
Inputs: no named parameters.
list_work_order_attachmentsA
List images and files attached to a specific work order (live data, read). Convenience wrapper around list_attachments with object_type_id fixed to 16.
Inputs: work_order_id (required).
list_work_ordersA
List all maintenance work orders from Rentvine (live data). Returns description, property, status, priority, estimated cost, and scheduling details.
Inputs: no named parameters.
search_transactionsA
Search accounting transactions in Rentvine (live data). Filter by keyword, date range, or amount range. Returns transaction type, amount, description, date, and associated property/ledger. Paginated — use page and page_size for large result sets.
Inputs: page, search, date_max, date_min, +4 more.
update_work_orderA
Update a maintenance work order in Rentvine (live data, write). Use this to change status (e.g. mark completed/cancelled to close a WO), priority, scheduling dates, estimated cost, description, or owner-approval flag. Setting status to 'completed' or 'cancelled' auto-stamps dateClosed to today unless date_closed is supplied. Find work_order_id via list_work_orders.
Inputs: work_order_id (required), status, priority, actual_end, +7 more.
upload_fileA
Upload a file to Rentvine and optionally attach it to a work order, property, lease, or unit (live data, write). ALWAYS use file_path when the file exists on disk — pass the absolute path and the server reads it directly. NEVER use file_content_base64 for local files; it is extremely slow and fills the context window. file_content_base64 exists only for remote/HTTP deployments with no shared filesystem. Use list_object_types to get valid object_type_id values.
Inputs: file_name, file_path, object_id, object_type_id, +1 more.
vendors_nearA
Find vendors within a radius of a property (live data). Uses the property's Rentvine-geocoded latitude/longitude as the center and approximates each vendor's location from their ZIP-code centroid (offline US lookup — Rentvine does not store per-vendor lat/lon). Returns vendors sorted by distance ascending, each annotated with distance_miles. Defaults: radius_mi=25, active_only=true. Coarse filter — not a precise distance.
Inputs: property_id (required), radius_mi, active_only.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
TDQS
Scored across 25 tools
Most tools map cleanly to distinct resources and actions. Minor overlap exists between list_attachments and list_work_order_attachments, and get_file vs download_file could be confused, but descriptions clarify intent.
Mostly follows list_/create_/update_ conventions, but breaks the pattern with get_tenant_balance, get_vendor, search_transactions, and vendors_near. A consistent verb prefix would improve predictability.
25 tools cover many property-management entities, but the count is heavy and includes near-duplicates like list_attachments/list_work_order_attachments and get_file/download_file.
Read coverage is broad across owners, tenants, vendors, bills, leases, and work orders, but write operations are limited to work orders and bills; obvious gaps like creating/updating properties, leases, or tenants remain.
Maintenance
Related MCP Connectors
Property management AI: work orders, vendors, appliances, and triage for Claude and ChatGPT.
Rent Manager MCP server for ChatGPT, Claude and any AI agent: post charge, create work order, message tenant and list tenants in your own Rent Manager account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Rent Manager yourself; Cotlet does not store your password and you need no Rent Manager API key. About the official Rent Manager API: Rent Manager has an API that reads and writes your database, but it is not self-serve (checked 11 Oct 2026). Its public pages offer no key signup: access goes through the Integrations Partner Program or an API support request, and a developer then builds the connector. Setup and FAQ: https://cotlet.dev/integrations/rent-manager-api-mcp-server
Yardi MCP server for ChatGPT, Claude and any AI agent: list tenants and balances, read tenant ledger, create work order, update work order, text tenant, email tenant, post charge and send owner report in your own Yardi account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Yardi yourself; Cotlet does not store your password and you need no Yardi API key. About the official Yardi API: Yardi has no self-serve API for Breeze (checked 27 Oct 2026). Data access goes through the Yardi Interfaces Program, a paid, per-interface license for approved software vendors, and Yardi's own MCP connector for Claude is part of Virtuoso Enterprise, which needs Yardi Voyager rather than Breeze. Setup and FAQ: https://cotlet.dev/integrations/yardi-api-mcp-server
Provide seamless access to Appfolio Property Manager Reporting API through a standardized MCP serv…
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA Model Context Protocol (MCP) server that wraps AppFolio's REST API so Claude can call it natively in any conversation. 38 tools covering portfolio structure, leasing, financials, maintenance, and admin.1-
- AlicenseNot gradedqualityDmaintenanceEnables 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.1MIT
- AlicenseNot gradedqualityDmaintenanceExposes multifamily rental data including average rent by market, occupancy anomalies, and property summaries to LLM tools via MCP.1MIT
- AlicenseNot gradedqualityDmaintenanceA reference Model Context Protocol (MCP) server that exposes real Belle Realty property-management data — properties, tenants, leases, maintenance tickets, rent roll — as tools that Claude Desktop, Cursor, or any MCP-compatible client can call directly.MIT