@dizzlkheinz/ynab-mcpb 0.16.1 → 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 (169) 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 +33 -47
  8. package/README.md +8 -10
  9. package/dist/bundle/index.cjs +54 -54
  10. package/dist/server/YNABMCPServer.d.ts +120 -54
  11. package/dist/server/YNABMCPServer.js +28 -381
  12. package/dist/server/config.d.ts +2 -0
  13. package/dist/server/config.js +1 -0
  14. package/dist/server/securityMiddleware.d.ts +37 -8
  15. package/dist/tools/accountTools.d.ts +2 -0
  16. package/dist/tools/accountTools.js +45 -0
  17. package/dist/tools/adapters.d.ts +12 -0
  18. package/dist/tools/adapters.js +25 -0
  19. package/dist/tools/budgetTools.d.ts +2 -0
  20. package/dist/tools/budgetTools.js +30 -0
  21. package/dist/tools/categoryTools.d.ts +2 -0
  22. package/dist/tools/categoryTools.js +45 -0
  23. package/dist/tools/monthTools.d.ts +2 -0
  24. package/dist/tools/monthTools.js +32 -0
  25. package/dist/tools/payeeTools.d.ts +2 -0
  26. package/dist/tools/payeeTools.js +32 -0
  27. package/dist/tools/reconciliation/index.d.ts +2 -0
  28. package/dist/tools/reconciliation/index.js +33 -0
  29. package/dist/tools/schemas/common.d.ts +3 -0
  30. package/dist/tools/schemas/common.js +3 -0
  31. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  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/transactionTools.d.ts +2 -0
  37. package/dist/tools/transactionTools.js +124 -0
  38. package/dist/tools/utilityTools.d.ts +2 -7
  39. package/dist/tools/utilityTools.js +19 -38
  40. package/dist/types/index.d.ts +1 -0
  41. package/dist/types/toolRegistration.d.ts +27 -0
  42. package/dist/types/toolRegistration.js +1 -0
  43. package/docs/maintainers/npm-publishing.md +27 -0
  44. package/docs/reference/API.md +15 -70
  45. package/docs/technical/reconciliation-system-architecture.md +2251 -2251
  46. package/package.json +6 -6
  47. package/scripts/analyze-bundle.mjs +41 -41
  48. package/scripts/generate-mcpb.ps1 +95 -95
  49. package/scripts/run-domain-integration-tests.js +4 -1
  50. package/scripts/watch-and-restart.ps1 +49 -49
  51. package/src/__tests__/comprehensive.integration.test.ts +0 -28
  52. package/src/__tests__/performance.test.ts +4 -12
  53. package/src/__tests__/setup.ts +45 -14
  54. package/src/__tests__/workflows.e2e.test.ts +1 -51
  55. package/src/server/YNABMCPServer.ts +33 -519
  56. package/src/server/__tests__/YNABMCPServer.test.ts +0 -1
  57. package/src/server/__tests__/toolRegistration.test.ts +236 -0
  58. package/src/server/config.ts +1 -0
  59. package/src/tools/__tests__/adapters.test.ts +113 -0
  60. package/src/tools/__tests__/transactionTools.integration.test.ts +63 -3
  61. package/src/tools/__tests__/utilityTools.integration.test.ts +1 -85
  62. package/src/tools/__tests__/utilityTools.test.ts +1 -123
  63. package/src/tools/accountTools.ts +53 -0
  64. package/src/tools/adapters.ts +74 -0
  65. package/src/tools/budgetTools.ts +37 -0
  66. package/src/tools/categoryTools.ts +53 -0
  67. package/src/tools/monthTools.ts +39 -0
  68. package/src/tools/payeeTools.ts +39 -0
  69. package/src/tools/reconciliation/index.ts +45 -0
  70. package/src/tools/schemas/common.ts +18 -0
  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/transactionTools.ts +140 -0
  75. package/src/tools/utilityTools.ts +24 -55
  76. package/src/types/index.ts +3 -0
  77. package/src/types/toolRegistration.ts +88 -0
  78. package/vitest.config.ts +2 -1
  79. package/.chunkhound.json +0 -11
  80. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +0 -3
  81. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +0 -3
  82. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +0 -1
  83. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +0 -5
  84. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +0 -1569
  85. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +0 -3
  86. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +0 -2832
  87. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +0 -2709
  88. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +0 -2832
  89. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +0 -2832
  90. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +0 -2709
  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/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +0 -231
  96. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +0 -2590
  97. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +0 -5195
  98. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +0 -286
  99. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +0 -218
  100. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +0 -180
  101. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +0 -3
  102. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +0 -1
  103. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +0 -747
  104. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +0 -1
  105. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +0 -2594
  106. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +0 -2594
  107. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +0 -144
  108. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +0 -416
  109. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +0 -2590
  110. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +0 -2590
  111. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +0 -2590
  112. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +0 -790
  113. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +0 -766
  114. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +0 -790
  115. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +0 -2594
  116. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +0 -1000
  117. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +0 -3489
  118. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +0 -766
  119. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +0 -2594
  120. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +0 -2456
  121. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +0 -2594
  122. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +0 -18
  123. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +0 -48
  124. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +0 -1
  125. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +0 -1
  126. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +0 -1
  127. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +0 -1
  128. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +0 -1271
  129. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +0 -1570
  130. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +0 -2590
  131. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +0 -3
  132. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +0 -3299
  133. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +0 -3299
  134. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +0 -1882
  135. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +0 -2594
  136. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +0 -1
  137. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +0 -170
  138. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +0 -1
  139. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +0 -3
  140. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +0 -1
  141. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +0 -1
  142. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +0 -3
  143. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +0 -1
  144. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +0 -1
  145. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +0 -5
  146. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +0 -1
  147. package/.github/workflows/pr-description-check.yml +0 -88
  148. package/AGENTS.md +0 -36
  149. package/NUL +0 -1
  150. package/docs/README.md +0 -72
  151. package/docs/getting-started/CONFIGURATION.md +0 -175
  152. package/docs/getting-started/INSTALLATION.md +0 -333
  153. package/docs/getting-started/QUICKSTART.md +0 -282
  154. package/docs/guides/ARCHITECTURE.md +0 -533
  155. package/docs/guides/DEPLOYMENT.md +0 -189
  156. package/docs/guides/INTEGRATION_TESTING.md +0 -730
  157. package/docs/guides/TESTING.md +0 -591
  158. package/docs/reconciliation-flow.md +0 -83
  159. package/docs/reference/EXAMPLES.md +0 -946
  160. package/docs/reference/TOOLS.md +0 -348
  161. package/docs/reference/TROUBLESHOOTING.md +0 -481
  162. package/package.json.tmp +0 -105
  163. package/temp-recon.ts +0 -126
  164. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +0 -23
  165. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +0 -23
  166. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +0 -44
  167. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +0 -58
  168. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +0 -3662
  169. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +0 -115
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dizzlkheinz/ynab-mcpb",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "description": "Model Context Protocol server for YNAB (You Need A Budget) integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -23,9 +23,9 @@
23
23
  "verify-build": "node scripts/verify-build.js",
