@daften/fireflyiii-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.
Files changed (101) hide show
  1. package/.env.example +15 -0
  2. package/CHANGELOG.md +28 -0
  3. package/LICENSE +21 -0
  4. package/README.md +605 -0
  5. package/dist/args.d.ts +10 -0
  6. package/dist/args.d.ts.map +1 -0
  7. package/dist/args.js +65 -0
  8. package/dist/args.js.map +1 -0
  9. package/dist/client.d.ts +25 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +146 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/http.d.ts +13 -0
  14. package/dist/http.d.ts.map +1 -0
  15. package/dist/http.js +271 -0
  16. package/dist/http.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +44 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/server.d.ts +5 -0
  22. package/dist/server.d.ts.map +1 -0
  23. package/dist/server.js +13 -0
  24. package/dist/server.js.map +1 -0
  25. package/dist/tools/_annotations.d.ts +17 -0
  26. package/dist/tools/_annotations.d.ts.map +1 -0
  27. package/dist/tools/_annotations.js +9 -0
  28. package/dist/tools/_annotations.js.map +1 -0
  29. package/dist/tools/_helpers.d.ts +17 -0
  30. package/dist/tools/_helpers.d.ts.map +1 -0
  31. package/dist/tools/_helpers.js +24 -0
  32. package/dist/tools/_helpers.js.map +1 -0
  33. package/dist/tools/accounts.d.ts +48 -0
  34. package/dist/tools/accounts.d.ts.map +1 -0
  35. package/dist/tools/accounts.js +151 -0
  36. package/dist/tools/accounts.js.map +1 -0
  37. package/dist/tools/attachments.d.ts +31 -0
  38. package/dist/tools/attachments.d.ts.map +1 -0
  39. package/dist/tools/attachments.js +112 -0
  40. package/dist/tools/attachments.js.map +1 -0
  41. package/dist/tools/bills.d.ts +43 -0
  42. package/dist/tools/bills.d.ts.map +1 -0
  43. package/dist/tools/bills.js +112 -0
  44. package/dist/tools/bills.js.map +1 -0
  45. package/dist/tools/budgets.d.ts +65 -0
  46. package/dist/tools/budgets.d.ts.map +1 -0
  47. package/dist/tools/budgets.js +209 -0
  48. package/dist/tools/budgets.js.map +1 -0
  49. package/dist/tools/categories.d.ts +27 -0
  50. package/dist/tools/categories.d.ts.map +1 -0
  51. package/dist/tools/categories.js +83 -0
  52. package/dist/tools/categories.js.map +1 -0
  53. package/dist/tools/currencies.d.ts +32 -0
  54. package/dist/tools/currencies.d.ts.map +1 -0
  55. package/dist/tools/currencies.js +104 -0
  56. package/dist/tools/currencies.js.map +1 -0
  57. package/dist/tools/exports.d.ts +10 -0
  58. package/dist/tools/exports.d.ts.map +1 -0
  59. package/dist/tools/exports.js +37 -0
  60. package/dist/tools/exports.js.map +1 -0
  61. package/dist/tools/index.d.ts +14 -0
  62. package/dist/tools/index.d.ts.map +1 -0
  63. package/dist/tools/index.js +94 -0
  64. package/dist/tools/index.js.map +1 -0
  65. package/dist/tools/object-groups.d.ts +30 -0
  66. package/dist/tools/object-groups.d.ts.map +1 -0
  67. package/dist/tools/object-groups.js +107 -0
  68. package/dist/tools/object-groups.js.map +1 -0
  69. package/dist/tools/piggy-banks.d.ts +41 -0
  70. package/dist/tools/piggy-banks.d.ts.map +1 -0
  71. package/dist/tools/piggy-banks.js +110 -0
  72. package/dist/tools/piggy-banks.js.map +1 -0
  73. package/dist/tools/recurring.d.ts +70 -0
  74. package/dist/tools/recurring.d.ts.map +1 -0
  75. package/dist/tools/recurring.js +279 -0
  76. package/dist/tools/recurring.js.map +1 -0
  77. package/dist/tools/reports.d.ts +40 -0
  78. package/dist/tools/reports.d.ts.map +1 -0
  79. package/dist/tools/reports.js +413 -0
  80. package/dist/tools/reports.js.map +1 -0
  81. package/dist/tools/rules.d.ts +105 -0
  82. package/dist/tools/rules.d.ts.map +1 -0
  83. package/dist/tools/rules.js +361 -0
  84. package/dist/tools/rules.js.map +1 -0
  85. package/dist/tools/transaction-links.d.ts +30 -0
  86. package/dist/tools/transaction-links.d.ts.map +1 -0
  87. package/dist/tools/transaction-links.js +91 -0
  88. package/dist/tools/transaction-links.js.map +1 -0
  89. package/dist/tools/transactions.d.ts +72 -0
  90. package/dist/tools/transactions.d.ts.map +1 -0
  91. package/dist/tools/transactions.js +262 -0
  92. package/dist/tools/transactions.js.map +1 -0
  93. package/dist/transform.d.ts +48 -0
  94. package/dist/transform.d.ts.map +1 -0
  95. package/dist/transform.js +29 -0
  96. package/dist/transform.js.map +1 -0
  97. package/dist/types.d.ts +2 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +2 -0
  100. package/dist/types.js.map +1 -0
  101. package/package.json +61 -0
