@dizzlkheinz/ynab-mcpb 0.17.0 → 0.18.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 (182) hide show
  1. package/.env.example +33 -33
  2. package/.github/workflows/ci-tests.yml +45 -45
  3. package/.github/workflows/claude-code-review.yml +57 -57
  4. package/.github/workflows/claude.yml +50 -50
  5. package/.github/workflows/full-integration.yml +22 -22
  6. package/.github/workflows/publish.yml +12 -3
  7. package/.github/workflows/release.yml +2 -2
  8. package/CHANGELOG.md +10 -1
  9. package/CLAUDE.md +16 -12
  10. package/README.md +6 -1
  11. package/dist/bundle/index.cjs +49 -49
  12. package/dist/server/YNABMCPServer.d.ts +125 -54
  13. package/dist/server/YNABMCPServer.js +42 -11
  14. package/dist/server/cacheManager.js +6 -5
  15. package/dist/server/completions.d.ts +25 -0
  16. package/dist/server/completions.js +160 -0
  17. package/dist/server/config.d.ts +2 -2
  18. package/dist/server/errorHandler.js +1 -0
  19. package/dist/server/rateLimiter.js +3 -1
  20. package/dist/server/resources.d.ts +1 -0
  21. package/dist/server/resources.js +33 -16
  22. package/dist/server/securityMiddleware.d.ts +38 -8
  23. package/dist/server/securityMiddleware.js +1 -0
  24. package/dist/server/toolRegistry.d.ts +9 -0
  25. package/dist/server/toolRegistry.js +11 -0
  26. package/dist/tools/adapters.d.ts +3 -1
  27. package/dist/tools/adapters.js +1 -0
  28. package/dist/tools/reconciliation/executor.d.ts +2 -0
  29. package/dist/tools/reconciliation/executor.js +26 -1
  30. package/dist/tools/reconciliation/index.d.ts +3 -2
  31. package/dist/tools/reconciliation/index.js +4 -3
  32. package/dist/tools/schemas/outputs/index.d.ts +2 -2
  33. package/dist/tools/schemas/outputs/index.js +2 -2
  34. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +0 -15
  35. package/dist/tools/schemas/outputs/utilityOutputs.js +0 -9
  36. package/dist/tools/utilityTools.d.ts +0 -7
  37. package/dist/tools/utilityTools.js +1 -50
  38. package/docs/maintainers/npm-publishing.md +27 -0
  39. package/docs/reference/API.md +83 -97
  40. package/docs/technical/reconciliation-system-architecture.md +2251 -2251
  41. package/package.json +6 -6
  42. package/scripts/analyze-bundle.mjs +41 -41
  43. package/scripts/generate-mcpb.ps1 +95 -95
  44. package/scripts/watch-and-restart.ps1 +49 -49
  45. package/src/__tests__/comprehensive.integration.test.ts +4 -32
  46. package/src/__tests__/performance.test.ts +5 -14
  47. package/src/__tests__/setup.ts +45 -14
  48. package/src/__tests__/smoke.e2e.test.ts +70 -0
  49. package/src/__tests__/testUtils.ts +2 -113
  50. package/src/server/YNABMCPServer.ts +64 -10
  51. package/src/server/__tests__/YNABMCPServer.test.ts +0 -1
  52. package/src/server/__tests__/completions.integration.test.ts +117 -0
  53. package/src/server/__tests__/completions.test.ts +319 -0
  54. package/src/server/__tests__/resources.template.test.ts +3 -3
  55. package/src/server/__tests__/resources.test.ts +3 -3
  56. package/src/server/__tests__/toolRegistration.test.ts +3 -3
  57. package/src/server/cacheManager.ts +7 -6
  58. package/src/server/completions.ts +279 -0
  59. package/src/server/errorHandler.ts +1 -0
  60. package/src/server/rateLimiter.ts +4 -1
  61. package/src/server/resources.ts +49 -13
  62. package/src/server/securityMiddleware.ts +1 -0
  63. package/src/server/toolRegistry.ts +42 -0
  64. package/src/tools/__tests__/transactionTools.integration.test.ts +63 -3
  65. package/src/tools/__tests__/utilityTools.integration.test.ts +1 -85
  66. package/src/tools/__tests__/utilityTools.test.ts +1 -123
  67. package/src/tools/adapters.ts +22 -1
  68. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +462 -0
  69. package/src/tools/reconciliation/executor.ts +55 -1
  70. package/src/tools/reconciliation/index.ts +7 -3
  71. package/src/tools/schemas/outputs/index.ts +0 -3
  72. package/src/tools/schemas/outputs/utilityOutputs.ts +2 -43
  73. package/src/tools/toolCategories.ts +0 -1
  74. package/src/tools/utilityTools.ts +5 -76
  75. package/vitest.config.ts +4 -1
  76. package/.chunkhound.json +0 -11
  77. package/.code/agents/0098661e-0fa3-4990-beb9-c0cbf3f123aa/status.txt +0 -1
  78. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +0 -3
  79. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +0 -3
  80. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +0 -1
  81. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +0 -5
  82. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +0 -1569
  83. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +0 -3
  84. package/.code/agents/1324/exec-call_tIpx9uV1TpARbAMZonRQm8AO.txt +0 -757
  85. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +0 -2832
  86. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +0 -2709
  87. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +0 -2832
  88. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +0 -2832
  89. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +0 -2709
  90. package/.code/agents/1572/exec-call_GjVFBFOWcY7lE0idc5nWlLNh.txt +0 -781
  91. package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +0 -1
  92. package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +0 -5217
  93. package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +0 -2594
  94. package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +0 -2594
  95. package/.code/agents/1846/exec-call_1YNAVD18RjrMN7JnfkkQhUP3.txt +0 -766
  96. package/.code/agents/1846/exec-call_lh3lDzE4WJAh1lFiomiiZ73D.txt +0 -766
  97. package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +0 -231
  98. package/.code/agents/2038/exec-call_DYwOukaYsL8VCONWmV2rUW5u.txt +0 -766
  99. package/.code/agents/2038/exec-call_c7fOQ7UrpVcTtvdfGBRM146V.txt +0 -652
  100. package/.code/agents/2038/exec-call_ySNyq9Mm55jWE480s54r5QcA.txt +0 -766
  101. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +0 -2590
  102. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +0 -5195
  103. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +0 -286
  104. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +0 -218
  105. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +0 -180
  106. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +0 -3
  107. package/.code/agents/2256/exec-call_AtPcRWPmFPMcmX6qOFm1fCEY.txt +0 -766
  108. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +0 -1
  109. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +0 -747
  110. package/.code/agents/2454/exec-call_aFJpupwjfZeOBm7ixI5Vc8z2.txt +0 -766
  111. package/.code/agents/2454/exec-call_wogZ4HfXTodTEXvdgXlVUBpv.txt +0 -766
  112. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +0 -1
  113. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +0 -2594
  114. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +0 -2594
  115. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +0 -144
  116. package/.code/agents/2e905864-aa07-4314-bcf9-c5b32277e4ac/result.txt +0 -36
  117. package/.code/agents/3073/exec-call_Peeagc9DxGYLgE6pNdMZhqIE.txt +0 -766
  118. package/.code/agents/3073/exec-call_d2YSE3hXF08KRSoUM3qd8Z3x.txt +0 -766
  119. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +0 -416
  120. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +0 -2590
  121. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +0 -2590
  122. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +0 -2590
  123. package/.code/agents/335aa031-466d-4fb7-925f-3cd864e264d0/result.txt +0 -191
  124. package/.code/agents/3364/exec-call_NbhIrsM5HhyDZDmJZG5CuCYL.txt +0 -766
  125. package/.code/agents/3364/exec-call_cKtJg0NrXiwXEFwlsE3uPZRA.txt +0 -766
  126. package/.code/agents/36d98414-5cde-4d9d-9a67-a240a18c1f07/result.txt +0 -189
  127. package/.code/agents/4604e866-b7b8-44f5-992f-2f683b0a523b/status.txt +0 -1
  128. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +0 -790
  129. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +0 -766
  130. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +0 -790
  131. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +0 -2594
  132. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +0 -1000
  133. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +0 -3489
  134. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +0 -766
  135. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +0 -2594
  136. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +0 -2456
  137. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +0 -2594
  138. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +0 -18
  139. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +0 -48
  140. package/.code/agents/5f8dc01c-47b3-4163-b0b3-aa31be89fcdc/status.txt +0 -1
  141. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +0 -1
  142. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +0 -1
  143. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +0 -1
  144. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +0 -1
  145. package/.code/agents/7/exec-call_HltHpkDox0Zm1vGEjdksUgpE.txt +0 -1120
  146. package/.code/agents/7/exec-call_LCATrOPPAgbxW9Q1z0XaVi2E.txt +0 -2646
  147. package/.code/agents/7/exec-call_W8DeRfNG9hvbgVFvf0clBf6R.txt +0 -2646
  148. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +0 -1271
  149. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +0 -1570
  150. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +0 -2590
  151. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +0 -3
  152. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +0 -3299
  153. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +0 -3299
  154. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +0 -1882
  155. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +0 -2594
  156. package/.code/agents/94a0ddf3-a304-4ec3-913e-3cceef509948/error.txt +0 -1
  157. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +0 -1
  158. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +0 -170
  159. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +0 -1
  160. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +0 -3
  161. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +0 -1
  162. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +0 -1
  163. package/.code/agents/e2c752b7-711d-423a-af57-f53c809deb84/result.txt +0 -160
  164. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +0 -3
  165. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +0 -1
  166. package/.code/agents/e6601719-c31f-4a0e-8c71-d70787d0ab71/status.txt +0 -1
  167. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +0 -1
  168. package/.code/agents/f250b7ed-5bd5-4036-aa8c-ce63caee7d61/result.txt +0 -20
  169. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +0 -5
  170. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +0 -1
  171. package/AGENTS.md +0 -1
  172. package/NUL +0 -0
  173. package/package.json.tmp +0 -105
  174. package/src/__tests__/delta.performance.test.ts +0 -80
  175. package/src/__tests__/workflows.e2e.test.ts +0 -1702
  176. package/temp-recon.ts +0 -126
  177. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +0 -23
  178. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +0 -23
  179. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +0 -44
  180. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +0 -58
  181. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +0 -3662
  182. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +0 -115
