quickbooks-online-mcp-server

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

QuickBooks Online MCP Server

QuickBooks Online MCP Server

Skill by ara.so — MCP Skills collection.
ara.so开发的Skill — MCP技能集合。

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 build
bash
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

2. 配置环境变量

Create a
.env
file in the project root:
env
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
在项目根目录创建
.env
文件:
env
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
    用于生产数据
  • QUICKBOOKS_REALM_ID
    : 公司ID(在OAuth连接过程中获取)
  • QUICKBOOKS_REFRESH_TOKEN
    : OAuth 2.0流程获取的长效令牌

3. Add to Claude Desktop Configuration

3. 添加到Claude Desktop配置

Edit your Claude Desktop MCP settings file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json

Windows:
%APPDATA%\Claude\claude_desktop_config.json
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.
编辑你的Claude Desktop MCP设置文件:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json

Windows:
%APPDATA%\Claude\claude_desktop_config.json
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"
      }
    }
  }
}
重启Claude Desktop以加载服务器。

OAuth 2.0 Setup

OAuth 2.0 设置

Obtaining Credentials

获取凭证

  1. Create an Intuit Developer Account: developer.intuit.com
  2. Create an App: Dashboard → Create an App → QuickBooks Online API
  3. Get Client ID and Secret: Keys & OAuth section
  4. Set Redirect URI:
    • Sandbox:
      http://localhost:8000/callback
    • Production: Must be HTTPS public URL (localhost rejected)
  1. 创建Intuit开发者账户developer.intuit.com
  2. 创建应用:控制台→创建应用→QuickBooks Online API
  3. 获取Client ID和Secret:Keys & OAuth部分
  4. 设置重定向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):
  1. Visit OAuth 2.0 Playground
  2. Select your app and scopes:
    com.intuit.quickbooks.accounting
  3. 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):
  1. 访问OAuth 2.0 Playground
  2. 选择你的应用和权限范围:
    com.intuit.quickbooks.accounting
  3. 授权并获取刷新令牌
手动方法(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 rows
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 rows

Profit & 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 50
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 50

Advanced 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
Line
arrays:
typescript
// 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: {}
}
大多数交易实体(发票、账单、销售收据)使用
Line
数组:
typescript
// 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
SyncToken
for optimistic locking:
typescript
// 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.
所有更新都需要当前的
SyncToken
用于乐观锁:
typescript
// 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
YYYY-MM-DD
format:
typescript
TxnDate: "2025-05-20"
DueDate: "2025-06-30"
start_date: "2025-01-01"
所有日期使用
YYYY-MM-DD
格式:
typescript
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:
401 Unauthorized
or
Invalid token
Solutions:
  1. Verify refresh token is current (refresh tokens expire after 100 days of non-use)
  2. Check
    QUICKBOOKS_REALM_ID
    matches the connected company
  3. Ensure environment (
    sandbox
    vs
    production
    ) matches your app configuration
  4. 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 Unauthorized
Invalid token
解决方案:
  1. 验证刷新令牌是否有效(刷新令牌100天未使用会过期)
  2. 检查
    QUICKBOOKS_REALM_ID
    是否与连接的公司匹配
  3. 确保环境(
    sandbox
    vs
    production
    )与应用配置一致
  4. 重新运行OAuth流程获取新的刷新令牌
typescript
// Check token expiration in QuickBooks API response headers
// X-Rate-Limit-* headers indicate API quota status

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:
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 updates

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
问题: 搜索返回空结果或错误
解决方案:
  • 检查实体名称拼写:
    SELECT * FROM Customer
    (不是
    Customers
  • 字符串值使用单引号:
    WHERE DisplayName = 'Acme'
  • 日期比较:
    WHERE TxnDate >= '2025-01-01'
  • 限制结果数量:
    MAXRESULTS 1000
    (默认是100)
有效实体名称:
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
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
  • 检查会计方法是否与公司设置匹配:
    Accrual
    vs
    Cash
  • 验证公司在该日期范围内有数据
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 Requests
QuickBooks 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 Requests
QuickBooks 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:
  1. Redirect URI must be HTTPS (not localhost)
  2. App must be published (not in development mode)
  3. 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
生产环境要求:
  1. 重定向URI必须是HTTPS(不能是localhost)
  2. 应用必须已发布(不能处于开发模式)
  3. 可能需要Intuit安全审核
开发环境 workaround:
  • 使用ngrok或类似工具隧道localhost:
    https://your-subdomain.ngrok.io/callback
  • 将此URL添加到应用的重定向URI列表
  • 更新OAuth流程以使用公共URL

SyncToken Conflicts

SyncToken冲突

Problem:
Stale object error
on update
Cause: 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
undefined

Run 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个测试用例(创建、获取、更新、删除、搜索)

References

参考资料