@dizzlkheinz/ynab-mcpb 0.17.0 → 0.17.1

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 (142) 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 +11 -2
  7. package/CLAUDE.md +7 -6
  8. package/dist/bundle/index.cjs +52 -52
  9. package/dist/server/YNABMCPServer.d.ts +120 -54
  10. package/dist/server/securityMiddleware.d.ts +37 -8
  11. package/dist/tools/schemas/outputs/index.d.ts +2 -2
  12. package/dist/tools/schemas/outputs/index.js +2 -2
  13. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +0 -15
  14. package/dist/tools/schemas/outputs/utilityOutputs.js +0 -9
  15. package/dist/tools/utilityTools.d.ts +0 -7
  16. package/dist/tools/utilityTools.js +1 -50
  17. package/docs/maintainers/npm-publishing.md +27 -0
  18. package/docs/reference/API.md +15 -70
  19. package/docs/technical/reconciliation-system-architecture.md +2251 -2251
  20. package/package.json +5 -5
  21. package/scripts/analyze-bundle.mjs +41 -41
  22. package/scripts/generate-mcpb.ps1 +95 -95
  23. package/scripts/watch-and-restart.ps1 +49 -49
  24. package/src/__tests__/comprehensive.integration.test.ts +0 -28
  25. package/src/__tests__/performance.test.ts +4 -12
  26. package/src/__tests__/setup.ts +45 -14
  27. package/src/__tests__/workflows.e2e.test.ts +0 -44
  28. package/src/server/__tests__/YNABMCPServer.test.ts +0 -1
  29. package/src/server/__tests__/toolRegistration.test.ts +2 -2
  30. package/src/tools/__tests__/transactionTools.integration.test.ts +63 -3
  31. package/src/tools/__tests__/utilityTools.integration.test.ts +1 -85
  32. package/src/tools/__tests__/utilityTools.test.ts +1 -123
  33. package/src/tools/schemas/outputs/index.ts +0 -3
  34. package/src/tools/schemas/outputs/utilityOutputs.ts +2 -43
  35. package/src/tools/toolCategories.ts +0 -1
  36. package/src/tools/utilityTools.ts +5 -76
  37. package/vitest.config.ts +2 -1
  38. package/.chunkhound.json +0 -11
  39. package/.code/agents/0098661e-0fa3-4990-beb9-c0cbf3f123aa/status.txt +0 -1
  40. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +0 -3
  41. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +0 -3
  42. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +0 -1
  43. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +0 -5
  44. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +0 -1569
  45. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +0 -3
  46. package/.code/agents/1324/exec-call_tIpx9uV1TpARbAMZonRQm8AO.txt +0 -757
  47. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +0 -2832
  48. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +0 -2709
  49. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +0 -2832
  50. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +0 -2832
  51. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +0 -2709
  52. package/.code/agents/1572/exec-call_GjVFBFOWcY7lE0idc5nWlLNh.txt +0 -781
  53. package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +0 -1
  54. package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +0 -5217
  55. package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +0 -2594
  56. package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +0 -2594
  57. package/.code/agents/1846/exec-call_1YNAVD18RjrMN7JnfkkQhUP3.txt +0 -766
  58. package/.code/agents/1846/exec-call_lh3lDzE4WJAh1lFiomiiZ73D.txt +0 -766
  59. package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +0 -231
  60. package/.code/agents/2038/exec-call_DYwOukaYsL8VCONWmV2rUW5u.txt +0 -766
  61. package/.code/agents/2038/exec-call_c7fOQ7UrpVcTtvdfGBRM146V.txt +0 -652
  62. package/.code/agents/2038/exec-call_ySNyq9Mm55jWE480s54r5QcA.txt +0 -766
  63. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +0 -2590
  64. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +0 -5195
  65. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +0 -286
  66. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +0 -218
  67. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +0 -180
  68. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +0 -3
  69. package/.code/agents/2256/exec-call_AtPcRWPmFPMcmX6qOFm1fCEY.txt +0 -766
  70. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +0 -1
  71. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +0 -747
  72. package/.code/agents/2454/exec-call_aFJpupwjfZeOBm7ixI5Vc8z2.txt +0 -766
  73. package/.code/agents/2454/exec-call_wogZ4HfXTodTEXvdgXlVUBpv.txt +0 -766
  74. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +0 -1
  75. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +0 -2594
  76. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +0 -2594
  77. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +0 -144
  78. package/.code/agents/2e905864-aa07-4314-bcf9-c5b32277e4ac/result.txt +0 -36
  79. package/.code/agents/3073/exec-call_Peeagc9DxGYLgE6pNdMZhqIE.txt +0 -766
  80. package/.code/agents/3073/exec-call_d2YSE3hXF08KRSoUM3qd8Z3x.txt +0 -766
  81. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +0 -416
  82. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +0 -2590
  83. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +0 -2590
  84. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +0 -2590
  85. package/.code/agents/335aa031-466d-4fb7-925f-3cd864e264d0/result.txt +0 -191
  86. package/.code/agents/3364/exec-call_NbhIrsM5HhyDZDmJZG5CuCYL.txt +0 -766
  87. package/.code/agents/3364/exec-call_cKtJg0NrXiwXEFwlsE3uPZRA.txt +0 -766
  88. package/.code/agents/36d98414-5cde-4d9d-9a67-a240a18c1f07/result.txt +0 -189
  89. package/.code/agents/4604e866-b7b8-44f5-992f-2f683b0a523b/status.txt +0 -1
  90. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +0 -790
  91. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +0 -766
  92. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +0 -790
  93. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +0 -2594
  94. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +0 -1000
  95. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +0 -3489
  96. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +0 -766
  97. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +0 -2594
  98. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +0 -2456
  99. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +0 -2594
  100. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +0 -18
  101. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +0 -48
  102. package/.code/agents/5f8dc01c-47b3-4163-b0b3-aa31be89fcdc/status.txt +0 -1
  103. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +0 -1
  104. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +0 -1
  105. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +0 -1
  106. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +0 -1
  107. package/.code/agents/7/exec-call_HltHpkDox0Zm1vGEjdksUgpE.txt +0 -1120
  108. package/.code/agents/7/exec-call_LCATrOPPAgbxW9Q1z0XaVi2E.txt +0 -2646
  109. package/.code/agents/7/exec-call_W8DeRfNG9hvbgVFvf0clBf6R.txt +0 -2646
  110. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +0 -1271
  111. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +0 -1570
  112. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +0 -2590
  113. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +0 -3
  114. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +0 -3299
  115. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +0 -3299
  116. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +0 -1882
  117. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +0 -2594
  118. package/.code/agents/94a0ddf3-a304-4ec3-913e-3cceef509948/error.txt +0 -1
  119. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +0 -1
  120. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +0 -170
  121. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +0 -1
  122. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +0 -3
  123. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +0 -1
  124. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +0 -1
  125. package/.code/agents/e2c752b7-711d-423a-af57-f53c809deb84/result.txt +0 -160
  126. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +0 -3
  127. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +0 -1
  128. package/.code/agents/e6601719-c31f-4a0e-8c71-d70787d0ab71/status.txt +0 -1
  129. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +0 -1
  130. package/.code/agents/f250b7ed-5bd5-4036-aa8c-ce63caee7d61/result.txt +0 -20
  131. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +0 -5
  132. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +0 -1
  133. package/AGENTS.md +0 -1
  134. package/NUL +0 -0
  135. package/package.json.tmp +0 -105
  136. package/temp-recon.ts +0 -126
  137. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +0 -23
  138. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +0 -23
  139. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +0 -44
  140. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +0 -58
  141. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +0 -3662
  142. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +0 -115
