@stupidcodefactory/freeagent-mcp-server 1.0.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 (226) hide show
  1. package/README.md +187 -0
  2. package/dist/api/freeagent-client.d.ts +12 -0
  3. package/dist/api/freeagent-client.d.ts.map +1 -0
  4. package/dist/api/freeagent-client.js +114 -0
  5. package/dist/api/freeagent-client.js.map +1 -0
  6. package/dist/api/index.d.ts +7 -0
  7. package/dist/api/index.d.ts.map +1 -0
  8. package/dist/api/index.js +4 -0
  9. package/dist/api/index.js.map +1 -0
  10. package/dist/api/rate-limiter.d.ts +15 -0
  11. package/dist/api/rate-limiter.d.ts.map +1 -0
  12. package/dist/api/rate-limiter.js +62 -0
  13. package/dist/api/rate-limiter.js.map +1 -0
  14. package/dist/api/retry-handler.d.ts +14 -0
  15. package/dist/api/retry-handler.d.ts.map +1 -0
  16. package/dist/api/retry-handler.js +49 -0
  17. package/dist/api/retry-handler.js.map +1 -0
  18. package/dist/auth/index.d.ts +7 -0
  19. package/dist/auth/index.d.ts.map +1 -0
  20. package/dist/auth/index.js +4 -0
  21. package/dist/auth/index.js.map +1 -0
  22. package/dist/auth/oauth.d.ts +16 -0
  23. package/dist/auth/oauth.d.ts.map +1 -0
  24. package/dist/auth/oauth.js +64 -0
  25. package/dist/auth/oauth.js.map +1 -0
  26. package/dist/auth/token-manager.d.ts +10 -0
  27. package/dist/auth/token-manager.d.ts.map +1 -0
  28. package/dist/auth/token-manager.js +71 -0
  29. package/dist/auth/token-manager.js.map +1 -0
  30. package/dist/auth/token-store.d.ts +10 -0
  31. package/dist/auth/token-store.d.ts.map +1 -0
  32. package/dist/auth/token-store.js +84 -0
  33. package/dist/auth/token-store.js.map +1 -0
  34. package/dist/config.d.ts +73 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +46 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +44 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/prompts/cash-flow-forecast.d.ts +12 -0
  43. package/dist/prompts/cash-flow-forecast.d.ts.map +1 -0
  44. package/dist/prompts/cash-flow-forecast.js +93 -0
  45. package/dist/prompts/cash-flow-forecast.js.map +1 -0
  46. package/dist/prompts/index.d.ts +8 -0
  47. package/dist/prompts/index.d.ts.map +1 -0
  48. package/dist/prompts/index.js +8 -0
  49. package/dist/prompts/index.js.map +1 -0
  50. package/dist/prompts/invoice-from-description.d.ts +12 -0
  51. package/dist/prompts/invoice-from-description.d.ts.map +1 -0
  52. package/dist/prompts/invoice-from-description.js +49 -0
  53. package/dist/prompts/invoice-from-description.js.map +1 -0
  54. package/dist/prompts/monthly-expense-summary.d.ts +12 -0
  55. package/dist/prompts/monthly-expense-summary.d.ts.map +1 -0
  56. package/dist/prompts/monthly-expense-summary.js +84 -0
  57. package/dist/prompts/monthly-expense-summary.js.map +1 -0
  58. package/dist/prompts/overdue-invoice-followup.d.ts +12 -0
  59. package/dist/prompts/overdue-invoice-followup.d.ts.map +1 -0
  60. package/dist/prompts/overdue-invoice-followup.js +63 -0
  61. package/dist/prompts/overdue-invoice-followup.js.map +1 -0
  62. package/dist/prompts/project-profitability.d.ts +12 -0
  63. package/dist/prompts/project-profitability.d.ts.map +1 -0
  64. package/dist/prompts/project-profitability.js +103 -0
  65. package/dist/prompts/project-profitability.js.map +1 -0
  66. package/dist/prompts/quarterly-tax-estimate.d.ts +12 -0
  67. package/dist/prompts/quarterly-tax-estimate.d.ts.map +1 -0
  68. package/dist/prompts/quarterly-tax-estimate.js +132 -0
  69. package/dist/prompts/quarterly-tax-estimate.js.map +1 -0
  70. package/dist/prompts/transaction-categorization.d.ts +12 -0
  71. package/dist/prompts/transaction-categorization.d.ts.map +1 -0
  72. package/dist/prompts/transaction-categorization.js +81 -0
  73. package/dist/prompts/transaction-categorization.js.map +1 -0
  74. package/dist/resources/bank-accounts.d.ts +8 -0
  75. package/dist/resources/bank-accounts.d.ts.map +1 -0
  76. package/dist/resources/bank-accounts.js +34 -0
  77. package/dist/resources/bank-accounts.js.map +1 -0
  78. package/dist/resources/bank-transactions.d.ts +11 -0
  79. package/dist/resources/bank-transactions.d.ts.map +1 -0
  80. package/dist/resources/bank-transactions.js +34 -0
  81. package/dist/resources/bank-transactions.js.map +1 -0
  82. package/dist/resources/bills.d.ts +13 -0
  83. package/dist/resources/bills.d.ts.map +1 -0
  84. package/dist/resources/bills.js +38 -0
  85. package/dist/resources/bills.js.map +1 -0
  86. package/dist/resources/categories.d.ts +4 -0
  87. package/dist/resources/categories.d.ts.map +1 -0
  88. package/dist/resources/categories.js +21 -0
  89. package/dist/resources/categories.js.map +1 -0
  90. package/dist/resources/company.d.ts +3 -0
  91. package/dist/resources/company.d.ts.map +1 -0
  92. package/dist/resources/company.js +12 -0
  93. package/dist/resources/company.js.map +1 -0
  94. package/dist/resources/contacts.d.ts +9 -0
  95. package/dist/resources/contacts.d.ts.map +1 -0
  96. package/dist/resources/contacts.js +37 -0
  97. package/dist/resources/contacts.js.map +1 -0
  98. package/dist/resources/expenses.d.ts +12 -0
  99. package/dist/resources/expenses.d.ts.map +1 -0
  100. package/dist/resources/expenses.js +36 -0
  101. package/dist/resources/expenses.js.map +1 -0
  102. package/dist/resources/index.d.ts +20 -0
  103. package/dist/resources/index.d.ts.map +1 -0
  104. package/dist/resources/index.js +12 -0
  105. package/dist/resources/index.js.map +1 -0
  106. package/dist/resources/invoices.d.ts +14 -0
  107. package/dist/resources/invoices.d.ts.map +1 -0
  108. package/dist/resources/invoices.js +40 -0
  109. package/dist/resources/invoices.js.map +1 -0
  110. package/dist/resources/projects.d.ts +10 -0
  111. package/dist/resources/projects.d.ts.map +1 -0
  112. package/dist/resources/projects.js +39 -0
  113. package/dist/resources/projects.js.map +1 -0
  114. package/dist/resources/timeslips.d.ts +12 -0
  115. package/dist/resources/timeslips.d.ts.map +1 -0
  116. package/dist/resources/timeslips.js +36 -0
  117. package/dist/resources/timeslips.js.map +1 -0
  118. package/dist/resources/users.d.ts +5 -0
  119. package/dist/resources/users.d.ts.map +1 -0
  120. package/dist/resources/users.js +30 -0
  121. package/dist/resources/users.js.map +1 -0
  122. package/dist/server.d.ts +9 -0
  123. package/dist/server.d.ts.map +1 -0
  124. package/dist/server.js +568 -0
  125. package/dist/server.js.map +1 -0
  126. package/dist/tools/bank-tools.d.ts +112 -0
  127. package/dist/tools/bank-tools.d.ts.map +1 -0
  128. package/dist/tools/bank-tools.js +143 -0
  129. package/dist/tools/bank-tools.js.map +1 -0
  130. package/dist/tools/bill-tools.d.ts +129 -0
  131. package/dist/tools/bill-tools.d.ts.map +1 -0
  132. package/dist/tools/bill-tools.js +121 -0
  133. package/dist/tools/bill-tools.js.map +1 -0
  134. package/dist/tools/contact-tools.d.ts +147 -0
  135. package/dist/tools/contact-tools.d.ts.map +1 -0
  136. package/dist/tools/contact-tools.js +140 -0
  137. package/dist/tools/contact-tools.js.map +1 -0
  138. package/dist/tools/index.d.ts +13 -0
  139. package/dist/tools/index.d.ts.map +1 -0
  140. package/dist/tools/index.js +13 -0
  141. package/dist/tools/index.js.map +1 -0
  142. package/dist/tools/invoice-tools.d.ts +171 -0
  143. package/dist/tools/invoice-tools.d.ts.map +1 -0
  144. package/dist/tools/invoice-tools.js +168 -0
  145. package/dist/tools/invoice-tools.js.map +1 -0
  146. package/dist/tools/project-tools.d.ts +117 -0
  147. package/dist/tools/project-tools.d.ts.map +1 -0
  148. package/dist/tools/project-tools.js +153 -0
  149. package/dist/tools/project-tools.js.map +1 -0
  150. package/dist/tools/query-tools.d.ts +91 -0
  151. package/dist/tools/query-tools.d.ts.map +1 -0
  152. package/dist/tools/query-tools.js +127 -0
  153. package/dist/tools/query-tools.js.map +1 -0
  154. package/dist/transformers/bank-transformer.d.ts +8 -0
  155. package/dist/transformers/bank-transformer.d.ts.map +1 -0
  156. package/dist/transformers/bank-transformer.js +46 -0
  157. package/dist/transformers/bank-transformer.js.map +1 -0
  158. package/dist/transformers/bill-transformer.d.ts +6 -0
  159. package/dist/transformers/bill-transformer.d.ts.map +1 -0
  160. package/dist/transformers/bill-transformer.js +34 -0
  161. package/dist/transformers/bill-transformer.js.map +1 -0
  162. package/dist/transformers/category-transformer.d.ts +5 -0
  163. package/dist/transformers/category-transformer.d.ts.map +1 -0
  164. package/dist/transformers/category-transformer.js +14 -0
  165. package/dist/transformers/category-transformer.js.map +1 -0
  166. package/dist/transformers/common.d.ts +7 -0
  167. package/dist/transformers/common.d.ts.map +1 -0
  168. package/dist/transformers/common.js +33 -0
  169. package/dist/transformers/common.js.map +1 -0
  170. package/dist/transformers/company-transformer.d.ts +4 -0
  171. package/dist/transformers/company-transformer.d.ts.map +1 -0
  172. package/dist/transformers/company-transformer.js +10 -0
  173. package/dist/transformers/company-transformer.js.map +1 -0
  174. package/dist/transformers/contact-transformer.d.ts +5 -0
  175. package/dist/transformers/contact-transformer.d.ts.map +1 -0
  176. package/dist/transformers/contact-transformer.js +18 -0
  177. package/dist/transformers/contact-transformer.js.map +1 -0
  178. package/dist/transformers/expense-transformer.d.ts +5 -0
  179. package/dist/transformers/expense-transformer.d.ts.map +1 -0
  180. package/dist/transformers/expense-transformer.js +19 -0
  181. package/dist/transformers/expense-transformer.js.map +1 -0
  182. package/dist/transformers/index.d.ts +11 -0
  183. package/dist/transformers/index.d.ts.map +1 -0
  184. package/dist/transformers/index.js +11 -0
  185. package/dist/transformers/index.js.map +1 -0
  186. package/dist/transformers/invoice-transformer.d.ts +6 -0
  187. package/dist/transformers/invoice-transformer.d.ts.map +1 -0
  188. package/dist/transformers/invoice-transformer.js +39 -0
  189. package/dist/transformers/invoice-transformer.js.map +1 -0
  190. package/dist/transformers/project-transformer.d.ts +9 -0
  191. package/dist/transformers/project-transformer.d.ts.map +1 -0
  192. package/dist/transformers/project-transformer.js +53 -0
  193. package/dist/transformers/project-transformer.js.map +1 -0
  194. package/dist/transformers/user-transformer.d.ts +5 -0
  195. package/dist/transformers/user-transformer.d.ts.map +1 -0
  196. package/dist/transformers/user-transformer.js +14 -0
  197. package/dist/transformers/user-transformer.js.map +1 -0
  198. package/dist/types/freeagent/index.d.ts +256 -0
  199. package/dist/types/freeagent/index.d.ts.map +1 -0
  200. package/dist/types/freeagent/index.js +3 -0
  201. package/dist/types/freeagent/index.js.map +1 -0
  202. package/dist/types/llm/index.d.ts +159 -0
  203. package/dist/types/llm/index.d.ts.map +1 -0
  204. package/dist/types/llm/index.js +3 -0
  205. package/dist/types/llm/index.js.map +1 -0
  206. package/dist/utils/error-handler.d.ts +11 -0
  207. package/dist/utils/error-handler.d.ts.map +1 -0
  208. package/dist/utils/error-handler.js +57 -0
  209. package/dist/utils/error-handler.js.map +1 -0
  210. package/dist/utils/index.d.ts +5 -0
  211. package/dist/utils/index.d.ts.map +1 -0
  212. package/dist/utils/index.js +5 -0
  213. package/dist/utils/index.js.map +1 -0
  214. package/dist/utils/sanitizer.d.ts +4 -0
  215. package/dist/utils/sanitizer.d.ts.map +1 -0
  216. package/dist/utils/sanitizer.js +42 -0
  217. package/dist/utils/sanitizer.js.map +1 -0
  218. package/dist/utils/validators.d.ts +19 -0
  219. package/dist/utils/validators.d.ts.map +1 -0
  220. package/dist/utils/validators.js +83 -0
  221. package/dist/utils/validators.js.map +1 -0
  222. package/dist/utils/zod-to-json-schema.d.ts +24 -0
  223. package/dist/utils/zod-to-json-schema.d.ts.map +1 -0
  224. package/dist/utils/zod-to-json-schema.js +141 -0
  225. package/dist/utils/zod-to-json-schema.js.map +1 -0
  226. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # FreeAgent MCP Server