24
24
  "lint": "npm run lint:eslint && npm run format:check",
25
25
  "lint:eslint": "eslint src --ext .ts",
26
- "lint:fix": "eslint src --ext .ts --fix && prettier --write .",
27
- "format": "prettier --write .",
28
- "format:check": "prettier --check .",
26
+ "lint:fix": "eslint src --ext .ts --fix && prettier --log-level silent --write .",
27
+ "format": "prettier --log-level silent --write .",
28
+ "format:check": "prettier --log-level silent --check .",
29
29
  "type-check": "tsc --noEmit",
30
30
  "test": "vitest run --project unit && npm run filter-test-results",
31
31
  "test:watch": "vitest",
@@ -66,7 +66,7 @@
66
66
  "author": "",
67
67
  "license": "AGPL-3.0",
68
68
  "dependencies": {
69
- "@modelcontextprotocol/sdk": "^1.22.0",
69
+ "@modelcontextprotocol/sdk": "^1.24.3",
70
70
  "chrono-node": "^2.9.0",
71
71
  "csv-parse": "^6.1.0",
72
72
  "d3-array": "^3.2.4",
@@ -90,7 +90,7 @@
90
90
  "eslint": "^9.35.0",
91
91
  "eslint-config-prettier": "^10.1.8",
92
92
  "prettier": "^3.3.3",
93
- "rimraf": "^6.0.1",
93
+ "rimraf": "^6.1.2",
94
94
  "tsx": "^4.20.6",
95
95
  "typescript": "^5.9.2",
96
96
  "typescript-eslint": "^8.42.0",
@@ -1,41 +1,41 @@
1
- import fs from 'fs';
2
-
3
- let meta;
4
- try {
5
- meta = JSON.parse(fs.readFileSync('meta.json', 'utf-8'));
6
- } catch (error) {
7
- console.error('❌ Error reading meta.json:', error.message);
8
- process.exit(1);
9
- }
10
- if (!meta.inputs || typeof meta.inputs !== 'object') {
11
- console.error('❌ Error: meta.inputs is missing or invalid in meta.json');
12
- process.exit(1);
13
- }
14
-
15
- const inputs = Object.entries(meta.inputs)
16
- .map(([path, data]) => ({
17
- path,
18
- bytes: data.bytes,
19
- }))
20
- .sort((a, b) => b.bytes - a.bytes)
21
- .slice(0, 20);
22
-
23
- const totalBytes = Object.values(meta.inputs).reduce((sum, item) => sum + item.bytes, 0);
24
- console.log(`\n📦 Total input size: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
25
- const outputKey = 'dist/bundle/index.cjs';
26
- if (!meta.outputs || !meta.outputs[outputKey]) {
27
- console.error(`❌ Error: meta.outputs['${outputKey}'] is missing in meta.json`);
28
- process.exit(1);
29
- }
30
- console.log(`📦 Output size: ${(meta.outputs[outputKey].bytes / 1024 / 1024).toFixed(2)} MB`);
31
- console.log('\n📊 Top 20 largest inputs in bundle:\n');
32
- inputs.forEach((item) => {
33
- const kb = (item.bytes / 1024).toFixed(1).padStart(8);
34
- console.log(`${kb} KB ${item.path}`);
35
- }); const kb = (item.bytes / 1024).toFixed(1).padStart(8);
36
- console.log(`${kb} KB ${item.path}`);
37
- });
38
-
39
- const totalBytes = Object.values(meta.inputs).reduce((sum, item) => sum + item.bytes, 0);
40
- console.log(`\n📦 Total input size: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
41
- console.log(`📦 Output size: ${(meta.outputs['dist/bundle/index.cjs'].bytes / 1024 / 1024).toFixed(2)} MB`);
1
+ import fs from 'fs';
2
+
3
+ let meta;
4
+ try {
5
+ meta = JSON.parse(fs.readFileSync('meta.json', 'utf-8'));
6
+ } catch (error) {
7
+ console.error('❌ Error reading meta.json:', error.message);
8
+ process.exit(1);
9
+ }
10
+ if (!meta.inputs || typeof meta.inputs !== 'object') {
11
+ console.error('❌ Error: meta.inputs is missing or invalid in meta.json');
12
+ process.exit(1);
13
+ }
14
+
15
+ const inputs = Object.entries(meta.inputs)
16
+ .map(([path, data]) => ({
17
+ path,
18
+ bytes: data.bytes,
19
+ }))
20
+ .sort((a, b) => b.bytes - a.bytes)
21
+ .slice(0, 20);
22
+
23
+ const totalBytes = Object.values(meta.inputs).reduce((sum, item) => sum + item.bytes, 0);
24
+ console.log(`\n📦 Total input size: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
25
+ const outputKey = 'dist/bundle/index.cjs';
26
+ if (!meta.outputs || !meta.outputs[outputKey]) {
27
+ console.error(`❌ Error: meta.outputs['${outputKey}'] is missing in meta.json`);
28
+ process.exit(1);
29
+ }
30
+ console.log(`📦 Output size: ${(meta.outputs[outputKey].bytes / 1024 / 1024).toFixed(2)} MB`);
31
+ console.log('\n📊 Top 20 largest inputs in bundle:\n');
32
+ inputs.forEach((item) => {
33
+ const kb = (item.bytes / 1024).toFixed(1).padStart(8);
34
+ console.log(`${kb} KB ${item.path}`);
35
+ }); const kb = (item.bytes / 1024).toFixed(1).padStart(8);
36
+ console.log(`${kb} KB ${item.path}`);
37
+ });
38
+
39
+ const totalBytes = Object.values(meta.inputs).reduce((sum, item) => sum + item.bytes, 0);
40
+ console.log(`\n📦 Total input size: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
41
+ console.log(`📦 Output size: ${(meta.outputs['dist/bundle/index.cjs'].bytes / 1024 / 1024).toFixed(2)} MB`);
@@ -1,96 +1,96 @@
1
- # PowerShell script to generate a .mcpb file using the official Anthropic MCPB CLI
2
-
3
- param(
4
- [string]$OutputDir = "dist"
5
- )
6
-
7
- Write-Host "Generating YNAB MCP Server .mcpb package using official CLI..." -ForegroundColor Green
8
-
9
- # Configuration
10
- $PackageJson = Get-Content "package.json" | ConvertFrom-Json
11
- $PackageName = $PackageJson.name
12
- $Version = $PackageJson.version
13
-
14
- # Ensure we have a built version
15
- if (-not (Test-Path "dist/index.js")) {
16
- Write-Host "Build not found. Running build first..." -ForegroundColor Red
17
- npm run build
18
- if ($LASTEXITCODE -ne 0) {
19
- Write-Host "Build failed!" -ForegroundColor Red
20
- exit 1
21
- }
22
- }
23
-
24
- # Check if official MCPB CLI is installed
25
- try {
26
- $mcpbVersion = & mcpb --version
27
- Write-Host "Using MCPB CLI version: $mcpbVersion" -ForegroundColor Green
28
- } catch {
29
- Write-Host "MCPB CLI not found. Installing..." -ForegroundColor Yellow
30
- npm install -g @anthropic-ai/mcpb
31
- if ($LASTEXITCODE -ne 0) {
32
- Write-Host "Failed to install MCPB CLI!" -ForegroundColor Red
33
- exit 1
34
- }
35
- }
36
-
37
- # Ensure manifest.json exists and is valid
38
- if (-not (Test-Path "manifest.json")) {
39
- Write-Host "manifest.json not found. Creating one..." -ForegroundColor Yellow
40
- & mcpb init -y
41
- if ($LASTEXITCODE -ne 0) {
42
- Write-Host "Failed to create manifest!" -ForegroundColor Red
43
- exit 1
44
- }
45
- }
46
-
47
- # Sync version from package.json to manifest.json
48
- Write-Host "Syncing version from package.json to manifest.json..." -ForegroundColor Yellow
49
- $ManifestPath = "manifest.json"
50
- $ManifestContent = Get-Content $ManifestPath -Raw
51
- $Manifest = $ManifestContent | ConvertFrom-Json
52
-
53
- if ($Manifest.version -ne $Version) {
54
- Write-Host "Updating manifest version from $($Manifest.version) to $Version" -ForegroundColor Cyan
55
- # Use regex to update version while preserving formatting
56
- $UpdatedContent = $ManifestContent -replace '("version"\s*:\s*)"[^"]*"', "`$1`"$Version`""
57
- $UpdatedContent | Set-Content $ManifestPath -NoNewline
58
- }
59
-
60
- # Validate the manifest
61
- Write-Host "Validating manifest..." -ForegroundColor Yellow
62
- & mcpb validate manifest.json
63
- if ($LASTEXITCODE -ne 0) {
64
- Write-Host "Manifest validation failed!" -ForegroundColor Red
65
- exit 1
66
- }
67
-
68
- # Pack the MCPB using official CLI
69
- Write-Host "Packing MCPB file..." -ForegroundColor Yellow
70
- $DxtFile = "$PackageName-$Version.mcpb"
71
- $OutputPath = Join-Path $OutputDir $DxtFile
72
-
73
- & mcpb pack . $OutputPath
74
- if ($LASTEXITCODE -ne 0) {
75
- Write-Host "MCPB packing failed!" -ForegroundColor Red
76
- exit 1
77
- }
78
-
79
- # Get file size
80
- if (Test-Path $OutputPath) {
81
- $FileSize = (Get-Item $OutputPath).Length
82
- $FileSizeKB = [math]::Round($FileSize / 1KB, 1)
83
- $FileSizeMB = [math]::Round($FileSize / 1MB, 1)
84
-
85
- Write-Host "Created $OutputPath" -ForegroundColor Green
86
- Write-Host "Size: $FileSizeKB KB ($FileSizeMB MB)" -ForegroundColor Cyan
87
- } else {
88
- Write-Host "MCPB file was not created!" -ForegroundColor Red
89
- exit 1
90
- }
91
-
92
- Write-Host ""
93
- Write-Host "Installation Instructions:" -ForegroundColor Yellow
94
- Write-Host "1. Drag and drop the .mcpb file into Claude Desktop" -ForegroundColor White
95
- Write-Host "2. Set YNAB_ACCESS_TOKEN environment variable" -ForegroundColor White
1
+ # PowerShell script to generate a .mcpb file using the official Anthropic MCPB CLI
2
+
3
+ param(
4
+ [string]$OutputDir = "dist"
5
+ )
6
+
7
+ Write-Host "Generating YNAB MCP Server .mcpb package using official CLI..." -ForegroundColor Green
8
+
9
+ # Configuration
10
+ $PackageJson = Get-Content "package.json" | ConvertFrom-Json
11
+ $PackageName = $PackageJson.name
12
+ $Version = $PackageJson.version
13
+
14
+ # Ensure we have a built version
15
+ if (-not (Test-Path "dist/index.js")) {
16
+ Write-Host "Build not found. Running build first..." -ForegroundColor Red
17
+ npm run build
18
+ if ($LASTEXITCODE -ne 0) {
19
+ Write-Host "Build failed!" -ForegroundColor Red
20
+ exit 1
21
+ }
22
+ }
23
+
24
+ # Check if official MCPB CLI is installed
25
+ try {
26
+ $mcpbVersion = & mcpb --version
27
+ Write-Host "Using MCPB CLI version: $mcpbVersion" -ForegroundColor Green
28
+ } catch {
29
+ Write-Host "MCPB CLI not found. Installing..." -ForegroundColor Yellow
30
+ npm install -g @anthropic-ai/mcpb
31
+ if ($LASTEXITCODE -ne 0) {
32
+ Write-Host "Failed to install MCPB CLI!" -ForegroundColor Red
33
+ exit 1
34
+ }
35
+ }
36
+
37
+ # Ensure manifest.json exists and is valid
38
+ if (-not (Test-Path "manifest.json")) {
39
+ Write-Host "manifest.json not found. Creating one..." -ForegroundColor Yellow
40
+ & mcpb init -y
41
+ if ($LASTEXITCODE -ne 0) {
42
+ Write-Host "Failed to create manifest!" -ForegroundColor Red
43
+ exit 1
44
+ }
45
+ }
46
+
47
+ # Sync version from package.json to manifest.json
48
+ Write-Host "Syncing version from package.json to manifest.json..." -ForegroundColor Yellow
49
+ $ManifestPath = "manifest.json"
50
+ $ManifestContent = Get-Content $ManifestPath -Raw
51
+ $Manifest = $ManifestContent | ConvertFrom-Json
52
+
53
+ if ($Manifest.version -ne $Version) {
54
+ Write-Host "Updating manifest version from $($Manifest.version) to $Version" -ForegroundColor Cyan
55
+ # Use regex to update version while preserving formatting
56
+ $UpdatedContent = $ManifestContent -replace '("version"\s*:\s*)"[^"]*"', "`$1`"$Version`""
57
+ $UpdatedContent | Set-Content $ManifestPath -NoNewline
58
+ }
59
+
60
+ # Validate the manifest
61
+ Write-Host "Validating manifest..." -ForegroundColor Yellow
62
+ & mcpb validate manifest.json
63
+ if ($LASTEXITCODE -ne 0) {
64
+ Write-Host "Manifest validation failed!" -ForegroundColor Red
65
+ exit 1
66
+ }
67
+
68
+ # Pack the MCPB using official CLI
69
+ Write-Host "Packing MCPB file..." -ForegroundColor Yellow
70
+ $DxtFile = "$PackageName-$Version.mcpb"
71
+ $OutputPath = Join-Path $OutputDir $DxtFile
72
+
73
+ & mcpb pack . $OutputPath
74
+ if ($LASTEXITCODE -ne 0) {
75
+ Write-Host "MCPB packing failed!" -ForegroundColor Red
76
+ exit 1
77
+ }
78
+
79
+ # Get file size
80
+ if (Test-Path $OutputPath) {
81
+ $FileSize = (Get-Item $OutputPath).Length
82
+ $FileSizeKB = [math]::Round($FileSize / 1KB, 1)
83
+ $FileSizeMB = [math]::Round($FileSize / 1MB, 1)
84
+
85
+ Write-Host "Created $OutputPath" -ForegroundColor Green
86
+ Write-Host "Size: $FileSizeKB KB ($FileSizeMB MB)" -ForegroundColor Cyan
87
+ } else {
88
+ Write-Host "MCPB file was not created!" -ForegroundColor Red
89
+ exit 1
90
+ }
91
+
92
+ Write-Host ""
93
+ Write-Host "Installation Instructions:" -ForegroundColor Yellow
94
+ Write-Host "1. Drag and drop the .mcpb file into Claude Desktop" -ForegroundColor White
95
+ Write-Host "2. Set YNAB_ACCESS_TOKEN environment variable" -ForegroundColor White
96
96
  Write-Host "3. Restart Claude Desktop" -ForegroundColor White
@@ -22,10 +22,13 @@ const env = {
22
22
  };
23
23
 
24
24
  const vitestArgs = ['vitest', 'run', '--project', 'integration:domain', ...passthroughArgs];
25
- const runner = process.platform === 'win32' ? 'npx.cmd' : 'npx';
25
+ const runner = 'npx';
26
+ const useShell = process.platform === 'win32';
27
+
26
28
  const child = spawn(runner, vitestArgs, {
27
29
  stdio: 'inherit',
28
30
  env,
31
+ shell: useShell,
29
32
  });
30
33
 
31
34
  child.on('close', (code) => {
@@ -1,50 +1,50 @@
1
- # PowerShell script to watch for file changes and restart MCP
2
- param(
3
- [string]$Path = ".\src",
4
- [string]$Filter = "*.ts",
5
- [int]$RestartDelay = 3
6
- )
7
-
8
- Write-Host "🔍 Watching for changes in: $Path"
9
- Write-Host "📁 Filter: $Filter"
10
-
11
- $watcher = New-Object System.IO.FileSystemWatcher
12
- $watcher.Path = Resolve-Path $Path
13
- $watcher.Filter = $Filter
14
- $watcher.EnableRaisingEvents = $true
15
- $watcher.IncludeSubdirectories = $true
16
-
17
- $action = {
18
- $path = $Event.SourceEventArgs.FullPath
19
- $changeType = $Event.SourceEventArgs.ChangeType
20
- Write-Host "🔄 File $changeType`: $path"
21
-
22
- # Build the project
23
- Write-Host "🏗️ Building project..."
24
- npm run build
25
-
26
- if ($LASTEXITCODE -eq 0) {
27
- Write-Host "✅ Build successful"
28
- Start-Sleep -Seconds $RestartDelay
29
-
30
- Write-Host "🔄 Reconnecting to YNAB MCP server..."
31
- /mcp reconnect ynab-mcp-server
32
- Start-Sleep -Seconds 1
33
- /mcp reconnect ynab-mcp-server
34
- Write-Host "✅ MCP server reconnected"
35
- } else {
36
- Write-Host "❌ Build failed, skipping MCP restart"
37
- }
38
- }
39
-
40
- Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action
41
- Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action
42
-
43
- try {
44
- Write-Host "✅ File watcher started. Press Ctrl+C to stop."
45
- while ($true) { Start-Sleep 1 }
46
- }
47
- finally {
48
- $watcher.Dispose()
49
- Write-Host "👋 File watcher stopped"
1
+ # PowerShell script to watch for file changes and restart MCP
2
+ param(
3
+ [string]$Path = ".\src",
4
+ [string]$Filter = "*.ts",
5
+ [int]$RestartDelay = 3
6
+ )
7
+
8
+ Write-Host "🔍 Watching for changes in: $Path"
9
+ Write-Host "📁 Filter: $Filter"
10
+
11
+ $watcher = New-Object System.IO.FileSystemWatcher
12
+ $watcher.Path = Resolve-Path $Path
13
+ $watcher.Filter = $Filter
14
+ $watcher.EnableRaisingEvents = $true
15
+ $watcher.IncludeSubdirectories = $true
16
+
17
+ $action = {
18
+ $path = $Event.SourceEventArgs.FullPath
19
+ $changeType = $Event.SourceEventArgs.ChangeType
20
+ Write-Host "🔄 File $changeType`: $path"
21
+
22
+ # Build the project
23
+ Write-Host "🏗️ Building project..."
24
+ npm run build
25
+
26
+ if ($LASTEXITCODE -eq 0) {
27
+ Write-Host "✅ Build successful"
28
+ Start-Sleep -Seconds $RestartDelay
29
+
30
+ Write-Host "🔄 Reconnecting to YNAB MCP server..."
31
+ /mcp reconnect ynab-mcp-server
32
+ Start-Sleep -Seconds 1
33
+ /mcp reconnect ynab-mcp-server
34
+ Write-Host "✅ MCP server reconnected"
35
+ } else {
36
+ Write-Host "❌ Build failed, skipping MCP restart"
37
+ }
38
+ }
39
+
40
+ Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action
41
+ Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action
42
+
43
+ try {
44
+ Write-Host "✅ File watcher started. Press Ctrl+C to stop."
45
+ while ($true) { Start-Sleep 1 }
46
+ }
47
+ finally {
48
+ $watcher.Dispose()
49
+ Write-Host "👋 File watcher stopped"
50
50
  }
@@ -748,34 +748,6 @@ describe('YNAB MCP Server - Comprehensive Integration Tests', () => {
748
748
  expect(mockYnabAPI.user.getUser).toHaveBeenCalledTimes(1);
749
749
  },
750
750
  );
751
-
752
- it(
753
- 'should handle amount conversion',
754
- { meta: { tier: 'domain', domain: 'workflows' } },
755
- async () => {
756
- // Test dollar to milliunits conversion
757
- const toMilliunitsResult = await executeToolCall(server, 'ynab:convert_amount', {
758
- amount: 25.75,
759
- to_milliunits: true,
760
- });
761
- validateToolResult(toMilliunitsResult);
762
-
763
- const toMilli = parseToolResult(toMilliunitsResult);
764
- expect(toMilli.data.conversion.converted_amount).toBe(25750);
765
- expect(toMilli.data.conversion.description).toBe('$25.75 = 25750 milliunits');
766
-
767
- // Test milliunits to dollar conversion
768
- const toDollarsResult = await executeToolCall(server, 'ynab:convert_amount', {
769
- amount: 25750,
770
- to_milliunits: false,
771
- });
772
- validateToolResult(toDollarsResult);
773
-
774
- const dollars = parseToolResult(toDollarsResult);
775
- expect(dollars.data.conversion.converted_amount).toBe(25.75);
776
- expect(dollars.data.conversion.description).toBe('25750 milliunits = $25.75');
777
- },
778
- );
779
751
  });
780
752
 
781
753
  describe('Error Handling Integration', () => {
@@ -623,12 +623,6 @@ describe('YNAB MCP Server - Performance Tests', () => {
623
623
 
624
624
  // Test multiple validation scenarios
625
625
  const validationTests = [
626
- // Valid parameters
627
- executeToolCall(server, 'ynab:convert_amount', {
628
- amount: 25.5,
629
- to_milliunits: true,
630
- }),
631
-
632
626
  // Invalid parameters (should fail quickly)
633
627
  executeToolCall(server, 'ynab:get_budget', {
634
628
  budget_id: '', // Empty string should fail validation
@@ -648,10 +642,9 @@ describe('YNAB MCP Server - Performance Tests', () => {
648
642
 
649
643
  const totalTime = endTime - startTime;
650
644
 
651
- expect(parsed).toHaveLength(3);
652
- expect(parsed[0]).toBeDefined(); // Valid call should succeed
653
- const firstError = parsed[1].error ?? parsed[1].data?.error;
654
- const secondError = parsed[2].error ?? parsed[2].data?.error;
645
+ expect(parsed).toHaveLength(2);
646
+ const firstError = parsed[0].error ?? parsed[0].data?.error;
647
+ const secondError = parsed[1].error ?? parsed[1].data?.error;
655
648
  expect(firstError?.code).toBe(SecurityErrorCode.VALIDATION_ERROR); // Invalid calls should fail
656
649
  expect(secondError?.code).toBe(SecurityErrorCode.VALIDATION_ERROR);
657
650
  expect(totalTime).toBeLessThan(1000); // Validation should be fast
@@ -715,7 +708,6 @@ describe('YNAB MCP Server - Performance Tests', () => {
715
708
  executeToolCall(server, 'ynab:list_accounts', { budget_id: 'test' }),
716
709
  executeToolCall(server, 'ynab:list_transactions', { budget_id: 'test' }),
717
710
  executeToolCall(server, 'ynab:list_categories', { budget_id: 'test' }),
718
- executeToolCall(server, 'ynab:convert_amount', { amount: i * 10, to_milliunits: true }),
719
711
  );
720
712
  }
721
713
 
@@ -724,7 +716,7 @@ describe('YNAB MCP Server - Performance Tests', () => {
724
716
 
725
717
  const totalTime = endTime - startTime;
726
718
 
727
- expect(results).toHaveLength(100); // 20 iterations × 5 tools
719
+ expect(results).toHaveLength(80); // 20 iterations × 4 tools
728
720
  results.forEach((result) => expect(result).toBeDefined());
729
721
  expect(totalTime).toBeLessThan(10000); // Should complete within 10 seconds
730
722
  });
@@ -12,6 +12,49 @@ const hasAccessToken = !!process.env['YNAB_ACCESS_TOKEN'];
12
12
  if (!process.env['SKIP_E2E_TESTS']) {
13
13
  process.env['SKIP_E2E_TESTS'] = hasAccessToken ? 'false' : 'true';
14
14
  }
15
+ if (!process.env['YNAB_ACCESS_TOKEN']) {
16
+ process.env['YNAB_ACCESS_TOKEN'] = 'test-token-for-mocked-tests';
17
+ }
18
+
19
+ // Set test environment variables immediately
20
+ process.env['NODE_ENV'] = 'test';
21
+ if (!process.env['LOG_LEVEL']) {
22
+ process.env['LOG_LEVEL'] = 'error';
23
+ }
24
+
25
+ // Disable console output for cleaner test output unless VERBOSE_TESTS is set
26
+ if (!process.env['VERBOSE_TESTS']) {
27
+ const originalConsoleError = console.error;
28
+
29
+ console.error = (...args: any[]) => {
30
+ const firstArg = args[0];
31
+ const isString = typeof firstArg === 'string';
32
+ // Only show errors that are part of test assertions, actual errors, or explicitly marked [ERROR]
33
+ if (
34
+ (isString &&
35
+ (firstArg.includes('❌') || firstArg.includes('Test') || firstArg.includes('[ERROR]'))) ||
36
+ firstArg instanceof Error
37
+ ) {
38
+ originalConsoleError(...args);
39
+ }
40
+ };
41
+
42
+ console.warn = () => {
43
+ // Suppress warnings by default
44
+ };
45
+
46
+ console.log = () => {
47
+ // Suppress logs by default
48
+ };
49
+
50
+ console.info = () => {
51
+ // Suppress info logs by default
52
+ };
53
+
54
+ console.debug = () => {
55
+ // Suppress debug logs by default
56
+ };
57
+ }
15
58
 
16
59
  type TierFilter = 'core' | 'domain' | 'full';
17
60
  interface TestMeta {
@@ -47,26 +90,14 @@ const shouldRunDomain = (domain?: string): boolean => {
47
90
  * Global test setup
48
91
  */
49
92
  beforeAll(async () => {
50
- // Set test environment variables
51
- process.env['NODE_ENV'] = 'test';
52
-
53
93
  // Set default test token if not provided
54
94
  if (!process.env['YNAB_ACCESS_TOKEN']) {
55
95
  process.env['YNAB_ACCESS_TOKEN'] = 'test-token-for-mocked-tests';
56
96
  }
57
97
 
58
- // Disable console.error for cleaner test output (except for specific tests)
59
- if (!process.env['VERBOSE_TESTS']) {
60
- const originalConsoleError = console.error;
61
- console.error = (...args: any[]) => {
62
- // Only show errors that are part of test assertions
63
- if (args[0]?.includes?.('❌') || args[0]?.includes?.('Test')) {
64
- originalConsoleError(...args);
65
- }
66
- };
98
+ if (process.env['VERBOSE_TESTS']) {
99
+ console.warn('🧪 Test environment initialized');
67
100
  }
68
-
69
- console.warn('🧪 Test environment initialized');
70
101
  });
71
102
 
72
103
  /**
@@ -821,34 +821,6 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
821
821
  });
822
822
  });
823
823
 
824
- describe('Utility Tools Workflow', () => {
825
- it('should convert amounts between dollars and milliunits', async () => {
826
- if (testConfig.skipE2ETests) return;
827
-
828
- // Convert dollars to milliunits
829
- const toMilliunitsResult = await executeToolCall(server, 'ynab:convert_amount', {
830
- amount: 25.5,
831
- to_milliunits: true,
832
- });
833
- const milliunits = parseToolResult(toMilliunitsResult);
834
-
835
- expect(milliunits.data?.conversion?.converted_amount).toBe(25500);
836
- expect(milliunits.data?.conversion?.description).toContain('25500');
837
- expect(milliunits.data?.conversion?.to_milliunits).toBe(true);
838
-
839
- // Convert milliunits to dollars
840
- const toDollarsResult = await executeToolCall(server, 'ynab:convert_amount', {
841
- amount: 25500,
842
- to_milliunits: false,
843
- });
844
- const dollars = parseToolResult(toDollarsResult);
845
-
846
- expect(dollars.data?.conversion?.converted_amount).toBe(25.5);
847
- expect(dollars.data?.conversion?.description).toContain('$25.50');
848
- expect(dollars.data?.conversion?.to_milliunits).toBe(false);
849
- });
850
- });
851
-
852
824
  describe('v0.8.x Architecture Integration Tests', () => {
853
825
  describe('Cache System Verification', () => {
854
826
  it('should demonstrate cache warming after default budget set', async () => {
@@ -1065,13 +1037,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1065
1037
  expect(tool.description).toBeDefined();
1066
1038
  expect(tool.inputSchema).toBeDefined();
1067
1039
 
1068
- // Verify that all tools define outputSchema (as guaranteed by CHANGELOG.md and docs/reference/TOOLS.md)
1069
- // Note: Some utility tools like diagnostic_info or clear_cache may not define structured outputs,
1070
- // but most data-retrieval and CRUD tools should have output schemas.
1071
- expect(
1072
- tool.outputSchema,
1073
- `Tool '${tool.name}' should define an outputSchema`,
1074
- ).toBeDefined();
1040
+ // Output schemas are optional; tools may omit them.
1075
1041
  }
1076
1042
  });
1077
1043
  });
@@ -1143,7 +1109,6 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1143
1109
  { name: 'ynab:list_categories', args: { budget_id: testBudgetId } },
1144
1110
  { name: 'ynab:list_payees', args: { budget_id: testBudgetId } },
1145
1111
  { name: 'ynab:get_user', args: {} },
1146
- { name: 'ynab:convert_amount', args: { amount: 100, to_milliunits: true } },
1147
1112
  ];
1148
1113
 
1149
1114
  for (const tool of v7Tools) {
@@ -1621,21 +1586,6 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1621
1586
  }
1622
1587
  });
1623
1588
 
1624
- it('should validate convert_amount output schema', async () => {
1625
- if (testConfig.skipE2ETests) return;
1626
-
1627
- const result = await executeToolCall(server, 'ynab:convert_amount', {
1628
- amount: 100,
1629
- to_milliunits: true,
1630
- });
1631
- const validation = validateOutputSchema(server, 'convert_amount', result);
1632
- expect(validation.hasSchema).toBe(true);
1633
- expect(validation.valid).toBe(true);
1634
- if (!validation.valid) {
1635
- console.error('Schema validation errors:', validation.errors);
1636
- }
1637
- });
1638
-
1639
1589
  it('should validate export_transactions output schema', async () => {
1640
1590
  if (testConfig.skipE2ETests) return;
1641
1591