@@ -9,6 +9,7 @@ import {
9
9
  handleUpdateTransactions,
10
10
  CreateTransactionsSchema,
11
11
  } from '../transactionTools.js';
12
+ import { waitFor } from '../../__tests__/testUtils.js';
12
13
 
13
14
  const isSkip = ['true', '1', 'yes', 'y', 'on'].includes(
14
15
  (process.env['SKIP_E2E_TESTS'] || '').toLowerCase().trim(),
@@ -325,9 +326,22 @@ describeIntegration('Transaction Tools Integration', () => {
325
326
  ],
326
327
  });
327
328
 
328
- const afterList = await fetchBudgetTransactions();
329
- const transactions =
330
- afterList.transactions || afterList.preview_transactions || afterList.transaction_preview;
329
+ let transactions: any[] | undefined;
330
+ await waitFor(
331
+ async () => {
332
+ const afterList = await fetchBudgetTransactions();
333
+ transactions =
334
+ afterList.transactions ||
335
+ afterList.preview_transactions ||
336
+ afterList.transaction_preview;
337
+ return (
338
+ (transactions as any[])?.some((transaction) => transaction.memo === memo) ?? false
339
+ );
340
+ },
341
+ 10000,
342
+ 500,
343
+ );
344
+
331
345
  expect(transactions).toBeDefined();