@@ -37,6 +37,7 @@ export declare class ResourceManager {
37
37
  readResource(uri: string): Promise<{
38
38
  contents: ResourceContents[];
39
39
  }>;
40
+ private executeResourceHandler;
40
41
  private matchTemplate;
41
42
  private validateTemplateDefinition;
42
43
  }
@@ -1,4 +1,6 @@
1
+ import { ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
1
2
  import { CacheManager, CACHE_TTLS } from './cacheManager.js';
3
+ const RESOURCE_NOT_FOUND_ERROR_CODE = -32002;
2
4
  const defaultResourceHandlers = {
3
5
  'ynab://budgets': async (uri, { ynabAPI, responseFormatter, cacheManager }) => {
4
6
  const cacheKey = CacheManager.generateKey('resources', 'budgets', 'list');
@@ -79,8 +81,9 @@ const defaultResourceTemplates = [
79
81
  mimeType: 'application/json',
80
82
  handler: async (uri, params, { ynabAPI, responseFormatter, cacheManager }) => {
81
83
  const budget_id = params['budget_id'];
82
- if (!budget_id)
83
- throw new Error('Missing budget_id parameter');
84
+ if (!budget_id) {
85
+ throw new McpError(ErrorCode.InvalidParams, 'Missing budget_id parameter');
86
+ }
84
87
  const cacheKey = CacheManager.generateKey('resources', 'budgets', 'get', budget_id);
85
88
  return cacheManager.wrap(cacheKey, {
86
89
  ttl: CACHE_TTLS.BUDGETS,
@@ -110,8 +113,9 @@ const defaultResourceTemplates = [
110
113
  mimeType: 'application/json',
111
114
  handler: async (uri, params, { ynabAPI, responseFormatter, cacheManager }) => {
112
115
  const budget_id = params['budget_id'];
113
- if (!budget_id)
114
- throw new Error('Missing budget_id parameter');
116
+ if (!budget_id) {
117
+ throw new McpError(ErrorCode.InvalidParams, 'Missing budget_id parameter');
118
+ }
115
119
  const cacheKey = CacheManager.generateKey('resources', 'accounts', 'list', budget_id);
116
120
  return cacheManager.wrap(cacheKey, {
117
121
  ttl: CACHE_TTLS.ACCOUNTS,
@@ -142,10 +146,12 @@ const defaultResourceTemplates = [
142
146
  handler: async (uri, params, { ynabAPI, responseFormatter, cacheManager }) => {
143
147
  const budget_id = params['budget_id'];
144
148
  const account_id = params['account_id'];
145
- if (!budget_id)
146
- throw new Error('Missing budget_id parameter');
147
- if (!account_id)
148
- throw new Error('Missing account_id parameter');
149
+ if (!budget_id) {
150
+ throw new McpError(ErrorCode.InvalidParams, 'Missing budget_id parameter');
151
+ }
152
+ if (!account_id) {
153
+ throw new McpError(ErrorCode.InvalidParams, 'Missing account_id parameter');
154
+ }
149
155
  const cacheKey = CacheManager.generateKey('resources', 'accounts', 'get', budget_id, account_id);
150
156
  return cacheManager.wrap(cacheKey, {
151
157
  ttl: CACHE_TTLS.ACCOUNTS,
@@ -208,20 +214,31 @@ export class ResourceManager {
208
214
  async readResource(uri) {
209
215
  const handler = this.resourceHandlers[uri];
210
216
  if (handler) {
211
- return { contents: await handler(uri, this.dependencies) };
217
+ return {
218
+ contents: await this.executeResourceHandler(() => handler(uri, this.dependencies), `resource ${uri}`),
219
+ };
212
220
  }
213
221
  for (const template of this.resourceTemplates) {
214
222
  const params = this.matchTemplate(template.uriTemplate, uri);
215
223
  if (params) {
216
- try {
217
- return { contents: await template.handler(uri, params, this.dependencies) };
218
- }
219
- catch (error) {
220
- throw new Error(`Failed to resolve template resource ${uri}: ${error instanceof Error ? error.message : String(error)}`);
221
- }
224
+ return {
225
+ contents: await this.executeResourceHandler(() => template.handler(uri, params, this.dependencies), `resource ${uri}`),
226
+ };
227
+ }
228
+ }
229
+ throw new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, `Resource not found: ${uri}`);
230
+ }
231
+ async executeResourceHandler(handler, label) {
232
+ try {
233
+ return await handler();
234
+ }
235
+ catch (error) {
236
+ if (error instanceof McpError) {
237
+ throw error;
222
238
  }
239
+ const message = error instanceof Error ? error.message : String(error);
240
+ throw new McpError(ErrorCode.InternalError, `Failed to read ${label}: ${message}`);
223
241
  }
224
- throw new Error(`Unknown resource: ${uri}`);
225
242
  }
226
243
  matchTemplate(template, uri) {
227
244
  if (!/^[a-z0-9:/\-_{}]+$/i.test(template)) {
@@ -24,6 +24,11 @@ export declare function withSecurityWrapper<T extends Record<string, unknown>>(t
24
24
  content: ({
25
25
  type: "text";
26
26
  text: string;
27
+ annotations?: {
28
+ audience?: ("user" | "assistant")[] | undefined;
29
+ priority?: number | undefined;
30
+ lastModified?: string | undefined;
31
+ } | undefined;
27
32
  _meta?: {
28
33
  [x: string]: unknown;
29
34
  } | undefined;
@@ -31,6 +36,11 @@ export declare function withSecurityWrapper<T extends Record<string, unknown>>(t
31
36
  type: "image";
32
37
  data: string;
33
38
  mimeType: string;
39
+ annotations?: {
40
+ audience?: ("user" | "assistant")[] | undefined;
41
+ priority?: number | undefined;
42
+ lastModified?: string | undefined;
43
+ } | undefined;
34
44
  _meta?: {
35
45
  [x: string]: unknown;
36
46
  } | undefined;
@@ -38,47 +48,67 @@ export declare function withSecurityWrapper<T extends Record<string, unknown>>(t
38
48
  type: "audio";
39
49
  data: string;
40
50
  mimeType: string;
51
+ annotations?: {
52
+ audience?: ("user" | "assistant")[] | undefined;
53
+ priority?: number | undefined;
54
+ lastModified?: string | undefined;
55
+ } | undefined;
41
56
  _meta?: {
42
57
  [x: string]: unknown;
43
58
  } | undefined;
44
59
  } | {
45
- type: "resource_link";
46
- name: string;
47
60
  uri: string;
61
+ name: string;
62
+ type: "resource_link";
63
+ description?: string | undefined;
64
+ mimeType?: string | undefined;
65
+ annotations?: {
66
+ audience?: ("user" | "assistant")[] | undefined;
67
+ priority?: number | undefined;
68
+ lastModified?: string | undefined;
69
+ } | undefined;
48
70
  _meta?: {
49
71
  [x: string]: unknown;
50
72
  } | undefined;
51
- mimeType?: string | undefined | undefined;
52
73
  icons?: {
53
74
  src: string;
54
- mimeType?: string | undefined | undefined;
75
+ mimeType?: string | undefined;
55
76
  sizes?: string[] | undefined;
77
+ theme?: "light" | "dark" | undefined;
56
78
  }[] | undefined;
57
- title?: string | undefined | undefined;
58
- description?: string | undefined | undefined;
79
+ title?: string | undefined;
59
80
  } | {
60
81
  type: "resource";
61
82
  resource: {
62
83
  uri: string;
63
84
  text: string;
85
+ mimeType?: string | undefined;
64
86
  _meta?: {
65
87
  [x: string]: unknown;
66
88
  } | undefined;
67
- mimeType?: string | undefined | undefined;
68
89
  } | {
69
90
  uri: string;
70
91
  blob: string;
92
+ mimeType?: string | undefined;
71
93
  _meta?: {
72
94
  [x: string]: unknown;
73
95
  } | undefined;
74
- mimeType?: string | undefined | undefined;
75
96
  };
97
+ annotations?: {
98
+ audience?: ("user" | "assistant")[] | undefined;
99
+ priority?: number | undefined;
100
+ lastModified?: string | undefined;
101
+ } | undefined;
76
102
  _meta?: {
77
103
  [x: string]: unknown;
78
104
  } | undefined;
79
105
  })[];
80
106
  _meta?: {
81
107
  [x: string]: unknown;
108
+ progressToken?: string | number | undefined;
109
+ "io.modelcontextprotocol/related-task"?: {
110
+ taskId: string;
111
+ } | undefined;
82
112
  } | undefined;
83
113
  structuredContent?: {
84
114
  [x: string]: unknown;
@@ -58,6 +58,7 @@ export class SecurityMiddleware {
58
58
  }
59
59
  static createRateLimitErrorResponse(error) {
60
60
  return {
61
+ isError: true,
61
62
  content: [
62
63
  {
63
64
  type: 'text',
@@ -32,12 +32,18 @@ export interface ToolMetadataOptions {
32
32
  inputJsonSchema?: Record<string, unknown>;
33
33
  annotations?: MCPToolAnnotations;
34
34
  }
35
+ export type ProgressCallback = (params: {
36
+ progress: number;
37
+ total?: number;
38
+ message?: string;
39
+ }) => Promise<void>;
35
40
  export interface ToolExecutionContext {
36
41
  accessToken: string;
37
42
  name: string;
38
43
  operation: string;
39
44
  rawArguments: Record<string, unknown>;
40
45
  cache?: ToolRegistryCacheHelpers;
46
+ sendProgress?: ProgressCallback;
41
47
  }
42
48
  export interface ToolExecutionPayload<TInput extends Record<string, unknown>> {
43
49
  input: TInput;
@@ -59,6 +65,7 @@ export interface ToolExecutionOptions {
59
65
  accessToken: string;
60
66
  arguments?: Record<string, unknown>;
61
67
  minifyOverride?: boolean;
68
+ sendProgress?: ProgressCallback;
62
69
  }
63
70
  export interface ToolRegistryDependencies {
64
71
  withSecurityWrapper: SecurityWrapperFactory;
@@ -74,11 +81,13 @@ export declare class ToolRegistry {
74
81
  constructor(deps: ToolRegistryDependencies);
75
82
  register<TInput extends Record<string, unknown>, TOutput extends Record<string, unknown>>(definition: ToolDefinition<TInput, TOutput>): void;
76
83
  listTools(): Tool[];
84
+ hasTool(name: string): boolean;
77
85
  getToolDefinitions(): ToolDefinition[];
78
86
  executeTool(options: ToolExecutionOptions): Promise<CallToolResult>;
79
87
  private isCallToolResult;
80
88
  private normalizeSecurityError;
81
89
  private extractMinifyOverride;
90
+ private static readonly MCP_TOOL_NAME_REGEX;
82
91
  private assertValidDefinition;
83
92
  private generateJsonSchema;
84
93
  private validateOutput;
@@ -51,6 +51,9 @@ export class ToolRegistry {
51
51
  return result;
52
52
  });
53
53
  }
54
+ hasTool(name) {
55
+ return this.tools.has(name);
56
+ }
54
57
  getToolDefinitions() {
55
58
  return Array.from(this.tools.values()).map((tool) => {
56
59
  const definition = {
@@ -128,6 +131,9 @@ export class ToolRegistry {
128
131
  if (this.deps.cacheHelpers) {
129
132
  context.cache = this.deps.cacheHelpers;
130
133
  }
134
+ if (options.sendProgress) {
135
+ context.sendProgress = options.sendProgress;
136
+ }
131
137
  const handlerResult = await tool.handler({
132
138
  input: validated,
133
139
  context,
@@ -189,6 +195,10 @@ export class ToolRegistry {
189
195
  if (!definition.name || typeof definition.name !== 'string') {
190
196
  throw new Error('Tool definition requires a non-empty name');
191
197
  }
198
+ if (!ToolRegistry.MCP_TOOL_NAME_REGEX.test(definition.name)) {
199
+ throw new Error(`Tool name '${definition.name}' violates MCP guidelines: ` +
200
+ `must be 1-128 chars using only [a-zA-Z0-9_.-]`);
201
+ }
192
202
  if (!definition.description || typeof definition.description !== 'string') {
193
203
  throw new Error(`Tool '${definition.name}' requires a description`);
194
204
  }
@@ -268,3 +278,4 @@ export class ToolRegistry {
268
278
  return output;
269
279
  }
270
280
  }
281
+ ToolRegistry.MCP_TOOL_NAME_REGEX = /^[a-zA-Z0-9_.-]{1,128}$/;
@@ -1,10 +1,12 @@
1
1
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
- import type { ToolExecutionPayload, DefaultArgumentResolver } from '../server/toolRegistry.js';
2
+ import type { ToolExecutionPayload, DefaultArgumentResolver, ProgressCallback } from '../server/toolRegistry.js';
3
3
  import type { ToolContext, Handler, DeltaHandler, WriteHandler, NoInputHandler } from '../types/toolRegistration.js';
4
+ import type { DeltaFetcher } from './deltaFetcher.js';
4
5
  export declare function createAdapters(context: ToolContext): {
5
6
  adapt: <TInput extends Record<string, unknown>>(handler: Handler<TInput>) => ({ input }: ToolExecutionPayload<TInput>) => Promise<CallToolResult>;
6
7
  adaptNoInput: (handler: NoInputHandler) => (_payload: ToolExecutionPayload<Record<string, unknown>>) => Promise<CallToolResult>;
7
8
  adaptWithDelta: <TInput extends Record<string, unknown>>(handler: DeltaHandler<TInput>) => ({ input }: ToolExecutionPayload<TInput>) => Promise<CallToolResult>;
9
+ adaptWithDeltaAndProgress: <TInput extends Record<string, unknown>>(handler: (api: import("ynab").API, deltaFetcher: DeltaFetcher, params: TInput, sendProgress?: ProgressCallback) => Promise<CallToolResult>) => ({ input, context }: ToolExecutionPayload<TInput>) => Promise<CallToolResult>;
8
10
  adaptWrite: <TInput extends Record<string, unknown>>(handler: WriteHandler<TInput>) => ({ input }: ToolExecutionPayload<TInput>) => Promise<CallToolResult>;
9
11
  };
10
12
  export declare function createBudgetResolver(context: ToolContext): <TInput extends {
@@ -6,6 +6,7 @@ export function createAdapters(context) {
6
6
  adapt: (handler) => async ({ input }) => handler(ynabAPI, input),
7
7
  adaptNoInput: (handler) => async (_payload) => handler(ynabAPI),
8
8
  adaptWithDelta: (handler) => async ({ input }) => handler(ynabAPI, deltaFetcher, input),
9
+ adaptWithDeltaAndProgress: (handler) => async ({ input, context }) => handler(ynabAPI, deltaFetcher, input, context.sendProgress),
9
10
  adaptWrite: (handler) => async ({ input }) => handler(ynabAPI, deltaCache, serverKnowledgeStore, input),
10
11
  };
11
12
  }
@@ -1,4 +1,5 @@
1
1
  import type * as ynab from 'ynab';
2
+ import type { ProgressCallback } from '../../server/toolRegistry.js';
2
3
  import type { ReconciliationAnalysis } from './types.js';
3
4
  import type { ReconcileAccountRequest } from './index.js';
4
5
  export interface AccountSnapshot {
@@ -14,6 +15,7 @@ export interface ExecutionOptions {
14
15
  accountId: string;
15
16
  initialAccount: AccountSnapshot;
16
17
  currencyCode: string;
18
+ sendProgress?: ProgressCallback;
17
19
  }
18
20
  export interface ExecutionActionRecord {
19
21
  type: string;
@@ -79,7 +79,7 @@ function isWithinStatementWindow(dateStr, window) {
79
79
  return true;
80
80
  }
81
81
  export async function executeReconciliation(options) {
82
- const { analysis, params, ynabAPI, budgetId, accountId, initialAccount, currencyCode } = options;
82
+ const { analysis, params, ynabAPI, budgetId, accountId, initialAccount, currencyCode, sendProgress, } = options;
83
83
  const actions_taken = [];
84
84
  const summary = {
85
85
  bank_transactions_count: analysis.summary.bank_transactions_count,
@@ -92,6 +92,23 @@ export async function executeReconciliation(options) {
92
92
  dates_adjusted: 0,
93
93
  dry_run: params.dry_run,
94
94
  };
95
+ const matchesNeedingUpdate = analysis.auto_matches.filter((match) => {
96
+ const flags = computeUpdateFlags(match, params);
97
+ return flags.needsClearedUpdate || flags.needsDateUpdate;
98
+ });
99
+ const totalOperations = (params.auto_create_transactions ? analysis.unmatched_bank.length : 0) +
100
+ matchesNeedingUpdate.length +
101
+ (params.auto_unclear_missing ? analysis.unmatched_ynab.length : 0);
102
+ let completedOperations = 0;
103
+ const reportProgress = async (message) => {
104
+ if (sendProgress && totalOperations > 0) {
105
+ await sendProgress({
106
+ progress: completedOperations,
107
+ total: totalOperations,
108
+ message,
109
+ });
110
+ }
111
+ };
95
112
  let afterAccount = { ...initialAccount };
96
113
  let accountSnapshotDirty = false;
97
114
  const statementTargetMilli = resolveStatementBalanceMilli(analysis.balance_info, params.statement_balance);
@@ -193,6 +210,8 @@ export async function executeReconciliation(options) {
193
210
  recordCreateAction(recordArgs);
194
211
  accountSnapshotDirty = true;
195
212
  applyClearedDelta(entry.amountMilli);
213
+ completedOperations += 1;
214
+ await reportProgress(`Created ${completedOperations} of ${totalOperations} transactions`);
196
215
  const trigger = options.chunkIndex
197
216
  ? `creating ${entry.bankTransaction.payee ?? 'missing transaction'} (chunk ${options.chunkIndex})`
198
217
  : `creating ${entry.bankTransaction.payee ?? 'missing transaction'}`;
@@ -343,6 +362,8 @@ export async function executeReconciliation(options) {
343
362
  try {
344
363
  await processBulkChunk(chunk, chunkIndex);
345
364
  bulkOperationDetails.bulk_successes += 1;
365
+ completedOperations += chunk.length;
366
+ await reportProgress(`Created ${completedOperations} of ${totalOperations} transactions`);
346
367
  }
347
368
  catch (error) {
348
369
  const ynabError = normalizeYnabError(error);
@@ -445,6 +466,8 @@ export async function executeReconciliation(options) {
445
466
  });
446
467
  }
447
468
  accountSnapshotDirty = true;
469
+ completedOperations += updatedTransactions.length;
470
+ await reportProgress(`Updated ${completedOperations} of ${totalOperations} transactions`);
448
471
  }
449
472
  catch (error) {
450
473
  const ynabError = normalizeYnabError(error);
@@ -533,6 +556,8 @@ export async function executeReconciliation(options) {
533
556
  });
534
557
  }
535
558
  accountSnapshotDirty = true;
559
+ completedOperations += updatedTransactions.length;
560
+ await reportProgress(`Marked ${completedOperations} of ${totalOperations} transactions uncleared`);
536
561
  }
537
562
  catch (error) {
538
563
  const ynabError = normalizeYnabError(error);
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod/v4';
2
2
  import type * as ynab from 'ynab';
3
- import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
4
+ import type { ProgressCallback } from '../../server/toolRegistry.js';
4
5
  import type { ToolFactory } from '../../types/toolRegistration.js';
5
6
  import type { DeltaFetcher } from '../deltaFetcher.js';
6
7
  export type * from './types.js';
@@ -51,6 +52,6 @@ export declare const ReconcileAccountSchema: z.ZodObject<{
51
52
  force_full_refresh: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
52
53
  }, z.core.$strip>;
53
54
  export type ReconcileAccountRequest = z.infer<typeof ReconcileAccountSchema>;
54
- export declare function handleReconcileAccount(ynabAPI: ynab.API, deltaFetcher: DeltaFetcher, params: ReconcileAccountRequest): Promise<CallToolResult>;
55
+ export declare function handleReconcileAccount(ynabAPI: ynab.API, deltaFetcher: DeltaFetcher, params: ReconcileAccountRequest, sendProgress?: ProgressCallback): Promise<CallToolResult>;
55
56
  export declare function handleReconcileAccount(ynabAPI: ynab.API, params: ReconcileAccountRequest): Promise<CallToolResult>;
56
57
  export declare const registerReconciliationTools: ToolFactory;
@@ -88,7 +88,7 @@ export const ReconcileAccountSchema = z
88
88
  message: 'Either csv_file_path or csv_data must be provided',
89
89
  path: ['csv_data'],
90
90
  });
91
- export async function handleReconcileAccount(ynabAPI, deltaFetcherOrParams, maybeParams) {
91
+ export async function handleReconcileAccount(ynabAPI, deltaFetcherOrParams, maybeParams, sendProgress) {
92
92
  const { deltaFetcher, params } = resolveDeltaFetcherArgs(ynabAPI, deltaFetcherOrParams, maybeParams);
93
93
  const forceFullRefresh = params.force_full_refresh ?? true;
94
94
  return await withToolErrorHandling(async () => {
@@ -274,6 +274,7 @@ export async function handleReconcileAccount(ynabAPI, deltaFetcherOrParams, mayb
274
274
  accountId: params.account_id,
275
275
  initialAccount,
276
276
  currencyCode,
277
+ ...(sendProgress !== undefined && { sendProgress }),
277
278
  });
278
279
  }
279
280
  const csvFormatForPayload = mapCsvFormatForPayload(params.csv_format);
@@ -307,7 +308,7 @@ export async function handleReconcileAccount(ynabAPI, deltaFetcherOrParams, mayb
307
308
  }, 'ynab:reconcile_account', 'analyzing account reconciliation');
308
309
  }
309
310
  export const registerReconciliationTools = (registry, context) => {
310
- const { adapt, adaptWithDelta } = createAdapters(context);
311
+ const { adapt, adaptWithDeltaAndProgress } = createAdapters(context);
311
312
  const budgetResolver = createBudgetResolver(context);
312
313
  registry.register({
313
314
  name: 'compare_transactions',
@@ -326,7 +327,7 @@ export const registerReconciliationTools = (registry, context) => {
326
327
  name: 'reconcile_account',
327
328
  description: 'Guided reconciliation workflow with human narrative, insight detection, and optional execution (create/update/unclear). Set include_structured_data=true to also get full JSON output (large).',
328
329
  inputSchema: ReconcileAccountSchema,
329
- handler: adaptWithDelta(handleReconcileAccount),
330
+ handler: adaptWithDeltaAndProgress(handleReconcileAccount),
330
331
  defaultArgumentResolver: budgetResolver(),
331
332
  metadata: {
332
333
  annotations: {
@@ -1,5 +1,5 @@
1
- export { GetUserOutputSchema, type GetUserOutput, ConvertAmountOutputSchema, type ConvertAmountOutput, GetDefaultBudgetOutputSchema, type GetDefaultBudgetOutput, SetDefaultBudgetOutputSchema, type SetDefaultBudgetOutput, ClearCacheOutputSchema, type ClearCacheOutput, SetOutputFormatOutputSchema, type SetOutputFormatOutput, DiagnosticInfoOutputSchema, type DiagnosticInfoOutput, GetBudgetOutputSchema, type GetBudgetOutput, } from './utilityOutputs.js';
2
- export { UserSchema, ConversionSchema, DateFormatSchema, CurrencyFormatSchema, BudgetDetailSchema, ServerInfoSchema, MemoryInfoSchema, EnvironmentInfoSchema, CacheInfoSchema, DeltaInfoSchema, } from './utilityOutputs.js';
1
+ export { GetUserOutputSchema, type GetUserOutput, GetDefaultBudgetOutputSchema, type GetDefaultBudgetOutput, SetDefaultBudgetOutputSchema, type SetDefaultBudgetOutput, ClearCacheOutputSchema, type ClearCacheOutput, SetOutputFormatOutputSchema, type SetOutputFormatOutput, DiagnosticInfoOutputSchema, type DiagnosticInfoOutput, GetBudgetOutputSchema, type GetBudgetOutput, } from './utilityOutputs.js';
2
+ export { UserSchema, DateFormatSchema, CurrencyFormatSchema, BudgetDetailSchema, ServerInfoSchema, MemoryInfoSchema, EnvironmentInfoSchema, CacheInfoSchema, DeltaInfoSchema, } from './utilityOutputs.js';
3
3
  export { ListBudgetsOutputSchema, type ListBudgetsOutput, BudgetSummarySchema, type BudgetSummary, } from './budgetOutputs.js';
4
4
  export { ListAccountsOutputSchema, type ListAccountsOutput, GetAccountOutputSchema, type GetAccountOutput, AccountSchema, type Account, } from './accountOutputs.js';
5
5
  export { ListTransactionsOutputSchema, type ListTransactionsOutput, GetTransactionOutputSchema, type GetTransactionOutput, TransactionSchema, type Transaction, TransactionPreviewSchema, type TransactionPreview, } from './transactionOutputs.js';
@@ -1,5 +1,5 @@
1
- export { GetUserOutputSchema, ConvertAmountOutputSchema, GetDefaultBudgetOutputSchema, SetDefaultBudgetOutputSchema, ClearCacheOutputSchema, SetOutputFormatOutputSchema, DiagnosticInfoOutputSchema, GetBudgetOutputSchema, } from './utilityOutputs.js';
2
- export { UserSchema, ConversionSchema, DateFormatSchema, CurrencyFormatSchema, BudgetDetailSchema, ServerInfoSchema, MemoryInfoSchema, EnvironmentInfoSchema, CacheInfoSchema, DeltaInfoSchema, } from './utilityOutputs.js';
1
+ export { GetUserOutputSchema, GetDefaultBudgetOutputSchema, SetDefaultBudgetOutputSchema, ClearCacheOutputSchema, SetOutputFormatOutputSchema, DiagnosticInfoOutputSchema, GetBudgetOutputSchema, } from './utilityOutputs.js';
2
+ export { UserSchema, DateFormatSchema, CurrencyFormatSchema, BudgetDetailSchema, ServerInfoSchema, MemoryInfoSchema, EnvironmentInfoSchema, CacheInfoSchema, DeltaInfoSchema, } from './utilityOutputs.js';
3
3
  export { ListBudgetsOutputSchema, BudgetSummarySchema, } from './budgetOutputs.js';
4
4
  export { ListAccountsOutputSchema, GetAccountOutputSchema, AccountSchema, } from './accountOutputs.js';
5
5
  export { ListTransactionsOutputSchema, GetTransactionOutputSchema, TransactionSchema, TransactionPreviewSchema, } from './transactionOutputs.js';
@@ -8,21 +8,6 @@ export declare const GetUserOutputSchema: z.ZodObject<{
8
8
  }, z.core.$strip>;
9
9
  }, z.core.$strip>;
10
10
  export type GetUserOutput = z.infer<typeof GetUserOutputSchema>;
11
- export declare const ConversionSchema: z.ZodObject<{
12
- original_amount: z.ZodNumber;
13
- converted_amount: z.ZodNumber;
14
- to_milliunits: z.ZodBoolean;
15
- description: z.ZodString;
16
- }, z.core.$strip>;
17
- export declare const ConvertAmountOutputSchema: z.ZodObject<{
18
- conversion: z.ZodObject<{
19
- original_amount: z.ZodNumber;
20
- converted_amount: z.ZodNumber;
21
- to_milliunits: z.ZodBoolean;
22
- description: z.ZodString;
23
- }, z.core.$strip>;
24
- }, z.core.$strip>;
25
- export type ConvertAmountOutput = z.infer<typeof ConvertAmountOutputSchema>;
26
11
  export declare const GetDefaultBudgetOutputSchema: z.ZodObject<{
27
12
  default_budget_id: z.ZodNullable<z.ZodString>;
28
13
  has_default: z.ZodBoolean;
@@ -6,15 +6,6 @@ export const UserSchema = z.object({
6
6
  export const GetUserOutputSchema = z.object({
7
7
  user: UserSchema,
8
8
  });
9
- export const ConversionSchema = z.object({
10
- original_amount: z.number(),
11
- converted_amount: z.number(),
12
- to_milliunits: z.boolean(),
13
- description: z.string(),
14
- });
15
- export const ConvertAmountOutputSchema = z.object({
16
- conversion: ConversionSchema,
17
- });
18
9
  export const GetDefaultBudgetOutputSchema = z.object({
19
10
  default_budget_id: z.string().nullable(),
20
11
  has_default: z.boolean(),
@@ -1,12 +1,5 @@
1
1
  import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
2
  import * as ynab from 'ynab';
3
- import { z } from 'zod/v4';
4
3
  import type { ToolFactory } from '../types/toolRegistration.js';
5
- export declare const ConvertAmountSchema: z.ZodObject<{
6
- amount: z.ZodNumber;
7
- to_milliunits: z.ZodBoolean;
8
- }, z.core.$strict>;
9
- export type ConvertAmountParams = z.infer<typeof ConvertAmountSchema>;
10
4
  export declare function handleGetUser(ynabAPI: ynab.API): Promise<CallToolResult>;
11
- export declare function handleConvertAmount(_ynabAPI: ynab.API, params: ConvertAmountParams): Promise<CallToolResult>;
12
5
  export declare const registerUtilityTools: ToolFactory;
@@ -1,15 +1,8 @@
1
- import { z } from 'zod/v4';
2
1
  import { responseFormatter } from '../server/responseFormatter.js';
3
2
  import { withToolErrorHandling } from '../types/index.js';
4
3
  import { createAdapters } from './adapters.js';
5
4
  import { emptyObjectSchema } from './schemas/common.js';
6
5
  import { ToolAnnotationPresets } from './toolCategories.js';
7
- export const ConvertAmountSchema = z
8
- .object({
9
- amount: z.number().finite(),
10
- to_milliunits: z.boolean(),
11
- })
12
- .strict();
13
6
  export async function handleGetUser(ynabAPI) {
14
7
  return await withToolErrorHandling(async () => {
15
8
  const response = await ynabAPI.user.getUser();
@@ -27,38 +20,8 @@ export async function handleGetUser(ynabAPI) {
27
20
  };
28
21
  }, 'ynab:get_user', 'getting user information');
29
22
  }
30
- export async function handleConvertAmount(_ynabAPI, params) {
31
- return await withToolErrorHandling(async () => {
32
- const { amount, to_milliunits } = params;
33
- let result;
34
- let description;
35
- if (to_milliunits) {
36
- result = Math.round(amount * 1000);
37
- description = `$${amount.toFixed(2)} = ${result} milliunits`;
38
- }
39
- else {
40
- result = amount / 1000;
41
- description = `${amount} milliunits = $${result.toFixed(2)}`;
42
- }
43
- return {
44
- content: [
45
- {
46
- type: 'text',
47
- text: responseFormatter.format({
48
- conversion: {
49
- original_amount: amount,
50
- converted_amount: result,
51
- to_milliunits,
52
- description,
53
- },
54
- }),
55
- },
56
- ],
57
- };
58
- }, 'ynab:convert_amount', 'converting amount');
59
- }
60
23
  export const registerUtilityTools = (registry, context) => {
61
- const { adapt, adaptNoInput } = createAdapters(context);
24
+ const { adaptNoInput } = createAdapters(context);
62
25
  registry.register({
63
26
  name: 'get_user',
64
27
  description: 'Get information about the authenticated user',
@@ -71,16 +34,4 @@ export const registerUtilityTools = (registry, context) => {
71
34
  },
72
35
  },
73
36
  });
74
- registry.register({
75
- name: 'convert_amount',
76
- description: 'Convert between dollars and milliunits with integer arithmetic for precision',
77
- inputSchema: ConvertAmountSchema,
78
- handler: adapt(handleConvertAmount),
79
- metadata: {
80
- annotations: {
81
- ...ToolAnnotationPresets.UTILITY_LOCAL,
82
- title: 'YNAB: Convert Amount',
83
- },
84
- },
85
- });
86
37
  };
@@ -0,0 +1,27 @@
1
+ # npm publishing (maintainers)
2
+
3
+ This repo publishes the package `@dizzlkheinz/ynab-mcpb` to the public npm registry via GitHub Actions (`.github/workflows/publish.yml`).
4
+
5
+ ## Trusted publishing (OIDC)
6
+
7
+ This package is configured as a **Trusted Publisher** on npm (OIDC). With that enabled, the publish workflow does **not** need an npm token and can still work even if the package is set to “disallow tokens”.
8
+
9
+ Note: `npm whoami` will still fail in CI under OIDC; authentication happens only during `npm publish`.
10
+
11
+ ## Common failure: `npm ERR! need auth`
12
+
13
+ This error means the runner has no valid auth for `https://registry.npmjs.org/`.
14
+
15
+ Checklist:
16
+
17
+ - The Trusted Publisher entry matches exactly (repo, workflow filename `publish.yml`, and environment `npm-publish`).
18
+ - The workflow has `permissions: id-token: write`.
19
+ - You’re using a recent npm CLI (the workflow uses Node `24`).
20
+ - For first-time scoped publishes, the workflow uses `npm publish --access public`.
21
+
22
+ ## Local manual publish (optional)
23
+
24
+ ```bash
25
+ npm adduser
26
+ npm publish --access public
27
+ ```