sorftime-mcp 0.1.0__tar.gz
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.
- sorftime_mcp-0.1.0/.env.example +10 -0
- sorftime_mcp-0.1.0/.gitignore +10 -0
- sorftime_mcp-0.1.0/PKG-INFO +214 -0
- sorftime_mcp-0.1.0/README.md +201 -0
- sorftime_mcp-0.1.0/bin/sorftime-mcp.js +32 -0
- sorftime_mcp-0.1.0/package.json +31 -0
- sorftime_mcp-0.1.0/pyproject.toml +33 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/__init__.py +6 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/__main__.py +6 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/audit.py +56 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/catalog.py +260 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/cli.py +32 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/client.py +118 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/config.py +45 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/domains.py +46 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/executor.py +67 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/models.py +227 -0
- sorftime_mcp-0.1.0/src/sorftime_mcp/server.py +62 -0
- sorftime_mcp-0.1.0/tests/conftest.py +67 -0
- sorftime_mcp-0.1.0/tests/test_client.py +58 -0
- sorftime_mcp-0.1.0/tests/test_models_and_costs.py +84 -0
- sorftime_mcp-0.1.0/tests/test_server_tools.py +200 -0
- sorftime_mcp-0.1.0/uv.lock +1516 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Sorftime Account-SK. Do not commit a real key.
|
|
2
|
+
SORFTIME_API_KEY=
|
|
3
|
+
|
|
4
|
+
# Optional runtime settings.
|
|
5
|
+
SORFTIME_API_BASE_URL=https://standardapi.sorftime.com/api
|
|
6
|
+
SORFTIME_API_TIMEOUT_SECONDS=120
|
|
7
|
+
SORFTIME_API_MAX_RETRIES=3
|
|
8
|
+
SORFTIME_API_RETRY_BASE_DELAY_SECONDS=2
|
|
9
|
+
SORFTIME_API_RETRY_MAX_DELAY_SECONDS=15
|
|
10
|
+
SORFTIME_AUDIT_LOG_PATH=
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sorftime-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Stdio MCP server for read-only Sorftime Enterprise API access.
|
|
5
|
+
Project-URL: Repository, https://github.com/ccchenhuohuo/sorftime-mcp
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: click>=8.1.8
|
|
8
|
+
Requires-Dist: fastmcp==3.4.2
|
|
9
|
+
Requires-Dist: httpx>=0.27.2
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.8.1
|
|
11
|
+
Requires-Dist: pydantic>=2.10.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Sorftime MCP
|
|
15
|
+
|
|
16
|
+
把 Sorftime Enterprise API 封装成 Codex、Claude Code 等 AI Agent 可直接调用的 MCP 服务。
|
|
17
|
+
|
|
18
|
+
本项目默认使用 `stdio` 模式,本地安装后只需要配置 `SORFTIME_API_KEY`。Sorftime Account-SK 保存在使用者自己的 MCP 客户端配置或服务器环境变量中,不写入代码仓库。
|
|
19
|
+
|
|
20
|
+
## 重点
|
|
21
|
+
|
|
22
|
+
- 默认传输:`stdio`
|
|
23
|
+
- 本地必需环境变量:`SORFTIME_API_KEY`
|
|
24
|
+
- 公开 MCP 工具数:10 个
|
|
25
|
+
- 支持安全取数方法:32 个
|
|
26
|
+
- 默认站点:`domain=1`,即美国站
|
|
27
|
+
- 返回统一结构:`endpoint`、`domain`、`estimatedRequestCost`、`requestConsumed`、`requestLeft`、`code`、`message`、`data`、`rawResponse`
|
|
28
|
+
- 默认排除账户修改、订阅管理、监控任务管理类接口
|
|
29
|
+
|
|
30
|
+
## 安装方式
|
|
31
|
+
|
|
32
|
+
### npm 安装
|
|
33
|
+
|
|
34
|
+
Codex 配置:
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[mcp_servers.sorftime]
|
|
38
|
+
command = "npx"
|
|
39
|
+
args = ["-y", "sorftime-mcp"]
|
|
40
|
+
|
|
41
|
+
[mcp_servers.sorftime.env]
|
|
42
|
+
SORFTIME_API_KEY = "你的 Sorftime Account-SK"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Claude Code 安装:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
claude mcp add --scope user \
|
|
49
|
+
-e SORFTIME_API_KEY="你的 Sorftime Account-SK" \
|
|
50
|
+
sorftime -- npx -y sorftime-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### PyPI 安装
|
|
54
|
+
|
|
55
|
+
Codex 配置:
|
|
56
|
+
|
|
57
|
+
```toml
|
|
58
|
+
[mcp_servers.sorftime]
|
|
59
|
+
command = "uvx"
|
|
60
|
+
args = ["sorftime-mcp"]
|
|
61
|
+
|
|
62
|
+
[mcp_servers.sorftime.env]
|
|
63
|
+
SORFTIME_API_KEY = "你的 Sorftime Account-SK"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Claude Code 安装:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
claude mcp add --scope user \
|
|
70
|
+
-e SORFTIME_API_KEY="你的 Sorftime Account-SK" \
|
|
71
|
+
sorftime -- uvx sorftime-mcp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### GitHub 源码安装
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uvx --from git+https://github.com/ccchenhuohuo/sorftime-mcp.git sorftime-mcp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
对应 Codex 配置:
|
|
81
|
+
|
|
82
|
+
```toml
|
|
83
|
+
[mcp_servers.sorftime]
|
|
84
|
+
command = "uvx"
|
|
85
|
+
args = [
|
|
86
|
+
"--from",
|
|
87
|
+
"git+https://github.com/ccchenhuohuo/sorftime-mcp.git",
|
|
88
|
+
"sorftime-mcp"
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[mcp_servers.sorftime.env]
|
|
92
|
+
SORFTIME_API_KEY = "你的 Sorftime Account-SK"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 工具列表
|
|
96
|
+
|
|
97
|
+
| 工具 | 用途 |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| `sorftime_methods` | 查看支持的方法、分类、消耗、是否异步、是否有快捷工具 |
|
|
100
|
+
| `sorftime_method_schema` | 查看单个方法的参数、示例、站点映射和消耗说明 |
|
|
101
|
+
| `sorftime_call` | 白名单路由工具,用于调用低频安全取数方法 |
|
|
102
|
+
| `product_request` | 产品详情,对应 `ProductRequest` |
|
|
103
|
+
| `category_request` | 类目 Top 100,对应 `CategoryRequest` |
|
|
104
|
+
| `keyword_request` | 关键词详情,对应 `KeywordRequest` |
|
|
105
|
+
| `product_query` | 产品搜索,对应 `ProductQuery` |
|
|
106
|
+
| `category_trend` | 类目趋势,对应 `CategoryTrend` |
|
|
107
|
+
| `request_stream_month` | Request 使用和余额,对应 `RequestStreamMonth` |
|
|
108
|
+
| `coin_query` | 积分余额,对应 `CoinQuery` |
|
|
109
|
+
|
|
110
|
+
高频场景优先使用快捷工具。低频场景使用 `sorftime_call`,先用 `sorftime_methods` 和 `sorftime_method_schema` 查询 method 和参数。
|
|
111
|
+
|
|
112
|
+
## 调用示例
|
|
113
|
+
|
|
114
|
+
查询产品详情:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"input": {
|
|
119
|
+
"asin": "B0CVM8TXHP",
|
|
120
|
+
"domain": 1,
|
|
121
|
+
"trend": 1
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
通过路由查询销量估算:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"input": {
|
|
131
|
+
"method": "AsinSalesVolume",
|
|
132
|
+
"domain": 1,
|
|
133
|
+
"params": {
|
|
134
|
+
"ASIN": "B0CVM8TXHP",
|
|
135
|
+
"Page": 1
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
查看某个方法的参数:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"input": {
|
|
146
|
+
"method": "ProductRequest"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 站点映射
|
|
152
|
+
|
|
153
|
+
| domain | 站点 |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| 1 | US 美国 |
|
|
156
|
+
| 2 | GB 英国 |
|
|
157
|
+
| 3 | DE 德国 |
|
|
158
|
+
| 4 | FR 法国 |
|
|
159
|
+
| 5 | IN 印度 |
|
|
160
|
+
| 6 | CA 加拿大 |
|
|
161
|
+
| 7 | JP 日本 |
|
|
162
|
+
| 8 | ES 西班牙 |
|
|
163
|
+
| 9 | IT 意大利 |
|
|
164
|
+
| 10 | MX 墨西哥 |
|
|
165
|
+
| 11 | AE 阿联酋 |
|
|
166
|
+
| 12 | AU 澳大利亚 |
|
|
167
|
+
| 13 | BR 巴西 |
|
|
168
|
+
| 14 | SA 沙特阿拉伯 |
|
|
169
|
+
|
|
170
|
+
IN、AE、AU、BR、SA 不支持历史数据回填。
|
|
171
|
+
|
|
172
|
+
## 返回结构
|
|
173
|
+
|
|
174
|
+
所有真实 Sorftime 请求都返回统一结构:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"endpoint": "ProductRequest",
|
|
179
|
+
"domain": 1,
|
|
180
|
+
"estimatedRequestCost": 1,
|
|
181
|
+
"requestConsumed": 1,
|
|
182
|
+
"requestLeft": 1200,
|
|
183
|
+
"code": 0,
|
|
184
|
+
"message": null,
|
|
185
|
+
"data": {},
|
|
186
|
+
"rawResponse": {}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Agent 应优先读取 `code`、`message`、`data`、`requestConsumed` 和 `requestLeft`。
|
|
191
|
+
|
|
192
|
+
## 安全边界
|
|
193
|
+
|
|
194
|
+
v1 只开放安全取数接口,默认不开放:
|
|
195
|
+
|
|
196
|
+
- 收藏词新增、修改、删除等账户状态变更接口
|
|
197
|
+
- 关键词监控、榜单监控、跟卖库存监控、ASIN 订阅等订阅管理接口
|
|
198
|
+
- 消耗监控点数或订阅点数、但不属于通用取数的接口
|
|
199
|
+
|
|
200
|
+
## 审计日志
|
|
201
|
+
|
|
202
|
+
默认不向标准输出写日志,避免污染 MCP stdio 协议。需要审计记录时,通过环境变量写入本地 JSONL 文件:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
SORFTIME_AUDIT_LOG_PATH=logs/sorftime-mcp-audit.jsonl
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
审计日志会记录 endpoint、domain、参数摘要、估算消耗、实际消耗、剩余 Request、耗时和 Sorftime 返回码。日志不会记录 Sorftime Account-SK。
|
|
209
|
+
|
|
210
|
+
## 仓库
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
https://github.com/ccchenhuohuo/sorftime-mcp
|
|
214
|
+
```
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Sorftime MCP
|
|
2
|
+
|
|
3
|
+
把 Sorftime Enterprise API 封装成 Codex、Claude Code 等 AI Agent 可直接调用的 MCP 服务。
|
|
4
|
+
|
|
5
|
+
本项目默认使用 `stdio` 模式,本地安装后只需要配置 `SORFTIME_API_KEY`。Sorftime Account-SK 保存在使用者自己的 MCP 客户端配置或服务器环境变量中,不写入代码仓库。
|
|
6
|
+
|
|
7
|
+
## 重点
|
|
8
|
+
|
|
9
|
+
- 默认传输:`stdio`
|
|
10
|
+
- 本地必需环境变量:`SORFTIME_API_KEY`
|
|
11
|
+
- 公开 MCP 工具数:10 个
|
|
12
|
+
- 支持安全取数方法:32 个
|
|
13
|
+
- 默认站点:`domain=1`,即美国站
|
|
14
|
+
- 返回统一结构:`endpoint`、`domain`、`estimatedRequestCost`、`requestConsumed`、`requestLeft`、`code`、`message`、`data`、`rawResponse`
|
|
15
|
+
- 默认排除账户修改、订阅管理、监控任务管理类接口
|
|
16
|
+
|
|
17
|
+
## 安装方式
|
|
18
|
+
|
|
19
|
+
### npm 安装
|
|
20
|
+
|
|
21
|
+
Codex 配置:
|
|
22
|
+
|
|
23
|
+
```toml
|
|
24
|
+
[mcp_servers.sorftime]
|
|
25
|
+
command = "npx"
|
|
26
|
+
args = ["-y", "sorftime-mcp"]
|
|
27
|
+
|
|
28
|
+
[mcp_servers.sorftime.env]
|
|
29
|
+
SORFTIME_API_KEY = "你的 Sorftime Account-SK"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Claude Code 安装:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
claude mcp add --scope user \
|
|
36
|
+
-e SORFTIME_API_KEY="你的 Sorftime Account-SK" \
|
|
37
|
+
sorftime -- npx -y sorftime-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### PyPI 安装
|
|
41
|
+
|
|
42
|
+
Codex 配置:
|
|
43
|
+
|
|
44
|
+
```toml
|
|
45
|
+
[mcp_servers.sorftime]
|
|
46
|
+
command = "uvx"
|
|
47
|
+
args = ["sorftime-mcp"]
|
|
48
|
+
|
|
49
|
+
[mcp_servers.sorftime.env]
|
|
50
|
+
SORFTIME_API_KEY = "你的 Sorftime Account-SK"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Claude Code 安装:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add --scope user \
|
|
57
|
+
-e SORFTIME_API_KEY="你的 Sorftime Account-SK" \
|
|
58
|
+
sorftime -- uvx sorftime-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### GitHub 源码安装
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uvx --from git+https://github.com/ccchenhuohuo/sorftime-mcp.git sorftime-mcp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
对应 Codex 配置:
|
|
68
|
+
|
|
69
|
+
```toml
|
|
70
|
+
[mcp_servers.sorftime]
|
|
71
|
+
command = "uvx"
|
|
72
|
+
args = [
|
|
73
|
+
"--from",
|
|
74
|
+
"git+https://github.com/ccchenhuohuo/sorftime-mcp.git",
|
|
75
|
+
"sorftime-mcp"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[mcp_servers.sorftime.env]
|
|
79
|
+
SORFTIME_API_KEY = "你的 Sorftime Account-SK"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 工具列表
|
|
83
|
+
|
|
84
|
+
| 工具 | 用途 |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `sorftime_methods` | 查看支持的方法、分类、消耗、是否异步、是否有快捷工具 |
|
|
87
|
+
| `sorftime_method_schema` | 查看单个方法的参数、示例、站点映射和消耗说明 |
|
|
88
|
+
| `sorftime_call` | 白名单路由工具,用于调用低频安全取数方法 |
|
|
89
|
+
| `product_request` | 产品详情,对应 `ProductRequest` |
|
|
90
|
+
| `category_request` | 类目 Top 100,对应 `CategoryRequest` |
|
|
91
|
+
| `keyword_request` | 关键词详情,对应 `KeywordRequest` |
|
|
92
|
+
| `product_query` | 产品搜索,对应 `ProductQuery` |
|
|
93
|
+
| `category_trend` | 类目趋势,对应 `CategoryTrend` |
|
|
94
|
+
| `request_stream_month` | Request 使用和余额,对应 `RequestStreamMonth` |
|
|
95
|
+
| `coin_query` | 积分余额,对应 `CoinQuery` |
|
|
96
|
+
|
|
97
|
+
高频场景优先使用快捷工具。低频场景使用 `sorftime_call`,先用 `sorftime_methods` 和 `sorftime_method_schema` 查询 method 和参数。
|
|
98
|
+
|
|
99
|
+
## 调用示例
|
|
100
|
+
|
|
101
|
+
查询产品详情:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"input": {
|
|
106
|
+
"asin": "B0CVM8TXHP",
|
|
107
|
+
"domain": 1,
|
|
108
|
+
"trend": 1
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
通过路由查询销量估算:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"input": {
|
|
118
|
+
"method": "AsinSalesVolume",
|
|
119
|
+
"domain": 1,
|
|
120
|
+
"params": {
|
|
121
|
+
"ASIN": "B0CVM8TXHP",
|
|
122
|
+
"Page": 1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
查看某个方法的参数:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"input": {
|
|
133
|
+
"method": "ProductRequest"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 站点映射
|
|
139
|
+
|
|
140
|
+
| domain | 站点 |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| 1 | US 美国 |
|
|
143
|
+
| 2 | GB 英国 |
|
|
144
|
+
| 3 | DE 德国 |
|
|
145
|
+
| 4 | FR 法国 |
|
|
146
|
+
| 5 | IN 印度 |
|
|
147
|
+
| 6 | CA 加拿大 |
|
|
148
|
+
| 7 | JP 日本 |
|
|
149
|
+
| 8 | ES 西班牙 |
|
|
150
|
+
| 9 | IT 意大利 |
|
|
151
|
+
| 10 | MX 墨西哥 |
|
|
152
|
+
| 11 | AE 阿联酋 |
|
|
153
|
+
| 12 | AU 澳大利亚 |
|
|
154
|
+
| 13 | BR 巴西 |
|
|
155
|
+
| 14 | SA 沙特阿拉伯 |
|
|
156
|
+
|
|
157
|
+
IN、AE、AU、BR、SA 不支持历史数据回填。
|
|
158
|
+
|
|
159
|
+
## 返回结构
|
|
160
|
+
|
|
161
|
+
所有真实 Sorftime 请求都返回统一结构:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"endpoint": "ProductRequest",
|
|
166
|
+
"domain": 1,
|
|
167
|
+
"estimatedRequestCost": 1,
|
|
168
|
+
"requestConsumed": 1,
|
|
169
|
+
"requestLeft": 1200,
|
|
170
|
+
"code": 0,
|
|
171
|
+
"message": null,
|
|
172
|
+
"data": {},
|
|
173
|
+
"rawResponse": {}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Agent 应优先读取 `code`、`message`、`data`、`requestConsumed` 和 `requestLeft`。
|
|
178
|
+
|
|
179
|
+
## 安全边界
|
|
180
|
+
|
|
181
|
+
v1 只开放安全取数接口,默认不开放:
|
|
182
|
+
|
|
183
|
+
- 收藏词新增、修改、删除等账户状态变更接口
|
|
184
|
+
- 关键词监控、榜单监控、跟卖库存监控、ASIN 订阅等订阅管理接口
|
|
185
|
+
- 消耗监控点数或订阅点数、但不属于通用取数的接口
|
|
186
|
+
|
|
187
|
+
## 审计日志
|
|
188
|
+
|
|
189
|
+
默认不向标准输出写日志,避免污染 MCP stdio 协议。需要审计记录时,通过环境变量写入本地 JSONL 文件:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
SORFTIME_AUDIT_LOG_PATH=logs/sorftime-mcp-audit.jsonl
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
审计日志会记录 endpoint、domain、参数摘要、估算消耗、实际消耗、剩余 Request、耗时和 Sorftime 返回码。日志不会记录 Sorftime Account-SK。
|
|
196
|
+
|
|
197
|
+
## 仓库
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
https://github.com/ccchenhuohuo/sorftime-mcp
|
|
201
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const userArgs = process.argv.slice(2);
|
|
7
|
+
const pythonArgs = userArgs.length === 0 ? ["stdio"] : userArgs;
|
|
8
|
+
const child = spawn(
|
|
9
|
+
"uvx",
|
|
10
|
+
["--from", "sorftime-mcp", "sorftime-mcp", ...pythonArgs],
|
|
11
|
+
{
|
|
12
|
+
env: process.env,
|
|
13
|
+
stdio: "inherit"
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
child.on("error", (error) => {
|
|
18
|
+
if (error.code === "ENOENT") {
|
|
19
|
+
console.error("未找到 uvx。请先安装 uv:https://docs.astral.sh/uv/getting-started/installation/");
|
|
20
|
+
process.exit(127);
|
|
21
|
+
}
|
|
22
|
+
console.error(`启动 sorftime-mcp 失败:${error.message}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
child.on("exit", (code, signal) => {
|
|
27
|
+
if (signal) {
|
|
28
|
+
process.kill(process.pid, signal);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
process.exit(code ?? 0);
|
|
32
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sorftime-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sorftime API MCP stdio launcher for Codex and Claude Code.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"sorftime-mcp": "bin/sorftime-mcp.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/ccchenhuohuo/sorftime-mcp.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"sorftime",
|
|
19
|
+
"codex",
|
|
20
|
+
"claude-code"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "node --check bin/sorftime-mcp.js"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sorftime-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Stdio MCP server for read-only Sorftime Enterprise API access."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.1.8",
|
|
9
|
+
"fastmcp==3.4.2",
|
|
10
|
+
"httpx>=0.27.2",
|
|
11
|
+
"pydantic>=2.10.6",
|
|
12
|
+
"pydantic-settings>=2.8.1",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
sorftime-mcp = "sorftime_mcp.cli:cli"
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Repository = "https://github.com/ccchenhuohuo/sorftime-mcp"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.3.5",
|
|
24
|
+
"pytest-asyncio>=0.25.3",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
asyncio_mode = "auto"
|
|
33
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
SENSITIVE_KEY_PARTS = ("authorization", "token", "secret", "password", "api_key", "sk")
|
|
8
|
+
LONG_VALUE_KEYS = ("image",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuditLogger:
|
|
12
|
+
def __init__(self, path: Path | None = None, *, emit_stdout: bool = True) -> None:
|
|
13
|
+
self._path = path
|
|
14
|
+
self._emit_stdout = emit_stdout
|
|
15
|
+
if self._path is not None:
|
|
16
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
|
|
18
|
+
def log(self, record: dict[str, Any]) -> None:
|
|
19
|
+
line = json.dumps(record, ensure_ascii=False, separators=(",", ":"))
|
|
20
|
+
if self._emit_stdout:
|
|
21
|
+
sys.stdout.write(f"{line}\n")
|
|
22
|
+
sys.stdout.flush()
|
|
23
|
+
if self._path is not None:
|
|
24
|
+
with self._path.open("a", encoding="utf-8") as file:
|
|
25
|
+
file.write(f"{line}\n")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def utc_now_iso() -> str:
|
|
29
|
+
return datetime.now(timezone.utc).isoformat()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sanitize_for_audit(value: Any) -> Any:
|
|
33
|
+
if isinstance(value, dict):
|
|
34
|
+
sanitized: dict[str, Any] = {}
|
|
35
|
+
for key, item in value.items():
|
|
36
|
+
lower_key = str(key).lower()
|
|
37
|
+
if any(part in lower_key for part in SENSITIVE_KEY_PARTS):
|
|
38
|
+
sanitized[key] = "[REDACTED]"
|
|
39
|
+
elif any(part in lower_key for part in LONG_VALUE_KEYS):
|
|
40
|
+
sanitized[key] = summarize_long_value(item)
|
|
41
|
+
else:
|
|
42
|
+
sanitized[key] = sanitize_for_audit(item)
|
|
43
|
+
return sanitized
|
|
44
|
+
if isinstance(value, list):
|
|
45
|
+
return [sanitize_for_audit(item) for item in value]
|
|
46
|
+
if isinstance(value, str):
|
|
47
|
+
return summarize_long_value(value)
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def summarize_long_value(value: Any) -> Any:
|
|
52
|
+
if not isinstance(value, str):
|
|
53
|
+
return sanitize_for_audit(value)
|
|
54
|
+
if len(value) <= 180:
|
|
55
|
+
return value
|
|
56
|
+
return f"{value[:80]}...[truncated:{len(value)}]"
|