mcp-quickbooks 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. mcp_quickbooks-0.1.0/.dockerignore +10 -0
  2. mcp_quickbooks-0.1.0/.env.example +36 -0
  3. mcp_quickbooks-0.1.0/.github/workflows/ci.yml +31 -0
  4. mcp_quickbooks-0.1.0/.gitignore +13 -0
  5. mcp_quickbooks-0.1.0/.mcp.json +8 -0
  6. mcp_quickbooks-0.1.0/Dockerfile +14 -0
  7. mcp_quickbooks-0.1.0/LICENSE +21 -0
  8. mcp_quickbooks-0.1.0/PKG-INFO +171 -0
  9. mcp_quickbooks-0.1.0/PUBLISHING.md +71 -0
  10. mcp_quickbooks-0.1.0/README.md +152 -0
  11. mcp_quickbooks-0.1.0/pyproject.toml +47 -0
  12. mcp_quickbooks-0.1.0/server.json +62 -0
  13. mcp_quickbooks-0.1.0/src/mcp_quickbooks/__init__.py +4 -0
  14. mcp_quickbooks-0.1.0/src/mcp_quickbooks/__main__.py +4 -0
  15. mcp_quickbooks-0.1.0/src/mcp_quickbooks/auth.py +242 -0
  16. mcp_quickbooks-0.1.0/src/mcp_quickbooks/cli.py +133 -0
  17. mcp_quickbooks-0.1.0/src/mcp_quickbooks/client.py +187 -0
  18. mcp_quickbooks-0.1.0/src/mcp_quickbooks/config.py +99 -0
  19. mcp_quickbooks-0.1.0/src/mcp_quickbooks/errors.py +105 -0
  20. mcp_quickbooks-0.1.0/src/mcp_quickbooks/models.py +168 -0
  21. mcp_quickbooks-0.1.0/src/mcp_quickbooks/query.py +87 -0
  22. mcp_quickbooks-0.1.0/src/mcp_quickbooks/rate_limiter.py +62 -0
  23. mcp_quickbooks-0.1.0/src/mcp_quickbooks/results.py +58 -0
  24. mcp_quickbooks-0.1.0/src/mcp_quickbooks/server.py +743 -0
  25. mcp_quickbooks-0.1.0/tests/conftest.py +100 -0
  26. mcp_quickbooks-0.1.0/tests/fake_qbo.py +295 -0
  27. mcp_quickbooks-0.1.0/tests/fixtures/companyinfo.json +18 -0
  28. mcp_quickbooks-0.1.0/tests/fixtures/customers.json +47 -0
  29. mcp_quickbooks-0.1.0/tests/fixtures/invoices.json +72 -0
  30. mcp_quickbooks-0.1.0/tests/fixtures/payments.json +19 -0
  31. mcp_quickbooks-0.1.0/tests/test_auth.py +99 -0
  32. mcp_quickbooks-0.1.0/tests/test_client.py +94 -0
  33. mcp_quickbooks-0.1.0/tests/test_errors.py +42 -0
  34. mcp_quickbooks-0.1.0/tests/test_query.py +31 -0
  35. mcp_quickbooks-0.1.0/tests/test_rate_limiter.py +40 -0
  36. mcp_quickbooks-0.1.0/tests/test_server.py +202 -0
  37. mcp_quickbooks-0.1.0/tests/test_startup.py +79 -0
  38. mcp_quickbooks-0.1.0/uv.lock +817 -0
