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: #{