332
346
  expect((transactions as any[]).some((transaction) => transaction.memo === memo)).toBe(true);
333
347
  },
@@ -576,6 +590,19 @@ describeIntegration('Transaction Tools Integration', () => {
576
590
  expect(updateResponse.results[1].correlation_key).toBe(transactionIds[1]);
577
591
 
578
592
  // Verify changes persisted
593
+ await waitFor(
594
+ async () => {
595
+ const getResult1 = await handleGetTransaction(ynabAPI, {
596
+ budget_id: testBudgetId,
597
+ transaction_id: transactionIds[0],
598
+ });
599
+ const transaction1 = parseToolResult(getResult1).transaction;
600
+ return transaction1.amount === -7.5 && transaction1.memo === 'Updated memo 1';
601
+ },
602
+ 10000,
603
+ 500,
604
+ );
605
+
579
606
  const getResult1 = await handleGetTransaction(ynabAPI, {
580
607
  budget_id: testBudgetId,
581
608
  transaction_id: transactionIds[0],
@@ -584,6 +611,19 @@ describeIntegration('Transaction Tools Integration', () => {
584
611
  expect(transaction1.amount).toBe(-7.5);
585
612
  expect(transaction1.memo).toBe('Updated memo 1');
586
613
 
614
+ await waitFor(
615
+ async () => {
616
+ const getResult2 = await handleGetTransaction(ynabAPI, {
617
+ budget_id: testBudgetId,
618
+ transaction_id: transactionIds[1],
619
+ });
620
+ const transaction2 = parseToolResult(getResult2).transaction;
621
+ return transaction2.memo === 'Updated memo 2' && transaction2.cleared === 'cleared';
622
+ },
623
+ 10000,
624
+ 500,
625
+ );
626
+
587
627
  const getResult2 = await handleGetTransaction(ynabAPI, {
588
628
  budget_id: testBudgetId,
589
629
  transaction_id: transactionIds[1],
@@ -635,6 +675,19 @@ describeIntegration('Transaction Tools Integration', () => {
635
675
  expect(updateResponse.summary.updated).toBe(1);
636
676
 
637
677
  // Verify change
678
+ await waitFor(
679
+ async () => {
680
+ const getResult = await handleGetTransaction(ynabAPI, {
681
+ budget_id: testBudgetId,
682
+ transaction_id: transactionId,
683
+ });
684
+ const transaction = parseToolResult(getResult).transaction;
685
+ return transaction.memo === 'Updated without metadata';
686
+ },
687
+ 10000,
688
+ 500,
689
+ );
690
+
638
691
  const getResult = await handleGetTransaction(ynabAPI, {
639
692
  budget_id: testBudgetId,
640
693
  transaction_id: transactionId,
@@ -749,6 +802,13 @@ describeIntegration('Transaction Tools Integration', () => {
749
802
  });
750
803
 
751
804
  const updateResponse = parseToolResult(updateResult);
805
+
806
+ if (updateResponse.error) {
807
+ throw new Error(
808
+ `Tool execution failed unexpectedly: ${JSON.stringify(updateResponse.error)}`,
809
+ );
810
+ }
811
+
752
812
  expect(updateResponse.summary.total_requested).toBe(2);
753
813
  expect(updateResponse.summary.updated).toBe(1);
754
814
  expect(updateResponse.summary.failed).toBeGreaterThan(0);
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, beforeAll } from 'vitest';
2
2
  import * as ynab from 'ynab';
3
- import { handleGetUser, handleConvertAmount } from '../utilityTools.js';
3
+ import { handleGetUser } from '../utilityTools.js';
4
4
  import { skipOnRateLimit } from '../../__tests__/testUtils.js';
5
5
 
6
6
  /**
@@ -41,88 +41,4 @@ describeIntegration('Utility Tools Integration Tests', () => {
41
41
  },
42
42
  );
43
43
  });
44
-
45
- describe('handleConvertAmount', () => {
46
- it(
47
- 'should convert various dollar amounts to milliunits',
48
- { meta: { tier: 'domain', domain: 'utility' } },
49
- async () => {
50
- const testCases = [
51
- { dollars: 1.0, expectedMilliunits: 1000 },
52
- { dollars: 0.01, expectedMilliunits: 10 },
53
- { dollars: 10.5, expectedMilliunits: 10500 },
54
- { dollars: 999.99, expectedMilliunits: 999990 },
55
- { dollars: 0, expectedMilliunits: 0 },
56
- { dollars: -5.25, expectedMilliunits: -5250 },
57
- ];
58
-
59
- for (const testCase of testCases) {
60
- const result = await handleConvertAmount({
61
- amount: testCase.dollars,
62
- to_milliunits: true,
63
- });
64
- const response = JSON.parse(result.content[0].text);
65
-
66
- expect(response.conversion.converted_amount).toBe(testCase.expectedMilliunits);
67
- expect(response.conversion.to_milliunits).toBe(true);
68
- expect(response.conversion.description).toContain(`$${testCase.dollars.toFixed(2)}`);
69
- expect(response.conversion.description).toContain(
70
- `${testCase.expectedMilliunits} milliunits`,
71
- );
72
- }
73
- },
74
- );
75
-
76
- it(
77
- 'should convert various milliunit amounts to dollars',
78
- { meta: { tier: 'domain', domain: 'utility' } },
79
- async () => {
80
- const testCases = [
81
- { milliunits: 1000, expectedDollars: 1.0 },
82
- { milliunits: 10, expectedDollars: 0.01 },
83
- { milliunits: 10500, expectedDollars: 10.5 },
84
- { milliunits: 999990, expectedDollars: 999.99 },
85
- { milliunits: 0, expectedDollars: 0 },
86
- { milliunits: -5250, expectedDollars: -5.25 },
87
- ];
88
-
89
- for (const testCase of testCases) {
90
- const result = await handleConvertAmount({
91
- amount: testCase.milliunits,
92
- to_milliunits: false,
93
- });
94
- const response = JSON.parse(result.content[0].text);
95
-
96
- expect(response.conversion.converted_amount).toBe(testCase.expectedDollars);
97
- expect(response.conversion.to_milliunits).toBe(false);
98
- expect(response.conversion.description).toContain(`${testCase.milliunits} milliunits`);
99
- expect(response.conversion.description).toContain(
100
- `$${testCase.expectedDollars.toFixed(2)}`,
101
- );
102
- }
103
- },
104
- );
105
-
106
- it(
107
- 'should handle precision edge cases',
108
- { meta: { tier: 'domain', domain: 'utility' } },
109
- async () => {
110
- // Test floating-point precision issues
111
- const precisionTests = [
112
- { amount: 0.1 + 0.2, to_milliunits: true }, // Should handle 0.30000000000000004
113
- { amount: 1.005, to_milliunits: true }, // Should round correctly
114
- { amount: 999.999, to_milliunits: true }, // Should handle near-integer values
115
- ];
116
-
117
- for (const test of precisionTests) {
118
- const result = await handleConvertAmount(test);
119
- const response = JSON.parse(result.content[0].text);
120
-
121
- expect(response.conversion).toHaveProperty('converted_amount');
122
- expect(typeof response.conversion.converted_amount).toBe('number');
123
- expect(Number.isInteger(response.conversion.converted_amount)).toBe(true);
124
- }
125
- },
126
- );
127
- });
128
44
  });
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import * as ynab from 'ynab';
3
- import { handleGetUser, handleConvertAmount, ConvertAmountSchema } from '../utilityTools.js';
3
+ import { handleGetUser } from '../utilityTools.js';
4
4
 
5
5
  // Mock the YNAB API
6
6
  const mockYnabAPI = {
@@ -80,126 +80,4 @@ describe('Utility Tools', () => {
80
80
  expect(result.content[0].text).toContain('Failed to get user information');
81
81
  });
82
82
  });
83
-
84
- describe('handleConvertAmount', () => {
85
- it('should convert dollars to milliunits correctly', async () => {
86
- const params = { amount: 10.5, to_milliunits: true };
87
-
88
- const result = await handleConvertAmount(mockYnabAPI, params);
89
- const response = JSON.parse(result.content[0].text);
90
-
91
- expect(response.conversion.original_amount).toBe(10.5);
92
- expect(response.conversion.converted_amount).toBe(10500);
93
- expect(response.conversion.to_milliunits).toBe(true);
94
- expect(response.conversion.description).toBe('$10.50 = 10500 milliunits');
95
- });
96
-
97
- it('should convert milliunits to dollars correctly', async () => {
98
- const params = { amount: 10500, to_milliunits: false };
99
-
100
- const result = await handleConvertAmount(mockYnabAPI, params);
101
- const response = JSON.parse(result.content[0].text);
102
-
103
- expect(response.conversion.original_amount).toBe(10500);
104
- expect(response.conversion.converted_amount).toBe(10.5);
105
- expect(response.conversion.to_milliunits).toBe(false);
106
- expect(response.conversion.description).toBe('10500 milliunits = $10.50');
107
- });
108
-
109
- it('should handle zero amounts', async () => {
110
- const params = { amount: 0, to_milliunits: true };
111
-
112
- const result = await handleConvertAmount(mockYnabAPI, params);
113
- const response = JSON.parse(result.content[0].text);
114
-
115
- expect(response.conversion.original_amount).toBe(0);
116
- expect(response.conversion.converted_amount).toBe(0);
117
- expect(response.conversion.description).toBe('$0.00 = 0 milliunits');
118
- });
119
-
120
- it('should handle negative amounts', async () => {
121
- const params = { amount: -5.25, to_milliunits: true };
122
-
123
- const result = await handleConvertAmount(mockYnabAPI, params);
124
- const response = JSON.parse(result.content[0].text);
125
-
126
- expect(response.conversion.original_amount).toBe(-5.25);
127
- expect(response.conversion.converted_amount).toBe(-5250);
128
- expect(response.conversion.description).toBe('$-5.25 = -5250 milliunits');
129
- });
130
-
131
- it('should handle floating-point precision correctly', async () => {
132
- const params = { amount: 0.01, to_milliunits: true };
133
-
134
- const result = await handleConvertAmount(mockYnabAPI, params);
135
- const response = JSON.parse(result.content[0].text);
136
-
137
- expect(response.conversion.converted_amount).toBe(10);
138
- });
139
-
140
- it('should handle large amounts', async () => {
141
- const params = { amount: 999999.99, to_milliunits: true };
142
-
143
- const result = await handleConvertAmount(mockYnabAPI, params);
144
- const response = JSON.parse(result.content[0].text);
145
-
146
- expect(response.conversion.converted_amount).toBe(999999990);
147
- });
148
-
149
- it('should round to nearest milliunit when converting from dollars', async () => {
150
- const params = { amount: 10.5555, to_milliunits: true };
151
-
152
- const result = await handleConvertAmount(mockYnabAPI, params);
153
- const response = JSON.parse(result.content[0].text);
154
-
155
- expect(response.conversion.converted_amount).toBe(10556); // Rounded from 10555.5
156
- });
157
- });
158
-
159
- describe('ConvertAmountSchema validation', () => {
160
- it('should validate correct parameters', () => {
161
- const validParams = { amount: 10.5, to_milliunits: true };
162
- const result = ConvertAmountSchema.safeParse(validParams);
163
-
164
- expect(result.success).toBe(true);
165
- if (result.success) {
166
- expect(result.data).toEqual(validParams);
167
- }
168
- });
169
-
170
- it('should reject non-finite numbers', () => {
171
- const invalidParams = { amount: Infinity, to_milliunits: true };
172
- const result = ConvertAmountSchema.safeParse(invalidParams);
173
-
174
- expect(result.success).toBe(false);
175
- });
176
-
177
- it('should reject NaN values', () => {
178
- const invalidParams = { amount: NaN, to_milliunits: true };
179
- const result = ConvertAmountSchema.safeParse(invalidParams);
180
-
181
- expect(result.success).toBe(false);
182
- });
183
-
184
- it('should reject missing amount parameter', () => {
185
- const invalidParams = { to_milliunits: true };
186
- const result = ConvertAmountSchema.safeParse(invalidParams);
187
-
188
- expect(result.success).toBe(false);
189
- });
190
-
191
- it('should reject missing to_milliunits parameter', () => {
192
- const invalidParams = { amount: 10.5 };
193
- const result = ConvertAmountSchema.safeParse(invalidParams);
194
-
195
- expect(result.success).toBe(false);
196
- });
197
-
198
- it('should reject non-boolean to_milliunits parameter', () => {
199
- const invalidParams = { amount: 10.5, to_milliunits: 'true' };
200
- const result = ConvertAmountSchema.safeParse(invalidParams);
201
-
202
- expect(result.success).toBe(false);
203
- });
204
- });
205
83
  });
@@ -25,8 +25,6 @@
25
25
  export {
26
26
  GetUserOutputSchema,
27
27
  type GetUserOutput,
28
- ConvertAmountOutputSchema,
29
- type ConvertAmountOutput,
30
28
  GetDefaultBudgetOutputSchema,
31
29
  type GetDefaultBudgetOutput,
32
30
  SetDefaultBudgetOutputSchema,
@@ -44,7 +42,6 @@ export {
44
42
  // Nested schemas that may be useful independently
45
43
  export {
46
44
  UserSchema,
47
- ConversionSchema,
48
45
  DateFormatSchema,
49
46
  CurrencyFormatSchema,
50
47
  BudgetDetailSchema,
@@ -2,8 +2,8 @@
2
2
  * @fileoverview Output schemas for utility tools
3
3
  *
4
4
  * This file contains comprehensive Zod schemas for validating the output
5
- * of utility tools including user info, amount conversion, budget defaults,
6
- * cache management, output formatting, and diagnostic information.
5
+ * of utility tools including user info, budget defaults, cache management,
6
+ * output formatting, and diagnostic information.
7
7
  *
8
8
  * All schemas include TypeScript type inference for type-safe usage throughout
9
9
  * the codebase. Reference the corresponding handler implementations for
@@ -46,47 +46,6 @@ export const GetUserOutputSchema = z.object({
46
46
 
47
47
  export type GetUserOutput = z.infer<typeof GetUserOutputSchema>;
48
48
 
49
- // ============================================================================
50
- // CONVERT AMOUNT OUTPUT
51
- // ============================================================================
52
-
53
- /**
54
- * Schema for amount conversion details
55
- *
56
- * Contains the conversion result between dollars and YNAB milliunits.
57
- */
58
- export const ConversionSchema = z.object({
59
- original_amount: z.number(),
60
- converted_amount: z.number(),
61
- to_milliunits: z.boolean(),
62
- description: z.string(),
63
- });
64
-
65
- /**
66
- * Output schema for convert_amount tool
67
- *
68
- * Converts between dollars and YNAB milliunits (1 dollar = 1000 milliunits).
69
- *
70
- * @see src/tools/utilityTools.ts:51-90 - Handler implementation
71
- *
72
- * @example
73
- * ```typescript
74
- * const output: ConvertAmountOutput = {
75
- * conversion: {
76
- * original_amount: 25.50,
77
- * converted_amount: 25500,
78
- * to_milliunits: true,
79
- * description: "$25.50 converted to 25500 milliunits"
80
- * }
81
- * };
82
- * ```
83
- */
84
- export const ConvertAmountOutputSchema = z.object({
85
- conversion: ConversionSchema,
86
- });
87
-
88
- export type ConvertAmountOutput = z.infer<typeof ConvertAmountOutputSchema>;
89
-
90
49
  // ============================================================================
91
50
  // GET DEFAULT BUDGET OUTPUT
92
51
  // ============================================================================
@@ -120,7 +120,6 @@ export const ToolAnnotationPresets = {
120
120
  * interacting with the YNAB API.
121
121
  *
122
122
  * Examples:
123
- * - convert_amount: Converts between dollars and milliunits
124
123
  * - set_output_format: Configures local output formatting
125
124
  * - diagnostic_info: Returns local server diagnostic information
126
125
  * - clear_cache: Clears local in-memory cache
@@ -1,6 +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 { responseFormatter } from '../server/responseFormatter.js';
5
4
  import { withToolErrorHandling } from '../types/index.js';
6
5
  import { createAdapters } from './adapters.js';
@@ -8,18 +7,6 @@ import { emptyObjectSchema } from './schemas/common.js';
8
7
  import { ToolAnnotationPresets } from './toolCategories.js';
9
8
  import type { ToolFactory } from '../types/toolRegistration.js';
10
9
 
11
- /**
12
- * Schema for ynab:convert_amount tool parameters
13
- */
14
- export const ConvertAmountSchema = z
15
- .object({
16
- amount: z.number().finite(),
17
- to_milliunits: z.boolean(),
18
- })
19
- .strict();
20
-
21
- export type ConvertAmountParams = z.infer<typeof ConvertAmountSchema>;
22
-
23
10
  /**
24
11
  * Handles the ynab:get_user tool call
25
12
  * Gets information about the authenticated user
@@ -49,58 +36,13 @@ export async function handleGetUser(ynabAPI: ynab.API): Promise<CallToolResult>
49
36
  }
50
37
 
51
38
  /**
52
- * Handles the ynab:convert_amount tool call
53
- * Converts between dollars and milliunits with integer arithmetic for precision
54
- */
55
- export async function handleConvertAmount(
56
- _ynabAPI: ynab.API,
57
- params: ConvertAmountParams,
58
- ): Promise<CallToolResult> {
59
- return await withToolErrorHandling(
60
- async () => {
61
- const { amount, to_milliunits } = params;
62
-
63
- let result: number;
64
- let description: string;
65
-
66
- if (to_milliunits) {
67
- // Convert from dollars to milliunits
68
- // Use integer arithmetic to avoid floating-point precision issues
69
- result = Math.round(amount * 1000);
70
- description = `$${amount.toFixed(2)} = ${result} milliunits`;
71
- } else {
72
- // Convert from milliunits to dollars
73
- // Assume input amount is in milliunits
74
- result = amount / 1000;
75
- description = `${amount} milliunits = $${result.toFixed(2)}`;
76
- }
77
-
78
- return {
79
- content: [
80
- {
81
- type: 'text',
82
- text: responseFormatter.format({
83
- conversion: {
84
- original_amount: amount,
85
- converted_amount: result,
86
- to_milliunits,
87
- description,
88
- },
89
- }),
90
- },
91
- ],
92
- };
93
- },
94
- 'ynab:convert_amount',
95
- 'converting amount',
96
- );
97
- }
98
-
99
- /**
100
- * Registers utility tools (get_user, convert_amount) using the shared factory pattern.
39
+ * Registers utility tools (get_user) using the shared factory pattern.
40
+ *
41
+ * Note: convert_amount was removed because all tool responses already return
42
+ * amounts in dollars (converted from YNAB milliunits internally).
101
43
  */
102
44
  export const registerUtilityTools: ToolFactory = (registry, context) => {
103
- const { adapt, adaptNoInput } = createAdapters(context);
45
+ const { adaptNoInput } = createAdapters(context);
104
46
 
105
47
  registry.register({
106
48
  name: 'get_user',
@@ -114,17 +56,4 @@ export const registerUtilityTools: ToolFactory = (registry, context) => {
114
56
  },
115
57
  },
116
58
  });
117
-
118
- registry.register({
119
- name: 'convert_amount',
120
- description: 'Convert between dollars and milliunits with integer arithmetic for precision',
121
- inputSchema: ConvertAmountSchema,
122
- handler: adapt(handleConvertAmount),
123
- metadata: {
124
- annotations: {
125
- ...ToolAnnotationPresets.UTILITY_LOCAL,
126
- title: 'YNAB: Convert Amount',
127
- },
128
- },
129
- });
130
59
  };
package/vitest.config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { defineConfig } from 'vitest/config';
2
2
 
3
3
  const integrationFiles = ['src/**/*.integration.test.ts'];
4
+ const isVerbose = !!process.env['VERBOSE_TESTS'];
4
5
 
5
6
  export default defineConfig({
6
7
  test: {
@@ -83,7 +84,7 @@ export default defineConfig({
83
84
  },
84
85
  },
85
86
  },
86
- reporters: ['verbose', 'html', './vitest-reporters/split-json-reporter.ts'],
87
+ reporters: [isVerbose ? 'verbose' : 'dot', 'html', './vitest-reporters/split-json-reporter.ts'],
87
88
  outputFile: {
88
89
  html: './test-results/index.html',
89
90
  },
package/.chunkhound.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "embedding": {
3
- "provider": "voyageai",
4
- "model": "voyage-3.5",
5
- "api_key": "pa-Q1WewxKAEUA6QeYTmreQmY11ufEk9mKIK0DfoqqZhV6",
6
- "rerank_model": "rerank-2.5"
7
- },
8
- "llm": {
9
- "provider": "claude-code-cli"
10
- }
11
- }
@@ -1 +0,0 @@
1
- Agent cancelled
@@ -1,3 +0,0 @@
1
- Command failed: Error loading configuration: config profile `From the repo root, list top-level files and directories using ls or dir, then exit. No file writes or edits.
2
-
3
- [Running in read-only mode - no modifications allowed]` not found
@@ -1,3 +0,0 @@
1
- Command failed: Error loading configuration: config profile `From the repo root, just run `pwd` and then exit. Do not modify any files.
2
-
3
- [Running in read-only mode - no modifications allowed]` not found
@@ -1 +0,0 @@
1
- Agent cancelled
@@ -1,5 +0,0 @@
1
- Command failed: ParserError: 
2
- Line |
3
-  5 |  [Running in read-only mode - no modifications allowed]
4
-  |  ~
5
-  | Missing ] at end of attribute or type literal.