| name | quickbooks-online-mcp-server |
| description | QuickBooks Online MCP server providing 144 tools for CRUD operations on 29 entities and 11 financial reports via OAuth 2.0 |
| triggers | ["integrate QuickBooks Online with Claude","set up QuickBooks MCP server","access QuickBooks data through MCP","create invoices in QuickBooks","query QuickBooks financial reports","manage QuickBooks customers and vendors","authenticate with QuickBooks Online API","search QuickBooks entities with filters"] |
QuickBooks Online MCP Server
Skill by ara.so — MCP Skills collection.
Overview
The QuickBooks Online MCP Server is a comprehensive Model Context Protocol server that exposes the complete QuickBooks Online API through 144 standardized tools. It provides full CRUD operations for 29 entity types (Customer, Invoice, Bill, Vendor, Payment, etc.) and 11 financial reports (Balance Sheet, P&L, Cash Flow, etc.). Built with TypeScript and Zod validation, it enables AI assistants like Claude to interact with QuickBooks data through OAuth 2.0 authenticated API calls.
Key capabilities:
- 29 entity types with create, read, update, delete, and search operations
- 11 financial reports with customizable date ranges and parameters
- OAuth 2.0 token management with automatic refresh
- Type-safe operations with Zod schema validation
- Sandbox and production environment support
Installation
1. Clone and Build
git clone https://github.com/intuit/quickbooks-online-mcp-server.git
cd quickbooks-online-mcp-server
npm install
npm run build
2. Configure Environment Variables
Create a .env file in the project root:
QUICKBOOKS_CLIENT_ID=your_client_id_from_intuit_developer
QUICKBOOKS_CLIENT_SECRET=your_client_secret_from_intuit_developer
QUICKBOOKS_ENVIRONMENT=sandbox
QUICKBOOKS_REFRESH_TOKEN=your_refresh_token_from_oauth_flow
QUICKBOOKS_REALM_ID=your_company_id
Environment values:
QUICKBOOKS_ENVIRONMENT: sandbox for testing, production for live data
QUICKBOOKS_REALM_ID: The company ID (obtained during OAuth connection)
QUICKBOOKS_REFRESH_TOKEN: Long-lived token from OAuth 2.0 flow
3. Add to Claude Desktop Configuration
Edit your Claude Desktop MCP settings file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"quickbooks": {
"command": "node",
"args": ["/absolute/path/to/quickbooks-online-mcp-server/dist/index.js"],
"env": {
"QUICKBOOKS_CLIENT_ID": "your_client_id",
"QUICKBOOKS_CLIENT_SECRET": "your_client_secret",
"QUICKBOOKS_REFRESH_TOKEN": "your_refresh_token",
"QUICKBOOKS_REALM_ID": "your_realm_id",
"QUICKBOOKS_ENVIRONMENT": "sandbox"
}
}
}
}
Restart Claude Desktop to load the server.
OAuth 2.0 Setup
Obtaining Credentials
- Create an Intuit Developer Account: developer.intuit.com
- Create an App: Dashboard → Create an App → QuickBooks Online API
- Get Client ID and Secret: Keys & OAuth section
- Set Redirect URI:
- Sandbox:
http://localhost:8000/callback
- Production: Must be HTTPS public URL (localhost rejected)
Getting a Refresh Token
The server requires a refresh token for ongoing access. You must complete the OAuth flow once to obtain it:
Quick Method (using Intuit OAuth Playground):
- Visit OAuth 2.0 Playground
- Select your app and scopes:
com.intuit.quickbooks.accounting
- Authorize and retrieve the refresh token
Manual Method (example Node.js script):
import express from 'express';
import axios from 'axios';
const app = express();
const CLIENT_ID = process.env.QUICKBOOKS_CLIENT_ID;
const CLIENT_SECRET = process.env.QUICKBOOKS_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:8000/callback';
app.get('/auth', (req, res) => {
const authUrl = `https://appcenter.intuit.com/connect/oauth2?` +
`client_id=${CLIENT_ID}&` +
`response_type=code&` +
`scope=com.intuit.quickbooks.accounting&` +
`redirect_uri=${encodeURIComponent(REDIRECT_URI)}&` +
`state=security_token`;
res.redirect(authUrl);
});
app.get('/callback', async (req, res) => {
const { code, realmId } = req.query;
try {
const response = await axios.post(
'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
new ({
: ,
: code ,
: ,
}),
{
: {
: ,
: + .().(),
},
}
);
.(, response..);
.(, realmId);
res.();
} (error) {
.(, error);
res.().();
}
});
app.(, .());
Core Entity Operations
Customer Management
const customer = await use_mcp_tool("quickbooks", "create_customer", {
DisplayName: "Acme Corporation",
GivenName: "John",
FamilyName: "Smith",
PrimaryEmailAddr: { Address: "john@acme.com" },
PrimaryPhone: { FreeFormNumber: "555-1234" },
BillAddr: {
Line1: "123 Main St",
City: "San Francisco",
CountrySubDivisionCode: "CA",
PostalCode: "94105",
Country: "USA"
}
});
const results = await use_mcp_tool("quickbooks", "search_customers", {
query: "SELECT * FROM Customer WHERE DisplayName LIKE '%Acme%'"
});
const customerDetail = await use_mcp_tool("quickbooks", "get_customer", {
id: "123"
});
const updated = await use_mcp_tool(, , {
: ,
: ,
: ,
:
});
Invoice Operations
const invoice = await use_mcp_tool("quickbooks", "create_invoice", {
CustomerRef: { value: "123" },
Line: [
{
Amount: 1000.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" },
Qty: 10,
UnitPrice: 100
}
}
],
DueDate: "2025-06-30",
TxnDate: "2025-05-20"
});
const unpaid = await use_mcp_tool("quickbooks", "search_invoices", {
query: "SELECT * FROM Invoice WHERE Balance > '0' MAXRESULTS 100"
});
const updatedInvoice = await use_mcp_tool("quickbooks", "update_invoice", {
Id: "456",
SyncToken: "1",
Line: [
{
Amount: 1000.00,
DetailType: ,
: {
: { : },
: ,
:
}
},
{
: ,
: ,
: {
: { : },
: ,
:
}
}
]
});
(, , {
: ,
:
});
Payment Recording
const payment = await use_mcp_tool("quickbooks", "create_payment", {
CustomerRef: { value: "123" },
TotalAmt: 1000.00,
TxnDate: "2025-05-20",
Line: [
{
Amount: 1000.00,
LinkedTxn: [
{
TxnId: "456",
TxnType: "Invoice"
}
]
}
]
});
const payments = await use_mcp_tool("quickbooks", "search_payments", {
query: "SELECT * FROM Payment WHERE TxnDate >= '2025-01-01' AND TxnDate <= '2025-05-31'"
});
Bill and Vendor Management
const vendor = await use_mcp_tool("quickbooks", "create_vendor", {
DisplayName: "Office Supplies Co",
PrimaryEmailAddr: { Address: "billing@officesupplies.com" },
BillAddr: {
Line1: "456 Vendor Ave",
City: "Oakland",
CountrySubDivisionCode: "CA",
PostalCode: "94612",
Country: "USA"
}
});
const bill = await use_mcp_tool("quickbooks", "create_bill", {
VendorRef: { value: "789" },
Line: [
{
Amount: 500.00,
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
AccountRef: { value: "45" }
}
}
],
DueDate: "2025-06-15",
TxnDate: "2025-05-20"
});
const billPayment = await use_mcp_tool(, , {
: { : },
: ,
: ,
: {
: { : }
},
: [
{
: ,
: [
{
: ,
:
}
]
}
]
});
Financial Reports
Balance Sheet
const balanceSheet = await use_mcp_tool("quickbooks", "get_balance_sheet", {
start_date: "2025-01-01",
end_date: "2025-05-31",
accounting_method: "Accrual",
summarize_column_by: "Month"
});
console.log(balanceSheet.Header.ReportName);
console.log(balanceSheet.Rows);
Profit & Loss
const profitLoss = await use_mcp_tool("quickbooks", "get_profit_and_loss", {
start_date: "2025-01-01",
end_date: "2025-05-31",
accounting_method: "Accrual",
summarize_column_by: "Quarter"
});
const customerPL = await use_mcp_tool("quickbooks", "get_profit_and_loss", {
start_date: "2025-01-01",
end_date: "2025-05-31",
customer: "123"
});
Cash Flow Report
const cashFlow = await use_mcp_tool("quickbooks", "get_cash_flow", {
start_date: "2025-01-01",
end_date: "2025-05-31",
summarize_column_by: "Month"
});
Aged Receivables
const agedReceivables = await use_mcp_tool("quickbooks", "get_aged_receivables", {
as_of_date: "2025-05-31",
aging_method: "Current",
num_periods: 4,
aging_period: 30
});
const detailedAging = await use_mcp_tool("quickbooks", "get_aged_receivables_detail", {
as_of_date: "2025-05-31",
num_periods: 4,
aging_period: 30
});
General Ledger
const generalLedger = await use_mcp_tool("quickbooks", "get_general_ledger", {
start_date: "2025-01-01",
end_date: "2025-05-31",
accounting_method: "Accrual"
});
Search Query Patterns
QuickBooks uses SQL-like queries for searching entities:
Basic Queries
SELECT * FROM Customer WHERE Active = true
SELECT * FROM Invoice WHERE TxnDate >= '2025-01-01' AND TxnDate <= '2025-05-31'
SELECT * FROM Invoice WHERE Balance > '1000' AND Balance > '0'
SELECT * FROM Vendor WHERE PrimaryEmailAddr IS NOT NULL
SELECT * FROM Customer WHERE Active = true MAXRESULTS 50
Advanced Queries
SELECT * FROM Invoice WHERE CustomerRef = '123' AND TxnDate >= '2025-01-01' ORDER BY TxnDate DESC
SELECT * FROM Customer WHERE DisplayName LIKE '%Corporation%'
SELECT COUNT(*) FROM Invoice WHERE Balance > '0'
SELECT Id, DisplayName, Balance FROM Customer WHERE Balance > '100'
Using Search Tools
const customers = await use_mcp_tool("quickbooks", "search_customers", {
query: "SELECT * FROM Customer WHERE Active = true MAXRESULTS 100"
});
customers.QueryResponse.Customer.forEach(customer => {
console.log(`${customer.DisplayName}: ${customer.Balance}`);
});
Common Patterns
Creating Line Items
Most transaction entities (Invoice, Bill, Sales Receipt) use Line arrays:
{
Amount: 100.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" },
Qty: 1,
UnitPrice: 100.00,
TaxCodeRef: { value: "TAX" }
}
}
{
Amount: 50.00,
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
AccountRef: { value: "45" },
ClassRef: { value: "200" }
}
}
{
Amount: 10.00,
DetailType: "DiscountLineDetail",
DiscountLineDetail: {
PercentBased: true,
DiscountPercent: 10
}
}
{
Amount: 100.00,
DetailType: "SubTotalLineDetail",
SubTotalLineDetail: {}
}
Handling SyncToken for Updates
All updates require the current SyncToken for optimistic locking:
const entity = await use_mcp_tool("quickbooks", "get_customer", { id: "123" });
const updated = await use_mcp_tool("quickbooks", "update_customer", {
Id: "123",
SyncToken: entity.Customer.SyncToken,
DisplayName: "New Name",
sparse: true
});
If SyncToken is stale, you'll get a 400 error. Always fetch before updating.
Sparse vs Full Updates
{
Id: "123",
SyncToken: "1",
DisplayName: "Updated Name",
sparse: true
}
{
Id: "123",
SyncToken: "1",
DisplayName: "Updated Name",
GivenName: "John",
FamilyName: "Smith",
sparse: false
}
Reference Objects
QuickBooks uses reference objects for relationships:
CustomerRef: { value: "123" }
ItemRef: { value: "45", name: "Consulting Service" }
AccountRef: { value: "67" }
ClassRef: { value: "200" }
DepartmentRef: { value: "300" }
Date Formats
All dates use YYYY-MM-DD format:
TxnDate: "2025-05-20"
DueDate: "2025-06-30"
start_date: "2025-01-01"
Account and Item Setup
Chart of Accounts
const account = await use_mcp_tool("quickbooks", "create_account", {
Name: "Office Expenses",
AccountType: "Expense",
AccountSubType: "OfficeExpenses"
});
const expenses = await use_mcp_tool("quickbooks", "search_accounts", {
query: "SELECT * FROM Account WHERE AccountType = 'Expense'"
});
Items (Products/Services)
const item = await use_mcp_tool("quickbooks", "create_item", {
Name: "Consulting Service",
Type: "Service",
IncomeAccountRef: { value: "89" },
UnitPrice: 150.00
});
const product = await use_mcp_tool("quickbooks", "create_item", {
Name: "Widget",
Type: "Inventory",
QtyOnHand: 100,
InvStartDate: "2025-01-01",
IncomeAccountRef: { value: "89" },
AssetAccountRef: { value: "90" },
ExpenseAccountRef: { value: "91" },
UnitPrice: 25.00
});
const services = await use_mcp_tool("quickbooks", "search_items", {
query: "SELECT * FROM Item WHERE Type = 'Service' AND Active = true"
});
Journal Entries
const journalEntry = await use_mcp_tool("quickbooks", "create_journal_entry", {
TxnDate: "2025-05-20",
Line: [
{
Amount: 1000.00,
DetailType: "JournalEntryLineDetail",
JournalEntryLineDetail: {
PostingType: "Debit",
AccountRef: { value: "35" }
}
},
{
Amount: 1000.00,
DetailType: "JournalEntryLineDetail",
JournalEntryLineDetail: {
PostingType: "Credit",
AccountRef: { value: "89" }
}
}
]
});
Journal entries must balance: Sum of debits = Sum of credits.
Class and Department Tracking
const projectClass = await use_mcp_tool("quickbooks", "create_class", {
Name: "Project Alpha"
});
const dept = await use_mcp_tool("quickbooks", "create_department", {
Name: "Marketing"
});
const invoice = await use_mcp_tool("quickbooks", "create_invoice", {
CustomerRef: { value: "123" },
Line: [
{
Amount: 1000.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" },
Qty: 1,
UnitPrice: 1000.00,
ClassRef: { value: "400" },
DepartmentRef: { value: "500" }
}
}
]
});
Troubleshooting
Authentication Errors
Problem: 401 Unauthorized or Invalid token
Solutions:
- Verify refresh token is current (refresh tokens expire after 100 days of non-use)
- Check
QUICKBOOKS_REALM_ID matches the connected company
- Ensure environment (
sandbox vs production) matches your app configuration
- Re-run OAuth flow to get new refresh token
Validation Errors
Problem: 400 Bad Request with validation message
Common causes:
- Missing required fields (e.g.,
CustomerRef on Invoice)
- Invalid reference IDs (entity doesn't exist)
- Stale
SyncToken on update
- Line items don't balance (Journal Entry)
- Date format incorrect (must be
YYYY-MM-DD)
Debug approach:
const existing = await use_mcp_tool("quickbooks", "get_invoice", { id: "123" });
console.log(JSON.stringify(existing, null, 2));
Query Errors
Problem: Search returns empty or error
Solutions:
- Check entity name spelling:
SELECT * FROM Customer (not Customers)
- Use single quotes for string values:
WHERE DisplayName = 'Acme'
- Date comparisons:
WHERE TxnDate >= '2025-01-01'
- Limit results:
MAXRESULTS 1000 (default is 100)
Valid entity names:
Account, Bill, BillPayment, Class, CompanyInfo, CreditMemo, Customer,
Department, Deposit, Employee, Estimate, Invoice, Item, JournalEntry,
Payment, PaymentMethod, Purchase, PurchaseOrder, RefundReceipt,
SalesReceipt, TaxAgency, TaxCode, TaxRate, Term, TimeActivity, Transfer,
Vendor, VendorCredit
Report Date Issues
Problem: Report returns no data or unexpected results
Solutions:
- Ensure date range is valid:
end_date >= start_date
- Use correct date format:
YYYY-MM-DD
- Check accounting method matches company settings:
Accrual vs Cash
- Verify company has data in the date range
const companyInfo = await use_mcp_tool("quickbooks", "get_company_info", {});
console.log(companyInfo.CompanyInfo.FiscalYearStartMonth);
Rate Limiting
Problem: 429 Too Many Requests
QuickBooks API limits:
- Sandbox: 100 requests per minute per app
- Production: 500 requests per minute per app per company
Solution: Implement exponential backoff:
async function retryOperation(operation, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
continue;
}
throw error;
}
}
}
Production Environment Setup
Problem: Can't connect to production QBO
Requirements for production:
- Redirect URI must be HTTPS (not localhost)
- App must be published (not in development mode)
- May require Intuit security review
Workaround for development:
- Use ngrok or similar to tunnel localhost:
https://your-subdomain.ngrok.io/callback
- Add this URL to your app's redirect URIs
- Update OAuth flow to use the public URL
SyncToken Conflicts
Problem: Stale object error on update
Cause: Another process updated the entity between your read and write
Solution:
try {
const updated = await use_mcp_tool("quickbooks", "update_customer", {...});
} catch (error) {
if (error.message.includes('stale')) {
const fresh = await use_mcp_tool("quickbooks", "get_customer", { id: "123" });
const retry = await use_mcp_tool("quickbooks", "update_customer", {
...updateData,
SyncToken: fresh.Customer.SyncToken
});
}
}
Testing
The project includes comprehensive Jest tests:
npm test
npm test -- customer.test.ts
npm test -- --coverage
Test structure:
src/__tests__/tools/ - Individual entity tool tests
src/__tests__/reports/ - Report tool tests
- Each entity has 5 test cases (create, get, update, delete, search)
References