lumio-admin-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.
- lumio_admin_mcp-0.1.0/.gitignore +7 -0
- lumio_admin_mcp-0.1.0/LICENSE +21 -0
- lumio_admin_mcp-0.1.0/PKG-INFO +187 -0
- lumio_admin_mcp-0.1.0/README.md +160 -0
- lumio_admin_mcp-0.1.0/pyproject.toml +41 -0
- lumio_admin_mcp-0.1.0/src/lumio_admin_mcp/__init__.py +3 -0
- lumio_admin_mcp-0.1.0/src/lumio_admin_mcp/__main__.py +6 -0
- lumio_admin_mcp-0.1.0/src/lumio_admin_mcp/client.py +63 -0
- lumio_admin_mcp-0.1.0/src/lumio_admin_mcp/server.py +390 -0
- lumio_admin_mcp-0.1.0/tests/test_client_paths.py +283 -0
- lumio_admin_mcp-0.1.0/uv.lock +823 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cyetstar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lumio-admin-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Lumio admin APIs (诗文 / 词库 / 字库) via Bot Token
|
|
5
|
+
Project-URL: Homepage, https://github.com/cyetstar/lumio
|
|
6
|
+
Project-URL: Repository, https://github.com/cyetstar/lumio
|
|
7
|
+
Project-URL: Documentation, https://github.com/cyetstar/lumio/tree/main/mcp/lumio-admin
|
|
8
|
+
Project-URL: Issues, https://github.com/cyetstar/lumio/issues
|
|
9
|
+
Author: cyetstar
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: admin,bot,lumio,mcp
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: mcp>=1.9.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# lumio-admin-mcp
|
|
29
|
+
|
|
30
|
+
MCP server (stdio) that exposes Lumio **诗文库 / 词库 / 字库** admin APIs to hosts such as **Cursor** and **Grok Bot**, authenticated with a long-lived Bot Token.
|
|
31
|
+
|
|
32
|
+
**PyPI:** [`lumio-admin-mcp`](https://pypi.org/project/lumio-admin-mcp/)
|
|
33
|
+
**源码:** [`mcp/lumio-admin`](https://github.com/cyetstar/lumio/tree/main/mcp/lumio-admin)
|
|
34
|
+
|
|
35
|
+
**设计规格:**
|
|
36
|
+
|
|
37
|
+
- 一期诗文:`docs/superpowers/specs/2026-10-22-lumio-classic-bot-mcp-design.md`
|
|
38
|
+
- 二期词库/字库:`docs/superpowers/specs/2026-10-23-lumio-mcp-words-hanzi.md`
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Python 3.11+
|
|
43
|
+
- [uv](https://docs.astral.sh/uv/)(推荐用 `uvx` 直接跑)
|
|
44
|
+
- 可达的 Lumio API(`LUMIO_API_BASE_URL`)
|
|
45
|
+
- Bot Token(scopes:`classic` / `words` / `hanzi`)
|
|
46
|
+
|
|
47
|
+
## 1. Create a Bot Token
|
|
48
|
+
|
|
49
|
+
在 monorepo 的 `backend` 目录:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd backend && ../.venv/bin/python -m scripts.create_bot_token --name grok-bot
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
默认 scopes:`classic,words,hanzi`。仅诗文时可:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
../.venv/bin/python -m scripts.create_bot_token --name classic-only --scopes classic
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
stdout 仅打印一次:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
BOT_TOKEN=lumio_bot_…
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 2. Cursor / Grok Bot MCP(推荐:PyPI + uvx)
|
|
68
|
+
|
|
69
|
+
发布到 PyPI 后,无需本地 checkout:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"lumio-admin": {
|
|
75
|
+
"command": "uvx",
|
|
76
|
+
"args": ["lumio-admin-mcp"],
|
|
77
|
+
"env": {
|
|
78
|
+
"LUMIO_API_BASE_URL": "https://your-lumio-host",
|
|
79
|
+
"LUMIO_BOT_TOKEN": "lumio_bot_…"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
固定版本示例:`"args": ["lumio-admin-mcp==0.1.0"]`。
|
|
87
|
+
|
|
88
|
+
### 本地开发(未发布 / 改源码时)
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"lumio-admin": {
|
|
94
|
+
"command": "uv",
|
|
95
|
+
"args": [
|
|
96
|
+
"--directory",
|
|
97
|
+
"/ABS/PATH/mcp/lumio-admin",
|
|
98
|
+
"run",
|
|
99
|
+
"lumio-admin-mcp"
|
|
100
|
+
],
|
|
101
|
+
"env": {
|
|
102
|
+
"LUMIO_API_BASE_URL": "http://127.0.0.1:7061",
|
|
103
|
+
"LUMIO_BOT_TOKEN": "lumio_bot_…"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 3. Publish to PyPI(维护者)
|
|
111
|
+
|
|
112
|
+
1. 在 [PyPI](https://pypi.org) 创建账号,Account settings → API tokens → 生成 token(`pypi-…`)。
|
|
113
|
+
2. 在本目录构建并发布:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
cd mcp/lumio-admin
|
|
117
|
+
uv build
|
|
118
|
+
uv publish
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`uv publish` 会提示输入用户名(可用 `__token__`)与 password(粘贴 API token)。也可用环境变量:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
export UV_PUBLISH_TOKEN=pypi-…
|
|
125
|
+
uv publish
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
发版前请 bump `pyproject.toml` 的 `version`,再 `uv build && uv publish`。
|
|
129
|
+
|
|
130
|
+
## 4. Local install / debug
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd mcp/lumio-admin
|
|
134
|
+
uv sync
|
|
135
|
+
export LUMIO_API_BASE_URL=http://127.0.0.1:7061
|
|
136
|
+
export LUMIO_BOT_TOKEN=lumio_bot_…
|
|
137
|
+
uv run lumio-admin-mcp
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
stdio:等待 MCP host;缺环境变量时 stderr 提示并以 code 1 退出。
|
|
141
|
+
|
|
142
|
+
## 5. Example prompts
|
|
143
|
+
|
|
144
|
+
- 「列出 online 古诗」
|
|
145
|
+
- 「给静夜思加注释 床=井栏边 并上线」
|
|
146
|
+
- 「在采实每日背诵词库新建分组 Unit 3-2,批量写入这些短语」
|
|
147
|
+
- 「列出字库,往某某字库的某分组批量加汉字」
|
|
148
|
+
|
|
149
|
+
## 6. Tools
|
|
150
|
+
|
|
151
|
+
### 诗文库(classic)
|
|
152
|
+
|
|
153
|
+
| Tool | HTTP | Notes |
|
|
154
|
+
|------|------|--------|
|
|
155
|
+
| `list_works` | GET `/api/admin/classic/works` | `kind`, `q`, `status`, `page`, `page_size` |
|
|
156
|
+
| `get_work` | GET `.../works/{id}` | |
|
|
157
|
+
| `create_work` | POST `.../works` | may set `status=online` |
|
|
158
|
+
| `update_work` | PATCH `.../works/{id}` | |
|
|
159
|
+
| `delete_work` | DELETE `.../works/{id}` | single work only |
|
|
160
|
+
| `list_glosses` | GET `.../works/{id}/glosses` | |
|
|
161
|
+
| `create_gloss` | POST `.../works/{id}/glosses` | |
|
|
162
|
+
| `update_gloss` | PATCH `.../glosses/{id}` | |
|
|
163
|
+
| `delete_gloss` | DELETE `.../glosses/{id}` | |
|
|
164
|
+
|
|
165
|
+
### 词库(words)
|
|
166
|
+
|
|
167
|
+
| Tool | HTTP | Notes |
|
|
168
|
+
|------|------|--------|
|
|
169
|
+
| `list_word_libraries` | GET `/api/admin/word-libraries` | |
|
|
170
|
+
| `list_word_groups` | GET `.../{library_id}/groups` | |
|
|
171
|
+
| `create_word_group` | POST `.../{library_id}/groups` | 不新建词库 |
|
|
172
|
+
| `batch_add_words` | POST `.../groups/{group_id}/words/batch` | |
|
|
173
|
+
|
|
174
|
+
### 字库(hanzi)
|
|
175
|
+
|
|
176
|
+
| Tool | HTTP | Notes |
|
|
177
|
+
|------|------|--------|
|
|
178
|
+
| `list_hanzi_libraries` | GET `/api/admin/hanzi-libraries` | |
|
|
179
|
+
| `list_hanzi_groups` | GET `.../{library_id}/groups` | |
|
|
180
|
+
| `create_hanzi_group` | POST `.../{library_id}/groups` | 不新建字库 |
|
|
181
|
+
| `batch_add_hanzis` | POST `.../groups/{group_id}/hanzis/batch` | |
|
|
182
|
+
|
|
183
|
+
## 7. Security
|
|
184
|
+
|
|
185
|
+
- **Never commit** `LUMIO_BOT_TOKEN` / PyPI token。
|
|
186
|
+
- Bot Token 按 `scopes` 限制路径前缀。
|
|
187
|
+
- 吊销:`cd backend && ../.venv/bin/python -m scripts.revoke_bot_token --id <id>`
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# lumio-admin-mcp
|
|
2
|
+
|
|
3
|
+
MCP server (stdio) that exposes Lumio **诗文库 / 词库 / 字库** admin APIs to hosts such as **Cursor** and **Grok Bot**, authenticated with a long-lived Bot Token.
|
|
4
|
+
|
|
5
|
+
**PyPI:** [`lumio-admin-mcp`](https://pypi.org/project/lumio-admin-mcp/)
|
|
6
|
+
**源码:** [`mcp/lumio-admin`](https://github.com/cyetstar/lumio/tree/main/mcp/lumio-admin)
|
|
7
|
+
|
|
8
|
+
**设计规格:**
|
|
9
|
+
|
|
10
|
+
- 一期诗文:`docs/superpowers/specs/2026-10-22-lumio-classic-bot-mcp-design.md`
|
|
11
|
+
- 二期词库/字库:`docs/superpowers/specs/2026-10-23-lumio-mcp-words-hanzi.md`
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Python 3.11+
|
|
16
|
+
- [uv](https://docs.astral.sh/uv/)(推荐用 `uvx` 直接跑)
|
|
17
|
+
- 可达的 Lumio API(`LUMIO_API_BASE_URL`)
|
|
18
|
+
- Bot Token(scopes:`classic` / `words` / `hanzi`)
|
|
19
|
+
|
|
20
|
+
## 1. Create a Bot Token
|
|
21
|
+
|
|
22
|
+
在 monorepo 的 `backend` 目录:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd backend && ../.venv/bin/python -m scripts.create_bot_token --name grok-bot
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
默认 scopes:`classic,words,hanzi`。仅诗文时可:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
../.venv/bin/python -m scripts.create_bot_token --name classic-only --scopes classic
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
stdout 仅打印一次:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
BOT_TOKEN=lumio_bot_…
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 2. Cursor / Grok Bot MCP(推荐:PyPI + uvx)
|
|
41
|
+
|
|
42
|
+
发布到 PyPI 后,无需本地 checkout:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"lumio-admin": {
|
|
48
|
+
"command": "uvx",
|
|
49
|
+
"args": ["lumio-admin-mcp"],
|
|
50
|
+
"env": {
|
|
51
|
+
"LUMIO_API_BASE_URL": "https://your-lumio-host",
|
|
52
|
+
"LUMIO_BOT_TOKEN": "lumio_bot_…"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
固定版本示例:`"args": ["lumio-admin-mcp==0.1.0"]`。
|
|
60
|
+
|
|
61
|
+
### 本地开发(未发布 / 改源码时)
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"lumio-admin": {
|
|
67
|
+
"command": "uv",
|
|
68
|
+
"args": [
|
|
69
|
+
"--directory",
|
|
70
|
+
"/ABS/PATH/mcp/lumio-admin",
|
|
71
|
+
"run",
|
|
72
|
+
"lumio-admin-mcp"
|
|
73
|
+
],
|
|
74
|
+
"env": {
|
|
75
|
+
"LUMIO_API_BASE_URL": "http://127.0.0.1:7061",
|
|
76
|
+
"LUMIO_BOT_TOKEN": "lumio_bot_…"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 3. Publish to PyPI(维护者)
|
|
84
|
+
|
|
85
|
+
1. 在 [PyPI](https://pypi.org) 创建账号,Account settings → API tokens → 生成 token(`pypi-…`)。
|
|
86
|
+
2. 在本目录构建并发布:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd mcp/lumio-admin
|
|
90
|
+
uv build
|
|
91
|
+
uv publish
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`uv publish` 会提示输入用户名(可用 `__token__`)与 password(粘贴 API token)。也可用环境变量:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
export UV_PUBLISH_TOKEN=pypi-…
|
|
98
|
+
uv publish
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
发版前请 bump `pyproject.toml` 的 `version`,再 `uv build && uv publish`。
|
|
102
|
+
|
|
103
|
+
## 4. Local install / debug
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd mcp/lumio-admin
|
|
107
|
+
uv sync
|
|
108
|
+
export LUMIO_API_BASE_URL=http://127.0.0.1:7061
|
|
109
|
+
export LUMIO_BOT_TOKEN=lumio_bot_…
|
|
110
|
+
uv run lumio-admin-mcp
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
stdio:等待 MCP host;缺环境变量时 stderr 提示并以 code 1 退出。
|
|
114
|
+
|
|
115
|
+
## 5. Example prompts
|
|
116
|
+
|
|
117
|
+
- 「列出 online 古诗」
|
|
118
|
+
- 「给静夜思加注释 床=井栏边 并上线」
|
|
119
|
+
- 「在采实每日背诵词库新建分组 Unit 3-2,批量写入这些短语」
|
|
120
|
+
- 「列出字库,往某某字库的某分组批量加汉字」
|
|
121
|
+
|
|
122
|
+
## 6. Tools
|
|
123
|
+
|
|
124
|
+
### 诗文库(classic)
|
|
125
|
+
|
|
126
|
+
| Tool | HTTP | Notes |
|
|
127
|
+
|------|------|--------|
|
|
128
|
+
| `list_works` | GET `/api/admin/classic/works` | `kind`, `q`, `status`, `page`, `page_size` |
|
|
129
|
+
| `get_work` | GET `.../works/{id}` | |
|
|
130
|
+
| `create_work` | POST `.../works` | may set `status=online` |
|
|
131
|
+
| `update_work` | PATCH `.../works/{id}` | |
|
|
132
|
+
| `delete_work` | DELETE `.../works/{id}` | single work only |
|
|
133
|
+
| `list_glosses` | GET `.../works/{id}/glosses` | |
|
|
134
|
+
| `create_gloss` | POST `.../works/{id}/glosses` | |
|
|
135
|
+
| `update_gloss` | PATCH `.../glosses/{id}` | |
|
|
136
|
+
| `delete_gloss` | DELETE `.../glosses/{id}` | |
|
|
137
|
+
|
|
138
|
+
### 词库(words)
|
|
139
|
+
|
|
140
|
+
| Tool | HTTP | Notes |
|
|
141
|
+
|------|------|--------|
|
|
142
|
+
| `list_word_libraries` | GET `/api/admin/word-libraries` | |
|
|
143
|
+
| `list_word_groups` | GET `.../{library_id}/groups` | |
|
|
144
|
+
| `create_word_group` | POST `.../{library_id}/groups` | 不新建词库 |
|
|
145
|
+
| `batch_add_words` | POST `.../groups/{group_id}/words/batch` | |
|
|
146
|
+
|
|
147
|
+
### 字库(hanzi)
|
|
148
|
+
|
|
149
|
+
| Tool | HTTP | Notes |
|
|
150
|
+
|------|------|--------|
|
|
151
|
+
| `list_hanzi_libraries` | GET `/api/admin/hanzi-libraries` | |
|
|
152
|
+
| `list_hanzi_groups` | GET `.../{library_id}/groups` | |
|
|
153
|
+
| `create_hanzi_group` | POST `.../{library_id}/groups` | 不新建字库 |
|
|
154
|
+
| `batch_add_hanzis` | POST `.../groups/{group_id}/hanzis/batch` | |
|
|
155
|
+
|
|
156
|
+
## 7. Security
|
|
157
|
+
|
|
158
|
+
- **Never commit** `LUMIO_BOT_TOKEN` / PyPI token。
|
|
159
|
+
- Bot Token 按 `scopes` 限制路径前缀。
|
|
160
|
+
- 吊销:`cd backend && ../.venv/bin/python -m scripts.revoke_bot_token --id <id>`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lumio-admin-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for Lumio admin APIs (诗文 / 词库 / 字库) via Bot Token"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "cyetstar" }]
|
|
9
|
+
keywords = ["mcp", "lumio", "admin", "bot"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp>=1.9.0",
|
|
23
|
+
"httpx>=0.27.0",
|
|
24
|
+
"pydantic>=2.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/cyetstar/lumio"
|
|
29
|
+
Repository = "https://github.com/cyetstar/lumio"
|
|
30
|
+
Documentation = "https://github.com/cyetstar/lumio/tree/main/mcp/lumio-admin"
|
|
31
|
+
Issues = "https://github.com/cyetstar/lumio/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
lumio-admin-mcp = "lumio_admin_mcp.server:main"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["hatchling"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/lumio_admin_mcp"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""HTTP client for Lumio admin APIs used by MCP (classic / words / hanzi)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
CLASSIC_API_PREFIX = "/api/admin/classic"
|
|
10
|
+
WORDS_API_PREFIX = "/api/admin/word-libraries"
|
|
11
|
+
HANZI_API_PREFIX = "/api/admin/hanzi-libraries"
|
|
12
|
+
|
|
13
|
+
# Back-compat alias for tests and callers that imported API_PREFIX.
|
|
14
|
+
API_PREFIX = CLASSIC_API_PREFIX
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClassicApiError(Exception):
|
|
18
|
+
def __init__(self, status_code: int, detail: object):
|
|
19
|
+
self.status_code = status_code
|
|
20
|
+
self.detail = detail
|
|
21
|
+
super().__init__(f"{status_code}: {detail}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ClassicApiClient:
|
|
25
|
+
def __init__(self, base_url: str, token: str):
|
|
26
|
+
self._base = base_url.rstrip("/")
|
|
27
|
+
self._token = token
|
|
28
|
+
|
|
29
|
+
def _full_path(self, path: str, *, prefix: str = CLASSIC_API_PREFIX) -> str:
|
|
30
|
+
if path.startswith("/api/admin/"):
|
|
31
|
+
return path
|
|
32
|
+
if path in ("", "/"):
|
|
33
|
+
return prefix
|
|
34
|
+
if not path.startswith("/"):
|
|
35
|
+
path = f"/{path}"
|
|
36
|
+
return f"{prefix}{path}"
|
|
37
|
+
|
|
38
|
+
async def request(
|
|
39
|
+
self,
|
|
40
|
+
method: str,
|
|
41
|
+
path: str,
|
|
42
|
+
*,
|
|
43
|
+
json: Any | None = None,
|
|
44
|
+
params: dict[str, Any] | None = None,
|
|
45
|
+
prefix: str = CLASSIC_API_PREFIX,
|
|
46
|
+
) -> Any:
|
|
47
|
+
async with httpx.AsyncClient(base_url=self._base, timeout=60.0) as client:
|
|
48
|
+
r = await client.request(
|
|
49
|
+
method,
|
|
50
|
+
self._full_path(path, prefix=prefix),
|
|
51
|
+
headers={"Authorization": f"Bearer {self._token}"},
|
|
52
|
+
json=json,
|
|
53
|
+
params=params,
|
|
54
|
+
)
|
|
55
|
+
if r.status_code >= 400:
|
|
56
|
+
try:
|
|
57
|
+
detail = r.json().get("detail", r.text)
|
|
58
|
+
except Exception:
|
|
59
|
+
detail = r.text
|
|
60
|
+
raise ClassicApiError(r.status_code, detail)
|
|
61
|
+
if r.status_code == 204 or not r.content:
|
|
62
|
+
return None
|
|
63
|
+
return r.json()
|