@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dizzlkheinz/ynab-mcpb",
3
- "version": "0.17.0",
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",
@@ -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
@@ -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 () => {
@@ -1137,7 +1109,6 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1137
1109
  { name: 'ynab:list_categories', args: { budget_id: testBudgetId } },
1138
1110
  { name: 'ynab:list_payees', args: { budget_id: testBudgetId } },
1139
1111
  { name: 'ynab:get_user', args: {} },
1140
- { name: 'ynab:convert_amount', args: { amount: 100, to_milliunits: true } },
1141
1112
  ];
1142
1113
 
1143
1114
  for (const tool of v7Tools) {
@@ -1615,21 +1586,6 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1615
1586
  }
1616
1587
  });
1617
1588
 
1618
- it('should validate convert_amount output schema', async () => {
1619
- if (testConfig.skipE2ETests) return;
1620
-
1621
- const result = await executeToolCall(server, 'ynab:convert_amount', {
1622
- amount: 100,
1623
- to_milliunits: true,
1624
- });
1625
- const validation = validateOutputSchema(server, 'convert_amount', result);
1626
- expect(validation.hasSchema).toBe(true);
1627
- expect(validation.valid).toBe(true);
1628
- if (!validation.valid) {
1629
- console.error('Schema validation errors:', validation.errors);
1630
- }
1631
- });
1632
-
1633
1589
  it('should validate export_transactions output schema', async () => {
1634
1590
  if (testConfig.skipE2ETests) return;
1635
1591
 
@@ -45,7 +45,6 @@ describe('YNABMCPServer', () => {
45
45
  'get_month',
46
46
  'list_months',
47
47
  'get_user',
48
- 'convert_amount',
49
48
  'diagnostic_info',
50
49
  'clear_cache',
51
50
  'set_output_format',
@@ -46,7 +46,7 @@ const EXPECTED_TOOLS_BY_DOMAIN = {
46
46
  payee: ['list_payees', 'get_payee'],
47
47
  month: ['get_month', 'list_months'],
48
48
  reconciliation: ['compare_transactions', 'reconcile_account'],
49
- utility: ['get_user', 'convert_amount'],
49
+ utility: ['get_user'],
50
50
  server: [
51
51
  'set_default_budget',
52
52
  'get_default_budget',
@@ -60,7 +60,7 @@ const EXPECTED_TOOLS_BY_DOMAIN = {
60
60
  const ALL_EXPECTED_TOOLS = Object.values(EXPECTED_TOOLS_BY_DOMAIN).flat();
61
61
 
62
62
  /** Expected total tool count */
63
- const EXPECTED_TOOL_COUNT = 30;
63
+ const EXPECTED_TOOL_COUNT = 29;
64
64
 
65
65
  describe('Tool Registration', () => {
66
66
  // Config is mocked at module level, no env setup needed