chaimi-bookkeeping-mcp 3.0.3 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,9 +70,22 @@ MCP Server 会显示:
70
70
 
71
71
  ## 配置说明
72
72
 
73
- npm 全局安装时会**自动配置**,无需手动操作。
74
-
75
- 如需手动配置(如从 GitHub 下载安装),参考 `~/.mcporter/mcporter.json` 格式。
73
+ 如果使用手动安装,需要配置 `~/.mcporter/mcporter.json`:
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "柴米记账": {
79
+ "command": "chaimi-bookkeeping-mcp",
80
+ "env": {
81
+ "MCP_OAUTH_URL": "https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth",
82
+ "MCP_HUB_URL": "https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp",
83
+ "MCP_PROMPT_URL": "https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ ```
76
89
 
77
90
  ## 常见问题
78
91
 
@@ -128,27 +141,6 @@ AI 会自动调用 `save_income` 工具记录收入。
128
141
 
129
142
  ## 更新日志
130
143
 
131
- ### v2.3.4 (2026-04-08)
132
- - **简化** `save_expense` 工具,只保留 `name` 和 `amount` 为必填参数
133
- - **优化** 其他参数使用默认值,简化 Agent 调用
134
-
135
- ### v2.3.3 (2026-04-08)
136
- - **修正** 版本号统一为 v2.3.3(包含 v2.3.2 的所有修复)
137
-
138
- ### v2.3.2 (2026-04-08)
139
- - **修复** `server.js` 中 `CURRENT_VERSION` 未定义错误,统一使用 `MCP_VERSION`
140
- - **修复** 所有工具的 `userMessage` 添加默认值,防止显示 `undefined`
141
-
142
- ### v2.3.1 (2026-04-08)
143
- - **修复** `bin/cli.js` 版本号显示问题,改为动态读取 package.json
144
- - **优化** OAuth 授权流程,轮询间隔缩短为 2 秒
145
-
146
- ### v2.3.0 (2026-04-08)
147
- - **修复** `save_receipt` 的 `items` 参数解析 bug,支持 JSON 字符串自动转换
148
- - **优化** `source` 字段规范:`save_expense`→`mcp_txt_expense`、`save_income`→`mcp_txt_income`、`save_receipt`→`mcp_receipt`
149
- - **新增** `mcp_version` 字段追踪,便于版本监控
150
- - **注释** `quick_book` 工具(暂时下线,待优化后重新开放)
151
-
152
144
  ### v2.2.0 (2026-04-05)
153
145
  - **新增收入记账功能** (`save_income`)
154
146
  - 支持工资、奖金、红包、投资收益等收入类型
package/bin/cli.js CHANGED
@@ -10,10 +10,6 @@ const fs = require('fs');
10
10
  const os = require('os');
11
11
  const { spawn } = require('child_process');
12
12
 
13
- // 读取 package.json 获取版本
14
- const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
15
- const CURRENT_VERSION = packageJson.version;
16
-
17
13
  // 获取 server.js 的绝对路径
18
14
  const serverPath = path.join(__dirname, '..', 'server.js');
19
15
 
@@ -187,7 +183,7 @@ function configureAllAgents() {
187
183
  function showWelcome() {
188
184
  console.log('╔════════════════════════════════════════════════════════╗');
189
185
  console.log('║ ║');
190
- console.log(`║ 柴米记账 MCP Server v${CURRENT_VERSION} ║`);
186
+ console.log('║ 柴米记账 MCP Server v2.1.0 ║');
191
187
  console.log('║ ║');
192
188
  console.log('║ 支持: OpenClaw | WorkBuddy | Claude | Cursor ║');
193
189
  console.log('║ ║');
@@ -0,0 +1,15 @@
1
+ # 柴米记账 MCP Server 配置文件示例
2
+ # 复制此文件为 config.yaml 并填入您的实际配置
3
+
4
+ chaihuo:
5
+ # JWT Token(从小程序调用 mcpAuth 云函数获取)
6
+ # 示例:token: "eyJhbGciOiJIUzI1NiIs..."
7
+ token: ""
8
+
9
+ # SCF函数URL(部署后获取)
10
+ # 示例:base_url: "https://mcpServer-xxx.gz.apigw.tencentcs.com"
11
+ base_url: ""
12
+
13
+ # 其他配置
14
+ port: 3000
15
+ environment: "production"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-bookkeeping-mcp",
3
- "version": "3.0.3",
3
+ "version": "3.1.1",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
package/.env DELETED
@@ -1,6 +0,0 @@
1
- # OAuth 测试配置(临时,用于测试 OAuth 流程)
2
- # 注意:没有 MCP_TOKEN,强制使用 OAuth 授权
3
-
4
- MCP_OAUTH_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth
5
- MCP_HUB_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp
6
- MCP_PROMPT_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt
package/.env.backup DELETED
@@ -1,5 +0,0 @@
1
- # /Users/solinxia/aider.git/caihuo_record_list/mcp-server-local/.env
2
-
3
- MCP_HUB_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp
4
- MCP_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVuaWQiOiJvaEM1eDNUdjRHLUxnVVdoWkJXWkhRNUg0UzRBIiwiaWF0IjoxNzc1MTEyNTc5LCJleHAiOjE3Nzc3MDQ1Nzl9.qmGS6eK9mwhFU7e---NlA7QvbcuRZqDsibkgYh74rGU
5
- MCP_OPENID=ohC5x3Tv4G-LgUWhZBWZHQ5H4S4A
package/.env.test DELETED
@@ -1,4 +0,0 @@
1
- # 测试环境配置
2
- MCP_OAUTH_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth
3
- MCP_HUB_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp
4
- MCP_PROMPT_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt
package/.env.test-oauth DELETED
@@ -1,6 +0,0 @@
1
- # OAuth 测试配置(临时,用于测试 OAuth 流程)
2
- # 注意:没有 MCP_TOKEN,强制使用 OAuth 授权
3
-
4
- MCP_OAUTH_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth
5
- MCP_HUB_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp
6
- MCP_PROMPT_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt
package/2.1.3/357/274/211 DELETED
File without changes
package/2.3.4 DELETED
File without changes
package/=0.4.0 DELETED
File without changes
package/config.yaml DELETED
@@ -1,18 +0,0 @@
1
- # 柴米记账 MCP Server 配置文件
2
- # 请从小程序获取Token并填入下方
3
-
4
- chaihuo:
5
- # JWT Token(从小程序调用 mcpAuth 云函数获取)
6
- # 示例:token: "eyJhbGciOiJIUzI1NiIs..."
7
- token: ""
8
-
9
- # SCF函数URL(部署后获取)
10
- # 示例:base_url: "https://mcpServer-xxx.gz.apigw.tencentcs.com"
11
- base_url: ""
12
-
13
- token: "FFeSVyBPLdnGNqaOP2NykCCyPU8ZLSKC6d5R2wkrwKo=" # ← 替换为从 mcpAuth 获取的 token
14
- base_url: "https://1412172089-4wbwsop8pe.ap-shanghai.tencentscf.com" # ← 替换为 mcpServer 的 HTTP 触发地址
15
-
16
- # 其他配置保持不变
17
- port: 3000
18
- nvironment: "production"
package/requirements.txt DELETED
@@ -1,13 +0,0 @@
1
- # 柴米记账 MCP Server 依赖
2
-
3
- # MCP框架
4
- fastmcp>=0.4.0
5
-
6
- # HTTP请求
7
- requests>=2.31.0
8
-
9
- # YAML配置解析
10
- pyyaml>=6.0.1
11
-
12
- # 其他依赖
13
- urllib3>=2.0.0
package/server.py DELETED
@@ -1,763 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 柴米记账 MCP Server - V2版本
5
- 使用 FastMCP 实现 MCP 协议服务
6
- 架构:本地MCP Server -> 腾讯云SCF -> 微信云数据库
7
-
8
- 支持两种记账场景:
9
- 1. 单商品记账(AI文字)- save_expense
10
- 2. 小票记账(多商品)- save_receipt
11
- """
12
-
13
- import os
14
- import sys
15
- import json
16
- import yaml
17
- from typing import Optional, List, Dict
18
- from datetime import datetime
19
-
20
- from fastmcp import FastMCP
21
- import requests
22
- from requests.adapters import HTTPAdapter
23
- from urllib3.util.retry import Retry
24
-
25
- # 初始化 MCP Server
26
- mcp = FastMCP("chaihuo-mcp")
27
-
28
- # ==================== 配置管理 ====================
29
-
30
- class Config:
31
- """配置管理类"""
32
-
33
- def __init__(self):
34
- self.config_path = os.path.join(os.path.dirname(__file__), 'config.yaml')
35
- self._config = self._load_config()
36
-
37
- def _load_config(self) -> dict:
38
- """加载配置文件"""
39
- default_config = {
40
- 'chaihuo': {
41
- 'token': '', # JWT Token(从小程序获取)
42
- 'base_url': '' # SCF函数URL
43
- }
44
- }
45
-
46
- if os.path.exists(self.config_path):
47
- # 检查文件权限
48
- self._check_file_permissions()
49
-
50
- try:
51
- with open(self.config_path, 'r', encoding='utf-8') as f:
52
- config = yaml.safe_load(f)
53
- # 合并默认配置
54
- for key, value in default_config.items():
55
- if key not in config:
56
- config[key] = value
57
- return config
58
- except Exception as e:
59
- print(f"加载配置文件失败: {e}")
60
-
61
- return default_config
62
-
63
- def _check_file_permissions(self):
64
- """检查配置文件权限,提醒用户设置合适的访问权限"""
65
- try:
66
- import stat
67
- file_stat = os.stat(self.config_path)
68
- mode = file_stat.st_mode
69
-
70
- # 检查是否对组或其他用户可读写
71
- if mode & stat.S_IRWXG or mode & stat.S_IRWXO:
72
- print(f"\n⚠️ 安全提醒:")
73
- print(f" 配置文件 {self.config_path} 的权限过于开放")
74
- print(f" 建议执行以下命令保护您的Token:")
75
- print(f" chmod 600 {self.config_path}\n")
76
- except Exception:
77
- # 权限检查失败不影响主程序运行
78
- pass
79
-
80
- @property
81
- def token(self) -> str:
82
- return self._config.get('chaihuo', {}).get('token', '')
83
-
84
- @property
85
- def base_url(self) -> str:
86
- return self._config.get('chaihuo', {}).get('base_url', '')
87
-
88
- def is_configured(self) -> bool:
89
- """检查配置是否完整"""
90
- return bool(self.token and self.base_url)
91
-
92
-
93
- # 全局配置对象
94
- config = Config()
95
-
96
- # ==================== HTTP Client ====================
97
-
98
- class SCFClient:
99
- """腾讯云SCF HTTP客户端"""
100
-
101
- def __init__(self, base_url: str, token: str):
102
- self.base_url = base_url.rstrip('/')
103
- self.token = token
104
-
105
- # 创建session并配置重试
106
- self.session = requests.Session()
107
- retry_strategy = Retry(
108
- total=3,
109
- backoff_factor=1,
110
- status_forcelist=[429, 500, 502, 503, 504],
111
- )
112
- adapter = HTTPAdapter(max_retries=retry_strategy)
113
- self.session.mount("http://", adapter)
114
- self.session.mount("https://", adapter)
115
-
116
- def _make_request(self, data: dict) -> dict:
117
- """发送HTTP请求到SCF"""
118
- url = self.base_url
119
-
120
- try:
121
- response = self.session.post(
122
- url,
123
- json=data,
124
- headers={
125
- 'Content-Type': 'application/json',
126
- 'Authorization': f'Bearer {self.token}'
127
- },
128
- timeout=30
129
- )
130
- response.raise_for_status()
131
- return response.json()
132
- except requests.exceptions.Timeout:
133
- raise Exception("请求超时,请检查网络连接")
134
- except requests.exceptions.ConnectionError:
135
- raise Exception("连接失败,请检查SCF函数URL配置")
136
- except requests.exceptions.HTTPError as e:
137
- error_msg = f"HTTP错误: {e.response.status_code}"
138
- try:
139
- error_data = e.response.json()
140
- if 'error' in error_data:
141
- error_msg = f"{error_msg} - {error_data['error']}"
142
- except:
143
- pass
144
- raise Exception(error_msg)
145
- except Exception as e:
146
- raise Exception(f"请求失败: {str(e)}")
147
-
148
- # ===== 单商品记账 =====
149
- def save_expense(self, data: dict) -> dict:
150
- """调用mcpServer保存单个消费记录"""
151
- return self._make_request({
152
- 'action': 'save_expense',
153
- 'data': data
154
- })
155
-
156
- # ===== 小票记账 =====
157
- def save_receipt(self, data: dict) -> dict:
158
- """调用mcpServer保存小票及商品明细"""
159
- return self._make_request({
160
- 'action': 'save_receipt',
161
- 'data': data
162
- })
163
-
164
- # ===== 查询功能 =====
165
- def get_expenses(self, params: dict = None) -> dict:
166
- """调用mcpServer查询消费记录"""
167
- return self._make_request({
168
- 'action': 'get_expenses',
169
- 'params': params or {}
170
- })
171
-
172
- def get_receipt_list(self, params: dict = None) -> dict:
173
- """调用mcpServer获取小票列表"""
174
- return self._make_request({
175
- 'action': 'get_receipt_list',
176
- 'params': params or {}
177
- })
178
-
179
- def get_receipt_detail(self, receipt_id: str) -> dict:
180
- """调用mcpServer获取小票详情"""
181
- return self._make_request({
182
- 'action': 'get_receipt_detail',
183
- 'params': {'receiptId': receipt_id}
184
- })
185
-
186
- # ===== 统计功能 =====
187
- def get_statistics(self, year_month: str, type: str = 'item') -> dict:
188
- """调用mcpServer获取统计信息"""
189
- return self._make_request({
190
- 'action': 'get_statistics',
191
- 'params': {'yearMonth': year_month, 'type': type}
192
- })
193
-
194
-
195
- # 初始化客户端
196
- client = None
197
- if config.is_configured():
198
- client = SCFClient(config.base_url, config.token)
199
-
200
- # ==================== MCP Tools - 单商品记账 ====================
201
-
202
- @mcp.tool()
203
- def save_expense(
204
- name: str,
205
- amount: float,
206
- category: str,
207
- store: str,
208
- date: Optional[str] = None,
209
- price: Optional[float] = None,
210
- quantity: Optional[str] = None,
211
- unit: Optional[str] = None,
212
- agent_type: str = "workbuddy"
213
- ) -> str:
214
- """
215
- 保存单个消费记录(AI文字记账)
216
-
217
- 适用场景:用户描述一笔消费,如"午餐35元麦当劳"
218
- 存储方式:直接保存到 receipt_items 表,不创建小票记录
219
-
220
- Args:
221
- name: 商品/服务名称,如"午餐"、"牛奶"
222
- amount: 总金额,如35.0、128.5
223
- category: 分类,如"餐饮"、"购物"、"交通"
224
- store: 商家名称,如"麦当劳"、"沃尔玛"
225
- date: 消费时间,格式YYYY-MM-DD HH:mm:ss(可选,默认当前时间)
226
- price: 单价(可选,默认等于amount)
227
- quantity: 数量(可选,默认"1")
228
- unit: 单位(可选)
229
- agent_type: MCP Agent类型(可选,默认workbuddy)
230
-
231
- Returns:
232
- 保存结果信息
233
- """
234
- try:
235
- if not client:
236
- return json.dumps({
237
- 'success': False,
238
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
239
- }, ensure_ascii=False)
240
-
241
- # 构建数据
242
- data = {
243
- 'name': name,
244
- 'amount': amount,
245
- 'price': price if price is not None else amount,
246
- 'quantity': quantity or '1',
247
- 'unit': unit or '',
248
- 'category': category,
249
- 'store': store,
250
- 'date': date or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
251
- 'source': 'mcp_text',
252
- 'agentType': agent_type
253
- }
254
-
255
- result = client.save_expense(data)
256
-
257
- if result.get('success'):
258
- return json.dumps({
259
- 'success': True,
260
- 'message': f'✅ 记账成功!\n商品:{name}\n金额:{amount}元\n商家:{store}\n分类:{category}',
261
- 'data': result.get('data', {})
262
- }, ensure_ascii=False)
263
- else:
264
- return json.dumps({
265
- 'success': False,
266
- 'error': result.get('error', '保存失败')
267
- }, ensure_ascii=False)
268
-
269
- except Exception as e:
270
- return json.dumps({
271
- 'success': False,
272
- 'error': f'保存异常: {str(e)}'
273
- }, ensure_ascii=False)
274
-
275
-
276
- # ==================== MCP Tools - 小票记账 ====================
277
-
278
- @mcp.tool()
279
- def save_receipt(
280
- store: str,
281
- total_amount: float,
282
- items: str,
283
- date: Optional[str] = None,
284
- receipt_no: Optional[str] = None,
285
- payment_method: Optional[str] = None,
286
- agent_type: str = "workbuddy"
287
- ) -> str:
288
- """
289
- 保存小票及商品明细(小票识别记账)
290
-
291
- 适用场景:用户上传小票图片,识别出多个商品
292
- 存储方式:先创建 receipts 记录,再批量创建 receipt_items
293
-
294
- Args:
295
- store: 商户名称,如"沃尔玛"、"永辉超市"
296
- total_amount: 小票总金额
297
- items: 商品列表JSON字符串,格式:[{"name":"牛奶","price":12.5,"quantity":"2","amount":25.0,"category":"食品"}]
298
- date: 消费时间,格式YYYY-MM-DD HH:mm:ss(可选,默认当前时间)
299
- receipt_no: 小票编号(可选)
300
- payment_method: 支付方式,如"微信支付"、"支付宝"(可选)
301
- agent_type: MCP Agent类型(可选,默认workbuddy)
302
-
303
- Returns:
304
- 保存结果信息
305
-
306
- Example:
307
- items参数示例:
308
- '[
309
- {"name":"纯牛奶","price":12.5,"quantity":"2","amount":25.0,"category":"食品","unit":"盒"},
310
- {"name":"面包","price":8.0,"quantity":"1","amount":8.0,"category":"食品","unit":"个"}
311
- ]'
312
- """
313
- try:
314
- if not client:
315
- return json.dumps({
316
- 'success': False,
317
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
318
- }, ensure_ascii=False)
319
-
320
- # 解析商品列表
321
- try:
322
- items_list = json.loads(items)
323
- if not isinstance(items_list, list):
324
- raise ValueError("items必须是数组")
325
- except json.JSONDecodeError as e:
326
- return json.dumps({
327
- 'success': False,
328
- 'error': f'商品列表JSON格式错误: {str(e)}'
329
- }, ensure_ascii=False)
330
-
331
- if len(items_list) == 0:
332
- return json.dumps({
333
- 'success': False,
334
- 'error': '商品列表不能为空'
335
- }, ensure_ascii=False)
336
-
337
- # 构建数据
338
- data = {
339
- 'store': store,
340
- 'totalAmount': total_amount,
341
- 'date': date or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
342
- 'receiptNo': receipt_no or '',
343
- 'paymentMethod': payment_method or '',
344
- 'items': items_list,
345
- 'agentType': agent_type
346
- }
347
-
348
- result = client.save_receipt(data)
349
-
350
- if result.get('success'):
351
- item_count = result.get('data', {}).get('itemCount', 0)
352
- return json.dumps({
353
- 'success': True,
354
- 'message': f'✅ 小票保存成功!\n商户:{store}\n总金额:{total_amount}元\n商品数:{item_count}个',
355
- 'data': result.get('data', {})
356
- }, ensure_ascii=False)
357
- else:
358
- return json.dumps({
359
- 'success': False,
360
- 'error': result.get('error', '保存小票失败')
361
- }, ensure_ascii=False)
362
-
363
- except Exception as e:
364
- return json.dumps({
365
- 'success': False,
366
- 'error': f'保存小票异常: {str(e)}'
367
- }, ensure_ascii=False)
368
-
369
-
370
- # ==================== MCP Tools - 查询功能 ====================
371
-
372
- @mcp.tool()
373
- def get_expenses(
374
- start_date: Optional[str] = None,
375
- end_date: Optional[str] = None,
376
- category: Optional[str] = None,
377
- type: str = "all",
378
- limit: int = 10
379
- ) -> str:
380
- """
381
- 查询消费记录(支持单商品和小票商品)
382
-
383
- Args:
384
- start_date: 开始日期,格式YYYY-MM-DD(可选)
385
- end_date: 结束日期,格式YYYY-MM-DD(可选)
386
- category: 分类筛选,如"餐饮"、"购物"(可选)
387
- type: 记录类型,all(全部)/single(单商品)/receipt(小票商品),默认all
388
- limit: 返回条数(可选,默认10条)
389
-
390
- Returns:
391
- 消费记录列表
392
- """
393
- try:
394
- if not client:
395
- return json.dumps({
396
- 'success': False,
397
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
398
- }, ensure_ascii=False)
399
-
400
- params = {'limit': limit, 'type': type}
401
-
402
- if start_date:
403
- params['startDate'] = start_date
404
- if end_date:
405
- params['endDate'] = end_date
406
- if category:
407
- params['category'] = category
408
-
409
- result = client.get_expenses(params)
410
-
411
- if result.get('success'):
412
- data = result.get('data', {})
413
- items = data.get('items', [])
414
- total = data.get('total', 0)
415
-
416
- if total == 0:
417
- return json.dumps({
418
- 'success': True,
419
- 'message': '暂无消费记录',
420
- 'data': data
421
- }, ensure_ascii=False)
422
-
423
- # 格式化输出
424
- lines = [f"📊 共查询到 {total} 条记录\n"]
425
- for i, item in enumerate(items, 1):
426
- receipt_tag = "🧾" if item.get('receiptId') else "📝"
427
- lines.append(f"{receipt_tag} {i}. {item.get('date', '')[:10]} | "
428
- f"{item.get('name', '')} | "
429
- f"¥{item.get('amount', 0)} | "
430
- f"{item.get('category', '')} | "
431
- f"{item.get('store', '')}")
432
-
433
- lines.append(f"\n📝 单商品 | 🧾 小票商品")
434
-
435
- return json.dumps({
436
- 'success': True,
437
- 'message': '\n'.join(lines),
438
- 'data': data
439
- }, ensure_ascii=False)
440
- else:
441
- return json.dumps({
442
- 'success': False,
443
- 'error': result.get('error', '查询失败')
444
- }, ensure_ascii=False)
445
-
446
- except Exception as e:
447
- return json.dumps({
448
- 'success': False,
449
- 'error': f'查询异常: {str(e)}'
450
- }, ensure_ascii=False)
451
-
452
-
453
- @mcp.tool()
454
- def get_receipt_list(limit: int = 10) -> str:
455
- """
456
- 获取小票列表
457
-
458
- Args:
459
- limit: 返回条数(可选,默认10条)
460
-
461
- Returns:
462
- 小票列表
463
- """
464
- try:
465
- if not client:
466
- return json.dumps({
467
- 'success': False,
468
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
469
- }, ensure_ascii=False)
470
-
471
- result = client.get_receipt_list({'limit': limit})
472
-
473
- if result.get('success'):
474
- data = result.get('data', {})
475
- receipts = data.get('receipts', [])
476
- total = data.get('total', 0)
477
-
478
- if total == 0:
479
- return json.dumps({
480
- 'success': True,
481
- 'message': '暂无小票记录',
482
- 'data': data
483
- }, ensure_ascii=False)
484
-
485
- # 格式化输出
486
- lines = [f"🧾 共查询到 {total} 张小票\n"]
487
- for i, r in enumerate(receipts, 1):
488
- lines.append(f"{i}. {r.get('date', '')[:10]} | "
489
- f"{r.get('store', '')} | "
490
- f"¥{r.get('totalAmount', 0)}")
491
-
492
- return json.dumps({
493
- 'success': True,
494
- 'message': '\n'.join(lines),
495
- 'data': data
496
- }, ensure_ascii=False)
497
- else:
498
- return json.dumps({
499
- 'success': False,
500
- 'error': result.get('error', '获取小票列表失败')
501
- }, ensure_ascii=False)
502
-
503
- except Exception as e:
504
- return json.dumps({
505
- 'success': False,
506
- 'error': f'获取小票列表异常: {str(e)}'
507
- }, ensure_ascii=False)
508
-
509
-
510
- @mcp.tool()
511
- def get_receipt_detail(receipt_id: str) -> str:
512
- """
513
- 获取小票详情(包含商品明细)
514
-
515
- Args:
516
- receipt_id: 小票ID
517
-
518
- Returns:
519
- 小票详情及商品明细
520
- """
521
- try:
522
- if not client:
523
- return json.dumps({
524
- 'success': False,
525
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
526
- }, ensure_ascii=False)
527
-
528
- result = client.get_receipt_detail(receipt_id)
529
-
530
- if result.get('success'):
531
- data = result.get('data', {})
532
- receipt = data.get('receipt', {})
533
- items = data.get('items', [])
534
-
535
- # 格式化输出
536
- lines = [
537
- f"🧾 小票详情",
538
- f"商户:{receipt.get('store', '')}",
539
- f"时间:{receipt.get('date', '')}",
540
- f"总金额:¥{receipt.get('totalAmount', 0)}",
541
- f""
542
- ]
543
-
544
- if receipt.get('originalAmount'):
545
- lines.append(f"原价:¥{receipt.get('originalAmount')}")
546
- if receipt.get('discountAmount'):
547
- lines.append(f"优惠:-¥{receipt.get('discountAmount')}")
548
- if receipt.get('paymentMethod'):
549
- lines.append(f"支付方式:{receipt.get('paymentMethod')}")
550
-
551
- lines.append(f"\n📦 商品明细(共{len(items)}个):")
552
-
553
- for i, item in enumerate(items, 1):
554
- lines.append(f"{i}. {item.get('name', '')} "
555
- f"¥{item.get('price', 0)} x {item.get('quantity', 1)} "
556
- f"= ¥{item.get('amount', 0)} "
557
- f"({item.get('category', '')})")
558
-
559
- return json.dumps({
560
- 'success': True,
561
- 'message': '\n'.join(lines),
562
- 'data': data
563
- }, ensure_ascii=False)
564
- else:
565
- return json.dumps({
566
- 'success': False,
567
- 'error': result.get('error', '获取小票详情失败')
568
- }, ensure_ascii=False)
569
-
570
- except Exception as e:
571
- return json.dumps({
572
- 'success': False,
573
- 'error': f'获取小票详情异常: {str(e)}'
574
- }, ensure_ascii=False)
575
-
576
-
577
- # ==================== MCP Tools - 统计功能 ====================
578
-
579
- @mcp.tool()
580
- def get_statistics(year_month: str, type: str = "item") -> str:
581
- """
582
- 获取月度消费统计
583
-
584
- Args:
585
- year_month: 年月,格式YYYY-MM,如"2026-03"
586
- type: 统计类型,item(按商品统计)/receipt(按小票统计),默认item
587
-
588
- Returns:
589
- 月度消费统计信息
590
- """
591
- try:
592
- if not client:
593
- return json.dumps({
594
- 'success': False,
595
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
596
- }, ensure_ascii=False)
597
-
598
- result = client.get_statistics(year_month, type)
599
-
600
- if result.get('success'):
601
- data = result.get('data', {})
602
- total_amount = data.get('totalAmount', '0')
603
- total_count = data.get('totalCount', 0)
604
- categories = data.get('categories', [])
605
- stat_type = data.get('type', 'item')
606
-
607
- # 格式化输出
608
- type_label = "商品" if stat_type == 'item' else "小票"
609
- lines = [
610
- f"📈 {year_month} 消费统计",
611
- f"统计方式:按{type_label}统计",
612
- f"总支出:¥{total_amount}",
613
- f"数量:{total_count}个\n"
614
- ]
615
-
616
- if categories:
617
- lines.append("分类统计:")
618
- for cat in categories:
619
- lines.append(f" {cat.get('name', '')}: ¥{cat.get('amount', '0')} "
620
- f"({cat.get('count', 0)}个, {cat.get('percentage', '0')}%)")
621
-
622
- return json.dumps({
623
- 'success': True,
624
- 'message': '\n'.join(lines),
625
- 'data': data
626
- }, ensure_ascii=False)
627
- else:
628
- return json.dumps({
629
- 'success': False,
630
- 'error': result.get('error', '获取统计失败')
631
- }, ensure_ascii=False)
632
-
633
- except Exception as e:
634
- return json.dumps({
635
- 'success': False,
636
- 'error': f'获取统计异常: {str(e)}'
637
- }, ensure_ascii=False)
638
-
639
-
640
- # ==================== MCP Tools - 快速记账 ====================
641
-
642
- @mcp.tool()
643
- def quick_record(text: str, agent_type: str = "workbuddy") -> str:
644
- """
645
- 快速记账 - 从自然语言描述中提取信息并保存(单商品)
646
-
647
- Args:
648
- text: 消费描述,如"午餐35元麦当劳"、"昨天在沃尔玛买了128块钱的东西"
649
- agent_type: MCP Agent类型(可选,默认workbuddy)
650
-
651
- Returns:
652
- 记账结果
653
- """
654
- try:
655
- if not client:
656
- return json.dumps({
657
- 'success': False,
658
- 'error': 'MCP Server未配置,请在config.yaml中设置token和base_url'
659
- }, ensure_ascii=False)
660
-
661
- # 使用简单的规则提取
662
- import re
663
-
664
- # 提取金额
665
- amount_match = re.search(r'(\d+\.?\d*)\s*元?', text)
666
- amount = float(amount_match.group(1)) if amount_match else 0
667
-
668
- # 提取商家(简单规则)
669
- store_keywords = ['麦当劳', '肯德基', '沃尔玛', '家乐福', '星巴克', '瑞幸', '滴滴', '高德', '永辉', '盒马']
670
- store = '未知商家'
671
- for keyword in store_keywords:
672
- if keyword in text:
673
- store = keyword
674
- break
675
-
676
- # 提取分类(简单规则)
677
- category = '其他'
678
- if any(kw in text for kw in ['午餐', '晚餐', '早餐', '吃饭', '餐厅', '麦当劳', '肯德基']):
679
- category = '餐饮'
680
- elif any(kw in text for kw in ['超市', '沃尔玛', '家乐福', '买东西', '永辉', '盒马']):
681
- category = '购物'
682
- elif any(kw in text for kw in ['打车', '滴滴', '地铁', '公交', '高德']):
683
- category = '交通'
684
-
685
- # 提取商品名称
686
- name = '消费'
687
- if '午餐' in text:
688
- name = '午餐'
689
- elif '晚餐' in text:
690
- name = '晚餐'
691
- elif '早餐' in text:
692
- name = '早餐'
693
-
694
- if amount <= 0:
695
- return json.dumps({
696
- 'success': False,
697
- 'error': '无法从描述中提取金额,请使用save_expense手动记账'
698
- }, ensure_ascii=False)
699
-
700
- # 保存记录
701
- data = {
702
- 'name': name,
703
- 'amount': amount,
704
- 'category': category,
705
- 'store': store,
706
- 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
707
- 'source': 'mcp_text',
708
- 'agentType': agent_type
709
- }
710
-
711
- result = client.save_expense(data)
712
-
713
- if result.get('success'):
714
- return json.dumps({
715
- 'success': True,
716
- 'message': f'✅ 快速记账成功!\n描述:{text}\n解析:{name} ¥{amount} @ {store} ({category})',
717
- 'data': result.get('data', {})
718
- }, ensure_ascii=False)
719
- else:
720
- return json.dumps({
721
- 'success': False,
722
- 'error': result.get('error', '保存失败')
723
- }, ensure_ascii=False)
724
-
725
- except Exception as e:
726
- return json.dumps({
727
- 'success': False,
728
- 'error': f'快速记账异常: {str(e)}'
729
- }, ensure_ascii=False)
730
-
731
-
732
- # ==================== 主入口 ====================
733
-
734
- if __name__ == "__main__":
735
- print("=" * 50)
736
- print("柴米记账 MCP Server - V2")
737
- print("支持场景:单商品记账 + 小票多商品记账")
738
- print("=" * 50)
739
-
740
- if not config.is_configured():
741
- print("\n⚠️ 配置不完整,请检查 config.yaml:")
742
- if not config.token:
743
- print(" - 缺少 token(从小程序获取)")
744
- if not config.base_url:
745
- print(" - 缺少 base_url(SCF函数URL)")
746
- print("\n配置文件位置:", config.config_path)
747
- print("\n配置示例:")
748
- print("""
749
- chaihuo:
750
- token: "eyJhbGciOiJIUzI1NiIs..." # 从小程序获取的JWT Token
751
- base_url: "https://mcpServer-xxx.gz.apigw.tencentcs.com"
752
- """)
753
- else:
754
- print(f"\n✅ 配置完整")
755
- print(f"SCF地址: {config.base_url}")
756
- print(f"Token: {config.token[:20]}...")
757
-
758
- print("\n" + "=" * 50)
759
- print("启动 MCP Server...")
760
- print("=" * 50 + "\n")
761
-
762
- # 启动MCP Server
763
- mcp.run()
package/test-mcp.sh DELETED
@@ -1,26 +0,0 @@
1
- #!/bin/bash
2
-
3
- # MCP Server 本地测试脚本
4
-
5
- echo "=== 柴米记账 MCP Server 本地测试 ==="
6
- echo ""
7
-
8
- # 设置环境变量(测试环境)
9
- export MCP_HUB_URL="https://chaihuo-xxx.ap-shanghai.app.tcloudbase.com/mcpHub-mcp"
10
- export MCP_PROMPT_URL="https://chaihuo-xxx.ap-shanghai.app.tcloudbase.com/mcpPrompt"
11
- export MCP_OAUTH_URL="https://chaihuo-xxx.ap-shanghai.app.tcloudbase.com/mcpOAuth"
12
- export AGENT_TYPE="test"
13
- export API_PROVIDER="test"
14
-
15
- echo "环境变量设置:"
16
- echo " MCP_HUB_URL: $MCP_HUB_URL"
17
- echo " AGENT_TYPE: $AGENT_TYPE"
18
- echo " API_PROVIDER: $API_PROVIDER"
19
- echo ""
20
-
21
- # 测试 MCP Server 启动
22
- echo "=== 测试 MCP Server 启动 ==="
23
- echo "按 Ctrl+C 停止测试"
24
- echo ""
25
-
26
- node server.js
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { execSync } = require('child_process');
4
- const os = require('os');
5
-
6
- console.log('='.repeat(60));
7
- console.log('父进程信息检测工具');
8
- console.log('='.repeat(60));
9
-
10
- console.log('\n1. 当前进程信息:');
11
- console.log(` PID: ${process.pid}`);
12
- console.log(` 父进程 PID (PPID): ${process.ppid}`);
13
- console.log(` 当前平台: ${process.platform}`);
14
-
15
- console.log('\n2. 尝试获取父进程信息:');
16
-
17
- if (process.platform === 'darwin' || process.platform === 'linux') {
18
- try {
19
- const psOutput = execSync(`ps -p ${process.ppid} -o pid,ppid,command`, { encoding: 'utf8' });
20
- console.log(' ' + psOutput.replace(/\n/g, '\n '));
21
-
22
- const ppidOutput = execSync(`ps -p ${process.ppid} -o command=`, { encoding: 'utf8' }).trim();
23
- console.log(`\n 父进程命令: ${ppidOutput}`);
24
-
25
- const guessAgentType = (cmd) => {
26
- if (/claude/i.test(cmd)) return 'claude-desktop';
27
- if (/cursor/i.test(cmd)) return 'cursor';
28
- if (/continue/i.test(cmd)) return 'continue';
29
- if (/zed/i.test(cmd)) return 'zed';
30
- if (/workbuddy/i.test(cmd)) return 'workbuddy';
31
- if (/openclaw/i.test(cmd)) return 'openclaw';
32
- return 'unknown';
33
- };
34
-
35
- console.log(` 猜测的 Agent 类型: ${guessAgentType(ppidOutput)}`);
36
-
37
- } catch (e) {
38
- console.log(` 获取父进程信息失败: ${e.message}`);
39
- }
40
- } else if (process.platform === 'win32') {
41
- try {
42
- const wmicOutput = execSync(`wmic process where processid=${process.ppid} get processid,parentprocessid,commandline`, { encoding: 'utf8' });
43
- console.log(' ' + wmicOutput.replace(/\n/g, '\n '));
44
- } catch (e) {
45
- console.log(` 获取父进程信息失败: ${e.message}`);
46
- }
47
- }
48
-
49
- console.log('\n' + '='.repeat(60));