2
+
3
+ MCP (Model Context Protocol) server for the FreeAgent accounting API. Enables LLMs to securely access and manage accounting data including contacts, invoices, bills, bank transactions, and more.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js 24 LTS or later
8
+ - A FreeAgent account with API access
9
+ - FreeAgent API credentials (Client ID and Secret)
10
+
11
+ ## Installation
12
+
13
+ ### From npm (recommended)
14
+
15
+ ```bash
16
+ npm install -g freeagent-mcp-server
17
+ ```
18
+
19
+ ### From source
20
+
21
+ ```bash
22
+ git clone https://github.com/StupidCodeFactory/freeagent-mcp.git
23
+ cd freeagent-mcp
24
+ npm install
25
+ npm run build
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ ### 1. Get FreeAgent API Credentials
31
+
32
+ 1. Log in to your FreeAgent account
33
+ 2. Go to **Settings** → **Integrations** → **Developer Dashboard**
34
+ 3. Create a new app to get your Client ID and Client Secret
35
+ 4. Set the redirect URI to `http://localhost:3000/callback`
36
+
37
+ ### 2. Set Environment Variables
38
+
39
+ Create a `.env` file or export the following environment variables:
40
+
41
+ ```bash
42
+ export FREEAGENT_CLIENT_ID="your_client_id"
43
+ export FREEAGENT_CLIENT_SECRET="your_client_secret"
44
+ export FREEAGENT_REDIRECT_URI="http://localhost:3000/callback"
45
+ export FREEAGENT_ENVIRONMENT="sandbox" # or "production"
46
+ ```
47
+
48
+ Optional:
49
+ ```bash
50
+ export TOKEN_ENCRYPTION_KEY="$(openssl rand -hex 32)" # For persistent token storage
51
+ export LOG_LEVEL="info"
52
+ ```
53
+
54
+ ## Usage with Claude Desktop
55
+
56
+ Add to your Claude Desktop configuration file:
57
+
58
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
59
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "freeagent": {
65
+ "command": "npx",
66
+ "args": ["freeagent-mcp-server"],
67
+ "env": {
68
+ "FREEAGENT_CLIENT_ID": "your_client_id",
69
+ "FREEAGENT_CLIENT_SECRET": "your_client_secret",
70
+ "FREEAGENT_REDIRECT_URI": "http://localhost:3000/callback",
71
+ "FREEAGENT_ENVIRONMENT": "sandbox"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ Or if installed from source:
79
+
80
+ ```json
81
+ {
82
+ "mcpServers": {
83
+ "freeagent": {
84
+ "command": "node",
85
+ "args": ["/path/to/freeagent-mcp/dist/index.js"],
86
+ "env": {
87
+ "FREEAGENT_CLIENT_ID": "your_client_id",
88
+ "FREEAGENT_CLIENT_SECRET": "your_client_secret",
89
+ "FREEAGENT_REDIRECT_URI": "http://localhost:3000/callback",
90
+ "FREEAGENT_ENVIRONMENT": "sandbox"
91
+ }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Authentication
98
+
99
+ On first use, the server will provide an authorization URL. Visit this URL to authorize the app with your FreeAgent account. After authorization, tokens are stored and refreshed automatically.
100
+
101
+ ## Available Resources
102
+
103
+ | Resource | URI | Description |
104
+ |----------|-----|-------------|
105
+ | Company | `freeagent://company` | Company profile and settings |
106
+ | Contacts | `freeagent://contacts` | Clients and suppliers |
107
+ | Invoices | `freeagent://invoices` | Sales invoices |
108
+ | Bills | `freeagent://bills` | Supplier bills |
109
+ | Bank Accounts | `freeagent://bank_accounts` | Bank account list |
110
+ | Bank Transactions | `freeagent://bank_transactions?bank_account={id}` | Transaction feed |
111
+ | Projects | `freeagent://projects` | Projects |
112
+ | Timeslips | `freeagent://timeslips` | Time entries |
113
+ | Expenses | `freeagent://expenses` | Expense claims |
114
+ | Categories | `freeagent://categories` | Account categories |
115
+ | Users | `freeagent://users` | Team members |
116
+
117
+ ## Available Tools
118
+
119
+ ### Invoices
120
+ - `create_invoice` - Create a new invoice
121
+ - `update_invoice` - Update a draft invoice
122
+ - `send_invoice` - Email invoice to contact
123
+ - `mark_invoice_sent` - Mark as sent without emailing
124
+ - `mark_invoice_paid` - Record payment
125
+ - `delete_invoice` - Delete draft invoice
126
+
127
+ ### Contacts
128
+ - `create_contact` - Add new client/supplier
129
+ - `update_contact` - Update contact details
130
+ - `delete_contact` - Remove contact
131
+
132
+ ### Bank Transactions
133
+ - `explain_transaction` - Categorize transaction
134
+ - `match_transaction_to_invoice` - Match to invoice payment
135
+ - `match_transaction_to_bill` - Match to bill payment
136
+ - `split_transaction` - Split across categories
137
+ - `unexplain_transaction` - Remove explanation
138
+
139
+ ### Bills
140
+ - `create_bill` - Record supplier bill
141
+ - `update_bill` - Modify bill
142
+ - `delete_bill` - Remove bill
143
+
144
+ ### Projects
145
+ - `create_project` - Create project
146
+ - `update_project` - Update project
147
+ - `create_task` - Add task to project
148
+ - `create_timeslip` - Log time entry
149
+
150
+ ### Queries
151
+ - `list_unpaid_invoices` - Get overdue/open invoices
152
+ - `get_bank_summary` - Aggregate balances
153
+ - `search_transactions` - Search by description
154
+ - `get_unexplained_transactions` - List unexplained transactions
155
+
156
+ ## Available Prompts
157
+
158
+ - `monthly_expense_summary` - Categorized expense report
159
+ - `invoice_from_description` - Create invoice from natural language
160
+ - `cash_flow_forecast` - Project cash position (30/60/90 days)
161
+ - `overdue_invoice_followup` - Draft reminder emails
162
+ - `transaction_categorization` - Suggest categories for unexplained transactions
163
+ - `project_profitability` - Analyze project margins
164
+ - `quarterly_tax_estimate` - Estimate tax liability
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ # Install dependencies
170
+ npm install
171
+
172
+ # Run in development mode
173
+ npm run dev
174
+
175
+ # Run tests
176
+ npm test
177
+
178
+ # Type check
179
+ npm run typecheck
180
+
181
+ # Build
182
+ npm run build
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT
@@ -0,0 +1,12 @@
1
+ import type { TokenManager } from '../auth/token-manager.js';
2
+ import { type RateLimiter } from './rate-limiter.js';
3
+ export interface FreeAgentClient {
4
+ get<T>(endpoint: string, params?: Record<string, string>): Promise<T>;
5
+ post<T>(endpoint: string, body: unknown): Promise<T>;
6
+ put<T>(endpoint: string, body: unknown): Promise<T>;
7
+ delete(endpoint: string): Promise<void>;
8
+ fetchAllPages<T>(endpoint: string, entityKey: string, params?: Record<string, string>): Promise<T[]>;
9
+ getRateLimitStatus(): ReturnType<RateLimiter['getStatus']>;
10
+ }
11
+ export declare function createFreeAgentClient(tokenManager: TokenManager): FreeAgentClient;
12
+ //# sourceMappingURL=freeagent-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freeagent-client.d.ts","sourceRoot":"","sources":["../../src/api/freeagent-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGxE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,aAAa,CAAC,CAAC,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAChB,kBAAkB,IAAI,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC;CAC5D;AAQD,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,YAAY,GAAG,eAAe,CAmJjF"}
@@ -0,0 +1,114 @@
1
+ import { FREEAGENT_API_BASE } from '../config.js';
2
+ import { createRateLimiter } from './rate-limiter.js';
3
+ import { withRetry } from './retry-handler.js';
4
+ export function createFreeAgentClient(tokenManager) {
5
+ const rateLimiter = createRateLimiter();
6
+ async function request(method, endpoint, body, params) {
7
+ return withRetry(async () => {
8
+ await rateLimiter.checkLimits();
9
+ const accessToken = await tokenManager.getAccessToken();
10
+ let url = `${FREEAGENT_API_BASE}${endpoint}`;
11
+ if (params && Object.keys(params).length > 0) {
12
+ const searchParams = new URLSearchParams(params);
13
+ url += `?${searchParams.toString()}`;
14
+ }
15
+ const headers = {
16
+ Authorization: `Bearer ${accessToken}`,
17
+ 'Content-Type': 'application/json',
18
+ Accept: 'application/json',
19
+ 'User-Agent': 'FreeAgent-MCP-Server/1.0.0',
20
+ };
21
+ const options = {
22
+ method,
23
+ headers,
24
+ };
25
+ if (body && (method === 'POST' || method === 'PUT')) {
26
+ options.body = JSON.stringify(body);
27
+ }
28
+ const response = await fetch(url, options);
29
+ rateLimiter.recordRequest();
30
+ if (!response.ok) {
31
+ const error = await parseErrorResponse(response);
32
+ throw error;
33
+ }
34
+ if (method === 'DELETE' || response.status === 204) {
35
+ return undefined;
36
+ }
37
+ return response.json();
38
+ });
39
+ }
40
+ async function parseErrorResponse(response) {
41
+ let message;
42
+ let retryAfter;
43
+ try {
44
+ const data = (await response.json());
45
+ if (data.errors && data.errors.length > 0) {
46
+ message = data.errors.map((e) => e.message).join('; ');
47
+ }
48
+ else if (data.error_description) {
49
+ message = data.error_description;
50
+ }
51
+ else if (data.error) {
52
+ message = data.error;
53
+ }
54
+ else {
55
+ message = `HTTP ${response.status}`;
56
+ }
57
+ }
58
+ catch {
59
+ message = `HTTP ${response.status}: ${response.statusText}`;
60
+ }
61
+ const retryAfterHeader = response.headers.get('Retry-After');
62
+ if (retryAfterHeader) {
63
+ retryAfter = parseInt(retryAfterHeader, 10);
64
+ }
65
+ const error = new Error(message);
66
+ error.status = response.status;
67
+ error.retryAfter = retryAfter;
68
+ return error;
69
+ }
70
+ const client = {
71
+ async get(endpoint, params) {
72
+ return request('GET', endpoint, undefined, params);
73
+ },
74
+ async post(endpoint, body) {
75
+ return request('POST', endpoint, body);
76
+ },
77
+ async put(endpoint, body) {
78
+ return request('PUT', endpoint, body);
79
+ },
80
+ async delete(endpoint) {
81
+ await request('DELETE', endpoint);
82
+ },
83
+ async fetchAllPages(endpoint, entityKey, params = {}) {
84
+ const results = [];
85
+ let page = 1;
86
+ const perPage = '100';
87
+ const maxPages = 50; // Safety limit
88
+ while (page <= maxPages) {
89
+ const pageParams = {
90
+ ...params,
91
+ page: page.toString(),
92
+ per_page: perPage,
93
+ };
94
+ const response = await request('GET', endpoint, undefined, pageParams);
95
+ const items = response[entityKey];
96
+ if (!items || items.length === 0) {
97
+ break;
98
+ }
99
+ results.push(...items);
100
+ // If we got fewer than requested, we're done
101
+ if (items.length < parseInt(perPage, 10)) {
102
+ break;
103
+ }
104
+ page++;
105
+ }
106
+ return results;
107
+ },
108
+ getRateLimitStatus() {
109
+ return rateLimiter.getStatus();
110
+ },
111
+ };
112
+ return client;
113
+ }
114
+ //# sourceMappingURL=freeagent-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freeagent-client.js","sourceRoot":"","sources":["../../src/api/freeagent-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { createFreeAgentClient } from './freeagent-client.js';
2
+ export type { FreeAgentClient } from './freeagent-client.js';
3
+ export { createRateLimiter } from './rate-limiter.js';
4
+ export type { RateLimiter, RateLimitStatus } from './rate-limiter.js';
5
+ export { withRetry, isHttpError, DEFAULT_RETRY_CONFIG } from './retry-handler.js';
6
+ export type { RetryConfig, HttpError } from './retry-handler.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { createFreeAgentClient } from './freeagent-client.js';
2
+ export { createRateLimiter } from './rate-limiter.js';
3
+ export { withRetry, isHttpError, DEFAULT_RETRY_CONFIG } from './retry-handler.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface RateLimiter {
2
+ checkLimits(): Promise<void>;
3
+ recordRequest(): void;
4
+ getStatus(): RateLimitStatus;
5
+ }
6
+ export interface RateLimitStatus {
7
+ minuteCount: number;
8
+ minuteLimit: number;
9
+ hourCount: number;
10
+ hourLimit: number;
11
+ minuteResetAt: number;
12
+ hourResetAt: number;
13
+ }
14
+ export declare function createRateLimiter(): RateLimiter;
15
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/api/rate-limiter.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,aAAa,IAAI,IAAI,CAAC;IACtB,SAAS,IAAI,eAAe,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD,wBAAgB,iBAAiB,IAAI,WAAW,CAiE/C"}
@@ -0,0 +1,62 @@
1
+ const MINUTE_LIMIT = 100; // Buffer below 120
2
+ const HOUR_LIMIT = 3400; // Buffer below 3600
3
+ export function createRateLimiter() {
4
+ let minuteCount = 0;
5
+ let hourCount = 0;
6
+ let minuteResetAt = Date.now() + 60 * 1000;
7
+ let hourResetAt = Date.now() + 60 * 60 * 1000;
8
+ function resetCountersIfNeeded() {
9
+ const now = Date.now();
10
+ if (now >= minuteResetAt) {
11
+ minuteCount = 0;
12
+ minuteResetAt = now + 60 * 1000;
13
+ }
14
+ if (now >= hourResetAt) {
15
+ hourCount = 0;
16
+ hourResetAt = now + 60 * 60 * 1000;
17
+ }
18
+ }
19
+ const limiter = {
20
+ async checkLimits() {
21
+ resetCountersIfNeeded();
22
+ const now = Date.now();
23
+ // Check minute limit
24
+ if (minuteCount >= MINUTE_LIMIT) {
25
+ const waitTime = minuteResetAt - now;
26
+ if (waitTime > 0) {
27
+ await sleep(waitTime);
28
+ resetCountersIfNeeded();
29
+ }
30
+ }
31
+ // Check hour limit
32
+ if (hourCount >= HOUR_LIMIT) {
33
+ const waitTime = hourResetAt - now;
34
+ if (waitTime > 0) {
35
+ await sleep(waitTime);
36
+ resetCountersIfNeeded();
37
+ }
38
+ }
39
+ },
40
+ recordRequest() {
41
+ resetCountersIfNeeded();
42
+ minuteCount++;
43
+ hourCount++;
44
+ },
45
+ getStatus() {
46
+ resetCountersIfNeeded();
47
+ return {
48
+ minuteCount,
49
+ minuteLimit: MINUTE_LIMIT,
50
+ hourCount,
51
+ hourLimit: HOUR_LIMIT,
52
+ minuteResetAt,
53
+ hourResetAt,
54
+ };
55
+ },
56
+ };
57
+ return limiter;
58
+ }
59
+ function sleep(ms) {
60
+ return new Promise((resolve) => setTimeout(resolve, ms));
61
+ }
62
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/api/rate-limiter.ts"],"names":[],"mappings":"AAeA,MAAM,YAAY,GAAG,GAAG,CAAC,CAAC,mBAAmB;AAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,oBAAoB;AAE7C,MAAM,UAAU,iBAAiB;IAC/B,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC3C,IAAI,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAE9C,SAAS,qBAAqB;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,GAAG,IAAI,aAAa,EAAE,CAAC;YACzB,WAAW,GAAG,CAAC,CAAC;YAChB,aAAa,GAAG,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC;QAClC,CAAC;QAED,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;YACvB,SAAS,GAAG,CAAC,CAAC;YACd,WAAW,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QACrC,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAgB;QAC3B,KAAK,CAAC,WAAW;YACf,qBAAqB,EAAE,CAAC;YAExB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAEvB,qBAAqB;YACrB,IAAI,WAAW,IAAI,YAAY,EAAE,CAAC;gBAChC,MAAM,QAAQ,GAAG,aAAa,GAAG,GAAG,CAAC;gBACrC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;oBACjB,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;oBACtB,qBAAqB,EAAE,CAAC;gBAC1B,CAAC;YACH,CAAC;YAED,mBAAmB;YACnB,IAAI,SAAS,IAAI,UAAU,EAAE,CAAC;gBAC5B,MAAM,QAAQ,GAAG,WAAW,GAAG,GAAG,CAAC;gBACnC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;oBACjB,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;oBACtB,qBAAqB,EAAE,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QAED,aAAa;YACX,qBAAqB,EAAE,CAAC;YACxB,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,CAAC;QACd,CAAC;QAED,SAAS;YACP,qBAAqB,EAAE,CAAC;YACxB,OAAO;gBACL,WAAW;gBACX,WAAW,EAAE,YAAY;gBACzB,SAAS;gBACT,SAAS,EAAE,UAAU;gBACrB,aAAa;gBACb,WAAW;aACZ,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface RetryConfig {
2
+ maxRetries: number;
3
+ baseDelayMs: number;
4
+ maxDelayMs: number;
5
+ retryableStatuses: number[];
6
+ }
7
+ export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
8
+ export interface HttpError extends Error {
9
+ status: number;
10
+ retryAfter?: number;
11
+ }
12
+ export declare function isHttpError(error: unknown): error is HttpError;
13
+ export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>;
14
+ //# sourceMappingURL=retry-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-handler.d.ts","sourceRoot":"","sources":["../../src/api/retry-handler.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,eAAO,MAAM,oBAAoB,EAAE,WAKlC,CAAC;AAEF,MAAM,WAAW,SAAU,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,GAAE,WAAkC,GACzC,OAAO,CAAC,CAAC,CAAC,CAwCZ"}
@@ -0,0 +1,49 @@
1
+ export const DEFAULT_RETRY_CONFIG = {
2
+ maxRetries: 3,
3
+ baseDelayMs: 1000,
4
+ maxDelayMs: 30000,
5
+ retryableStatuses: [429, 500, 502, 503, 504],
6
+ };
7
+ export function isHttpError(error) {
8
+ return error instanceof Error && 'status' in error && typeof error.status === 'number';
9
+ }
10
+ export async function withRetry(fn, config = DEFAULT_RETRY_CONFIG) {
11
+ let lastError;
12
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
13
+ try {
14
+ return await fn();
15
+ }
16
+ catch (error) {
17
+ if (!(error instanceof Error)) {
18
+ throw error;
19
+ }
20
+ lastError = error;
21
+ // Check if error is retryable
22
+ if (!isHttpError(error) || !config.retryableStatuses.includes(error.status)) {
23
+ throw error;
24
+ }
25
+ // Don't retry after last attempt
26
+ if (attempt === config.maxRetries) {
27
+ throw error;
28
+ }
29
+ // Calculate delay
30
+ let delay;
31
+ if (error.status === 429 && error.retryAfter) {
32
+ // Use server-provided retry-after value
33
+ delay = error.retryAfter * 1000;
34
+ }
35
+ else {
36
+ // Exponential backoff with jitter
37
+ const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt);
38
+ const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter
39
+ delay = Math.min(exponentialDelay + jitter, config.maxDelayMs);
40
+ }
41
+ await sleep(delay);
42
+ }
43
+ }
44
+ throw lastError ?? new Error('Retry failed');
45
+ }
46
+ function sleep(ms) {
47
+ return new Promise((resolve) => setTimeout(resolve, ms));
48
+ }
49
+ //# sourceMappingURL=retry-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-handler.js","sourceRoot":"","sources":["../../src/api/retry-handler.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,KAAK;IACjB,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;CAC7C,CAAC;AAOF,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,KAAK,IAAI,QAAQ,IAAI,KAAK,IAAI,OAAQ,KAAmB,CAAC,MAAM,KAAK,QAAQ,CAAC;AACxG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,SAAsB,oBAAoB;IAE1C,IAAI,SAA4B,CAAC;IAEjC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC9D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,EAAE,CAAC;gBAC9B,MAAM,KAAK,CAAC;YACd,CAAC;YAED,SAAS,GAAG,KAAK,CAAC;YAElB,8BAA8B;YAC9B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5E,MAAM,KAAK,CAAC;YACd,CAAC;YAED,iCAAiC;YACjC,IAAI,OAAO,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;gBAClC,MAAM,KAAK,CAAC;YACd,CAAC;YAED,kBAAkB;YAClB,IAAI,KAAa,CAAC;YAClB,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBAC7C,wCAAwC;gBACxC,KAAK,GAAG,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,kCAAkC;gBAClC,MAAM,gBAAgB,GAAG,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBACnE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,gBAAgB,CAAC,CAAC,eAAe;gBACtE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,GAAG,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YACjE,CAAC;YAED,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { generateAuthorizationUrl, exchangeCodeForTokens, refreshAccessToken } from './oauth.js';
2
+ export type { TokenData } from './oauth.js';
3
+ export { createTokenStore } from './token-store.js';
4
+ export type { TokenStore } from './token-store.js';
5
+ export { createTokenManager } from './token-manager.js';
6
+ export type { TokenManager } from './token-manager.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACjG,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { generateAuthorizationUrl, exchangeCodeForTokens, refreshAccessToken } from './oauth.js';
2
+ export { createTokenStore } from './token-store.js';
3
+ export { createTokenManager } from './token-manager.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface TokenResponse {
2
+ access_token: string;
3
+ token_type: string;
4
+ expires_in: number;
5
+ refresh_token: string;
6
+ }
7
+ export interface TokenData {
8
+ accessToken: string;
9
+ refreshToken: string;
10
+ expiresAt: number;
11
+ refreshTokenExpiresAt: number;
12
+ }
13
+ export declare function generateAuthorizationUrl(state?: string): string;
14
+ export declare function exchangeCodeForTokens(code: string): Promise<TokenData>;
15
+ export declare function refreshAccessToken(refreshToken: string): Promise<TokenData>;
16
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,wBAAwB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAY/D;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAyB5E;AAED,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAwBjF"}
@@ -0,0 +1,64 @@
1
+ import { config, FREEAGENT_AUTH_URL, FREEAGENT_TOKEN_URL } from '../config.js';
2
+ export function generateAuthorizationUrl(state) {
3
+ const params = new URLSearchParams({
4
+ client_id: config.freeagent.clientId,
5
+ redirect_uri: config.freeagent.redirectUri,
6
+ response_type: 'code',
7
+ });
8
+ if (state) {
9
+ params.set('state', state);
10
+ }
11
+ return `${FREEAGENT_AUTH_URL}?${params.toString()}`;
12
+ }
13
+ export async function exchangeCodeForTokens(code) {
14
+ const credentials = Buffer.from(`${config.freeagent.clientId}:${config.freeagent.clientSecret}`).toString('base64');
15
+ const response = await fetch(FREEAGENT_TOKEN_URL, {
16
+ method: 'POST',
17
+ headers: {
18
+ 'Content-Type': 'application/x-www-form-urlencoded',
19
+ Authorization: `Basic ${credentials}`,
20
+ },
21
+ body: new URLSearchParams({
22
+ grant_type: 'authorization_code',
23
+ code,
24
+ redirect_uri: config.freeagent.redirectUri,
25
+ }),
26
+ });
27
+ if (!response.ok) {
28
+ const error = await response.text();
29
+ throw new Error(`Token exchange failed: ${response.status} - ${error}`);
30
+ }
31
+ const data = (await response.json());
32
+ return tokenResponseToTokenData(data);
33
+ }
34
+ export async function refreshAccessToken(refreshToken) {
35
+ const credentials = Buffer.from(`${config.freeagent.clientId}:${config.freeagent.clientSecret}`).toString('base64');
36
+ const response = await fetch(FREEAGENT_TOKEN_URL, {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/x-www-form-urlencoded',
40
+ Authorization: `Basic ${credentials}`,
41
+ },
42
+ body: new URLSearchParams({
43
+ grant_type: 'refresh_token',
44
+ refresh_token: refreshToken,
45
+ }),
46
+ });
47
+ if (!response.ok) {
48
+ const error = await response.text();
49
+ throw new Error(`Token refresh failed: ${response.status} - ${error}`);
50
+ }
51
+ const data = (await response.json());
52
+ return tokenResponseToTokenData(data);
53
+ }
54
+ function tokenResponseToTokenData(response) {
55
+ const now = Date.now();
56
+ return {
57
+ accessToken: response.access_token,
58
+ refreshToken: response.refresh_token,
59
+ expiresAt: now + response.expires_in * 1000,
60
+ // Refresh tokens typically expire in 14 days
61
+ refreshTokenExpiresAt: now + 14 * 24 * 60 * 60 * 1000,
62
+ };
63
+ }
64
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type TokenData } from './oauth.js';
2
+ import type { TokenStore } from './token-store.js';
3
+ export interface TokenManager {
4
+ getAccessToken(): Promise<string>;
5
+ setTokens(tokens: TokenData): Promise<void>;
6
+ isAuthenticated(): boolean;
7
+ clearTokens(): Promise<void>;
8
+ }
9
+ export declare function createTokenManager(store: TokenStore): TokenManager;
10
+ //# sourceMappingURL=token-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,MAAM,WAAW,YAAY;IAC3B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,eAAe,IAAI,OAAO,CAAC;IAC3B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CAiFlE"}