@dizzlkheinz/ynab-mcpb 0.12.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +3 -0
- package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +3 -0
- package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +1 -0
- package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +5 -0
- package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +1569 -0
- package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +3 -0
- package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +2832 -0
- package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +2709 -0
- package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +2832 -0
- package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +2832 -0
- package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +2709 -0
- package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +1 -0
- package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +5217 -0
- package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +2594 -0
- package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +2594 -0
- package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +231 -0
- package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +2590 -0
- package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +5195 -0
- package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +286 -0
- package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +218 -0
- package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +180 -0
- package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +3 -0
- package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +1 -0
- package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +747 -0
- package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +1 -0
- package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +2594 -0
- package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +2594 -0
- package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +144 -0
- package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +416 -0
- package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +2590 -0
- package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +2590 -0
- package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +2590 -0
- package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +790 -0
- package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +766 -0
- package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +790 -0
- package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +2594 -0
- package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +1000 -0
- package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +3489 -0
- package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +766 -0
- package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +2594 -0
- package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +2456 -0
- package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +2594 -0
- package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +18 -0
- package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +48 -0
- package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +1 -0
- package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +1 -0
- package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +1 -0
- package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +1 -0
- package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +1271 -0
- package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +1570 -0
- package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +2590 -0
- package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +3 -0
- package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +3299 -0
- package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +3299 -0
- package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +1882 -0
- package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +2594 -0
- package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +1 -0
- package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +170 -0
- package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +1 -0
- package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +3 -0
- package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +1 -0
- package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +1 -0
- package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +3 -0
- package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +1 -0
- package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +1 -0
- package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +5 -0
- package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +1 -0
- package/.github/workflows/ci-tests.yml +6 -2
- package/.github/workflows/publish.yml +3 -3
- package/.github/workflows/release.yml +4 -0
- package/CHANGELOG.md +89 -1
- package/NUL +1 -1
- package/README.md +36 -10
- package/dist/bundle/index.cjs +65 -42
- package/dist/index.js +9 -20
- package/dist/server/YNABMCPServer.d.ts +2 -1
- package/dist/server/YNABMCPServer.js +61 -27
- package/dist/server/cacheKeys.d.ts +8 -0
- package/dist/server/cacheKeys.js +8 -0
- package/dist/server/config.d.ts +22 -3
- package/dist/server/config.js +16 -17
- package/dist/server/errorHandler.d.ts +2 -0
- package/dist/server/errorHandler.js +49 -5
- package/dist/server/securityMiddleware.js +3 -6
- package/dist/server/toolRegistry.js +8 -10
- package/dist/tools/accountTools.js +4 -3
- package/dist/tools/categoryTools.js +8 -7
- package/dist/tools/monthTools.js +2 -1
- package/dist/tools/payeeTools.js +2 -1
- package/dist/tools/reconcileAdapter.js +10 -5
- package/dist/tools/reconciliation/analyzer.d.ts +4 -2
- package/dist/tools/reconciliation/analyzer.js +120 -404
- package/dist/tools/reconciliation/csvParser.d.ts +51 -0
- package/dist/tools/reconciliation/csvParser.js +413 -0
- package/dist/tools/reconciliation/executor.d.ts +8 -0
- package/dist/tools/reconciliation/executor.js +277 -50
- package/dist/tools/reconciliation/index.d.ts +7 -7
- package/dist/tools/reconciliation/index.js +115 -39
- package/dist/tools/reconciliation/matcher.d.ts +24 -3
- package/dist/tools/reconciliation/matcher.js +175 -133
- package/dist/tools/reconciliation/recommendationEngine.js +22 -18
- package/dist/tools/reconciliation/reportFormatter.js +9 -8
- package/dist/tools/reconciliation/signDetector.d.ts +2 -0
- package/dist/tools/reconciliation/signDetector.js +54 -0
- package/dist/tools/reconciliation/types.d.ts +20 -34
- package/dist/tools/reconciliation/types.js +1 -7
- package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
- package/dist/tools/reconciliation/ynabAdapter.js +15 -0
- package/dist/tools/transactionTools.d.ts +3 -17
- package/dist/tools/transactionTools.js +5 -17
- package/dist/types/reconciliation.d.ts +24 -0
- package/dist/types/reconciliation.js +1 -0
- package/dist/utils/baseError.d.ts +3 -0
- package/dist/utils/baseError.js +7 -0
- package/dist/utils/errors.d.ts +13 -0
- package/dist/utils/errors.js +15 -0
- package/dist/utils/validationError.d.ts +3 -0
- package/dist/utils/validationError.js +3 -0
- package/docs/guides/ARCHITECTURE.md +12 -129
- package/docs/plans/2025-11-20-reloadable-config-token-validation.md +93 -0
- package/docs/plans/2025-11-21-fix-transaction-cached-property.md +362 -0
- package/docs/plans/2025-11-21-reconciliation-error-handling.md +90 -0
- package/docs/plans/2025-11-21-v014-hardening.md +153 -0
- package/docs/plans/reconciliation-v2-redesign.md +1571 -0
- package/package.json +8 -2
- package/scripts/run-throttled-integration-tests.js +9 -3
- package/scripts/test-recommendations.ts +1 -1
- package/src/__tests__/performance.test.ts +12 -5
- package/src/__tests__/testUtils.ts +62 -5
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
- package/src/__tests__/workflows.e2e.test.ts +33 -0
- package/src/index.ts +8 -31
- package/src/server/YNABMCPServer.ts +81 -42
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +10 -12
- package/src/server/__tests__/YNABMCPServer.test.ts +27 -15
- package/src/server/__tests__/config.test.ts +76 -152
- package/src/server/__tests__/server-startup.integration.test.ts +42 -14
- package/src/server/__tests__/toolRegistry.test.ts +1 -1
- package/src/server/cacheKeys.ts +8 -0
- package/src/server/config.ts +20 -38
- package/src/server/errorHandler.ts +52 -5
- package/src/server/securityMiddleware.ts +3 -7
- package/src/server/toolRegistry.ts +14 -10
- package/src/tools/__tests__/categoryTools.test.ts +37 -19
- package/src/tools/__tests__/transactionTools.test.ts +58 -2
- package/src/tools/accountTools.ts +8 -3
- package/src/tools/categoryTools.ts +12 -7
- package/src/tools/monthTools.ts +7 -1
- package/src/tools/payeeTools.ts +7 -1
- package/src/tools/reconcileAdapter.ts +10 -5
- package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +26 -6
- package/src/tools/reconciliation/__tests__/executor.test.ts +133 -60
- package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
- package/src/tools/reconciliation/analyzer.ts +174 -545
- package/src/tools/reconciliation/csvParser.ts +617 -0
- package/src/tools/reconciliation/executor.ts +344 -58
- package/src/tools/reconciliation/index.ts +141 -48
- package/src/tools/reconciliation/matcher.ts +234 -214
- package/src/tools/reconciliation/recommendationEngine.ts +23 -19
- package/src/tools/reconciliation/reportFormatter.ts +16 -11
- package/src/tools/reconciliation/signDetector.ts +117 -0
- package/src/tools/reconciliation/types.ts +39 -61
- package/src/tools/reconciliation/ynabAdapter.ts +33 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +1 -1
- package/src/tools/transactionTools.ts +7 -18
- package/src/types/reconciliation.ts +49 -0
- package/src/utils/baseError.ts +7 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/validationError.ts +3 -0
- package/temp-recon.ts +126 -0
- package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
- package/test_mcp_tools.mjs +75 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
- package/.gemini/settings.json +0 -8
- package/ADOS-2-Module-1-Complete-Manual.md +0 -757
- package/WARP.md +0 -245
|
@@ -16,12 +16,11 @@ import * as ynab from 'ynab';
|
|
|
16
16
|
import {
|
|
17
17
|
AuthenticationError,
|
|
18
18
|
ConfigurationError,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from '
|
|
24
|
-
import { createErrorHandler } from './errorHandler.js';
|
|
19
|
+
ValidationError as ConfigValidationError,
|
|
20
|
+
} from '../utils/errors.js';
|
|
21
|
+
import { YNABErrorCode, ValidationError } from '../types/index.js';
|
|
22
|
+
import { loadConfig, type AppConfig } from './config.js';
|
|
23
|
+
import { createErrorHandler, ErrorHandler } from './errorHandler.js';
|
|
25
24
|
import { BudgetResolver } from './budgetResolver.js';
|
|
26
25
|
import { SecurityMiddleware, withSecurityWrapper } from './securityMiddleware.js';
|
|
27
26
|
import { handleListBudgets, handleGetBudget, GetBudgetSchema } from '../tools/budgetTools.js';
|
|
@@ -87,7 +86,6 @@ import {
|
|
|
87
86
|
type DefaultArgumentResolver,
|
|
88
87
|
type ToolExecutionPayload,
|
|
89
88
|
} from './toolRegistry.js';
|
|
90
|
-
import { validateEnvironment } from './config.js';
|
|
91
89
|
import { ResourceManager } from './resources.js';
|
|
92
90
|
import { PromptManager } from './prompts.js';
|
|
93
91
|
import { DiagnosticManager } from './diagnostics.js';
|
|
@@ -107,25 +105,15 @@ import {
|
|
|
107
105
|
ListBudgetsOutputSchema,
|
|
108
106
|
ListAccountsOutputSchema,
|
|
109
107
|
GetAccountOutputSchema,
|
|
110
|
-
CreateAccountOutputSchema,
|
|
111
|
-
ListTransactionsOutputSchema,
|
|
112
108
|
GetTransactionOutputSchema,
|
|
113
109
|
ExportTransactionsOutputSchema,
|
|
114
110
|
CompareTransactionsOutputSchema,
|
|
115
|
-
CreateTransactionOutputSchema,
|
|
116
|
-
CreateTransactionsOutputSchema,
|
|
117
|
-
UpdateTransactionOutputSchema,
|
|
118
|
-
UpdateTransactionsOutputSchema,
|
|
119
|
-
DeleteTransactionOutputSchema,
|
|
120
|
-
CreateReceiptSplitTransactionOutputSchema,
|
|
121
111
|
ListCategoriesOutputSchema,
|
|
122
112
|
GetCategoryOutputSchema,
|
|
123
|
-
UpdateCategoryOutputSchema,
|
|
124
113
|
ListPayeesOutputSchema,
|
|
125
114
|
GetPayeeOutputSchema,
|
|
126
115
|
GetMonthOutputSchema,
|
|
127
116
|
ListMonthsOutputSchema,
|
|
128
|
-
ReconcileAccountOutputSchema,
|
|
129
117
|
} from '../tools/schemas/outputs/index.js';
|
|
130
118
|
|
|
131
119
|
/**
|
|
@@ -134,9 +122,9 @@ import {
|
|
|
134
122
|
export class YNABMCPServer {
|
|
135
123
|
private server: Server;
|
|
136
124
|
private ynabAPI: ynab.API;
|
|
137
|
-
private config: ServerConfig;
|
|
138
125
|
private exitOnError: boolean;
|
|
139
126
|
private defaultBudgetId: string | undefined;
|
|
127
|
+
private configInstance: AppConfig;
|
|
140
128
|
private serverVersion: string;
|
|
141
129
|
private toolRegistry: ToolRegistry;
|
|
142
130
|
private resourceManager: ResourceManager;
|
|
@@ -149,14 +137,12 @@ export class YNABMCPServer {
|
|
|
149
137
|
|
|
150
138
|
constructor(exitOnError: boolean = true) {
|
|
151
139
|
this.exitOnError = exitOnError;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.defaultBudgetId = this.config.defaultBudgetId;
|
|
156
|
-
}
|
|
140
|
+
this.configInstance = loadConfig();
|
|
141
|
+
// Config is now imported and validated at startup
|
|
142
|
+
this.defaultBudgetId = process.env['YNAB_DEFAULT_BUDGET_ID'];
|
|
157
143
|
|
|
158
144
|
// Initialize YNAB API
|
|
159
|
-
this.ynabAPI = new ynab.API(this.
|
|
145
|
+
this.ynabAPI = new ynab.API(this.configInstance.YNAB_ACCESS_TOKEN);
|
|
160
146
|
|
|
161
147
|
// Determine server version (prefer package.json)
|
|
162
148
|
this.serverVersion = this.readPackageVersion() ?? '0.0.0';
|
|
@@ -169,9 +155,9 @@ export class YNABMCPServer {
|
|
|
169
155
|
},
|
|
170
156
|
{
|
|
171
157
|
capabilities: {
|
|
172
|
-
tools: {},
|
|
173
|
-
resources: {},
|
|
174
|
-
prompts: {},
|
|
158
|
+
tools: { listChanged: true },
|
|
159
|
+
resources: { listChanged: true },
|
|
160
|
+
prompts: { listChanged: true },
|
|
175
161
|
},
|
|
176
162
|
},
|
|
177
163
|
);
|
|
@@ -217,7 +203,7 @@ export class YNABMCPServer {
|
|
|
217
203
|
},
|
|
218
204
|
},
|
|
219
205
|
validateAccessToken: (token: string) => {
|
|
220
|
-
const expected = this.
|
|
206
|
+
const expected = this.configInstance.YNAB_ACCESS_TOKEN.trim();
|
|
221
207
|
const provided = typeof token === 'string' ? token.trim() : '';
|
|
222
208
|
if (!provided) {
|
|
223
209
|
throw this.errorHandler.createYNABError(
|
|
@@ -269,6 +255,10 @@ export class YNABMCPServer {
|
|
|
269
255
|
await this.ynabAPI.user.getUser();
|
|
270
256
|
return true;
|
|
271
257
|
} catch (error) {
|
|
258
|
+
if (this.isMalformedTokenResponse(error)) {
|
|
259
|
+
throw new AuthenticationError('Unexpected response from YNAB during token validation');
|
|
260
|
+
}
|
|
261
|
+
|
|
272
262
|
if (error instanceof Error) {
|
|
273
263
|
// Check for authentication-related errors
|
|
274
264
|
if (error.message.includes('401') || error.message.includes('Unauthorized')) {
|
|
@@ -277,9 +267,37 @@ export class YNABMCPServer {
|
|
|
277
267
|
if (error.message.includes('403') || error.message.includes('Forbidden')) {
|
|
278
268
|
throw new AuthenticationError('YNAB access token has insufficient permissions');
|
|
279
269
|
}
|
|
270
|
+
|
|
271
|
+
const reason = error.message || String(error);
|
|
272
|
+
throw new AuthenticationError(`Token validation failed: ${reason}`);
|
|
280
273
|
}
|
|
281
|
-
|
|
274
|
+
|
|
275
|
+
throw new AuthenticationError(`Token validation failed: ${String(error)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private isMalformedTokenResponse(error: unknown): boolean {
|
|
280
|
+
if (error instanceof SyntaxError) {
|
|
281
|
+
return true;
|
|
282
282
|
}
|
|
283
|
+
|
|
284
|
+
const message =
|
|
285
|
+
typeof error === 'string'
|
|
286
|
+
? error
|
|
287
|
+
: typeof (error as { message?: unknown })?.message === 'string'
|
|
288
|
+
? String((error as { message: unknown }).message)
|
|
289
|
+
: null;
|
|
290
|
+
|
|
291
|
+
if (!message) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const normalized = message.toLowerCase();
|
|
296
|
+
return (
|
|
297
|
+
normalized.includes('unexpected token') ||
|
|
298
|
+
normalized.includes('unexpected end of json') ||
|
|
299
|
+
normalized.includes('<html')
|
|
300
|
+
);
|
|
283
301
|
}
|
|
284
302
|
|
|
285
303
|
/**
|
|
@@ -345,7 +363,7 @@ export class YNABMCPServer {
|
|
|
345
363
|
minifyOverride?: boolean;
|
|
346
364
|
} = {
|
|
347
365
|
name: request.params.name,
|
|
348
|
-
accessToken: this.
|
|
366
|
+
accessToken: this.configInstance.YNAB_ACCESS_TOKEN,
|
|
349
367
|
arguments: sanitizedArgs ?? {},
|
|
350
368
|
};
|
|
351
369
|
|
|
@@ -436,6 +454,8 @@ export class YNABMCPServer {
|
|
|
436
454
|
pretty_spaces: z.number().int().min(0).max(10).optional(),
|
|
437
455
|
})
|
|
438
456
|
.strict();
|
|
457
|
+
// Permissive object schema used where hosts require a top-level object type
|
|
458
|
+
const LooseObjectSchema = z.object({}).passthrough();
|
|
439
459
|
|
|
440
460
|
register({
|
|
441
461
|
name: 'list_budgets',
|
|
@@ -576,7 +596,7 @@ export class YNABMCPServer {
|
|
|
576
596
|
name: 'create_account',
|
|
577
597
|
description: 'Create a new account in the specified budget',
|
|
578
598
|
inputSchema: CreateAccountSchema,
|
|
579
|
-
outputSchema:
|
|
599
|
+
outputSchema: LooseObjectSchema,
|
|
580
600
|
handler: adaptWrite(handleCreateAccount),
|
|
581
601
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateAccountSchema>>(),
|
|
582
602
|
metadata: {
|
|
@@ -591,7 +611,7 @@ export class YNABMCPServer {
|
|
|
591
611
|
name: 'list_transactions',
|
|
592
612
|
description: 'List transactions for a budget with optional filtering',
|
|
593
613
|
inputSchema: ListTransactionsSchema,
|
|
594
|
-
outputSchema:
|
|
614
|
+
outputSchema: LooseObjectSchema,
|
|
595
615
|
handler: adaptWithDelta(handleListTransactions),
|
|
596
616
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListTransactionsSchema>>(),
|
|
597
617
|
metadata: {
|
|
@@ -638,7 +658,7 @@ export class YNABMCPServer {
|
|
|
638
658
|
description:
|
|
639
659
|
'Guided reconciliation workflow with human narrative, insight detection, and optional execution (create/update/unclear). Set include_structured_data=true to also get full JSON output (large).',
|
|
640
660
|
inputSchema: ReconcileAccountSchema,
|
|
641
|
-
outputSchema:
|
|
661
|
+
outputSchema: LooseObjectSchema,
|
|
642
662
|
handler: adaptWithDelta(handleReconcileAccount),
|
|
643
663
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ReconcileAccountSchema>>(),
|
|
644
664
|
metadata: {
|
|
@@ -668,7 +688,7 @@ export class YNABMCPServer {
|
|
|
668
688
|
name: 'create_transaction',
|
|
669
689
|
description: 'Create a new transaction in the specified budget and account',
|
|
670
690
|
inputSchema: CreateTransactionSchema,
|
|
671
|
-
outputSchema:
|
|
691
|
+
outputSchema: LooseObjectSchema,
|
|
672
692
|
handler: adaptWrite(handleCreateTransaction),
|
|
673
693
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateTransactionSchema>>(),
|
|
674
694
|
metadata: {
|
|
@@ -684,7 +704,7 @@ export class YNABMCPServer {
|
|
|
684
704
|
description:
|
|
685
705
|
'Create multiple transactions in a single batch (1-100 items) with duplicate detection, dry-run validation, and automatic response size management with correlation metadata.',
|
|
686
706
|
inputSchema: CreateTransactionsSchema,
|
|
687
|
-
outputSchema:
|
|
707
|
+
outputSchema: LooseObjectSchema,
|
|
688
708
|
handler: adaptWrite(handleCreateTransactions),
|
|
689
709
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateTransactionsSchema>>(),
|
|
690
710
|
metadata: {
|
|
@@ -700,7 +720,7 @@ export class YNABMCPServer {
|
|
|
700
720
|
description:
|
|
701
721
|
'Update multiple transactions in a single batch (1-100 items) with dry-run validation, automatic cache invalidation, and response size management. Supports optional original_account_id and original_date metadata for efficient cache invalidation.',
|
|
702
722
|
inputSchema: UpdateTransactionsSchema,
|
|
703
|
-
outputSchema:
|
|
723
|
+
outputSchema: LooseObjectSchema,
|
|
704
724
|
handler: adaptWrite(handleUpdateTransactions),
|
|
705
725
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateTransactionsSchema>>(),
|
|
706
726
|
metadata: {
|
|
@@ -715,7 +735,7 @@ export class YNABMCPServer {
|
|
|
715
735
|
name: 'create_receipt_split_transaction',
|
|
716
736
|
description: 'Create a split transaction from receipt items with proportional tax allocation',
|
|
717
737
|
inputSchema: CreateReceiptSplitTransactionSchema,
|
|
718
|
-
outputSchema:
|
|
738
|
+
outputSchema: LooseObjectSchema,
|
|
719
739
|
handler: adaptWrite(handleCreateReceiptSplitTransaction),
|
|
720
740
|
defaultArgumentResolver:
|
|
721
741
|
resolveBudgetId<z.infer<typeof CreateReceiptSplitTransactionSchema>>(),
|
|
@@ -731,7 +751,7 @@ export class YNABMCPServer {
|
|
|
731
751
|
name: 'update_transaction',
|
|
732
752
|
description: 'Update an existing transaction',
|
|
733
753
|
inputSchema: UpdateTransactionSchema,
|
|
734
|
-
outputSchema:
|
|
754
|
+
outputSchema: LooseObjectSchema,
|
|
735
755
|
handler: adaptWrite(handleUpdateTransaction),
|
|
736
756
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateTransactionSchema>>(),
|
|
737
757
|
metadata: {
|
|
@@ -746,7 +766,7 @@ export class YNABMCPServer {
|
|
|
746
766
|
name: 'delete_transaction',
|
|
747
767
|
description: 'Delete a transaction from the specified budget',
|
|
748
768
|
inputSchema: DeleteTransactionSchema,
|
|
749
|
-
outputSchema:
|
|
769
|
+
outputSchema: LooseObjectSchema,
|
|
750
770
|
handler: adaptWrite(handleDeleteTransaction),
|
|
751
771
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof DeleteTransactionSchema>>(),
|
|
752
772
|
metadata: {
|
|
@@ -791,7 +811,7 @@ export class YNABMCPServer {
|
|
|
791
811
|
name: 'update_category',
|
|
792
812
|
description: 'Update the budgeted amount for a category in the current month',
|
|
793
813
|
inputSchema: UpdateCategorySchema,
|
|
794
|
-
outputSchema:
|
|
814
|
+
outputSchema: LooseObjectSchema,
|
|
795
815
|
handler: adaptWrite(handleUpdateCategory),
|
|
796
816
|
defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateCategorySchema>>(),
|
|
797
817
|
metadata: {
|
|
@@ -999,8 +1019,14 @@ export class YNABMCPServer {
|
|
|
999
1019
|
|
|
1000
1020
|
console.error('YNAB MCP Server started successfully');
|
|
1001
1021
|
} catch (error) {
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
1022
|
+
if (
|
|
1023
|
+
error instanceof AuthenticationError ||
|
|
1024
|
+
error instanceof ConfigurationError ||
|
|
1025
|
+
error instanceof ConfigValidationError ||
|
|
1026
|
+
error instanceof ValidationError ||
|
|
1027
|
+
(error as { name?: string })?.name === 'ValidationError'
|
|
1028
|
+
) {
|
|
1029
|
+
console.error(`Server startup failed: ${error instanceof Error ? error.message : error}`);
|
|
1004
1030
|
if (this.exitOnError) {
|
|
1005
1031
|
process.exit(1);
|
|
1006
1032
|
} else {
|
|
@@ -1172,6 +1198,19 @@ export class YNABMCPServer {
|
|
|
1172
1198
|
} catch {
|
|
1173
1199
|
// ignore
|
|
1174
1200
|
}
|
|
1201
|
+
try {
|
|
1202
|
+
// CJS bundles can rely on __dirname being defined; add nearby package.json fallbacks
|
|
1203
|
+
const dir = typeof __dirname === 'string' ? __dirname : undefined;
|
|
1204
|
+
if (dir) {
|
|
1205
|
+
candidates.push(
|
|
1206
|
+
path.resolve(dir, '../../package.json'),
|
|
1207
|
+
path.resolve(dir, '../package.json'),
|
|
1208
|
+
path.resolve(dir, 'package.json'),
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
// ignore additional fallbacks
|
|
1213
|
+
}
|
|
1175
1214
|
for (const p of candidates) {
|
|
1176
1215
|
try {
|
|
1177
1216
|
if (fs.existsSync(p)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { YNABMCPServer } from '../YNABMCPServer.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ValidationError } from '../../types/index.js';
|
|
4
4
|
import { ToolRegistry } from '../toolRegistry.js';
|
|
5
5
|
import { cacheManager } from '../../server/cacheManager.js';
|
|
6
6
|
import { responseFormatter } from '../../server/responseFormatter.js';
|
|
@@ -42,16 +42,13 @@ describeIntegration('YNABMCPServer', () => {
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
it(
|
|
45
|
-
'should throw
|
|
45
|
+
'should throw ValidationError when YNAB_ACCESS_TOKEN is missing',
|
|
46
46
|
{ meta: { tier: 'domain', domain: 'server' } },
|
|
47
47
|
() => {
|
|
48
48
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
49
49
|
delete process.env['YNAB_ACCESS_TOKEN'];
|
|
50
50
|
|
|
51
|
-
expect(() => new YNABMCPServer()).toThrow(
|
|
52
|
-
expect(() => new YNABMCPServer()).toThrow(
|
|
53
|
-
'YNAB_ACCESS_TOKEN environment variable is required but not set',
|
|
54
|
-
);
|
|
51
|
+
expect(() => new YNABMCPServer()).toThrow(/YNAB_ACCESS_TOKEN/i);
|
|
55
52
|
|
|
56
53
|
// Restore token
|
|
57
54
|
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|
|
@@ -59,13 +56,12 @@ describeIntegration('YNABMCPServer', () => {
|
|
|
59
56
|
);
|
|
60
57
|
|
|
61
58
|
it(
|
|
62
|
-
'should throw
|
|
59
|
+
'should throw ValidationError when YNAB_ACCESS_TOKEN is empty string',
|
|
63
60
|
{ meta: { tier: 'domain', domain: 'server' } },
|
|
64
61
|
() => {
|
|
65
62
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
66
63
|
process.env['YNAB_ACCESS_TOKEN'] = '';
|
|
67
64
|
|
|
68
|
-
expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
|
|
69
65
|
expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
|
|
70
66
|
|
|
71
67
|
// Restore token
|
|
@@ -74,13 +70,12 @@ describeIntegration('YNABMCPServer', () => {
|
|
|
74
70
|
);
|
|
75
71
|
|
|
76
72
|
it(
|
|
77
|
-
'should throw
|
|
73
|
+
'should throw ValidationError when YNAB_ACCESS_TOKEN is only whitespace',
|
|
78
74
|
{ meta: { tier: 'domain', domain: 'server' } },
|
|
79
75
|
() => {
|
|
80
76
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
81
77
|
process.env['YNAB_ACCESS_TOKEN'] = ' ';
|
|
82
78
|
|
|
83
|
-
expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
|
|
84
79
|
expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
|
|
85
80
|
|
|
86
81
|
// Restore token
|
|
@@ -167,7 +162,10 @@ describeIntegration('YNABMCPServer', () => {
|
|
|
167
162
|
|
|
168
163
|
try {
|
|
169
164
|
const invalidServer = new YNABMCPServer(false);
|
|
170
|
-
await expect(invalidServer.validateToken()).rejects.
|
|
165
|
+
await expect(invalidServer.validateToken()).rejects.toHaveProperty(
|
|
166
|
+
'name',
|
|
167
|
+
'AuthenticationError',
|
|
168
|
+
);
|
|
171
169
|
} finally {
|
|
172
170
|
// Restore original token
|
|
173
171
|
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|
|
@@ -200,7 +198,7 @@ describeIntegration('YNABMCPServer', () => {
|
|
|
200
198
|
} catch (error) {
|
|
201
199
|
// Expected to fail on stdio connection in test environment
|
|
202
200
|
// Token was already validated above, so this error should be transport-related
|
|
203
|
-
expect(error).not.toBeInstanceOf(
|
|
201
|
+
expect(error).not.toBeInstanceOf(ValidationError);
|
|
204
202
|
}
|
|
205
203
|
|
|
206
204
|
consoleSpy.mockRestore();
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } from 'vite
|
|
|
2
2
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
|
|
4
4
|
import { YNABMCPServer } from '../YNABMCPServer.js';
|
|
5
|
-
import { AuthenticationError,
|
|
5
|
+
import { AuthenticationError, ValidationError } from '../../types/index.js';
|
|
6
6
|
import { ToolRegistry } from '../toolRegistry.js';
|
|
7
7
|
import { cacheManager } from '../../server/cacheManager.js';
|
|
8
8
|
import { responseFormatter } from '../../server/responseFormatter.js';
|
|
@@ -80,41 +80,56 @@ describe('YNABMCPServer', () => {
|
|
|
80
80
|
expect(server.getYNABAPI()).toBeDefined();
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it('should throw
|
|
83
|
+
it('should throw ValidationError when YNAB_ACCESS_TOKEN is missing', () => {
|
|
84
84
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
85
85
|
delete process.env['YNAB_ACCESS_TOKEN'];
|
|
86
86
|
|
|
87
|
-
expect(() => new YNABMCPServer()).toThrow(
|
|
88
|
-
expect(() => new YNABMCPServer()).toThrow(
|
|
89
|
-
'YNAB_ACCESS_TOKEN environment variable is required but not set',
|
|
90
|
-
);
|
|
87
|
+
expect(() => new YNABMCPServer()).toThrow(/YNAB_ACCESS_TOKEN/i);
|
|
91
88
|
|
|
92
89
|
// Restore token
|
|
93
90
|
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|
|
94
91
|
});
|
|
95
92
|
|
|
96
|
-
it('should throw
|
|
93
|
+
it('should throw ValidationError when YNAB_ACCESS_TOKEN is empty string', () => {
|
|
97
94
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
98
95
|
process.env['YNAB_ACCESS_TOKEN'] = '';
|
|
99
96
|
|
|
100
|
-
expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
|
|
101
97
|
expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
|
|
102
98
|
|
|
103
99
|
// Restore token
|
|
104
100
|
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|
|
105
101
|
});
|
|
106
102
|
|
|
107
|
-
it('should throw
|
|
103
|
+
it('should throw ValidationError when YNAB_ACCESS_TOKEN is only whitespace', () => {
|
|
108
104
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
109
105
|
process.env['YNAB_ACCESS_TOKEN'] = ' ';
|
|
110
106
|
|
|
111
|
-
expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
|
|
112
107
|
expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
|
|
113
108
|
|
|
114
109
|
// Restore token
|
|
115
110
|
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|
|
116
111
|
});
|
|
117
112
|
|
|
113
|
+
it('should reload configuration for each server instance', () => {
|
|
114
|
+
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
115
|
+
|
|
116
|
+
process.env['YNAB_ACCESS_TOKEN'] = 'token-one';
|
|
117
|
+
const firstServer = new YNABMCPServer(false);
|
|
118
|
+
const firstConfig = (
|
|
119
|
+
firstServer as unknown as { configInstance: { YNAB_ACCESS_TOKEN: string } }
|
|
120
|
+
).configInstance;
|
|
121
|
+
expect(firstConfig.YNAB_ACCESS_TOKEN).toBe('token-one');
|
|
122
|
+
|
|
123
|
+
process.env['YNAB_ACCESS_TOKEN'] = 'token-two';
|
|
124
|
+
const secondServer = new YNABMCPServer(false);
|
|
125
|
+
const secondConfig = (
|
|
126
|
+
secondServer as unknown as { configInstance: { YNAB_ACCESS_TOKEN: string } }
|
|
127
|
+
).configInstance;
|
|
128
|
+
expect(secondConfig.YNAB_ACCESS_TOKEN).toBe('token-two');
|
|
129
|
+
|
|
130
|
+
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|
|
131
|
+
});
|
|
132
|
+
|
|
118
133
|
it('should trim whitespace from access token', () => {
|
|
119
134
|
const originalToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
120
135
|
process.env['YNAB_ACCESS_TOKEN'] = ` ${originalToken} `;
|
|
@@ -193,7 +208,7 @@ describe('YNABMCPServer', () => {
|
|
|
193
208
|
// Expected to fail on stdio connection in test environment
|
|
194
209
|
// But should not fail on token validation
|
|
195
210
|
expect(error).not.toBeInstanceOf(AuthenticationError);
|
|
196
|
-
expect(error).not.toBeInstanceOf(
|
|
211
|
+
expect(error).not.toBeInstanceOf(ValidationError);
|
|
197
212
|
}
|
|
198
213
|
|
|
199
214
|
consoleSpy.mockRestore();
|
|
@@ -739,10 +754,7 @@ describe('YNABMCPServer', () => {
|
|
|
739
754
|
delete process.env['YNAB_ACCESS_TOKEN'];
|
|
740
755
|
|
|
741
756
|
try {
|
|
742
|
-
expect(() => new YNABMCPServer()).toThrow(
|
|
743
|
-
expect(() => new YNABMCPServer()).toThrow(
|
|
744
|
-
'YNAB_ACCESS_TOKEN environment variable is required but not set',
|
|
745
|
-
);
|
|
757
|
+
expect(() => new YNABMCPServer()).toThrow(/YNAB_ACCESS_TOKEN/i);
|
|
746
758
|
} finally {
|
|
747
759
|
// Restore token
|
|
748
760
|
process.env['YNAB_ACCESS_TOKEN'] = originalToken;
|