Commite3052172Recorded30 Mar 2026Repositorytally
Fix MCP crash, add error handling, rename tools and CLI command
Message
Root cause: sigil-wise was missing (sigil array) import for array-map, causing unbound variable crash during API calls. Fixed in sigil-wise.
Additional changes: - Add guard/catch error handling to tool-pull, tool-import, tool-balance, and tool-report so API failures return MCP error responses with descriptive messages instead of crashing the server - Rename MCP tools: drop tally/ prefix and use descriptive names (fetch-transactions, import-transactions, wise-balances, journal-report, uncategorized-transactions) - Rename CLI pull command to fetch for consistency - Add --log/--log-level options to serve command - Load .env-tally at startup via load-env-file!
Changed
src/tally/main.sgl | 20 +++++++++++++++-----
src/tally/tools.sgl | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------
2 files changed, 70 insertions(+), 47 deletions(-)Diff
src/tally/main.sglmodified
@@ -8,6 +8,8 @@
8
(sigil string) 9
(sigil process) 10
(sigil args)+11
(sigil env)+12
(sigil log) 13
(sigil mcp server) 14
(wise) 15
(ledger)@@ -23,7 +25,7 @@
25
(register-tally-tools! server config) 26
(mcp-server-run server))) 27
−26
(define (cli-pull config opts since-default until-default)+28
(define (cli-fetch config opts since-default until-default) 29
(let* ((client (require-wise-client config)) 30
(since (or (alist-get 'since opts) since-default)) 31
(until (or (alist-get 'until opts) until-default))@@ -136,14 +138,21 @@
138
(command 139
name: "serve" 140
description: "Start MCP server"−139
handler: (lambda (opts args) (run-server config)))+141
options: (list+142
(option name: 'log long: "log" value: "FILE"+143
description: "Log output file path")+144
(option name: 'log-level long: "log-level" value: "LEVEL"+145
description: "Log level (trace, debug, info, warn, error, fatal)"))+146
handler: (lambda (opts args)+147
(log-configure-from-args!)+148
(run-server config))) 149
150
(command−142
name: "pull"−143
description: "Pull transactions from Wise"+151
name: "fetch"+152
description: "Fetch transactions from Wise" 153
options: (list (since-option) (until-option) (profile-option)) 154
handler: (lambda (opts args)−146
(cli-pull config opts since-default until-default)))+155
(cli-fetch config opts since-default until-default))) 156
157
(command 158
name: "import"@@ -193,6 +202,7 @@
202
;; ============================================================ 203
204
(define (main)+205
(load-env-file! ".env-tally") 206
(let* ((config (load-tally-config)) 207
(since-default (default-since-date)) 208
(until-default (default-until-date))src/tally/tools.sglmodified
@@ -10,6 +10,7 @@
10
(sigil struct) 11
(sigil json) 12
(sigil time)+13
(sigil error) 14
(sigil mcp server) 15
(wise) 16
(ledger)@@ -95,49 +96,61 @@
96
;; MCP tool handlers 97
98
(define (tool-pull config params default-since default-until)−98
(let* ((client (require-wise-client config))−99
(since (dict-ref params since: default-since))−100
(until (dict-ref params until: default-until))−101
(profile-type (dict-ref params profile:−102
(tally-config-profile-type config)))−103
(profile-id (resolve-profile-id client profile-type))−104
(txns (tally-pull-transactions client profile-id since until))−105
(count (length txns)))−106
(string-append−107
"Pulled " (number->string count) " transactions.\n\n"−108
(if (= count 0) ""−109
(string-join (map format-transaction-summary txns) "\n")))))+99
(guard (exn (else+100
(error (string-append "Failed to fetch transactions: "+101
(format-exception exn)))))+102
(let* ((client (require-wise-client config))+103
(since (dict-ref params since: default-since))+104
(until (dict-ref params until: default-until))+105
(profile-type (dict-ref params profile:+106
(tally-config-profile-type config)))+107
(profile-id (resolve-profile-id client profile-type))+108
(txns (tally-pull-transactions client profile-id since until))+109
(count (length txns)))+110
(string-append+111
"Pulled " (number->string count) " transactions.\n\n"+112
(if (= count 0) ""+113
(string-join (map format-transaction-summary txns) "\n")))))) 114
115
(define (tool-import config params default-since default-until)−112
(let* ((client (require-wise-client config))−113
(journal (dict-ref params journal:−114
(tally-config-journal-path config)))−115
(since (dict-ref params since: default-since))−116
(until (dict-ref params until: default-until))−117
(profile-type (dict-ref params profile:−118
(tally-config-profile-type config)))−119
(profile-id (resolve-profile-id client profile-type))−120
(count (tally-import client profile-id since until journal)))−121
(string-append−122
"Imported " (number->string count) " new transactions to " journal ".")))+116
(guard (exn (else+117
(error (string-append "Failed to import transactions: "+118
(format-exception exn)))))+119
(let* ((client (require-wise-client config))+120
(journal (dict-ref params journal:+121
(tally-config-journal-path config)))+122
(since (dict-ref params since: default-since))+123
(until (dict-ref params until: default-until))+124
(profile-type (dict-ref params profile:+125
(tally-config-profile-type config)))+126
(profile-id (resolve-profile-id client profile-type))+127
(count (tally-import client profile-id since until journal)))+128
(string-append+129
"Imported " (number->string count) " new transactions to " journal ".")))) 130
131
(define (tool-balance config params)−125
(let* ((client (require-wise-client config))−126
(profile-type (dict-ref params profile:−127
(tally-config-profile-type config)))−128
(profile-id (resolve-profile-id client profile-type))−129
(balances (wise-balances client profile-id)))−130
(if (null? balances)−131
"No balances found."−132
(string-join (map format-balance-summary balances) "\n"))))+132
(guard (exn (else+133
(error (string-append "Failed to fetch balances: "+134
(format-exception exn)))))+135
(let* ((client (require-wise-client config))+136
(profile-type (dict-ref params profile:+137
(tally-config-profile-type config)))+138
(profile-id (resolve-profile-id client profile-type))+139
(balances (wise-balances client profile-id)))+140
(if (null? balances)+141
"No balances found."+142
(string-join (map format-balance-summary balances) "\n"))))) 143
144
(define (tool-report config params)−135
(let* ((journal (dict-ref params journal:−136
(tally-config-journal-path config)))−137
(report-type (dict-ref params type: "bal"))−138
(query (dict-ref params query: #f))−139
(args (if query (list query) '())))−140
(run-hledger-report report-type journal args)))+145
(guard (exn (else+146
(error (string-append "Failed to run report: "+147
(format-exception exn)))))+148
(let* ((journal (dict-ref params journal:+149
(tally-config-journal-path config)))+150
(report-type (dict-ref params type: "bal"))+151
(query (dict-ref params query: #f))+152
(args (if query (list query) '())))+153
(run-hledger-report report-type journal args)))) 154
155
(define (tool-categorize config params) 156
(let* ((journal-path (dict-ref params journal:@@ -174,7 +187,7 @@
187
(until-default (default-until-date))) 188
189
(mcp-server-register-tool! server−177
"tally/pull"+190
"fetch-transactions" 191
"Pull recent transactions from Wise API. Returns a summary of transactions found." 192
#{ type: "object" 193
properties: #{@@ -187,7 +200,7 @@
200
(lambda (params) (tool-pull config params since-default until-default))) 201
202
(mcp-server-register-tool! server−190
"tally/import"+203
"import-transactions" 204
"Pull transactions from Wise and import new ones into the hledger journal file. Deduplicates automatically." 205
#{ type: "object" 206
properties: #{@@ -202,7 +215,7 @@
215
(lambda (params) (tool-import config params since-default until-default))) 216
217
(mcp-server-register-tool! server−205
"tally/balance"+218
"wise-balances" 219
"Show current Wise account balances across all currencies." 220
#{ type: "object" 221
properties: #{@@ -211,7 +224,7 @@
224
(lambda (params) (tool-balance config params))) 225
226
(mcp-server-register-tool! server−214
"tally/report"+227
"journal-report" 228
"Run an hledger report on the journal file. Supports balance (bal), register (reg), and income statement (is)." 229
#{ type: "object" 230
properties: #{@@ -224,7 +237,7 @@
237
(lambda (params) (tool-report config params))) 238
239
(mcp-server-register-tool! server−227
"tally/categorize"+240
"uncategorized-transactions" 241
"List uncategorized transactions in the journal. Use this to find transactions that need category assignment." 242
#{ type: "object" 243
properties: #{