What that means for my QuickBooks custom action
The ecosystem is standardizing on the Model Context Protocol (MCP) to connect AI to real apps and data. Instead of wiring bespoke “custom actions” (like our QuickBooks action), we can expose a QuickBooks MCP server and plug it into any MCP-aware agent or workflow tool. That gives us portability, better tooling, and less glue code—while still letting us add guardrails where it matters. Our old custom QuickBooks action isn’t wrong, just increasingly redundant.
What changed
- MCP became the “USB-C of AI apps.” It’s an open standard that lets AI assistants connect to tools, files, APIs, and workflows through a consistent interface. Multiple vendors (Anthropic, Microsoft, etc.) are rallying around it, so integrations we build once can be reused across agents and platforms. Model Context Protocol
- Transport ≠ protocol. SSE is just one way to stream model output. MCP sits a layer above (JSON-RPC semantics, tools/resources/prompts), and can ride over SSE or other transports. So the “shift” isn’t really SSE → MCP; it’s custom, one-off integrations → standardized MCP servers. ni18 Blog
What this means for our QuickBooks integration
- There are now off-the-shelf QuickBooks MCP servers and connectors (e.g., n8n, Knit, Zapier MCP) that expose full QBO operations as MCP tools. That means less custom code for auth, schema mapping, and error handling—and we can invoke the same tools from Claude, ChatGPT (where supported), desktop agents, or workflow engines.
- Because MCP is portable, our integration isn’t tied to a single GPT/action definition. It’s easier to compose workflows (validation, approvals, posting, notifications) around QuickBooks and swap the front-end agent without rewiring everything.
So… is our custom QuickBooks action obsolete?
openapi: 3.1.0
info:
title: QuickBooks Reports API
description: Access live Profit and Loss and Transaction List data from your QuickBooks Online company using a custom GPT.
version: 1.2.0
servers:
- url: https://quickbooks.api.intuit.com/v3/company/xxxxxxxxxxxxxxxxx
paths:
/reports/ProfitAndLoss:
get:
operationId: getProfitAndLoss
summary: Retrieve a Profit and Loss (Income Statement) report.
parameters:
- name: start_date
in: query
required: false
description: Start of reporting period (YYYY-MM-DD). Defaults to start of current week if not provided.
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: End of reporting period (YYYY-MM-DD). Defaults to end of current week if not provided.
schema:
type: string
format: date
- name: accounting_method
in: query
required: false
description: Choose 'Cash' or 'Accrual' method. Optional.
schema:
type: string
enum: [Accrual, Cash]
security:
- oauth2: []
responses:
'200':
description: Profit and Loss report returned successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ProfitAndLossResponse'
/reports/TransactionList:
get:
operationId: getTransactionList
summary: Retrieve a list of transactions, including payees, for a given date range.
parameters:
- name: start_date
in: query
required: false
description: Start of reporting period (YYYY-MM-DD). Defaults to start of current week if not provided.
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: End of reporting period (YYYY-MM-DD). Defaults to end of current week if not provided.
schema:
type: string
format: date
- name: accounting_method
in: query
required: false
description: Choose 'Cash' or 'Accrual' method. Optional.
schema:
type: string
enum: [Accrual, Cash]
security:
- oauth2: []
responses:
'200':
description: Transaction list returned successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionListResponse'
components:
securitySchemes:
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://appcenter.intuit.com/connect/oauth2
tokenUrl: https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer
scopes:
com.intuit.quickbooks.accounting: Access accounting data
schemas:
ProfitAndLossResponse:
type: object
properties:
Header:
type: object
properties:
ReportName:
type: string
StartPeriod:
type: string
EndPeriod:
type: string
Rows:
type: object
properties:
Row:
type: array
items:
type: object
properties:
Header:
type: object
properties:
ColData:
type: array
items:
type: object
properties:
value:
type: string
Summary:
type: object
properties:
ColData:
type: array
items:
type: object
properties:
value:
type: string
TransactionListResponse:
type: object
properties:
Header:
type: object
properties:
ReportName:
type: string
StartPeriod:
type: string
EndPeriod:
type: string
Rows:
type: object
properties:
Row:
type: array
items:
type: object
properties:
ColData:
type: array
items:
type: object
properties:
id:
type: string
value:
type: string
Not entirely, but it’s less strategic now:
- Keep it if you need very specific business logic, custom validations, or you’re stuck on a non-MCP platform.
- Otherwise, prefer an MCP server + workflow: you’ll gain reuse, observability, and easier maintenance—even if we still wrap certain calls with policy checks.
