quickbooks-online-mcp-server
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuickBooks Online MCP Server
QuickBooks Online MCP Server
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
QuickBooks Online MCP Server是一款全面的Model Context Protocol服务器,通过144个标准化工具暴露完整的QuickBooks Online API。它为29种实体类型(客户、发票、账单、供应商、付款等)提供完整的CRUD操作,并支持生成11种财务报表(资产负债表、损益表、现金流量表等)。基于TypeScript和Zod验证构建,它允许Claude等AI助手通过OAuth 2.0认证的API调用与QuickBooks数据交互。
核心功能:
- 29种实体类型,支持创建、读取、更新、删除和搜索操作
- 11种财务报表,支持自定义日期范围和参数
- OAuth 2.0令牌管理,支持自动刷新
- 基于Zod模式验证的类型安全操作
- 支持沙箱和生产环境
Installation
安装
1. Clone and Build
1. 克隆并构建
bash
git clone https://github.com/intuit/quickbooks-online-mcp-server.git
cd quickbooks-online-mcp-server
npm install
npm run buildbash
git clone https://github.com/intuit/quickbooks-online-mcp-server.git
cd quickbooks-online-mcp-server
npm install
npm run build2. Configure Environment Variables
2. 配置环境变量
Create a file in the project root:
.envenv
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_idEnvironment values:
- :
QUICKBOOKS_ENVIRONMENTfor testing,sandboxfor live dataproduction - : The company ID (obtained during OAuth connection)
QUICKBOOKS_REALM_ID - : Long-lived token from OAuth 2.0 flow
QUICKBOOKS_REFRESH_TOKEN
在项目根目录创建文件:
.envenv
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环境变量说明:
- :
QUICKBOOKS_ENVIRONMENT用于测试,sandbox用于生产数据production - : 公司ID(在OAuth连接过程中获取)
QUICKBOOKS_REALM_ID - : OAuth 2.0流程获取的长效令牌
QUICKBOOKS_REFRESH_TOKEN
3. Add to Claude Desktop Configuration
3. 添加到Claude Desktop配置
Edit your Claude Desktop MCP settings file:
macOS:
Windows:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonjson
{
"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.
编辑你的Claude Desktop MCP设置文件:
macOS:
Windows:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonjson
{
"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"
}
}
}
}重启Claude Desktop以加载服务器。
OAuth 2.0 Setup
OAuth 2.0 设置
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)
- Sandbox:
- 创建Intuit开发者账户:developer.intuit.com
- 创建应用:控制台→创建应用→QuickBooks Online API
- 获取Client ID和Secret:Keys & OAuth部分
- 设置重定向URI:
- 沙箱环境:
http://localhost:8000/callback - 生产环境:必须是HTTPS公共URL(localhost会被拒绝)
- 沙箱环境:
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):
typescript
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';
// Step 1: Authorization URL
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);
});
// Step 2: Handle callback and exchange code for tokens
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 URLSearchParams({
grant_type: 'authorization_code',
code: code as string,
redirect_uri: REDIRECT_URI,
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),
},
}
);
console.log('Refresh Token:', response.data.refresh_token);
console.log('Realm ID:', realmId);
res.send(`Success! Save these:\nRefresh Token: ${response.data.refresh_token}\nRealm ID: ${realmId}`);
} catch (error) {
console.error('Token exchange failed:', error);
res.status(500).send('Token exchange failed');
}
});
app.listen(8000, () => console.log('Visit http://localhost:8000/auth to start OAuth flow'));服务器需要刷新令牌以持续访问数据。你必须完成一次OAuth流程来获取它:
快速方法(使用Intuit OAuth Playground):
- 访问OAuth 2.0 Playground
- 选择你的应用和权限范围:
com.intuit.quickbooks.accounting - 授权并获取刷新令牌
手动方法(Node.js脚本示例):
typescript
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';
// Step 1: Authorization URL
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);
});
// Step 2: Handle callback and exchange code for tokens
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 URLSearchParams({
grant_type: 'authorization_code',
code: code as string,
redirect_uri: REDIRECT_URI,
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),
},
}
);
console.log('Refresh Token:', response.data.refresh_token);
console.log('Realm ID:', realmId);
res.send(`Success! Save these:\nRefresh Token: ${response.data.refresh_token}\nRealm ID: ${realmId}`);
} catch (error) {
console.error('Token exchange failed:', error);
res.status(500).send('Token exchange failed');
}
});
app.listen(8000, () => console.log('Visit http://localhost:8000/auth to start OAuth flow'));Core Entity Operations
核心实体操作
Customer Management
客户管理
typescript
// Create a customer
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"
}
});
// Search customers
const results = await use_mcp_tool("quickbooks", "search_customers", {
query: "SELECT * FROM Customer WHERE DisplayName LIKE '%Acme%'"
});
// Get customer by ID
const customerDetail = await use_mcp_tool("quickbooks", "get_customer", {
id: "123"
});
// Update customer
const updated = await use_mcp_tool("quickbooks", "update_customer", {
Id: "123",
SyncToken: "0", // Required for updates (from get_customer response)
DisplayName: "Acme Corporation Ltd",
sparse: true // Partial update
});typescript
// Create a customer
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"
}
});
// Search customers
const results = await use_mcp_tool("quickbooks", "search_customers", {
query: "SELECT * FROM Customer WHERE DisplayName LIKE '%Acme%'"
});
// Get customer by ID
const customerDetail = await use_mcp_tool("quickbooks", "get_customer", {
id: "123"
});
// Update customer
const updated = await use_mcp_tool("quickbooks", "update_customer", {
Id: "123",
SyncToken: "0", // Required for updates (from get_customer response)
DisplayName: "Acme Corporation Ltd",
sparse: true // Partial update
});Invoice Operations
发票操作
typescript
// Create an invoice
const invoice = await use_mcp_tool("quickbooks", "create_invoice", {
CustomerRef: { value: "123" },
Line: [
{
Amount: 1000.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" }, // Item ID from QuickBooks
Qty: 10,
UnitPrice: 100
}
}
],
DueDate: "2025-06-30",
TxnDate: "2025-05-20"
});
// Search invoices (unpaid)
const unpaid = await use_mcp_tool("quickbooks", "search_invoices", {
query: "SELECT * FROM Invoice WHERE Balance > '0' MAXRESULTS 100"
});
// Update invoice (add a line)
const updatedInvoice = await use_mcp_tool("quickbooks", "update_invoice", {
Id: "456",
SyncToken: "1",
Line: [
{
Amount: 1000.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" },
Qty: 10,
UnitPrice: 100
}
},
{
Amount: 500.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "2" },
Qty: 5,
UnitPrice: 100
}
}
]
});
// Void/delete invoice
await use_mcp_tool("quickbooks", "delete_invoice", {
id: "456",
syncToken: "2"
});typescript
// Create an invoice
const invoice = await use_mcp_tool("quickbooks", "create_invoice", {
CustomerRef: { value: "123" },
Line: [
{
Amount: 1000.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" }, // Item ID from QuickBooks
Qty: 10,
UnitPrice: 100
}
}
],
DueDate: "2025-06-30",
TxnDate: "2025-05-20"
});
// Search invoices (unpaid)
const unpaid = await use_mcp_tool("quickbooks", "search_invoices", {
query: "SELECT * FROM Invoice WHERE Balance > '0' MAXRESULTS 100"
});
// Update invoice (add a line)
const updatedInvoice = await use_mcp_tool("quickbooks", "update_invoice", {
Id: "456",
SyncToken: "1",
Line: [
{
Amount: 1000.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" },
Qty: 10,
UnitPrice: 100
}
},
{
Amount: 500.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "2" },
Qty: 5,
UnitPrice: 100
}
}
]
});
// Void/delete invoice
await use_mcp_tool("quickbooks", "delete_invoice", {
id: "456",
syncToken: "2"
});Payment Recording
付款记录
typescript
// Record a customer payment
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", // Invoice ID
TxnType: "Invoice"
}
]
}
]
});
// Search payments in date range
const payments = await use_mcp_tool("quickbooks", "search_payments", {
query: "SELECT * FROM Payment WHERE TxnDate >= '2025-01-01' AND TxnDate <= '2025-05-31'"
});typescript
// Record a customer payment
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", // Invoice ID
TxnType: "Invoice"
}
]
}
]
});
// Search payments in date range
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
账单与供应商管理
typescript
// Create a vendor
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"
}
});
// Create a bill
const bill = await use_mcp_tool("quickbooks", "create_bill", {
VendorRef: { value: "789" },
Line: [
{
Amount: 500.00,
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
AccountRef: { value: "45" } // Expense account ID
}
}
],
DueDate: "2025-06-15",
TxnDate: "2025-05-20"
});
// Pay a bill
const billPayment = await use_mcp_tool("quickbooks", "create_bill_payment", {
VendorRef: { value: "789" },
TotalAmt: 500.00,
PayType: "Check",
CheckPayment: {
BankAccountRef: { value: "35" } // Bank account ID
},
Line: [
{
Amount: 500.00,
LinkedTxn: [
{
TxnId: "890", // Bill ID
TxnType: "Bill"
}
]
}
]
});typescript
// Create a vendor
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"
}
});
// Create a bill
const bill = await use_mcp_tool("quickbooks", "create_bill", {
VendorRef: { value: "789" },
Line: [
{
Amount: 500.00,
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
AccountRef: { value: "45" } // Expense account ID
}
}
],
DueDate: "2025-06-15",
TxnDate: "2025-05-20"
});
// Pay a bill
const billPayment = await use_mcp_tool("quickbooks", "create_bill_payment", {
VendorRef: { value: "789" },
TotalAmt: 500.00,
PayType: "Check",
CheckPayment: {
BankAccountRef: { value: "35" } // Bank account ID
},
Line: [
{
Amount: 500.00,
LinkedTxn: [
{
TxnId: "890", // Bill ID
TxnType: "Bill"
}
]
}
]
});Financial Reports
财务报表
Balance Sheet
资产负债表
typescript
const balanceSheet = await use_mcp_tool("quickbooks", "get_balance_sheet", {
start_date: "2025-01-01",
end_date: "2025-05-31",
accounting_method: "Accrual", // or "Cash"
summarize_column_by: "Month" // Daily, Week, Month, Quarter, Year
});
// Access report data
console.log(balanceSheet.Header.ReportName);
console.log(balanceSheet.Rows); // Array of report rowstypescript
const balanceSheet = await use_mcp_tool("quickbooks", "get_balance_sheet", {
start_date: "2025-01-01",
end_date: "2025-05-31",
accounting_method: "Accrual", // or "Cash"
summarize_column_by: "Month" // Daily, Week, Month, Quarter, Year
});
// Access report data
console.log(balanceSheet.Header.ReportName);
console.log(balanceSheet.Rows); // Array of report rowsProfit & Loss
损益表
typescript
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"
});
// Filter by customer
const customerPL = await use_mcp_tool("quickbooks", "get_profit_and_loss", {
start_date: "2025-01-01",
end_date: "2025-05-31",
customer: "123" // Customer ID
});typescript
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"
});
// Filter by customer
const customerPL = await use_mcp_tool("quickbooks", "get_profit_and_loss", {
start_date: "2025-01-01",
end_date: "2025-05-31",
customer: "123" // Customer ID
});Cash Flow Report
现金流量表
typescript
const cashFlow = await use_mcp_tool("quickbooks", "get_cash_flow", {
start_date: "2025-01-01",
end_date: "2025-05-31",
summarize_column_by: "Month"
});typescript
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
应收账款账龄
typescript
const agedReceivables = await use_mcp_tool("quickbooks", "get_aged_receivables", {
as_of_date: "2025-05-31",
aging_method: "Current", // Current, Report_Date
num_periods: 4,
aging_period: 30 // Days per aging bucket
});
// Detailed aging
const detailedAging = await use_mcp_tool("quickbooks", "get_aged_receivables_detail", {
as_of_date: "2025-05-31",
num_periods: 4,
aging_period: 30
});typescript
const agedReceivables = await use_mcp_tool("quickbooks", "get_aged_receivables", {
as_of_date: "2025-05-31",
aging_method: "Current", // Current, Report_Date
num_periods: 4,
aging_period: 30 // Days per aging bucket
});
// Detailed aging
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
总账
typescript
const generalLedger = await use_mcp_tool("quickbooks", "get_general_ledger", {
start_date: "2025-01-01",
end_date: "2025-05-31",
accounting_method: "Accrual"
});typescript
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:
QuickBooks使用类SQL查询来搜索实体:
Basic Queries
基础查询
typescript
// All active customers
SELECT * FROM Customer WHERE Active = true
// Invoices in date range
SELECT * FROM Invoice WHERE TxnDate >= '2025-01-01' AND TxnDate <= '2025-05-31'
// Unpaid invoices over $1000
SELECT * FROM Invoice WHERE Balance > '1000' AND Balance > '0'
// Vendors with email
SELECT * FROM Vendor WHERE PrimaryEmailAddr IS NOT NULL
// Limit results
SELECT * FROM Customer WHERE Active = true MAXRESULTS 50typescript
// All active customers
SELECT * FROM Customer WHERE Active = true
// Invoices in date range
SELECT * FROM Invoice WHERE TxnDate >= '2025-01-01' AND TxnDate <= '2025-05-31'
// Unpaid invoices over $1000
SELECT * FROM Invoice WHERE Balance > '1000' AND Balance > '0'
// Vendors with email
SELECT * FROM Vendor WHERE PrimaryEmailAddr IS NOT NULL
// Limit results
SELECT * FROM Customer WHERE Active = true MAXRESULTS 50Advanced Queries
高级查询
typescript
// Multiple conditions
SELECT * FROM Invoice WHERE CustomerRef = '123' AND TxnDate >= '2025-01-01' ORDER BY TxnDate DESC
// Text search (LIKE)
SELECT * FROM Customer WHERE DisplayName LIKE '%Corporation%'
// Count query
SELECT COUNT(*) FROM Invoice WHERE Balance > '0'
// Specific fields
SELECT Id, DisplayName, Balance FROM Customer WHERE Balance > '100'typescript
// Multiple conditions
SELECT * FROM Invoice WHERE CustomerRef = '123' AND TxnDate >= '2025-01-01' ORDER BY TxnDate DESC
// Text search (LIKE)
SELECT * FROM Customer WHERE DisplayName LIKE '%Corporation%'
// Count query
SELECT COUNT(*) FROM Invoice WHERE Balance > '0'
// Specific fields
SELECT Id, DisplayName, Balance FROM Customer WHERE Balance > '100'Using Search Tools
使用搜索工具
typescript
// Search with query
const customers = await use_mcp_tool("quickbooks", "search_customers", {
query: "SELECT * FROM Customer WHERE Active = true MAXRESULTS 100"
});
// Iterate results
customers.QueryResponse.Customer.forEach(customer => {
console.log(`${customer.DisplayName}: ${customer.Balance}`);
});typescript
// Search with query
const customers = await use_mcp_tool("quickbooks", "search_customers", {
query: "SELECT * FROM Customer WHERE Active = true MAXRESULTS 100"
});
// Iterate results
customers.QueryResponse.Customer.forEach(customer => {
console.log(`${customer.DisplayName}: ${customer.Balance}`);
});Common Patterns
常见模式
Creating Line Items
创建行项目
Most transaction entities (Invoice, Bill, Sales Receipt) use arrays:
Linetypescript
// Sales item line (for products/services)
{
Amount: 100.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" }, // Item from QuickBooks
Qty: 1,
UnitPrice: 100.00,
TaxCodeRef: { value: "TAX" } // Optional
}
}
// Account-based expense line (for bills/purchases)
{
Amount: 50.00,
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
AccountRef: { value: "45" }, // Chart of accounts ID
ClassRef: { value: "200" } // Optional class tracking
}
}
// Discount line
{
Amount: 10.00,
DetailType: "DiscountLineDetail",
DiscountLineDetail: {
PercentBased: true,
DiscountPercent: 10
}
}
// Subtotal line
{
Amount: 100.00,
DetailType: "SubTotalLineDetail",
SubTotalLineDetail: {}
}大多数交易实体(发票、账单、销售收据)使用数组:
Linetypescript
// Sales item line (for products/services)
{
Amount: 100.00,
DetailType: "SalesItemLineDetail",
SalesItemLineDetail: {
ItemRef: { value: "1" }, // Item from QuickBooks
Qty: 1,
UnitPrice: 100.00,
TaxCodeRef: { value: "TAX" } // Optional
}
}
// Account-based expense line (for bills/purchases)
{
Amount: 50.00,
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
AccountRef: { value: "45" }, // Chart of accounts ID
ClassRef: { value: "200" } // Optional class tracking
}
}
// Discount line
{
Amount: 10.00,
DetailType: "DiscountLineDetail",
DiscountLineDetail: {
PercentBased: true,
DiscountPercent: 10
}
}
// Subtotal line
{
Amount: 100.00,
DetailType: "SubTotalLineDetail",
SubTotalLineDetail: {}
}Handling SyncToken for Updates
更新时处理SyncToken
All updates require the current for optimistic locking:
SyncTokentypescript
// 1. Get current entity
const entity = await use_mcp_tool("quickbooks", "get_customer", { id: "123" });
// 2. Update with SyncToken
const updated = await use_mcp_tool("quickbooks", "update_customer", {
Id: "123",
SyncToken: entity.Customer.SyncToken, // Required!
DisplayName: "New Name",
sparse: true // Only update specified fields
});If SyncToken is stale, you'll get a 400 error. Always fetch before updating.
所有更新都需要当前的用于乐观锁:
SyncTokentypescript
// 1. Get current entity
const entity = await use_mcp_tool("quickbooks", "get_customer", { id: "123" });
// 2. Update with SyncToken
const updated = await use_mcp_tool("quickbooks", "update_customer", {
Id: "123",
SyncToken: entity.Customer.SyncToken, // Required!
DisplayName: "New Name",
sparse: true // Only update specified fields
});如果SyncToken过期,你会收到400错误。更新前务必先获取最新数据。
Sparse vs Full Updates
稀疏更新与完整更新
typescript
// Sparse update (only changes specified fields)
{
Id: "123",
SyncToken: "1",
DisplayName: "Updated Name",
sparse: true // Leave other fields unchanged
}
// Full update (replaces entire object)
{
Id: "123",
SyncToken: "1",
DisplayName: "Updated Name",
GivenName: "John",
FamilyName: "Smith",
// Must include ALL fields you want to keep
sparse: false // or omit sparse parameter
}typescript
// Sparse update (only changes specified fields)
{
Id: "123",
SyncToken: "1",
DisplayName: "Updated Name",
sparse: true // Leave other fields unchanged
}
// Full update (replaces entire object)
{
Id: "123",
SyncToken: "1",
DisplayName: "Updated Name",
GivenName: "John",
FamilyName: "Smith",
// Must include ALL fields you want to keep
sparse: false // or omit sparse parameter
}Reference Objects
引用对象
QuickBooks uses reference objects for relationships:
typescript
// Customer reference
CustomerRef: { value: "123" } // Customer ID
// Item reference
ItemRef: { value: "45", name: "Consulting Service" } // name is optional
// Account reference
AccountRef: { value: "67" }
// Class reference (for tracking)
ClassRef: { value: "200" }
// Department reference
DepartmentRef: { value: "300" }QuickBooks使用引用对象来表示关系:
typescript
// Customer reference
CustomerRef: { value: "123" } // Customer ID
// Item reference
ItemRef: { value: "45", name: "Consulting Service" } // name is optional
// Account reference
AccountRef: { value: "67" }
// Class reference (for tracking)
ClassRef: { value: "200" }
// Department reference
DepartmentRef: { value: "300" }Date Formats
日期格式
All dates use format:
YYYY-MM-DDtypescript
TxnDate: "2025-05-20"
DueDate: "2025-06-30"
start_date: "2025-01-01"所有日期使用格式:
YYYY-MM-DDtypescript
TxnDate: "2025-05-20"
DueDate: "2025-06-30"
start_date: "2025-01-01"Account and Item Setup
账户与项目设置
Chart of Accounts
会计科目表
typescript
// Create an account
const account = await use_mcp_tool("quickbooks", "create_account", {
Name: "Office Expenses",
AccountType: "Expense", // Asset, Liability, Equity, Revenue, Expense
AccountSubType: "OfficeExpenses"
});
// Search accounts by type
const expenses = await use_mcp_tool("quickbooks", "search_accounts", {
query: "SELECT * FROM Account WHERE AccountType = 'Expense'"
});typescript
// Create an account
const account = await use_mcp_tool("quickbooks", "create_account", {
Name: "Office Expenses",
AccountType: "Expense", // Asset, Liability, Equity, Revenue, Expense
AccountSubType: "OfficeExpenses"
});
// Search accounts by type
const expenses = await use_mcp_tool("quickbooks", "search_accounts", {
query: "SELECT * FROM Account WHERE AccountType = 'Expense'"
});Items (Products/Services)
项目(产品/服务)
typescript
// Create a service item
const item = await use_mcp_tool("quickbooks", "create_item", {
Name: "Consulting Service",
Type: "Service",
IncomeAccountRef: { value: "89" }, // Revenue account
UnitPrice: 150.00
});
// Create an inventory item
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
});
// Search items
const services = await use_mcp_tool("quickbooks", "search_items", {
query: "SELECT * FROM Item WHERE Type = 'Service' AND Active = true"
});typescript
// Create a service item
const item = await use_mcp_tool("quickbooks", "create_item", {
Name: "Consulting Service",
Type: "Service",
IncomeAccountRef: { value: "89" }, // Revenue account
UnitPrice: 150.00
});
// Create an inventory item
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
});
// Search items
const services = await use_mcp_tool("quickbooks", "search_items", {
query: "SELECT * FROM Item WHERE Type = 'Service' AND Active = true"
});Journal Entries
日记账分录
typescript
// Create a journal entry
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" } // Bank account
}
},
{
Amount: 1000.00,
DetailType: "JournalEntryLineDetail",
JournalEntryLineDetail: {
PostingType: "Credit",
AccountRef: { value: "89" } // Revenue account
}
}
]
});Journal entries must balance: Sum of debits = Sum of credits.
typescript
// Create a journal entry
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" } // Bank account
}
},
{
Amount: 1000.00,
DetailType: "JournalEntryLineDetail",
JournalEntryLineDetail: {
PostingType: "Credit",
AccountRef: { value: "89" } // Revenue account
}
}
]
});日记账分录必须平衡:借方总和 = 贷方总和。
Class and Department Tracking
类别与部门跟踪
typescript
// Create a class
const projectClass = await use_mcp_tool("quickbooks", "create_class", {
Name: "Project Alpha"
});
// Create a department
const dept = await use_mcp_tool("quickbooks", "create_department", {
Name: "Marketing"
});
// Use in transaction
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" }, // Project tracking
DepartmentRef: { value: "500" } // Department tracking
}
}
]
});typescript
// Create a class
const projectClass = await use_mcp_tool("quickbooks", "create_class", {
Name: "Project Alpha"
});
// Create a department
const dept = await use_mcp_tool("quickbooks", "create_department", {
Name: "Marketing"
});
// Use in transaction
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" }, // Project tracking
DepartmentRef: { value: "500" } // Department tracking
}
}
]
});Troubleshooting
故障排除
Authentication Errors
认证错误
Problem: or
401 UnauthorizedInvalid tokenSolutions:
- Verify refresh token is current (refresh tokens expire after 100 days of non-use)
- Check matches the connected company
QUICKBOOKS_REALM_ID - Ensure environment (vs
sandbox) matches your app configurationproduction - Re-run OAuth flow to get new refresh token
typescript
// Check token expiration in QuickBooks API response headers
// X-Rate-Limit-* headers indicate API quota status问题: 或
401 UnauthorizedInvalid token解决方案:
- 验证刷新令牌是否有效(刷新令牌100天未使用会过期)
- 检查是否与连接的公司匹配
QUICKBOOKS_REALM_ID - 确保环境(vs
sandbox)与应用配置一致production - 重新运行OAuth流程获取新的刷新令牌
typescript
// Check token expiration in QuickBooks API response headers
// X-Rate-Limit-* headers indicate API quota statusValidation Errors
验证错误
Problem: with validation message
400 Bad RequestCommon causes:
- Missing required fields (e.g., on Invoice)
CustomerRef - Invalid reference IDs (entity doesn't exist)
- Stale on update
SyncToken - Line items don't balance (Journal Entry)
- Date format incorrect (must be )
YYYY-MM-DD
Debug approach:
typescript
// Get current entity first to see valid structure
const existing = await use_mcp_tool("quickbooks", "get_invoice", { id: "123" });
console.log(JSON.stringify(existing, null, 2));
// Use response as template for updates问题: 并带有验证消息
400 Bad Request常见原因:
- 缺少必填字段(例如,发票上的)
CustomerRef - 无效的引用ID(实体不存在)
- 更新时使用过期的
SyncToken - 行项目不平衡(日记账分录)
- 日期格式不正确(必须是)
YYYY-MM-DD
调试方法:
typescript
// Get current entity first to see valid structure
const existing = await use_mcp_tool("quickbooks", "get_invoice", { id: "123" });
console.log(JSON.stringify(existing, null, 2));
// Use response as template for updatesQuery Errors
查询错误
Problem: Search returns empty or error
Solutions:
- Check entity name spelling: (not
SELECT * FROM Customer)Customers - Use single quotes for string values:
WHERE DisplayName = 'Acme' - Date comparisons:
WHERE TxnDate >= '2025-01-01' - Limit results: (default is 100)
MAXRESULTS 1000
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问题: 搜索返回空结果或错误
解决方案:
- 检查实体名称拼写:(不是
SELECT * FROM Customer)Customers - 字符串值使用单引号:
WHERE DisplayName = 'Acme' - 日期比较:
WHERE TxnDate >= '2025-01-01' - 限制结果数量:(默认是100)
MAXRESULTS 1000
有效实体名称:
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, VendorCreditReport 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: vs
AccrualCash - Verify company has data in the date range
typescript
// Get company info to check fiscal year and accounting method
const companyInfo = await use_mcp_tool("quickbooks", "get_company_info", {});
console.log(companyInfo.CompanyInfo.FiscalYearStartMonth);问题: 报表返回无数据或意外结果
解决方案:
- 确保日期范围有效:
end_date >= start_date - 使用正确的日期格式:
YYYY-MM-DD - 检查会计方法是否与公司设置匹配:vs
AccrualCash - 验证公司在该日期范围内有数据
typescript
// Get company info to check fiscal year and accounting method
const companyInfo = await use_mcp_tool("quickbooks", "get_company_info", {});
console.log(companyInfo.CompanyInfo.FiscalYearStartMonth);Rate Limiting
速率限制
Problem:
429 Too Many RequestsQuickBooks API limits:
- Sandbox: 100 requests per minute per app
- Production: 500 requests per minute per app per company
Solution: Implement exponential backoff:
typescript
// The MCP server doesn't automatically retry
// Implement retry logic in your calling code
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;
}
}
}问题:
429 Too Many RequestsQuickBooks API限制:
- 沙箱环境:每个应用每分钟100次请求
- 生产环境:每个应用每个公司每分钟500次请求
解决方案: 实现指数退避:
typescript
// The MCP server doesn't automatically retry
// Implement retry logic in your calling code
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
问题: 无法连接到生产环境QBO
生产环境要求:
- 重定向URI必须是HTTPS(不能是localhost)
- 应用必须已发布(不能处于开发模式)
- 可能需要Intuit安全审核
开发环境 workaround:
- 使用ngrok或类似工具隧道localhost:
https://your-subdomain.ngrok.io/callback - 将此URL添加到应用的重定向URI列表
- 更新OAuth流程以使用公共URL
SyncToken Conflicts
SyncToken冲突
Problem: on update
Stale object errorCause: Another process updated the entity between your read and write
Solution:
typescript
// Retry with fresh fetch
try {
const updated = await use_mcp_tool("quickbooks", "update_customer", {...});
} catch (error) {
if (error.message.includes('stale')) {
// Refetch and try again
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
});
}
}问题: 更新时出现
Stale object error原因: 在你读取和写入之间,另一个进程更新了该实体
解决方案:
typescript
// Retry with fresh fetch
try {
const updated = await use_mcp_tool("quickbooks", "update_customer", {...});
} catch (error) {
if (error.message.includes('stale')) {
// Refetch and try again
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:
bash
undefined项目包含全面的Jest测试:
bash
undefinedRun all tests
Run all tests
npm test
npm test
Run specific test suite
Run specific test suite
npm test -- customer.test.ts
npm test -- customer.test.ts
Run with coverage
Run with coverage
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)npm test -- --coverage
测试结构:
- `src/__tests__/tools/` - 单个实体工具测试
- `src/__tests__/reports/` - 报表工具测试
- 每个实体有5个测试用例(创建、获取、更新、删除、搜索)