package/dist/args.js ADDED
@@ -0,0 +1,65 @@
1
+ import { PRESETS, TOOL_GROUPS } from './tools/index.js';
2
+ export function parseArgs(args) {
3
+ let transport = 'stdio';
4
+ let host = '127.0.0.1';
5
+ let port = 3000;
6
+ let portWasExplicit = false;
7
+ let preset;
8
+ let groups;
9
+ let readOnly = false;
10
+ for (let i = 0; i < args.length; i++) {
11
+ const arg = args[i];
12
+ if (arg === '--transport' && args[i + 1]) {
13
+ const val = args[++i];
14
+ if (val !== 'stdio' && val !== 'http') {
15
+ throw new Error(`--transport must be "stdio" or "http", got "${val}"`);
16
+ }
17
+ transport = val;
18
+ }
19
+ else if (arg === '--host' && args[i + 1]) {
20
+ host = args[++i];
21
+ }
22
+ else if (arg === '--port' && args[i + 1]) {
23
+ const parsed = parseInt(args[++i], 10);
24
+ if (Number.isNaN(parsed) || parsed < 1 || parsed > 65535) {
25
+ throw new Error('--port must be a valid port number (1–65535)');
26
+ }
27
+ port = parsed;
28
+ portWasExplicit = true;
29
+ }
30
+ else if (arg === '--preset' && args[i + 1]) {
31
+ const val = args[++i];
32
+ if (!(val in PRESETS)) {
33
+ throw new Error(`Unknown preset "${val}". Valid presets: ${Object.keys(PRESETS).join(', ')}`);
34
+ }
35
+ preset = val;
36
+ }
37
+ else if (arg === '--groups' && args[i + 1]) {
38
+ const parts = args[++i]
39
+ .split(',')
40
+ .map((g) => g.trim())
41
+ .filter(Boolean);
42
+ for (const g of parts) {
43
+ if (!TOOL_GROUPS.includes(g)) {
44
+ throw new Error(`Unknown group "${g}". Valid groups: ${TOOL_GROUPS.join(', ')}`);
45
+ }
46
+ }
47
+ groups = parts;
48
+ }
49
+ else if (arg === '--read-only') {
50
+ readOnly = true;
51
+ }
52
+ }
53
+ if (preset !== undefined && groups !== undefined) {
54
+ throw new Error('Cannot use both --preset and --groups. Choose one.');
55
+ }
56
+ const filterOptions = {};
57
+ if (preset !== undefined)
58
+ filterOptions.preset = preset;
59
+ if (groups !== undefined)
60
+ filterOptions.groups = groups;
61
+ if (readOnly)
62
+ filterOptions.readOnly = true;
63
+ return { transport, host, port, portWasExplicit, filterOptions };
64
+ }
65
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { QueryParams } from './types.js';
2
+ export declare class FireflyError extends Error {
3
+ readonly status: number;
4
+ readonly url: string;
5
+ readonly body: string;
6
+ constructor(status: number, url: string, body: string);
7
+ }
8
+ export declare function formatError(err: unknown): string;
9
+ export declare class FireflyClient {
10
+ private readonly tokenResolver;
11
+ private readonly baseUrl;
12
+ private readonly timeoutMs;
13
+ constructor(baseUrl: string, tokenResolver: string | (() => string));
14
+ private getToken;
15
+ private buildUrl;
16
+ private rawFetch;
17
+ private request;
18
+ get<T = unknown>(path: string, params?: QueryParams): Promise<T>;
19
+ post<T = unknown>(path: string, body: unknown, params?: QueryParams): Promise<T>;
20
+ put<T = unknown>(path: string, body: unknown): Promise<T>;
21
+ delete(path: string): Promise<void>;
22
+ postBinary(path: string, body: Uint8Array): Promise<void>;
23
+ getText(path: string, params?: QueryParams): Promise<string>;
24
+ }
25
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;aACd,GAAG,EAAE,MAAM;aACX,IAAI,EAAE,MAAM;gBAFZ,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM;CAK/B;AAgBD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAiBhD;AAED,qBAAa,aAAa;IAMtB,OAAO,CAAC,QAAQ,CAAC,aAAa;IALhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;gBAGlC,OAAO,EAAE,MAAM,EACE,aAAa,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC;IAKzD,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,QAAQ;YAeF,QAAQ;YAqBR,OAAO;IAcf,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhE,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhF,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIzD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAWzD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;CAUnE"}
package/dist/client.js ADDED
@@ -0,0 +1,146 @@
1
+ export class FireflyError extends Error {
2
+ status;
3
+ url;
4
+ body;
5
+ constructor(status, url, body) {
6
+ super(`Firefly III API error ${status} at ${url.split('?')[0]}: ${body}`);
7
+ this.status = status;
8
+ this.url = url;
9
+ this.body = body;
10
+ this.name = 'FireflyError';
11
+ }
12
+ }
13
+ function parseFieldErrors(body) {
14
+ try {
15
+ const parsed = JSON.parse(body);
16
+ if (parsed.errors && Object.keys(parsed.errors).length > 0) {
17
+ return Object.entries(parsed.errors)
18
+ .map(([field, msgs]) => `${field} — ${msgs.join(', ')}`)
19
+ .join('; ');
20
+ }
21
+ }
22
+ catch {
23
+ // fall through
24
+ }
25
+ return null;
26
+ }
27
+ export function formatError(err) {
28
+ if (err instanceof FireflyError) {
29
+ if (err.status === 400) {
30
+ const details = parseFieldErrors(err.body);
31
+ return details ? `Bad request: ${details}` : 'Bad request — check your input parameters.';
32
+ }
33
+ if (err.status === 401)
34
+ return 'Authentication failed. Check your FIREFLY_TOKEN.';
35
+ if (err.status === 404)
36
+ return 'Resource not found.';
37
+ if (err.status === 422) {
38
+ const details = parseFieldErrors(err.body);
39
+ return details ? `Validation failed: ${details}` : 'Invalid request parameters.';
40
+ }
41
+ if (err.status >= 500)
42
+ return 'Firefly III server error. Try again later.';
43
+ return `API error ${err.status}.`;
44
+ }
45
+ if (err instanceof Error)
46
+ return err.message;
47
+ return 'An unknown error occurred.';
48
+ }
49
+ export class FireflyClient {
50
+ tokenResolver;
51
+ baseUrl;
52
+ timeoutMs = 30_000;
53
+ constructor(baseUrl, tokenResolver) {
54
+ this.tokenResolver = tokenResolver;
55
+ this.baseUrl = baseUrl.replace(/\/$/, '');
56
+ }
57
+ getToken() {
58
+ return typeof this.tokenResolver === 'function' ? this.tokenResolver() : this.tokenResolver;
59
+ }
60
+ buildUrl(path, params) {
61
+ const url = new URL(`${this.baseUrl}/api/v1${path}`);
62
+ if (params) {
63
+ for (const [key, value] of Object.entries(params)) {
64
+ if (value === undefined)
65
+ continue;
66
+ if (Array.isArray(value)) {
67
+ for (const v of value)
68
+ url.searchParams.append(key, String(v));
69
+ }
70
+ else {
71
+ url.searchParams.set(key, String(value));
72
+ }
73
+ }
74
+ }
75
+ return url.toString();
76
+ }
77
+ async rawFetch(url, init) {
78
+ const controller = new AbortController();
79
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
80
+ let response;
81
+ try {
82
+ response = await fetch(url, { ...init, signal: controller.signal });
83
+ }
84
+ catch (err) {
85
+ if (err instanceof Error && err.name === 'AbortError') {
86
+ throw new Error(`Request to ${url} timed out after ${this.timeoutMs}ms.`);
87
+ }
88
+ throw err;
89
+ }
90
+ finally {
91
+ clearTimeout(timer);
92
+ }
93
+ if (!response.ok) {
94
+ const responseBody = await response.text().catch(() => '');
95
+ throw new FireflyError(response.status, url, responseBody);
96
+ }
97
+ return response;
98
+ }
99
+ async request(method, url, body) {
100
+ const response = await this.rawFetch(url, {
101
+ method,
102
+ headers: {
103
+ Authorization: `Bearer ${this.getToken()}`,
104
+ Accept: 'application/json',
105
+ ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
106
+ },
107
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
108
+ });
109
+ if (response.status === 204)
110
+ return undefined;
111
+ return response.json();
112
+ }
113
+ async get(path, params) {
114
+ return this.request('GET', this.buildUrl(path, params));
115
+ }
116
+ async post(path, body, params) {
117
+ return this.request('POST', this.buildUrl(path, params), body);
118
+ }
119
+ async put(path, body) {
120
+ return this.request('PUT', this.buildUrl(path), body);
121
+ }
122
+ async delete(path) {
123
+ await this.request('DELETE', this.buildUrl(path));
124
+ }
125
+ async postBinary(path, body) {
126
+ await this.rawFetch(this.buildUrl(path), {
127
+ method: 'POST',
128
+ headers: {
129
+ Authorization: `Bearer ${this.getToken()}`,
130
+ 'Content-Type': 'application/octet-stream',
131
+ },
132
+ body: body,
133
+ });
134
+ }
135
+ async getText(path, params) {
136
+ const response = await this.rawFetch(this.buildUrl(path, params), {
137
+ method: 'GET',
138
+ headers: {
139
+ Authorization: `Bearer ${this.getToken()}`,
140
+ Accept: '*/*',
141
+ },
142
+ });
143
+ return response.text();
144
+ }
145
+ }
146
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import * as http from 'node:http';
3
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ interface RequestContext {
5
+ token: string;
6
+ }
7
+ export declare const requestContext: AsyncLocalStorage<RequestContext>;
8
+ export declare function createOAuthHandler(fireflyUrl: string, oauthClientId: string, mcpHandler: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>): (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
9
+ export declare function classifyHost(host: string): 'loopback' | 'non-loopback';
10
+ export declare function tryListen(httpServer: http.Server, host: string, port: number): Promise<void>;
11
+ export declare function startHttpServer(createMcpServer: () => McpServer, host: string, requestedPort: number, portWasExplicit: boolean, oauthClientId: string, fireflyUrl: string, tryListenFn?: (server: http.Server, host: string, port: number) => Promise<void>): Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,cAAc,mCAA0C,CAAC;AAyBtE,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GACjF,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CA6KxE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,cAAc,CAEtE;AAED,wBAAsB,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlG;AAED,wBAAsB,eAAe,CACnC,eAAe,EAAE,MAAM,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAa,GAC1F,OAAO,CAAC,IAAI,CAAC,CA4Ef"}
package/dist/http.js ADDED
@@ -0,0 +1,271 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import * as http from 'node:http';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ export const requestContext = new AsyncLocalStorage();
5
+ function readBody(req) {
6
+ return new Promise((resolve, reject) => {
7
+ let data = '';
8
+ req.on('data', (chunk) => {
9
+ data += chunk.toString();
10
+ });
11
+ req.on('end', () => resolve(data));
12
+ req.on('error', reject);
13
+ });
14
+ }
15
+ const LOOPBACK_REDIRECT_PREFIXES = ['http://127.0.0.1:', 'http://localhost:', 'http://[::1]:'];
16
+ function isRedirectUriAllowed(uri) {
17
+ if (LOOPBACK_REDIRECT_PREFIXES.some((p) => uri.startsWith(p)))
18
+ return true;
19
+ const extra = process.env.MCP_ALLOWED_REDIRECT_PREFIXES?.trim();
20
+ if (!extra)
21
+ return false;
22
+ return extra
23
+ .split(',')
24
+ .map((s) => s.trim())
25
+ .some((p) => p && uri.startsWith(p));
26
+ }
27
+ export function createOAuthHandler(fireflyUrl, oauthClientId, mcpHandler) {
28
+ const FLOW_TTL_MS = 10 * 60 * 1000;
29
+ const pendingFlows = new Map();
30
+ function evictExpiredFlows() {
31
+ const now = Date.now();
32
+ for (const [key, entry] of pendingFlows) {
33
+ if (now - entry.createdAt > FLOW_TTL_MS)
34
+ pendingFlows.delete(key);
35
+ }
36
+ }
37
+ return async (req, res) => {
38
+ const baseUrl = (process.env.MCP_BASE_URL?.trim().replace(/\/$/, '') || null) ?? `http://${req.headers.host ?? '127.0.0.1:3000'}`;
39
+ if (req.method === 'GET' && req.url === '/.well-known/oauth-authorization-server') {
40
+ const metadata = {
41
+ issuer: fireflyUrl,
42
+ // Point both endpoints at our proxy so we can substitute redirect_uri transparently.
43
+ authorization_endpoint: `${baseUrl}/oauth/authorize`,
44
+ token_endpoint: `${baseUrl}/oauth/token`,
45
+ registration_endpoint: `${baseUrl}/oauth/register`,
46
+ response_types_supported: ['code'],
47
+ grant_types_supported: ['authorization_code', 'refresh_token'],
48
+ code_challenge_methods_supported: ['S256'],
49
+ client_id: oauthClientId,
50
+ };
51
+ res.writeHead(200, { 'Content-Type': 'application/json' });
52
+ res.end(JSON.stringify(metadata));
53
+ return;
54
+ }
55
+ // Authorization proxy — no auth required.
56
+ // Claude sends its dynamic redirect_uri here. We store it, substitute our stable callback URL,
57
+ // then 302 the browser to Firefly III's real authorize endpoint.
58
+ // Firefly III does exact URI matching, so substituting here is the only way to make it work
59
+ // without updating the registered URI on every new Claude session.
60
+ if (req.method === 'GET' && req.url?.startsWith('/oauth/authorize')) {
61
+ const incomingUrl = new URL(req.url, baseUrl);
62
+ const clientRedirectUri = incomingUrl.searchParams.get('redirect_uri');
63
+ if (clientRedirectUri && !isRedirectUriAllowed(clientRedirectUri)) {
64
+ res.writeHead(400, { 'Content-Type': 'application/json' });
65
+ res.end(JSON.stringify({ error: 'invalid_redirect_uri', error_description: 'redirect_uri is not allowed' }));
66
+ return;
67
+ }
68
+ const state = incomingUrl.searchParams.get('state');
69
+ if (clientRedirectUri && state) {
70
+ evictExpiredFlows();
71
+ pendingFlows.set(state, { redirectUri: clientRedirectUri, createdAt: Date.now() });
72
+ }
73
+ const fireflyAuthUrl = new URL(`${fireflyUrl}/oauth/authorize`);
74
+ incomingUrl.searchParams.forEach((value, key) => {
75
+ fireflyAuthUrl.searchParams.set(key, key === 'redirect_uri' ? `${baseUrl}/oauth/callback` : value);
76
+ });
77
+ res.writeHead(302, { Location: fireflyAuthUrl.toString() });
78
+ res.end();
79
+ return;
80
+ }
81
+ // Dynamic client registration stub (RFC 7591) — no auth required.
82
+ // Firefly III does not support dynamic registration, so we handle it here.
83
+ if (req.method === 'POST' && req.url === '/oauth/register') {
84
+ const body = await readBody(req);
85
+ let redirectUris = [];
86
+ try {
87
+ const parsed = JSON.parse(body);
88
+ if (Array.isArray(parsed.redirect_uris)) {
89
+ redirectUris = parsed.redirect_uris;
90
+ }
91
+ }
92
+ catch {
93
+ // no body or invalid JSON — return empty redirect_uris
94
+ }
95
+ if (redirectUris[0] && !isRedirectUriAllowed(redirectUris[0])) {
96
+ res.writeHead(400, { 'Content-Type': 'application/json' });
97
+ res.end(JSON.stringify({ error: 'invalid_redirect_uri', error_description: 'redirect_uri is not allowed' }));
98
+ return;
99
+ }
100
+ const registration = {
101
+ client_id: oauthClientId,
102
+ client_id_issued_at: Math.floor(Date.now() / 1000),
103
+ client_secret_expires_at: 0,
104
+ token_endpoint_auth_method: 'none',
105
+ grant_types: ['authorization_code', 'refresh_token'],
106
+ response_types: ['code'],
107
+ redirect_uris: redirectUris.length > 0 ? [`${baseUrl}/oauth/callback`] : [],
108
+ };
109
+ res.writeHead(201, { 'Content-Type': 'application/json' });
110
+ res.end(JSON.stringify(registration));
111
+ return;
112
+ }
113
+ // Token proxy — no auth required.
114
+ // Claude sends its dynamic redirect_uri in the token exchange, but Firefly III validates that
115
+ // it matches the one used in the authorization request (our stable callback URL). Substitute here.
116
+ if (req.method === 'POST' && req.url === '/oauth/token') {
117
+ const body = await readBody(req);
118
+ const params = new URLSearchParams(body);
119
+ if (params.get('redirect_uri')) {
120
+ params.set('redirect_uri', `${baseUrl}/oauth/callback`);
121
+ }
122
+ const tokenController = new AbortController();
123
+ const tokenTimer = setTimeout(() => tokenController.abort(), 30_000);
124
+ let tokenResponse;
125
+ try {
126
+ tokenResponse = await fetch(`${fireflyUrl}/oauth/token`, {
127
+ method: 'POST',
128
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
129
+ body: params.toString(),
130
+ signal: tokenController.signal,
131
+ });
132
+ }
133
+ catch (err) {
134
+ if (err instanceof Error && err.name === 'AbortError') {
135
+ res.writeHead(504, { 'Content-Type': 'application/json' });
136
+ res.end(JSON.stringify({ error: 'timeout' }));
137
+ return;
138
+ }
139
+ throw err;
140
+ }
141
+ finally {
142
+ clearTimeout(tokenTimer);
143
+ }
144
+ const responseBody = await tokenResponse.text();
145
+ const contentType = tokenResponse.headers.get('Content-Type') ?? 'application/json';
146
+ res.writeHead(tokenResponse.status, { 'Content-Type': contentType });
147
+ res.end(responseBody);
148
+ return;
149
+ }
150
+ // Callback proxy — no auth required.
151
+ // Firefly III redirects here after authorization. We forward code+state to Claude's actual
152
+ // dynamic-port callback so Claude can complete the token exchange.
153
+ if (req.method === 'GET' && req.url?.startsWith('/oauth/callback')) {
154
+ const incomingUrl = new URL(req.url, baseUrl);
155
+ const state = incomingUrl.searchParams.get('state');
156
+ if (!state) {
157
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
158
+ res.end('No pending OAuth flow for this state. Start authorization from your MCP client.');
159
+ return;
160
+ }
161
+ const entry = pendingFlows.get(state);
162
+ const isExpired = entry ? Date.now() - entry.createdAt > FLOW_TTL_MS : false;
163
+ if (!entry || isExpired) {
164
+ evictExpiredFlows();
165
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
166
+ res.end(isExpired
167
+ ? 'OAuth flow expired. Start authorization again from your MCP client.'
168
+ : 'No pending OAuth flow for this state. Start authorization from your MCP client.');
169
+ return;
170
+ }
171
+ pendingFlows.delete(state);
172
+ const target = new URL(entry.redirectUri);
173
+ incomingUrl.searchParams.forEach((value, key) => {
174
+ target.searchParams.set(key, value);
175
+ });
176
+ res.writeHead(302, { Location: target.toString() });
177
+ res.end();
178
+ return;
179
+ }
180
+ const authHeader = req.headers.authorization;
181
+ const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
182
+ if (!token) {
183
+ res.writeHead(401, {
184
+ 'WWW-Authenticate': 'Bearer resource="Firefly III MCP"',
185
+ 'Content-Type': 'application/json',
186
+ });
187
+ res.end(JSON.stringify({ error: 'unauthorized', error_description: 'Bearer token required' }));
188
+ return;
189
+ }
190
+ await requestContext.run({ token }, () => mcpHandler(req, res));
191
+ };
192
+ }
193
+ export function classifyHost(host) {
194
+ return ['127.0.0.1', '::1', 'localhost'].includes(host) ? 'loopback' : 'non-loopback';
195
+ }
196
+ export async function tryListen(httpServer, host, port) {
197
+ return new Promise((resolve, reject) => {
198
+ httpServer.once('error', reject);
199
+ httpServer.listen(port, host, () => {
200
+ httpServer.removeListener('error', reject);
201
+ resolve();
202
+ });
203
+ });
204
+ }
205
+ export async function startHttpServer(createMcpServer, host, requestedPort, portWasExplicit, oauthClientId, fireflyUrl, tryListenFn = tryListen) {
206
+ if (!process.env.MCP_BASE_URL?.trim()) {
207
+ if (classifyHost(host) === 'non-loopback') {
208
+ process.stderr.write(`Error: MCP_BASE_URL must be set when binding to a non-loopback interface (--host ${host}).\n` +
209
+ `Without it, the Host header controls OAuth callback URLs — an attacker can forge it.\n` +
210
+ `Set MCP_BASE_URL to the public URL of this server, e.g.:\n` +
211
+ ` MCP_BASE_URL=https://mcp.example.com\n`);
212
+ process.exit(1);
213
+ }
214
+ else {
215
+ process.stderr.write(`Warning: MCP_BASE_URL is not set. OAuth URLs use the Host header — safe for local use only.\n`);
216
+ }
217
+ }
218
+ // Stateless HTTP transport requires a fresh transport + server per request.
219
+ // The WebStandardStreamableHTTPServerTransport throws if reused across requests.
220
+ const oauthHandler = createOAuthHandler(fireflyUrl, oauthClientId, async (req, res) => {
221
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
222
+ const server = createMcpServer();
223
+ await server.connect(transport);
224
+ await transport.handleRequest(req, res);
225
+ });
226
+ const httpServer = http.createServer(async (req, res) => {
227
+ try {
228
+ await oauthHandler(req, res);
229
+ }
230
+ catch (err) {
231
+ process.stderr.write(`HTTP request handler error: ${err}\n`);
232
+ if (!res.headersSent) {
233
+ res.writeHead(500);
234
+ res.end('Internal server error');
235
+ }
236
+ }
237
+ });
238
+ let port = requestedPort;
239
+ let moved = false;
240
+ while (true) {
241
+ try {
242
+ await tryListenFn(httpServer, host, port);
243
+ break;
244
+ }
245
+ catch (err) {
246
+ const nodeErr = err;
247
+ if (nodeErr.code !== 'EADDRINUSE') {
248
+ throw err;
249
+ }
250
+ if (portWasExplicit) {
251
+ process.stderr.write(`Error: Port ${port} on ${host} is already in use. Choose a different port with --port.\n`);
252
+ process.exit(1);
253
+ }
254
+ const attempted = port - requestedPort;
255
+ if (attempted >= 10) {
256
+ process.stderr.write(`Error: Ports ${requestedPort}–${requestedPort + 10} on ${host} are all in use. Specify an available port with --port.\n`);
257
+ process.exit(1);
258
+ }
259
+ port++;
260
+ moved = true;
261
+ }
262
+ }
263
+ httpServer.on('error', (err) => {
264
+ process.stderr.write(`HTTP server error: ${err}\n`);
265
+ });
266
+ process.stdout.write(`Firefly III MCP server listening on http://${host}:${port}\n`);
267
+ if (moved) {
268
+ process.stdout.write(`(port ${requestedPort} was in use — moved up automatically)\n`);
269
+ }
270
+ }
271
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { parseArgs } from './args.js';
4
+ import { FireflyClient } from './client.js';
5
+ import { requestContext, startHttpServer } from './http.js';
6
+ import { createServer } from './server.js';
7
+ let parsed;
8
+ try {
9
+ parsed = parseArgs(process.argv.slice(2));
10
+ }
11
+ catch (err) {
12
+ process.stderr.write(`Error: ${err.message}\n`);
13
+ process.exit(1);
14
+ }
15
+ const { transport, host, port, portWasExplicit, filterOptions } = parsed;
16
+ const url = process.env.FIREFLY_URL;
17
+ if (transport === 'http') {
18
+ const oauthClientId = process.env.FIREFLY_OAUTH_CLIENT_ID;
19
+ if (!url || !oauthClientId) {
20
+ process.stderr.write('Error: FIREFLY_URL and FIREFLY_OAUTH_CLIENT_ID environment variables are required for HTTP transport.\n' +
21
+ 'See .env.example for configuration instructions.\n');
22
+ process.exit(1);
23
+ }
24
+ const client = new FireflyClient(url, () => {
25
+ const store = requestContext.getStore();
26
+ if (!store)
27
+ throw new Error('No request context — Bearer token was not set before this call');
28
+ return store.token;
29
+ });
30
+ await startHttpServer(() => createServer(client, filterOptions), host, port, portWasExplicit, oauthClientId, url);
31
+ }
32
+ else {
33
+ const token = process.env.FIREFLY_TOKEN;
34
+ if (!url || !token) {
35
+ process.stderr.write('Error: FIREFLY_URL and FIREFLY_TOKEN environment variables are required for stdio transport.\n' +
36
+ 'See .env.example for configuration instructions.\n');
37
+ process.exit(1);
38
+ }
39
+ const client = new FireflyClient(url, token);
40
+ const server = createServer(client, filterOptions);
41
+ const stdioTransport = new StdioServerTransport();
42
+ await server.connect(stdioTransport);
43
+ }
44
+ //# 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;AACjF,OAAO,EAAmB,SAAS,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,IAAI,MAAkB,CAAC;AACvB,IAAI,CAAC;IACH,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5C,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAW,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;AAEzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;AAEpC,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;IACzB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;IAC1D,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yGAAyG;YACvG,oDAAoD,CACvD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QAC9F,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;AACpH,CAAC;KAAM,CAAC;IACN,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gGAAgG;YAC9F,oDAAoD,CACvD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACnD,MAAM,cAAc,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAClD,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { FireflyClient } from './client.js';
3
+ import { type ToolFilterOptions } from './tools/index.js';
4
+ export declare function createServer(client: FireflyClient, filterOptions?: ToolFilterOptions): McpServer;
5
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAI5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,GAAE,iBAAsB,GAAG,SAAS,CASpG"}