@@ -0,0 +1,10 @@
1
+ .git
2
+ .venv
3
+ __pycache__
4
+ .pytest_cache
5
+ .ruff_cache
6
+ dist
7
+ build
8
+ *.egg-info
9
+ .env
10
+ .qbo_tokens.json
@@ -0,0 +1,36 @@
1
+ # Copy to .env and fill in with your Intuit Developer app credentials.
2
+ # Never commit the filled-in .env; it is gitignored.
3
+
4
+ # --- OAuth app credentials (from developer.intuit.com -> your app -> Keys & OAuth) ---
5
+ QBO_CLIENT_ID=
6
+ QBO_CLIENT_SECRET=
7
+
8
+ # Must exactly match a redirect URI registered on the Intuit app.
9
+ QBO_REDIRECT_URI=http://localhost:8765/callback
10
+
11
+ # --- Target company ---
12
+ # sandbox for the developer sandbox company, production for real books.
13
+ QBO_ENVIRONMENT=sandbox
14
+ # The company (realm) id. The auth flow captures this automatically; set it to pin a company.
15
+ QBO_REALM_ID=
16
+
17
+ # --- Least-privilege scopes (space-separated) ---
18
+ # Default requests only the accounting scope. Add com.intuit.quickbooks.payment
19
+ # only if you connect payment processing.
20
+ QBO_SCOPES=com.intuit.quickbooks.accounting
21
+
22
+ # --- Local token storage (gitignored) ---
23
+ QBO_TOKEN_PATH=.qbo_tokens.json
24
+
25
+ # --- Client-side rate limiting (kept under Intuit's throttles) ---
26
+ QBO_REQUESTS_PER_MINUTE=450
27
+ QBO_REQUESTS_PER_SECOND=8
28
+ QBO_MAX_RETRIES=4
29
+
30
+ # --- Streamable HTTP transport bind (only used by `mcp-quickbooks http`) ---
31
+ QBO_HTTP_HOST=127.0.0.1
32
+ QBO_HTTP_PORT=8000
33
+
34
+ # --- Local loopback used by `mcp-quickbooks auth` to catch the OAuth redirect ---
35
+ QBO_CALLBACK_HOST=localhost
36
+ QBO_CALLBACK_PORT=8765
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v5
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ run: uv python install ${{ matrix.python-version }}
22
+
23
+ - name: Create venv and install
24
+ run: |
25
+ uv venv --python ${{ matrix.python-version }} .venv
26
+ uv pip install -e ".[dev]"
27
+
28
+ - name: Run tests
29
+ run: |
30
+ source .venv/bin/activate
31
+ pytest
@@ -0,0 +1,13 @@
1
+ .venv/
2
+ node_modules/
3
+ __pycache__/
4
+ *.py[cod]
5
+ .env
6
+ .qbo_tokens.json
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .DS_Store
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "quickbooks": {
4
+ "command": "uvx",
5
+ "args": ["mcp-quickbooks", "stdio"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,14 @@
1
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
2
+
3
+ ENV PYTHONUNBUFFERED=1 \
4
+ UV_COMPILE_BYTECODE=1 \
5
+ UV_LINK_MODE=copy
6
+
7
+ WORKDIR /app
8
+
9
+ COPY pyproject.toml README.md ./
10
+ COPY src ./src
11
+
12
+ RUN uv pip install --system --no-cache .
13
+
14
+ ENTRYPOINT ["mcp-quickbooks", "stdio"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amin Ale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-quickbooks
3
+ Version: 0.1.0
4
+ Summary: QuickBooks Online MCP server for invoices, customers, and payments. OAuth 2.1 + PKCE, stdio/HTTP.
5
+ Author-email: Amin Ale <amin.ale.business@gmail.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: accounting,intuit,mcp,model-context-protocol,oauth,quickbooks,quickbooks-online,quickbooks-online-api
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: anyio==4.14.2
11
+ Requires-Dist: httpx==0.28.1
12
+ Requires-Dist: mcp==1.28.1
13
+ Requires-Dist: pydantic-settings==2.14.2
14
+ Requires-Dist: pydantic==2.13.4
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest-asyncio==1.3.0; extra == 'dev'
17
+ Requires-Dist: pytest==9.1.1; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # QuickBooks Online MCP Server
21
+
22
+ <!-- mcp-name: io.github.amin-ale/mcp-quickbooks -->
23
+
24
+ [![CI](https://github.com/amin-ale/mcp-quickbooks/actions/workflows/ci.yml/badge.svg)](https://github.com/amin-ale/mcp-quickbooks/actions/workflows/ci.yml)
25
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
26
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
27
+
28
+ QuickBooks Online MCP server for Claude Desktop and any MCP client, written in Python on the official
29
+ MCP SDK (FastMCP). It exposes 18 tools over invoices, customers, and payments (create, read, update,
30
+ delete, list, search) plus read-only company and receivables resources, behind a real OAuth 2.1
31
+ authorization-code + PKCE flow with automatic token refresh, client-side rate limiting that honors
32
+ QuickBooks throttles, and structured errors that tell an agent what to do next. It runs over stdio and
33
+ Streamable HTTP.
34
+
35
+ Related: [HubSpot CRM MCP Server](https://github.com/amin-ale/hubspot-mcp-server) ·
36
+ [MCP Audit Gateway](https://github.com/amin-ale/mcp-audit-gateway) ·
37
+ [What production MCP actually requires](https://amin-ale.github.io/portfolio-site/what-production-mcp-actually-requires.html)
38
+
39
+ ## Architecture
40
+
41
+ ```mermaid
42
+ flowchart LR
43
+ Agent["MCP client<br/>(Claude Desktop / HTTP)"]
44
+ subgraph Server["mcp-quickbooks (FastMCP)"]
45
+ Tools["18 tools<br/>invoices · customers · payments"]
46
+ Resources["resources<br/>company · receivables · customers"]
47
+ Client["QBOClient<br/>retry · backoff · error mapping"]
48
+ RL["RateLimiter<br/>per-second + per-minute buckets"]
49
+ Auth["AuthManager<br/>OAuth 2.1 + PKCE · token refresh"]
50
+ Store[("token store<br/>.qbo_tokens.json")]
51
+ end
52
+ QBO["Intuit QuickBooks Online API<br/>/v3/company/{realmId}"]
53
+
54
+ Agent <-->|stdio / streamable-http| Tools
55
+ Agent <-->|resources/read| Resources
56
+ Tools --> Client
57
+ Resources --> Client
58
+ Client --> RL
59
+ Client --> Auth
60
+ Auth <--> Store
61
+ Auth <-->|token + refresh| QBO
62
+ Client -->|REST + query| QBO
63
+ ```
64
+
65
+ The server holds no state and stores no customer data: it is a stateless proxy over the QuickBooks REST API. Tokens live in a local file you control; the client deploys with its own Intuit credentials.
66
+
67
+ ## Tools
68
+
69
+ Every tool returns a structured `{ "ok": true, ... }` result, or `{ "ok": false, "error": {...} }` with a `suggestion`. Each one carries MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) and a declared output schema.
70
+
71
+ - `create_customer`: Create a customer. The display name must be unique; QuickBooks rejects duplicates with error 6240.
72
+ - `get_customer`: Read one customer by Id, including balance, contact details, and the current SyncToken.
73
+ - `update_customer`: Sparse update of an existing customer. Needs the Id and a fresh SyncToken.
74
+ - `delete_customer`: Deactivate a customer (QuickBooks has no hard delete for customers), preserving history.
75
+ - `list_customers`: List customers, most recently updated first, with caller-driven pagination.
76
+ - `search_customers`: Find customers by display-name prefix, exact email, or active flag.
77
+ - `create_invoice`: Create an invoice for an existing customer with one or more line items.
78
+ - `get_invoice`: Read one invoice by Id, including lines, totals, balance, and the current SyncToken.
79
+ - `update_invoice`: Replace an invoice. Lines are replaced wholesale, so send every line it should end with.
80
+ - `delete_invoice`: Delete an invoice permanently. Needs the Id and a fresh SyncToken.
81
+ - `list_invoices`: List invoices, most recent transaction date first, with caller-driven pagination.
82
+ - `search_invoices`: Find invoices by customer Id, transaction-date range, or document number.
83
+ - `create_payment`: Record a payment received, optionally applied against a specific invoice.
84
+ - `get_payment`: Read one payment by Id, including linked transactions and the current SyncToken.
85
+ - `update_payment`: Replace a payment. Dropping the invoice link reopens that invoice's balance.
86
+ - `delete_payment`: Delete a payment permanently. Any invoice it settled goes back to unpaid.
87
+ - `list_payments`: List payments, most recent transaction date first, with caller-driven pagination.
88
+ - `search_payments`: Find payments by customer Id or transaction-date range.
89
+
90
+ ## Resources
91
+
92
+ Read-only JSON:
93
+
94
+ - `qbo://company`: company profile and legal address
95
+ - `qbo://summary/receivables`: open/overdue invoice counts and outstanding balance
96
+ - `qbo://summary/customers`: active customers ranked by outstanding balance
97
+
98
+ ## Least-privilege scopes
99
+
100
+ The default scope is `com.intuit.quickbooks.accounting` only. Add `com.intuit.quickbooks.payment` (via `QBO_SCOPES`) solely if you connect payment processing. The scope string is validated at startup against the known Intuit scope set, so a typo fails fast rather than silently under-authorizing. Identity scopes (`openid`, `profile`, `email`) are never requested unless you opt in.
101
+
102
+ ## Rate limiting and retries
103
+
104
+ A dual token-bucket limiter caps outbound traffic under both the QuickBooks per-second and per-minute ceilings (configurable via `QBO_REQUESTS_PER_SECOND` / `QBO_REQUESTS_PER_MINUTE`). On `429` the client honors the `Retry-After` header; on `429`/`5xx` without one it uses exponential backoff with jitter, up to `QBO_MAX_RETRIES`. A single `401` triggers a token refresh and one transparent retry.
105
+
106
+ ## Quickstart
107
+
108
+ ```bash
109
+ uv venv --python 3.12 .venv
110
+ uv pip install -e ".[dev]"
111
+
112
+ cp .env.example .env # fill in QBO_CLIENT_ID / QBO_CLIENT_SECRET
113
+ mcp-quickbooks auth # opens Intuit, captures the redirect, stores tokens
114
+ mcp-quickbooks status # verify the token refreshes
115
+
116
+ mcp-quickbooks stdio # run over stdio (Claude Desktop)
117
+ mcp-quickbooks http --port 8000 # run over Streamable HTTP
118
+ ```
119
+
120
+ Credentials are read lazily. The server starts, answers `initialize`, and serves `tools/list` with no `QBO_*` variables set at all; a tool call without credentials returns a structured `401` telling the caller what to configure. That keeps registry introspection and container smoke tests working without secrets.
121
+
122
+ ### Claude Desktop
123
+
124
+ ```json
125
+ {
126
+ "mcpServers": {
127
+ "quickbooks": {
128
+ "command": "mcp-quickbooks",
129
+ "args": ["stdio"],
130
+ "env": { "QBO_ENVIRONMENT": "sandbox" }
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Docker
137
+
138
+ ```bash
139
+ docker build -t mcp-quickbooks .
140
+ docker run --rm -i --env-file .env mcp-quickbooks
141
+ ```
142
+
143
+ ## Running against a real Intuit sandbox
144
+
145
+ 1. Create an app at the [Intuit Developer portal](https://developer.intuit.com/) and open its **Keys & OAuth** section. Copy the **Development** client id and secret.
146
+ 2. Add a redirect URI that matches `QBO_REDIRECT_URI` in your `.env` (default `http://localhost:8765/callback`).
147
+ 3. Create a **sandbox company** from the developer dashboard; its company id is your `QBO_REALM_ID`.
148
+ 4. Set `QBO_ENVIRONMENT=sandbox`, fill in `QBO_CLIENT_ID` / `QBO_CLIENT_SECRET`, then run `mcp-quickbooks auth`. The browser flow returns a `realmId` automatically; it is stored alongside the tokens.
149
+ 5. `mcp-quickbooks status` confirms the tokens refresh. You are now driving the live sandbox.
150
+
151
+ Switch `QBO_ENVIRONMENT=production` (with production keys and a connected company) to point at real books. Credentials and tokens are yours; nothing is committed: `.env` and `.qbo_tokens.json` are gitignored.
152
+
153
+ ## Tests
154
+
155
+ The suite runs fully offline. Every QuickBooks and OAuth call is served by an in-memory fake (`tests/fake_qbo.py`) seeded from recorded-style fixtures in `tests/fixtures/`, wired in through an `httpx` mock transport: no network, no real credentials.
156
+
157
+ ```bash
158
+ uv run pytest
159
+ ```
160
+
161
+ ## Registry metadata
162
+
163
+ `server.json` describes the server for the MCP registry, and `.mcp.json` is the client-config snippet directory crawlers look for. **Publishing is intentionally left as a manual step.** See `PUBLISHING.md`. Nothing here submits to any registry.
164
+
165
+ ## Hire me
166
+
167
+ I make AI-era and money-critical integrations production-safe: real auth, real rate limits, real error handling, real tests. Available for MCP server builds and API-integration hardening. Portfolio and contact: https://amin-ale.github.io/portfolio-site · amin.ale.business@gmail.com
168
+
169
+ ## License
170
+
171
+ MIT: see [LICENSE](./LICENSE).
@@ -0,0 +1,71 @@
1
+ # Publishing (manual)
2
+
3
+ This repository ships registry metadata but **submits to nothing automatically**. Every step below is a deliberate human action.
4
+
5
+ Order matters: PyPI comes first, because the MCP registry verifies package ownership by reading the `mcp-name:` marker off the live PyPI description page, and that description is this README.
6
+
7
+ ## 1. PyPI
8
+
9
+ The `<!-- mcp-name: io.github.amin-ale/mcp-quickbooks -->` marker in `README.md` must be in place **before** the package is uploaded, and it must match the `name` field in `server.json` exactly.
10
+
11
+ ```bash
12
+ uv build
13
+ uv publish --token "$PYPI_TOKEN"
14
+ ```
15
+
16
+ Then confirm the marker made it into the published description. It is an HTML comment, so the project page will not display it; check the raw description instead:
17
+
18
+ ```bash
19
+ curl -s https://pypi.org/pypi/mcp-quickbooks/json | grep -c "mcp-name: io.github.amin-ale/mcp-quickbooks"
20
+ ```
21
+
22
+ A count of 1 means the registry publish in step 3 will pass ownership verification; 0 means it will fail.
23
+
24
+ ## 2. server.json
25
+
26
+ `server.json` targets the `2025-12-11` server schema. Constraints worth knowing before editing it: `title` and `description` are capped at 100 characters, `version` must be a plain semver string (ranges such as `^1.2.3` or `1.x` are rejected), and `name` takes exactly one slash.
27
+
28
+ Validate it against the live endpoint before touching the publisher CLI. The endpoint is unauthenticated and has no side effects:
29
+
30
+ ```bash
31
+ curl -s -X POST https://registry.modelcontextprotocol.io/v0/validate \
32
+ -H 'content-type: application/json' \
33
+ --data-binary @server.json
34
+ ```
35
+
36
+ A pass returns `{"valid": true, "issues": []}`. A failure returns HTTP 422 with per-field errors.
37
+
38
+ Keep `version` identical in `pyproject.toml` and `server.json`.
39
+
40
+ ## 3. MCP registry (registry.modelcontextprotocol.io)
41
+
42
+ ```bash
43
+ brew install mcp-publisher
44
+ mcp-publisher login github
45
+ mcp-publisher validate
46
+ mcp-publisher publish
47
+ ```
48
+
49
+ `login github` runs a device-code flow: it prints a code to enter at https://github.com/login/device in a browser signed in as the account that owns the `io.github.<owner>` namespace. That GitHub login is the only namespace authorization mechanism; there is no separate registry account and no web upload form. The login token can expire mid-session, so a publish may need a fresh `login`.
50
+
51
+ Confirm the listing:
52
+
53
+ ```bash
54
+ curl -s -G https://registry.modelcontextprotocol.io/v0.1/servers --data-urlencode search=quickbooks
55
+ ```
56
+
57
+ **Published version metadata is immutable and there is no unpublish.** A mistake in the name, description, or package identifier is fixed only by publishing a new version, and the wrong one stays visible. `mcp-publisher status` can mark a version deprecated, which is not the same as removing it. Get steps 1 and 2 right the first time.
58
+
59
+ ## 4. Other directories
60
+
61
+ Other MCP directories take manual submissions, each with its own form and review queue. None of them is driven from this repository.
62
+
63
+ ## Pre-publish checklist
64
+
65
+ - [ ] `uv run pytest` is green
66
+ - [ ] `version` matches across `pyproject.toml` and `server.json`
67
+ - [ ] The `mcp-name:` marker in `README.md` matches `server.json`'s `name`
68
+ - [ ] `POST /v0/validate` returns `{"valid": true}`
69
+ - [ ] README badges and links point at the real repository
70
+ - [ ] `docker build -t mcp-quickbooks .` succeeds and the container answers `tools/list` with no credentials
71
+ - [ ] No secrets committed (`.env`, `.qbo_tokens.json` stay ignored)
@@ -0,0 +1,152 @@
1
+ # QuickBooks Online MCP Server
2
+
3
+ <!-- mcp-name: io.github.amin-ale/mcp-quickbooks -->
4
+
5
+ [![CI](https://github.com/amin-ale/mcp-quickbooks/actions/workflows/ci.yml/badge.svg)](https://github.com/amin-ale/mcp-quickbooks/actions/workflows/ci.yml)
6
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
8
+
9
+ QuickBooks Online MCP server for Claude Desktop and any MCP client, written in Python on the official
10
+ MCP SDK (FastMCP). It exposes 18 tools over invoices, customers, and payments (create, read, update,
11
+ delete, list, search) plus read-only company and receivables resources, behind a real OAuth 2.1
12
+ authorization-code + PKCE flow with automatic token refresh, client-side rate limiting that honors
13
+ QuickBooks throttles, and structured errors that tell an agent what to do next. It runs over stdio and
14
+ Streamable HTTP.
15
+
16
+ Related: [HubSpot CRM MCP Server](https://github.com/amin-ale/hubspot-mcp-server) ·
17
+ [MCP Audit Gateway](https://github.com/amin-ale/mcp-audit-gateway) ·
18
+ [What production MCP actually requires](https://amin-ale.github.io/portfolio-site/what-production-mcp-actually-requires.html)
19
+
20
+ ## Architecture
21
+
22
+ ```mermaid
23
+ flowchart LR
24
+ Agent["MCP client<br/>(Claude Desktop / HTTP)"]
25
+ subgraph Server["mcp-quickbooks (FastMCP)"]
26
+ Tools["18 tools<br/>invoices · customers · payments"]
27
+ Resources["resources<br/>company · receivables · customers"]
28
+ Client["QBOClient<br/>retry · backoff · error mapping"]
29
+ RL["RateLimiter<br/>per-second + per-minute buckets"]
30
+ Auth["AuthManager<br/>OAuth 2.1 + PKCE · token refresh"]
31
+ Store[("token store<br/>.qbo_tokens.json")]
32
+ end
33
+ QBO["Intuit QuickBooks Online API<br/>/v3/company/{realmId}"]
34
+
35
+ Agent <-->|stdio / streamable-http| Tools
36
+ Agent <-->|resources/read| Resources
37
+ Tools --> Client
38
+ Resources --> Client
39
+ Client --> RL
40
+ Client --> Auth
41
+ Auth <--> Store
42
+ Auth <-->|token + refresh| QBO
43
+ Client -->|REST + query| QBO
44
+ ```
45
+
46
+ The server holds no state and stores no customer data: it is a stateless proxy over the QuickBooks REST API. Tokens live in a local file you control; the client deploys with its own Intuit credentials.
47
+
48
+ ## Tools
49
+
50
+ Every tool returns a structured `{ "ok": true, ... }` result, or `{ "ok": false, "error": {...} }` with a `suggestion`. Each one carries MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) and a declared output schema.
51
+
52
+ - `create_customer`: Create a customer. The display name must be unique; QuickBooks rejects duplicates with error 6240.
53
+ - `get_customer`: Read one customer by Id, including balance, contact details, and the current SyncToken.
54
+ - `update_customer`: Sparse update of an existing customer. Needs the Id and a fresh SyncToken.
55
+ - `delete_customer`: Deactivate a customer (QuickBooks has no hard delete for customers), preserving history.
56
+ - `list_customers`: List customers, most recently updated first, with caller-driven pagination.
57
+ - `search_customers`: Find customers by display-name prefix, exact email, or active flag.
58
+ - `create_invoice`: Create an invoice for an existing customer with one or more line items.
59
+ - `get_invoice`: Read one invoice by Id, including lines, totals, balance, and the current SyncToken.
60
+ - `update_invoice`: Replace an invoice. Lines are replaced wholesale, so send every line it should end with.
61
+ - `delete_invoice`: Delete an invoice permanently. Needs the Id and a fresh SyncToken.
62
+ - `list_invoices`: List invoices, most recent transaction date first, with caller-driven pagination.
63
+ - `search_invoices`: Find invoices by customer Id, transaction-date range, or document number.
64
+ - `create_payment`: Record a payment received, optionally applied against a specific invoice.
65
+ - `get_payment`: Read one payment by Id, including linked transactions and the current SyncToken.
66
+ - `update_payment`: Replace a payment. Dropping the invoice link reopens that invoice's balance.
67
+ - `delete_payment`: Delete a payment permanently. Any invoice it settled goes back to unpaid.
68
+ - `list_payments`: List payments, most recent transaction date first, with caller-driven pagination.
69
+ - `search_payments`: Find payments by customer Id or transaction-date range.
70
+
71
+ ## Resources
72
+
73
+ Read-only JSON:
74
+
75
+ - `qbo://company`: company profile and legal address
76
+ - `qbo://summary/receivables`: open/overdue invoice counts and outstanding balance
77
+ - `qbo://summary/customers`: active customers ranked by outstanding balance
78
+
79
+ ## Least-privilege scopes
80
+
81
+ The default scope is `com.intuit.quickbooks.accounting` only. Add `com.intuit.quickbooks.payment` (via `QBO_SCOPES`) solely if you connect payment processing. The scope string is validated at startup against the known Intuit scope set, so a typo fails fast rather than silently under-authorizing. Identity scopes (`openid`, `profile`, `email`) are never requested unless you opt in.
82
+
83
+ ## Rate limiting and retries
84
+
85
+ A dual token-bucket limiter caps outbound traffic under both the QuickBooks per-second and per-minute ceilings (configurable via `QBO_REQUESTS_PER_SECOND` / `QBO_REQUESTS_PER_MINUTE`). On `429` the client honors the `Retry-After` header; on `429`/`5xx` without one it uses exponential backoff with jitter, up to `QBO_MAX_RETRIES`. A single `401` triggers a token refresh and one transparent retry.
86
+
87
+ ## Quickstart
88
+
89
+ ```bash
90
+ uv venv --python 3.12 .venv
91
+ uv pip install -e ".[dev]"
92
+
93
+ cp .env.example .env # fill in QBO_CLIENT_ID / QBO_CLIENT_SECRET
94
+ mcp-quickbooks auth # opens Intuit, captures the redirect, stores tokens
95
+ mcp-quickbooks status # verify the token refreshes
96
+
97
+ mcp-quickbooks stdio # run over stdio (Claude Desktop)
98
+ mcp-quickbooks http --port 8000 # run over Streamable HTTP
99
+ ```
100
+
101
+ Credentials are read lazily. The server starts, answers `initialize`, and serves `tools/list` with no `QBO_*` variables set at all; a tool call without credentials returns a structured `401` telling the caller what to configure. That keeps registry introspection and container smoke tests working without secrets.
102
+
103
+ ### Claude Desktop
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "quickbooks": {
109
+ "command": "mcp-quickbooks",
110
+ "args": ["stdio"],
111
+ "env": { "QBO_ENVIRONMENT": "sandbox" }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Docker
118
+
119
+ ```bash
120
+ docker build -t mcp-quickbooks .
121
+ docker run --rm -i --env-file .env mcp-quickbooks
122
+ ```
123
+
124
+ ## Running against a real Intuit sandbox
125
+
126
+ 1. Create an app at the [Intuit Developer portal](https://developer.intuit.com/) and open its **Keys & OAuth** section. Copy the **Development** client id and secret.
127
+ 2. Add a redirect URI that matches `QBO_REDIRECT_URI` in your `.env` (default `http://localhost:8765/callback`).
128
+ 3. Create a **sandbox company** from the developer dashboard; its company id is your `QBO_REALM_ID`.
129
+ 4. Set `QBO_ENVIRONMENT=sandbox`, fill in `QBO_CLIENT_ID` / `QBO_CLIENT_SECRET`, then run `mcp-quickbooks auth`. The browser flow returns a `realmId` automatically; it is stored alongside the tokens.
130
+ 5. `mcp-quickbooks status` confirms the tokens refresh. You are now driving the live sandbox.
131
+
132
+ Switch `QBO_ENVIRONMENT=production` (with production keys and a connected company) to point at real books. Credentials and tokens are yours; nothing is committed: `.env` and `.qbo_tokens.json` are gitignored.
133
+
134
+ ## Tests
135
+
136
+ The suite runs fully offline. Every QuickBooks and OAuth call is served by an in-memory fake (`tests/fake_qbo.py`) seeded from recorded-style fixtures in `tests/fixtures/`, wired in through an `httpx` mock transport: no network, no real credentials.
137
+
138
+ ```bash
139
+ uv run pytest
140
+ ```
141
+
142
+ ## Registry metadata
143
+
144
+ `server.json` describes the server for the MCP registry, and `.mcp.json` is the client-config snippet directory crawlers look for. **Publishing is intentionally left as a manual step.** See `PUBLISHING.md`. Nothing here submits to any registry.
145
+
146
+ ## Hire me
147
+
148
+ I make AI-era and money-critical integrations production-safe: real auth, real rate limits, real error handling, real tests. Available for MCP server builds and API-integration hardening. Portfolio and contact: https://amin-ale.github.io/portfolio-site · amin.ale.business@gmail.com
149
+
150
+ ## License
151
+
152
+ MIT: see [LICENSE](./LICENSE).
@@ -0,0 +1,47 @@
1
+ [project]
2
+ name = "mcp-quickbooks"
3
+ version = "0.1.0"
4
+ description = "QuickBooks Online MCP server for invoices, customers, and payments. OAuth 2.1 + PKCE, stdio/HTTP."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Amin Ale", email = "amin.ale.business@gmail.com" }]
9
+ keywords = [
10
+ "mcp",
11
+ "quickbooks",
12
+ "quickbooks-online",
13
+ "quickbooks-online-api",
14
+ "intuit",
15
+ "accounting",
16
+ "oauth",
17
+ "model-context-protocol",
18
+ ]
19
+
20
+ dependencies = [
21
+ "mcp==1.28.1",
22
+ "httpx==0.28.1",
23
+ "pydantic==2.13.4",
24
+ "pydantic-settings==2.14.2",
25
+ "anyio==4.14.2",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest==9.1.1",
31
+ "pytest-asyncio==1.3.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ mcp-quickbooks = "mcp_quickbooks.cli:main"
36
+
37
+ [build-system]
38
+ requires = ["hatchling"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/mcp_quickbooks"]
43
+
44
+ [tool.pytest.ini_options]
45
+ asyncio_mode = "auto"
46
+ testpaths = ["tests"]
47
+ addopts = "-q"
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.amin-ale/mcp-quickbooks",
4
+ "title": "QuickBooks Online MCP Server",
5
+ "description": "QuickBooks Online MCP server for invoices, customers, and payments. OAuth 2.1 + PKCE, stdio/HTTP.",
6
+ "version": "0.1.0",
7
+ "websiteUrl": "https://amin-ale.github.io/portfolio-site/offer-mcp.html",
8
+ "repository": {
9
+ "url": "https://github.com/amin-ale/mcp-quickbooks",
10
+ "source": "github"
11
+ },
12
+ "packages": [
13
+ {
14
+ "registryType": "pypi",
15
+ "registryBaseUrl": "https://pypi.org",
16
+ "identifier": "mcp-quickbooks",
17
+ "version": "0.1.0",
18
+ "transport": {
19
+ "type": "stdio"
20
+ },
21
+ "runtimeArguments": [
22
+ {
23
+ "type": "positional",
24
+ "value": "stdio",
25
+ "isRequired": true,
26
+ "description": "Run the server over stdio."
27
+ }
28
+ ],
29
+ "environmentVariables": [
30
+ {
31
+ "name": "QBO_CLIENT_ID",
32
+ "description": "Intuit app client id.",
33
+ "isRequired": true,
34
+ "isSecret": true
35
+ },
36
+ {
37
+ "name": "QBO_CLIENT_SECRET",
38
+ "description": "Intuit app client secret.",
39
+ "isRequired": true,
40
+ "isSecret": true
41
+ },
42
+ {
43
+ "name": "QBO_ENVIRONMENT",
44
+ "description": "sandbox or production.",
45
+ "isRequired": false,
46
+ "default": "sandbox"
47
+ },
48
+ {
49
+ "name": "QBO_REALM_ID",
50
+ "description": "QuickBooks company (realm) id.",
51
+ "isRequired": false
52
+ },
53
+ {
54
+ "name": "QBO_SCOPES",
55
+ "description": "Space-separated OAuth scopes; defaults to accounting only.",
56
+ "isRequired": false,
57
+ "default": "com.intuit.quickbooks.accounting"
58
+ }
59
+ ]
60
+ }
61
+ ]
62
+ }
@@ -0,0 +1,4 @@
1
+ from .config import Settings, load_settings
2
+ from .server import build_server
3
+
4
+ __all__ = ["Settings", "load_settings", "build_server"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())