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