paperless-tax-mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Rosehill
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 ADDED
@@ -0,0 +1,87 @@
1
+ # paperless-tax-mcp
2
+
3
+ An **unofficial** MCP server for [paperless.tax](https://www.paperless.tax), the Israeli
4
+ bookkeeping/expense platform.
5
+
6
+ > ⚠️ Not affiliated with, endorsed by, or supported by Paperless. This talks to the app's
7
+ > own private backend, not a published developer API, and can break without notice.
8
+ > Use it against your own account.
9
+
10
+ > 🚧 **Authentication is unresolved.** The self-service API key the UI issues is *not*
11
+ > accepted as a bearer token on these endpoints — see
12
+ > [`docs/api-surface.md`](docs/api-surface.md#authentication--unresolved). The server is
13
+ > usable today only with a token taken from a logged-in browser session, which is
14
+ > short-lived. Getting this to a durable credential is the project's main open problem;
15
+ > help welcome.
16
+
17
+ The value here is as much [`docs/api-surface.md`](docs/api-surface.md) as the code — no
18
+ public documentation of this API exists anywhere, so the map is the point. The server
19
+ grows as the surface gets documented.
20
+
21
+ ## Use
22
+
23
+ Register with any MCP client — Claude Code, Claude Desktop, or a gateway. No install step:
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "paperless-tax": {
29
+ "command": "npx",
30
+ "args": ["-y", "paperless-tax-mcp"],
31
+ "env": { "PAPERLESS_TOKEN": "..." }
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ Claude Code, in one line:
38
+
39
+ ```bash
40
+ claude mcp add paperless-tax --env PAPERLESS_TOKEN=... -- npx -y paperless-tax-mcp
41
+ ```
42
+
43
+ ### Configuration
44
+
45
+ | Variable | Required | Meaning |
46
+ |---|---|---|
47
+ | `PAPERLESS_TOKEN` | yes | Bearer token — see the auth caveat above |
48
+ | `PAPERLESS_USER_ID` | no | 10-char account id; `whoami` discovers it |
49
+ | `PAPERLESS_BASE_URL` | no | Defaults to `https://api.paperless.tax/` |
50
+
51
+ ### From source
52
+
53
+ ```bash
54
+ npm install && npm run build && node dist/index.js
55
+ ```
56
+
57
+ ## Tools
58
+
59
+ | Tool | Endpoint | Confidence |
60
+ |---|---|---|
61
+ | `whoami` | `GET users/get/params` | endpoint confirmed |
62
+ | `pending_documents` | `GET documents/get/pending/{sUserID}` | endpoint confirmed |
63
+ | `list_documents` | `POST bydate` | endpoint confirmed, body inferred |
64
+ | `get_document` | `GET document/{id}` | inferred |
65
+ | `get_download_url` | `POST downloadurl` | endpoint confirmed, response inferred |
66
+ | `raw_request` | anything | escape hatch for mapping new surface |
67
+
68
+ ## Two things that will bite you
69
+
70
+ Both are documented at length in `docs/api-surface.md`, and both cost real time to find:
71
+
72
+ 1. **`bydate` returns an empty list for a bad parameter, not an error.** A zero-document
73
+ response is indistinguishable from a genuinely empty range. Cross-check any count
74
+ against the web UI before believing it.
75
+ 2. **A 204 from this API is not success.** Send no credentials at all and you get the same
76
+ 204. Control-test every auth scheme against a deliberately invalid credential.
77
+
78
+ ## Contributing
79
+
80
+ Corrections to `docs/api-surface.md` are the most valuable contribution — especially
81
+ anything that moves a row from *inferred* to *confirmed*, or that cracks authentication.
82
+ Please mark what you verified versus what you assumed, and never include account
83
+ identifiers, tokens, or ingest addresses in an issue or PR.
84
+
85
+ ## Licence
86
+
87
+ MIT.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Minimal HTTP client for api.paperless.tax.
3
+ *
4
+ * Endpoint names were read out of the vendor's own Angular bundle; request and
5
+ * response *shapes* are largely inferred, so nothing here assumes a field exists.
6
+ * See docs/api-surface.md for what is confirmed versus guessed.
7
+ */
8
+ export declare const DEFAULT_BASE_URL = "https://api.paperless.tax/";
9
+ export declare class PaperlessError extends Error {
10
+ status?: number;
11
+ body?: string;
12
+ constructor(message: string, status?: number, body?: string);
13
+ }
14
+ /** 401/403 — see docs/api-surface.md §Authentication, which is unresolved. */
15
+ export declare class PaperlessAuthError extends PaperlessError {
16
+ constructor(message: string, status?: number, body?: string);
17
+ }
18
+ export interface ClientOptions {
19
+ token?: string;
20
+ userId?: string;
21
+ baseUrl?: string;
22
+ minIntervalMs?: number;
23
+ timeoutMs?: number;
24
+ maxRetries?: number;
25
+ }
26
+ export declare class PaperlessClient {
27
+ private token?;
28
+ private userId?;
29
+ private baseUrl;
30
+ private minIntervalMs;
31
+ private timeoutMs;
32
+ private maxRetries;
33
+ private lastCall;
34
+ constructor(options?: ClientOptions);
35
+ buildUrl(...parts: string[]): string;
36
+ private throttle;
37
+ request(method: string, path: string, body?: unknown): Promise<unknown>;
38
+ getParams(): Promise<unknown>;
39
+ resolveUserId(): Promise<string>;
40
+ pending(): Promise<unknown>;
41
+ byDate(dateStart: string, dateEnd: string, docType?: number, extra?: Record<string, unknown>): Promise<unknown>;
42
+ document(docId: string): Promise<unknown>;
43
+ downloadUrl(docId: string, english?: boolean): Promise<unknown>;
44
+ }
45
+ /**
46
+ * Depth-first search for a key anywhere in a nested structure. Response
47
+ * envelopes are inconsistent between endpoints, so locating a field by name is
48
+ * more reliable than assuming a path.
49
+ */
50
+ export declare function findKey(obj: unknown, key: string): unknown;
package/dist/client.js ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Minimal HTTP client for api.paperless.tax.
3
+ *
4
+ * Endpoint names were read out of the vendor's own Angular bundle; request and
5
+ * response *shapes* are largely inferred, so nothing here assumes a field exists.
6
+ * See docs/api-surface.md for what is confirmed versus guessed.
7
+ */
8
+ export const DEFAULT_BASE_URL = "https://api.paperless.tax/";
9
+ /**
10
+ * The SPA's auth interceptor skips these hosts, so neither do we — a presigned
11
+ * S3 GET rejects an Authorization header it did not sign for.
12
+ */
13
+ const UNAUTHENTICATED_HOSTS = ["s3-eu-west-1.amazonaws.com"];
14
+ export class PaperlessError extends Error {
15
+ status;
16
+ body;
17
+ constructor(message, status, body) {
18
+ super(message);
19
+ this.name = "PaperlessError";
20
+ this.status = status;
21
+ this.body = body;
22
+ }
23
+ }
24
+ /** 401/403 — see docs/api-surface.md §Authentication, which is unresolved. */
25
+ export class PaperlessAuthError extends PaperlessError {
26
+ constructor(message, status, body) {
27
+ super(message, status, body);
28
+ this.name = "PaperlessAuthError";
29
+ }
30
+ }
31
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
32
+ export class PaperlessClient {
33
+ token;
34
+ userId;
35
+ baseUrl;
36
+ minIntervalMs;
37
+ timeoutMs;
38
+ maxRetries;
39
+ lastCall = 0;
40
+ constructor(options = {}) {
41
+ this.token = options.token ?? process.env.PAPERLESS_TOKEN;
42
+ this.userId = options.userId ?? process.env.PAPERLESS_USER_ID;
43
+ const base = options.baseUrl ?? process.env.PAPERLESS_BASE_URL ?? DEFAULT_BASE_URL;
44
+ this.baseUrl = base.replace(/\/+$/, "") + "/";
45
+ this.minIntervalMs = options.minIntervalMs ?? 500;
46
+ this.timeoutMs = options.timeoutMs ?? 60_000;
47
+ this.maxRetries = options.maxRetries ?? 3;
48
+ }
49
+ buildUrl(...parts) {
50
+ return this.baseUrl + parts.filter(Boolean).map((p) => p.replace(/^\/+|\/+$/g, "")).join("/");
51
+ }
52
+ async throttle() {
53
+ const elapsed = Date.now() - this.lastCall;
54
+ if (elapsed < this.minIntervalMs)
55
+ await sleep(this.minIntervalMs - elapsed);
56
+ this.lastCall = Date.now();
57
+ }
58
+ async request(method, path, body) {
59
+ const url = path.startsWith("http") ? path : this.baseUrl + path.replace(/^\/+/, "");
60
+ if (!this.token) {
61
+ throw new PaperlessAuthError("No token — set PAPERLESS_TOKEN");
62
+ }
63
+ const headers = {
64
+ Accept: "application/json, text/plain, */*",
65
+ Origin: "https://www.paperless.tax",
66
+ Referer: "https://www.paperless.tax/",
67
+ "User-Agent": "paperless-tax-mcp (unofficial)",
68
+ };
69
+ if (!UNAUTHENTICATED_HOSTS.some((host) => url.includes(host))) {
70
+ headers["Authorization"] = `Bearer ${this.token}`;
71
+ }
72
+ if (body !== undefined)
73
+ headers["Content-Type"] = "application/json";
74
+ let lastError;
75
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
76
+ await this.throttle();
77
+ let response;
78
+ try {
79
+ response = await fetch(url, {
80
+ method: method.toUpperCase(),
81
+ headers,
82
+ body: body === undefined ? undefined : JSON.stringify(body),
83
+ signal: AbortSignal.timeout(this.timeoutMs),
84
+ });
85
+ }
86
+ catch (error) {
87
+ lastError = error;
88
+ await sleep(2 ** attempt * 1000);
89
+ continue;
90
+ }
91
+ if (response.status === 401 || response.status === 403) {
92
+ throw new PaperlessAuthError(`${response.status} from ${url}. A self-service API key is NOT accepted here — ` +
93
+ "see docs/api-surface.md §Authentication.", response.status, (await response.text()).slice(0, 1000));
94
+ }
95
+ if (response.status >= 500) {
96
+ lastError = new PaperlessError(`${response.status} from ${url}`, response.status);
97
+ await sleep(2 ** attempt * 1000);
98
+ continue;
99
+ }
100
+ if (response.status >= 400) {
101
+ throw new PaperlessError(`${response.status} from ${url}: ${(await response.text()).slice(0, 400)}`, response.status);
102
+ }
103
+ const text = await response.text();
104
+ if (!text)
105
+ return null;
106
+ try {
107
+ return JSON.parse(text);
108
+ }
109
+ catch {
110
+ return text;
111
+ }
112
+ }
113
+ throw new PaperlessError(`${url} failed after ${this.maxRetries} attempts: ${lastError}`);
114
+ }
115
+ // ------------------------------------------------------------- endpoints
116
+ getParams() {
117
+ return this.request("GET", this.buildUrl("users", "get", "params"));
118
+ }
119
+ async resolveUserId() {
120
+ if (this.userId)
121
+ return this.userId;
122
+ const found = findKey(await this.getParams(), "sUserID");
123
+ if (!found)
124
+ throw new PaperlessError("sUserID not found; set PAPERLESS_USER_ID");
125
+ this.userId = String(found);
126
+ return this.userId;
127
+ }
128
+ async pending() {
129
+ const uid = await this.resolveUserId();
130
+ return this.request("GET", this.buildUrl("documents", "get", "pending", uid));
131
+ }
132
+ async byDate(dateStart, dateEnd, docType, extra = {}) {
133
+ const payload = {
134
+ sUserID: await this.resolveUserId(),
135
+ sDateStart: dateStart,
136
+ sDateEnd: dateEnd,
137
+ ...extra,
138
+ };
139
+ if (docType !== undefined)
140
+ payload.iType = docType;
141
+ return this.request("POST", this.buildUrl("bydate"), payload);
142
+ }
143
+ document(docId) {
144
+ return this.request("GET", this.buildUrl("document", docId));
145
+ }
146
+ downloadUrl(docId, english = false) {
147
+ return this.request("POST", this.buildUrl("downloadurl"), {
148
+ sDocID: docId,
149
+ bIsEng: english,
150
+ });
151
+ }
152
+ }
153
+ /**
154
+ * Depth-first search for a key anywhere in a nested structure. Response
155
+ * envelopes are inconsistent between endpoints, so locating a field by name is
156
+ * more reliable than assuming a path.
157
+ */
158
+ export function findKey(obj, key) {
159
+ if (Array.isArray(obj)) {
160
+ for (const item of obj) {
161
+ const found = findKey(item, key);
162
+ if (found)
163
+ return found;
164
+ }
165
+ }
166
+ else if (obj && typeof obj === "object") {
167
+ const record = obj;
168
+ if (record[key])
169
+ return record[key];
170
+ for (const value of Object.values(record)) {
171
+ const found = findKey(value, key);
172
+ if (found)
173
+ return found;
174
+ }
175
+ }
176
+ return undefined;
177
+ }
178
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAE7D;;;GAGG;AACH,MAAM,qBAAqB,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAE7D,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,MAAM,CAAU;IAChB,IAAI,CAAU;IAEd,YAAY,OAAe,EAAE,MAAe,EAAE,IAAa;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,8EAA8E;AAC9E,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IACpD,YAAY,OAAe,EAAE,MAAe,EAAE,IAAa;QACzD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAWD,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEhF,MAAM,OAAO,eAAe;IAClB,KAAK,CAAU;IACf,MAAM,CAAU;IAChB,OAAO,CAAS;IAChB,aAAa,CAAS;IACtB,SAAS,CAAS;IAClB,UAAU,CAAS;IACnB,QAAQ,GAAG,CAAC,CAAC;IAErB,YAAY,UAAyB,EAAE;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC1D,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,gBAAgB,CAAC;QACnF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC;QAC9C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,GAAG,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;QAC7C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,QAAQ,CAAC,GAAG,KAAe;QACzB,OAAO,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChG,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3C,IAAI,OAAO,GAAG,IAAI,CAAC,aAAa;YAAE,MAAM,KAAK,CAAC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,CAAC;QAC5E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,IAAY,EAAE,IAAc;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,kBAAkB,CAAC,gCAAgC,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,mCAAmC;YAC3C,MAAM,EAAE,2BAA2B;YACnC,OAAO,EAAE,4BAA4B;YACrC,YAAY,EAAE,gCAAgC;SAC/C,CAAC;QACF,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC9D,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;QACpD,CAAC;QACD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAErE,IAAI,SAAkB,CAAC;QACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;YAC3D,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YAEtB,IAAI,QAAkB,CAAC;YACvB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;oBAC1B,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;oBAC5B,OAAO;oBACP,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;oBAC3D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;iBAC5C,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAK,CAAC;gBAClB,MAAM,KAAK,CAAC,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,CAAC;gBACjC,SAAS;YACX,CAAC;YAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvD,MAAM,IAAI,kBAAkB,CAC1B,GAAG,QAAQ,CAAC,MAAM,SAAS,GAAG,kDAAkD;oBAC9E,0CAA0C,EAC5C,QAAQ,CAAC,MAAM,EACf,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CACvC,CAAC;YACJ,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAC3B,SAAS,GAAG,IAAI,cAAc,CAAC,GAAG,QAAQ,CAAC,MAAM,SAAS,GAAG,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAClF,MAAM,KAAK,CAAC,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,CAAC;gBACjC,SAAS;YACX,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAC3B,MAAM,IAAI,cAAc,CACtB,GAAG,QAAQ,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAC1E,QAAQ,CAAC,MAAM,CAChB,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,MAAM,IAAI,cAAc,CAAC,GAAG,GAAG,iBAAiB,IAAI,CAAC,UAAU,cAAc,SAAS,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,0EAA0E;IAE1E,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QACpC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,cAAc,CAAC,0CAA0C,CAAC,CAAC;QACjF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,MAAM,CACV,SAAiB,EACjB,OAAe,EACf,OAAgB,EAChB,QAAiC,EAAE;QAEnC,MAAM,OAAO,GAA4B;YACvC,OAAO,EAAE,MAAM,IAAI,CAAC,aAAa,EAAE;YACnC,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,OAAO;YACjB,GAAG,KAAK;SACT,CAAC;QACF,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC;QACnD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAChE,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,WAAW,CAAC,KAAa,EAAE,OAAO,GAAG,KAAK;QACxC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;YACxD,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,OAAO;SAChB,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,GAAY,EAAE,GAAW;IAC/C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACjC,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;SAAM,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,IAAI,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAClC,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ async function main() {
5
+ const server = createServer();
6
+ await server.connect(new StdioServerTransport());
7
+ }
8
+ main().catch((error) => {
9
+ // stdout carries the protocol, so diagnostics must go to stderr.
10
+ console.error("paperless-tax-mcp failed to start:", error);
11
+ process.exit(1);
12
+ });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,iEAAiE;IACjE,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * MCP server exposing the paperless.tax backend.
3
+ *
4
+ * Tool descriptions track what is actually confirmed about the API. Where a
5
+ * request shape is inferred, the description says so — an agent calling these
6
+ * should know which results to trust and which to sanity-check.
7
+ */
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * MCP server exposing the paperless.tax backend.
3
+ *
4
+ * Tool descriptions track what is actually confirmed about the API. Where a
5
+ * request shape is inferred, the description says so — an agent calling these
6
+ * should know which results to trust and which to sanity-check.
7
+ */
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { z } from "zod";
10
+ import { PaperlessClient, PaperlessError } from "./client.js";
11
+ let client;
12
+ function getClient() {
13
+ if (!client)
14
+ client = new PaperlessClient();
15
+ return client;
16
+ }
17
+ function text(value, isError = false) {
18
+ return { content: [{ type: "text", text: value }], isError: isError || undefined };
19
+ }
20
+ /** Every tool reports failure as text rather than throwing, so an agent can react. */
21
+ async function call(fn) {
22
+ try {
23
+ return text(JSON.stringify(await fn(), null, 2));
24
+ }
25
+ catch (error) {
26
+ if (error instanceof PaperlessError)
27
+ return text(`error: ${error.message}`, true);
28
+ return text(`error: ${error instanceof Error ? error.message : String(error)}`, true);
29
+ }
30
+ }
31
+ export function createServer() {
32
+ const server = new McpServer({ name: "paperless-tax", version: "0.1.0" });
33
+ server.tool("whoami", "Fetch account context from `users/get/params`. Confirmed endpoint. Use this first — " +
34
+ "it is the cheapest check that the configured token is actually accepted.", {}, () => call(() => getClient().getParams()));
35
+ server.tool("list_documents", "List documents in a date range via `POST bydate`. Endpoint confirmed; the request " +
36
+ "body is INFERRED (sUserID, sDateStart, sDateEnd, optional iType) and dates are " +
37
+ "assumed YYYY-MM-DD. An empty result may mean the range is genuinely empty OR that " +
38
+ "a parameter name is wrong — this API answers a bad parameter with an empty list " +
39
+ "rather than an error. Verify a non-empty range against the web UI before trusting " +
40
+ "a count.", {
41
+ date_start: z.string().describe("Start date, assumed YYYY-MM-DD"),
42
+ date_end: z.string().describe("End date, assumed YYYY-MM-DD"),
43
+ doc_type: z.number().int().optional().describe("iType filter; meaning not yet mapped"),
44
+ }, ({ date_start, date_end, doc_type }) => call(() => getClient().byDate(date_start, date_end, doc_type)));
45
+ server.tool("get_document", "Fetch one document's detail via `GET document/{id}`. Response shape inferred.", { doc_id: z.string().describe("sDocID") }, ({ doc_id }) => call(() => getClient().document(doc_id)));
46
+ server.tool("get_download_url", "Resolve a document to a fetchable URL via `POST downloadurl`. Returns a presigned S3 " +
47
+ "URL that expires. Do not send an Authorization header when fetching it — the " +
48
+ "signature does not cover one.", {
49
+ doc_id: z.string().describe("sDocID"),
50
+ english: z.boolean().optional().describe("bIsEng — request the English rendering"),
51
+ }, ({ doc_id, english }) => call(() => getClient().downloadUrl(doc_id, english ?? false)));
52
+ server.tool("pending_documents", "List uploaded-but-unfiled documents via `GET documents/get/pending/{sUserID}`.", {}, () => call(() => getClient().pending()));
53
+ server.tool("raw_request", "Call an arbitrary endpoint — the escape hatch for mapping unknown surface. `path` is " +
54
+ "relative to the API base (e.g. `documents/get/search`). Prefer a named tool where " +
55
+ "one exists. Pass sUserID as the literal \"auto\" to have it filled in.", {
56
+ method: z.string().describe("GET or POST"),
57
+ path: z.string().describe("Path relative to https://api.paperless.tax/"),
58
+ body: z.string().optional().describe("JSON request body as a string"),
59
+ }, async ({ method, path, body }) => {
60
+ let payload;
61
+ if (body) {
62
+ try {
63
+ payload = JSON.parse(body);
64
+ }
65
+ catch (error) {
66
+ return text(`error: body is not valid JSON: ${error}`, true);
67
+ }
68
+ }
69
+ return call(async () => {
70
+ if (payload && typeof payload === "object") {
71
+ const record = payload;
72
+ if (record.sUserID === "auto")
73
+ record.sUserID = await getClient().resolveUserId();
74
+ }
75
+ return getClient().request(method, path, payload);
76
+ });
77
+ });
78
+ return server;
79
+ }
80
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ # paperless.tax API surface
2
+
3
+ What is known about the private backend behind [paperless.tax](https://www.paperless.tax),
4
+ an Israeli bookkeeping/expense SaaS. There is **no public API documentation** for this
5
+ service — searched 2026-07-25, nothing exists. Everything here was derived by reading the
6
+ app's own Angular bundle and by probing the live API.
7
+
8
+ Each claim is marked **confirmed** (observed against the live API or read directly from
9
+ the shipped bundle) or **inferred** (a reasonable guess not yet verified). Account
10
+ identifiers are redacted throughout.
11
+
12
+ - **Base URL:** `https://api.paperless.tax/` — confirmed
13
+ - **Front door:** Azure (responses carry `x-azure-ref`) — confirmed
14
+ - **Web app:** `https://www.paperless.tax`, Angular SPA — confirmed
15
+ - **Verified on:** 2026-07-25
16
+
17
+ ---
18
+
19
+ ## Authentication — UNRESOLVED
20
+
21
+ This is the open problem. Summary of what was established on 2026-07-25:
22
+
23
+ | Attempt | Result |
24
+ |---|---|
25
+ | `Authorization: Bearer <self-service API key>` | **401** `UnauthorizedAccessException` |
26
+ | `Authorization: <key>` (no scheme) | 401 |
27
+ | `Authorization: Token <key>` / `ApiKey <key>` | 401 |
28
+ | `x-api-key`, `apikey`, `X-API-KEY`, `token` headers | 204 — **but see below** |
29
+
30
+ **The 204 is a trap.** A bogus key and *no credentials at all* produce the identical
31
+ 204-with-no-body. It is an edge behaviour when no `Authorization` header is present, not a
32
+ successful call. Any client that treats 204 as success will silently "succeed" at nothing.
33
+ Always control-test an auth scheme against a deliberately invalid credential.
34
+
35
+ So: `Authorization` is the header the backend actually reads, and the **self-service API
36
+ key issued by the UI's API/webhooks settings modal (`POST getapikey`) is not accepted as a
37
+ bearer token on the SPA's own endpoints.**
38
+
39
+ Leading hypothesis (untested): that key is for *inbound* verification — the value Paperless
40
+ signs its outgoing webhook calls with — rather than a credential for calling the API. The
41
+ key and the webhook configuration are issued by the same settings modal, which is at least
42
+ consistent with this.
43
+
44
+ What is known to work: the bearer token the SPA itself holds after login authenticates
45
+ these endpoints. Login is passwordless — email, then an SMS one-time code — so that token
46
+ is short-lived and cannot be obtained non-interactively.
47
+
48
+ `/api/…` and `/v1/…` prefixes return **404**; there is no separate versioned surface.
49
+ Contributions establishing a durable auth path are very welcome.
50
+
51
+ ---
52
+
53
+ ## Endpoints
54
+
55
+ Existence is confirmed for anything returning 401 rather than 404 — the route is real and
56
+ merely refused.
57
+
58
+ | Method | Path | Purpose | Confidence |
59
+ |---|---|---|---|
60
+ | GET | `users/get/params` | Account context loaded on app boot | endpoint confirmed |
61
+ | GET | `documents/get/pending/{sUserID}` | Uploaded but not yet filed | endpoint confirmed |
62
+ | POST | `bydate` | List documents over a date range | endpoint confirmed, body inferred |
63
+ | POST | `documents/get/search` | Full document search | inferred |
64
+ | POST | `documents/get/review` | Documents awaiting review | inferred |
65
+ | GET | `document/{id}` | Single document detail | inferred |
66
+ | POST | `downloadurl` | Resolve a document to a fetchable URL | endpoint confirmed, response inferred |
67
+ | POST | `getapikey` | Issue the self-service API key | confirmed from bundle |
68
+ | POST | `setapiwebhooks` | Configure webhook targets | confirmed from bundle |
69
+ | — | `export/*` | MOVEIN / BKMV (מבנה אחיד) exports | confirmed from bundle |
70
+
71
+ ### `POST bydate` — inferred body
72
+
73
+ ```json
74
+ { "sUserID": "<10-char opaque id>", "sDateStart": "2025-01-01", "sDateEnd": "2025-12-31", "iType": 0 }
75
+ ```
76
+
77
+ Date format is assumed ISO. **An unrecognised parameter yields an empty list, not an
78
+ error** — the single most important gotcha here. A zero-document response is
79
+ indistinguishable from a genuinely empty range unless cross-checked against the UI.
80
+
81
+ ### `POST downloadurl`
82
+
83
+ ```json
84
+ { "sDocID": "<doc id>", "bIsEng": false }
85
+ ```
86
+
87
+ Returns a presigned URL on `s3-eu-west-1.amazonaws.com`. **Do not send an `Authorization`
88
+ header** when fetching it — the signature does not cover one, and the request will be
89
+ rejected. The SPA's own auth interceptor skips that host, which is what confirms the
90
+ mechanism.
91
+
92
+ ---
93
+
94
+ ## Field naming
95
+
96
+ Hungarian-ish prefixes throughout: `s` string, `i` integer, `b` boolean, `d` date,
97
+ `a` array. Observed: `sUserID`, `sDocID`, `sKey`, `sBucketName`, `iType`, `bIsEng`,
98
+ `iCode`, `sMessage`, `aParams`.
99
+
100
+ `sUserID` is a 10-character opaque account id — **not** the business VAT number.
101
+
102
+ Error envelope:
103
+
104
+ ```json
105
+ { "iCode": -1, "aParams": ["UnauthorizedAccessException"], "sMessage": "Unauthorized Exception occurred" }
106
+ ```
107
+
108
+ ## Other integration surfaces
109
+
110
+ Documents can be ingested by **email**: each account gets an address at
111
+ `mail.paperless.tax`, in both a shared and an account-scoped form. Anyone who knows the
112
+ account-scoped address can post documents into that ledger, so treat it as a secret.
113
+
114
+ ## Prefer the sanctioned export where it fits
115
+
116
+ The UI can produce a **MOVEIN / BKMV (מבנה אחיד)** export — the Israeli Tax Authority's
117
+ uniform structure, ingestible by every Israeli accounting system and not dependent on a
118
+ reverse-engineered API. Whether it carries original file attachments is an open question.
119
+ Use the API for what that export does not cover.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "paperless-tax-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial MCP server for paperless.tax, the Israeli bookkeeping platform",
5
+ "type": "module",
6
+ "bin": {
7
+ "paperless-tax-mcp": "dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "docs",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "npm run build",
19
+ "start": "node dist/index.js"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "modelcontextprotocol",
27
+ "paperless",
28
+ "paperless.tax",
29
+ "bookkeeping",
30
+ "accounting",
31
+ "israel",
32
+ "expenses"
33
+ ],
34
+ "author": "Daniel Rosehill",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/danielrosehill/paperless-tax-mcp.git"
39
+ },
40
+ "homepage": "https://github.com/danielrosehill/paperless-tax-mcp#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/danielrosehill/paperless-tax-mcp/issues"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.12.0",
46
+ "zod": "^3.23.8"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.14.0",
50
+ "typescript": "^5.6.0"
51
+ }
52
+ }