expensify-mcp
Provides full read/write access to Expensify workspaces, expenses, reports, policies, categories, tags, employees, approval routing, and expense rules via the Integration Server API.
Click on "Install 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., "@expensify-mcpcreate a new expense report for client meeting"
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.
expensify-mcp
Full-capability MCP server for Expensify, built on the Integration Server API.
Expensify's official hosted MCP (expensify.com/mcp) is deliberately read-only — it "cannot approve transactions, edit data, or move money." This server adds the write surface the Integration Server API actually exposes: creating expenses and reports, managing policies, categories, tags, members, approval routing, and expense rules.
What this can and cannot do
Can do (17 tools):
Tool | Type | Purpose |
| read | List workspaces + IDs |
| read | Categories, tags, report fields, tax rates, employees |
| read | Corporate card assignments |
| read | Export reports → filename |
| read | Export card transactions → filename |
| read | Fetch an exported file's contents |
| write | Create expenses on an account |
| write | Create a report, optionally with expenses |
| write | Approved → Reimbursed |
| write | New workspace |
| write | Merge/replace categories |
| write | Merge/replace tag groups |
| write | Add/update members, roles, routing |
| write | Remove members |
| write | Per-tag approvers |
| write | Auto-tag / billable rules |
| write | Modify a rule |
Cannot do — no supported API exists:
Approving a report. There is no approve endpoint.
mark_reports_reimbursedonly movesApproved → Reimbursed; getting a report to Approved is app-only.Moving money. Marking Reimbursed is a bookkeeping flag recording that you paid outside Expensify. No ACH, no payment.
Submitting a report into the approval workflow, SmartScan OCR, card issuance/limits, bank account setup, most workspace settings, Concierge chat.
The practical ceiling is "create and configure everything, read everything, but cannot approve or move money."
Related MCP server: Spendesk MCP Server
Setup
npm install
npm run buildGenerate credentials at https://www.expensify.com/tools/integrations/. They are shown once.
cp .env.example .env # then fill in the two credential valuesSafety model
These tools write to real financial records. Expensify has no sandbox tier, so protection is enforced locally:
EXPENSIFY_DRY_RUNdefaults totrue. Mutating tools return a preview of the exact payload instead of sending it. Only the literal stringfalsedisables this — a typo fails closed.EXPENSIFY_ALLOWED_POLICY_IDS(optional) refuses any mutation touching a policy outside the list.EXPENSIFY_MAX_BATCH_SIZE(default 100) caps records per write.Guards run before the dry-run check, so a blocked write is never even previewed.
The partner secret is redacted from every preview and error message.
Start with dry-run on, read the previews, then flip it off for the specific operation you intend.
Hosted deployment (optional)
The server also ships an HTTP transport at api/mcp.ts, so it can run on Vercel
and be added as a custom connector instead of a local subprocess.
This hosts your credentials behind your token — it is not multi-tenant. Expensify's Integration Server API has no OAuth and no delegated access, so there is no way for other users to connect their own accounts through a hosted instance. Anyone with the URL and the bearer token acts as the account whose credentials are in the environment.
Required environment variables:
Variable | Purpose |
| Your Expensify credential |
| Your Expensify credential |
| Bearer token gating the endpoint. Generate with |
| Recommended |
The auth gate fails closed: if MCP_AUTH_TOKEN is unset, every request is
refused with a 503 rather than exposing an unauthenticated write endpoint.
Requests without a valid Authorization: Bearer <token> header get a 401.
Add it as a custom connector with the deployment's /mcp URL and the bearer
token. Rotate the token by updating the env var and redeploying.
Client configuration
Claude Code:
claude mcp add expensify -- node /absolute/path/to/expensify-mcp/dist/index.jsOr in .mcp.json / claude_desktop_config.json:
{
"mcpServers": {
"expensify": {
"command": "node",
"args": ["/absolute/path/to/expensify-mcp/dist/index.js"],
"env": {
"EXPENSIFY_PARTNER_USER_ID": "...",
"EXPENSIFY_PARTNER_USER_SECRET": "...",
"EXPENSIFY_DRY_RUN": "true"
}
}
}
}Live verification status
Verified against the real API on 2026-07-27 using a throwaway workspace.
Tool | Status |
| verified |
| verified |
| verified — created the test workspace |
| verified |
| verified |
| verified — persistence confirmed by read-back |
| verified — see the data-loss warning below |
| verified |
| verified — duplicate correctly rejected on re-run |
| verified — exported 18 real reports |
| verified — retrieved the exported CSV |
| untested — account returns 403 |
| untested — needs a card domain |
| untested — needs an Approved report, unreachable via API |
| untested — needs a verified domain |
Four bugs were found and fixed, every one of them a payload-placement mistake that this API reports opaquely:
Categories and tags belong at the top level of the job description, not inside
inputSettings. Nested, the API returns200and silently discards the change.The employee updater needs
dataSource: "request",entity: "generic", and the roster in a separatedataform field.Export jobs need
onReceive.immediateResponse, andfileExtensiongoes inoutputSettings, notinputSettings. Without it the request blocks and then fails with a bare500that looks like an outage.The download job takes
fileName/fileSystemat the top level and noinputSettingsat all.
All four are regression-tested. The lesson generalises: when this API returns a
500, or a 200 that changes nothing, suspect payload placement before
concluding the endpoint is broken or the account is limited. Comparing against
a raw curl built straight from the docs is the fastest way to tell.
Data-loss warning: tag merges
Verified against the live API: a tag group is replaced wholesale even with
action: "merge". Sending a group with one tag deletes every other tag in
that group. merge only protects groups you did not mention.
Always expensify_get_policy first and send the complete tag list plus your
additions. Categories do not behave this way — they genuinely merge.
API conventions worth knowing
These bite hard, so the schemas enforce them:
Amounts are integer cents.
1234means $12.34. Floats are rejected outright — passing12.34would otherwise post a 100×-wrong expense.Dates are strictly
yyyy-MM-dd.Categories and tags must already exist on the policy. Call
expensify_get_policyfirst.action: "replace"on categories/tags deletes everything not in the payload."merge"is the safe default.Rate limits: 5 requests / 10s and 20 / 60s. Both windows are enforced client-side with a queue; 429s are retried with backoff.
responseCode207 means partial success — checkfailedReports/skippedReportsin the response.
Development
npm run dev # run from source via bun
npm test # 27 tests
npm run type-check # tsc --noEmit, clean
npm run buildTests cover the rate limiter's dual-window behavior, the write-guard matrix (dry-run, allowlist, batch cap, secret redaction), transport encoding, and error mapping. The server was additionally smoke-tested over the real MCP stdio protocol.
Structure
src/
index.ts # MCP server, tool registration, error formatting
lib/
config.ts # env parsing, fail-closed dry-run
client.ts # form-encoded transport, 429 retry, error mapping
rate-limiter.ts # dual sliding windows, serialized
write-guard.ts # the single chokepoint for all mutations
errors.ts # typed errors with explicit constructors
schemas.ts # shared Zod schemas (cents, dates, currency)
tools/
read.ts # policy + card reads
export.ts # report/reconciliation export + download
write-expenses.ts # expenses, reports, reimbursement status
write-policy.ts # policies, categories, tags, members, rulesMaintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/BilalAtique/expensify-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server