@desktopaccountingapi/quickbooks-desktop-mcp 0.0.0-stage → 0.1.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release of `@desktopaccountingapi/quickbooks-desktop-mcp`, generated from API contract sha256 `1cc3058cecb5` (API version 1.0.0, 275 operations).
6
+
7
+ - Local MCP server over stdio: `npx -y @desktopaccountingapi/quickbooks-desktop-mcp`. Node.js 20 or later on Windows, macOS and Linux; no other runtime and no runtime dependencies.
8
+ - Tools: `list_end_users`, `list_api_endpoints`, `get_api_endpoint_schema`, `invoke_api_endpoint`, `search_docs`; optional one tool per operation with `--resources`.
9
+ - Writes carry an `Idempotency-Key`; an unknown outcome is reported with recovery steps, never resent.
10
+ - `--read-only` hides and refuses writes; read-only secret keys are enforced by the API.
11
+ - Library exports (`McpServer`, `handleMcpRequest`, `Tools`) to embed the server in your own process.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Desktop Accounting API
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.
package/README.md CHANGED
@@ -1,3 +1,78 @@
1
- # Temporary Holding Version
1
+ # Desktop Accounting API MCP server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@desktopaccountingapi/quickbooks-desktop-mcp` is the local [Model Context Protocol](https://modelcontextprotocol.io) server for [Desktop Accounting API](https://www.desktopaccountingapi.com/docs/), the REST API for QuickBooks Desktop. It lets Claude, Cursor, VS Code, Codex and other AI tools look up and change QuickBooks Desktop data in plain English: overdue invoices, a vendor's payment history, last month's profit and loss.
4
+
5
+ It runs over stdio with Node.js 20 or later on Windows, macOS and Linux, and has no runtime dependencies. It covers all 275 operations of API version 1.0.0.
6
+
7
+ If your client supports remote servers with a header, you can skip the install and use the hosted server at `https://mcp.desktopaccountingapi.com/`. Setup for every client: [MCP guide](https://www.desktopaccountingapi.com/docs/guides/mcp/).
8
+
9
+ ## Setup
10
+
11
+ You need a secret key from the dashboard (**API keys**) and at least one connected QuickBooks Desktop company file.
12
+
13
+ Claude Desktop (`claude_desktop_config.json`, **Settings > Developer > Edit Config**):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "quickbooks-desktop": {
19
+ "command": "npx",
20
+ "args": ["-y", "@desktopaccountingapi/quickbooks-desktop-mcp"],
21
+ "env": { "DAAPI_SECRET_KEY": "sk_live_..." }
22
+ }
23
+ }
24
+ }
25
+ ```
26
+
27
+ Claude Code:
28
+
29
+ ```sh
30
+ claude mcp add quickbooks-desktop --env DAAPI_SECRET_KEY=sk_live_... -- npx -y @desktopaccountingapi/quickbooks-desktop-mcp
31
+ ```
32
+
33
+ Any other stdio client: run `npx -y @desktopaccountingapi/quickbooks-desktop-mcp` with `DAAPI_SECRET_KEY` set.
34
+
35
+ ## Options
36
+
37
+ | Flag | Environment | Meaning |
38
+ | --- | --- | --- |
39
+ | | `DAAPI_SECRET_KEY` | Secret key (`sk_live_...` or `sk_test_...`). |
40
+ | `--read-only` | `DAAPI_MCP_READ_ONLY=true` | Hide and refuse operations that change data. |
41
+ | `--resources invoices,customers` | `DAAPI_MCP_RESOURCES` | Also expose one tool per operation for these resources (`all` for every operation). |
42
+ | `--end-user-id eu_...` | `DAAPI_END_USER_ID` | Default end user (company file) for QuickBooks operations. |
43
+ | `--base-url <url>` | `DAAPI_BASE_URL` | API origin. Default `https://api.desktopaccountingapi.com`. |
44
+
45
+ For a limit the API itself enforces, create a **read-only** secret key in the dashboard. The API rejects every write made with it (`403 API_KEY_READ_ONLY`), whatever the client does.
46
+
47
+ ## Tools
48
+
49
+ | Tool | What it does |
50
+ | --- | --- |
51
+ | `list_end_users` | Your end users (one QuickBooks company file each) and their connection status. |
52
+ | `list_api_endpoints` | Search the operations by resource, name or words. |
53
+ | `get_api_endpoint_schema` | One operation's description and input schema. |
54
+ | `invoke_api_endpoint` | Call an operation. Writes carry an `Idempotency-Key`; an unknown outcome is reported with recovery steps, never resent. |
55
+ | `search_docs` | Search the documentation. |
56
+
57
+ ## Use as a library
58
+
59
+ ```ts
60
+ import { McpServer, handleMcpRequest } from "@desktopaccountingapi/quickbooks-desktop-mcp";
61
+ ```
62
+
63
+ `handleMcpRequest(request, options)` serves Streamable HTTP from any runtime with web-standard `Request` and `Response`.
64
+
65
+ ## Versioning
66
+
67
+ Generated from the Desktop Accounting API contract (sha256 `1cc3058cecb5`) by the same pipeline as the SDKs, and released in lockstep with them. See [CHANGELOG.md](CHANGELOG.md).
68
+
69
+ ## Development
70
+
71
+ ```sh
72
+ mise install # pinned Node.js
73
+ mise run check # install, typecheck, build, tests with the official MCP client, smoke test, package contents
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,47 @@
1
+ export type JsonSchema = Record<string, unknown>;
2
+ export interface CatalogParam {
3
+ name: string;
4
+ required: boolean;
5
+ description: string;
6
+ schema: JsonSchema;
7
+ }
8
+ export interface CatalogEndpoint {
9
+ /** The contract operationId, for example `qbd.invoices.create`. */
10
+ name: string;
11
+ method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
12
+ path: string;
13
+ /** First contract tag, for example `Invoices`. */
14
+ tag: string;
15
+ /** x-tagGroups group of the tag, for example `Transactions`. */
16
+ group: string;
17
+ summary: string;
18
+ description: string;
19
+ /** Anything other than GET changes data (read-only keys are rejected by the API). */
20
+ write: boolean;
21
+ /** The operation requires the Daapi-End-User-Id header. */
22
+ endUser: boolean;
23
+ /** The operation accepts an Idempotency-Key header. */
24
+ idempotent: boolean;
25
+ pathParams: CatalogParam[];
26
+ queryParams: CatalogParam[];
27
+ /** JSON request body, when the operation takes one. `schema` may contain `$ref: #/$defs/<name>`. */
28
+ body: {
29
+ required: boolean;
30
+ schema: JsonSchema;
31
+ } | null;
32
+ }
33
+ export interface Catalog {
34
+ apiVersion: string;
35
+ servers: {
36
+ url: string;
37
+ description: string;
38
+ }[];
39
+ endpoints: CatalogEndpoint[];
40
+ /** Every component schema reachable from an endpoint input, keyed by component name. */
41
+ defs: Record<string, JsonSchema>;
42
+ }
43
+ type Obj = Record<string, unknown>;
44
+ export declare function buildCatalog(doc: Obj): Catalog;
45
+ /** The `$defs` an input schema needs, transitively. */
46
+ export declare function defsFor(catalog: Catalog, roots: unknown[]): Record<string, JsonSchema>;
47
+ export {};
@@ -0,0 +1,140 @@
1
+ // Code generated by packages/sdk-generator from packages/mcp/src. DO NOT EDIT.
2
+ // Contract: packages/api-contract/generated/openapi.json (OpenAPI 3.1.0) sha256:1cc3058cecb557ce1cc724d5236d36860df6bec39636e2d427c8a408bf5f2ca2
3
+ // MCP endpoint catalog: a compact, input-only view of the public OpenAPI contract that the MCP
4
+ // tools search, describe and invoke. Built from packages/api-contract/generated/openapi.json by
5
+ // scripts/api-contract.mjs (committed as packages/mcp/generated/catalog.json, drift-checked) and
6
+ // shipped inside the stdio package by packages/sdk-generator. Pure data; no runtime imports.
7
+ const METHODS = ['get', 'post', 'put', 'patch', 'delete'];
8
+ function deref(doc, value) {
9
+ let v = value;
10
+ for (let i = 0; i < 10 && v && typeof v.$ref === 'string'; i++) {
11
+ const parts = v.$ref.replace(/^#\//, '').split('/');
12
+ let cur = doc;
13
+ for (const p of parts)
14
+ cur = cur[p];
15
+ v = cur;
16
+ }
17
+ return v;
18
+ }
19
+ /** Rewrites component refs to `#/$defs/<name>` and records the referenced names. */
20
+ function rewrite(value, seen) {
21
+ if (Array.isArray(value))
22
+ return value.map((v) => rewrite(v, seen));
23
+ if (!value || typeof value !== 'object')
24
+ return value;
25
+ const out = {};
26
+ for (const [k, v] of Object.entries(value)) {
27
+ if (k === '$ref' && typeof v === 'string' && v.startsWith('#/components/schemas/')) {
28
+ const name = v.slice('#/components/schemas/'.length);
29
+ seen.add(name);
30
+ out.$ref = `#/$defs/${name}`;
31
+ }
32
+ else if (k === 'example' || k === 'examples') {
33
+ // Examples stay: they show agents the expected shape.
34
+ out[k] = v;
35
+ }
36
+ else
37
+ out[k] = rewrite(v, seen);
38
+ }
39
+ return out;
40
+ }
41
+ export function buildCatalog(doc) {
42
+ const groups = new Map();
43
+ for (const g of doc['x-tagGroups'] ?? [])
44
+ for (const t of g.tags)
45
+ groups.set(t, g.name);
46
+ const pending = new Set();
47
+ const endpoints = [];
48
+ for (const [path, item] of Object.entries(doc.paths)) {
49
+ for (const method of METHODS) {
50
+ const op = item[method];
51
+ if (!op)
52
+ continue;
53
+ const pathParams = [];
54
+ const queryParams = [];
55
+ let endUser = false;
56
+ let idempotent = false;
57
+ for (const raw of op.parameters ?? []) {
58
+ const p = deref(doc, raw);
59
+ if (p.in === 'header') {
60
+ if (p.name === 'Daapi-End-User-Id')
61
+ endUser = true;
62
+ if (p.name === 'Idempotency-Key')
63
+ idempotent = true;
64
+ continue;
65
+ }
66
+ const schema = { ...rewrite(p.schema, pending) };
67
+ if (schema.description === p.description)
68
+ delete schema.description;
69
+ const param = { name: String(p.name), required: p.required === true, description: String(p.description ?? ''), schema };
70
+ if (p.in === 'path')
71
+ pathParams.push(param);
72
+ else if (p.in === 'query')
73
+ queryParams.push(param);
74
+ }
75
+ const rb = op.requestBody ? deref(doc, op.requestBody) : undefined;
76
+ const json = rb?.content?.['application/json'];
77
+ const tag = String(op.tags[0]);
78
+ endpoints.push({
79
+ name: String(op.operationId),
80
+ method: method.toUpperCase(),
81
+ path,
82
+ tag,
83
+ group: groups.get(tag) ?? 'Other',
84
+ summary: String(op.summary ?? ''),
85
+ description: String(op.description ?? ''),
86
+ write: method !== 'get',
87
+ endUser,
88
+ idempotent,
89
+ pathParams,
90
+ queryParams,
91
+ body: json ? { required: rb?.required === true, schema: rewrite(json.schema, pending) } : null,
92
+ });
93
+ }
94
+ }
95
+ const schemas = (doc.components.schemas ?? {});
96
+ const defs = {};
97
+ while (pending.size) {
98
+ const [name] = pending;
99
+ pending.delete(name);
100
+ if (defs[name])
101
+ continue;
102
+ const found = new Set();
103
+ defs[name] = rewrite(schemas[name], found);
104
+ for (const n of found)
105
+ if (!defs[n])
106
+ pending.add(n);
107
+ }
108
+ const sortedDefs = {};
109
+ for (const k of Object.keys(defs).sort())
110
+ sortedDefs[k] = defs[k];
111
+ return {
112
+ apiVersion: String(doc.info.version),
113
+ servers: doc.servers ?? [],
114
+ endpoints,
115
+ defs: sortedDefs,
116
+ };
117
+ }
118
+ /** The `$defs` an input schema needs, transitively. */
119
+ export function defsFor(catalog, roots) {
120
+ const out = {};
121
+ const visit = (v) => {
122
+ if (Array.isArray(v))
123
+ return v.forEach(visit);
124
+ if (!v || typeof v !== 'object')
125
+ return;
126
+ for (const [k, x] of Object.entries(v)) {
127
+ if (k === '$ref' && typeof x === 'string' && x.startsWith('#/$defs/')) {
128
+ const name = x.slice('#/$defs/'.length);
129
+ if (!out[name] && catalog.defs[name]) {
130
+ out[name] = catalog.defs[name];
131
+ visit(catalog.defs[name]);
132
+ }
133
+ }
134
+ else
135
+ visit(x);
136
+ }
137
+ };
138
+ roots.forEach(visit);
139
+ return out;
140
+ }