base64-file-mcp 1.0.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.
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: base64-file-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP 插件:将 Base64 编码字符串解码为 docx、pdf、png 等任意格式文件
|
|
5
|
+
Author: CM-Pub
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: base64,converter,docx,file,mcp
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: anyio>=4.0
|
|
12
|
+
Requires-Dist: mcp>=1.0.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# base64-file-mcp
|
|
16
|
+
|
|
17
|
+
MCP 插件:将 Base64 编码字符串解码为任意格式文件(docx、xlsx、pdf、png、jpg、txt 等)。
|
|
18
|
+
|
|
19
|
+
## 3 个工具函数
|
|
20
|
+
|
|
21
|
+
| 工具名 | 功能 |
|
|
22
|
+
|--------|------|
|
|
23
|
+
| `base64_to_file` | Base64 → 文件,写入磁盘,返回文件路径和元数据 |
|
|
24
|
+
| `base64_to_file_and_return_b64` | Base64 → 文件 + 写入磁盘 + 同时返回 Base64 数据 |
|
|
25
|
+
| `file_to_base64` | 读取本地文件 → 编码为 Base64 字符串 |
|
|
26
|
+
|
|
27
|
+
## 在商道平台导入(推荐方式)
|
|
28
|
+
|
|
29
|
+
在商道平台的 MCP 插件配置中,使用 uvx 命令导入:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
uvx --from base64-file-mcp base64-to-file
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
商道平台会自动发现并注册 3 个工具函数。
|
|
36
|
+
|
|
37
|
+
## 本地测试
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1. 安装 uv(如果未安装)
|
|
41
|
+
pip install uv
|
|
42
|
+
|
|
43
|
+
# 2. 直接通过 uvx 运行(不需要提前安装)
|
|
44
|
+
uvx --from base64-file-mcp base64-to-file
|
|
45
|
+
|
|
46
|
+
# 3. 或先安装再运行
|
|
47
|
+
uv pip install -e .
|
|
48
|
+
base64-to-file
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## MCP STDIO 协议交互示例
|
|
52
|
+
|
|
53
|
+
运行后,MCP 服务器通过 stdin/stdout 与平台通信。以下是手动测试步骤:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 列出工具
|
|
57
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | uvx --from base64-file-mcp base64-to-file
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 输出说明
|
|
61
|
+
|
|
62
|
+
`base64_to_file` 工具返回的 JSON:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"success": true,
|
|
67
|
+
"file_path": "C:\\Users\\...\\base64_to_file_xxx\\报告.docx",
|
|
68
|
+
"file_name": "报告.docx",
|
|
69
|
+
"file_size": 24576,
|
|
70
|
+
"mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
71
|
+
"output_dir": "C:\\Users\\...\\base64_to_file_xxx"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 支持的文件格式
|
|
76
|
+
|
|
77
|
+
通过 `file_name` 参数的扩展名自动识别,支持但不限于:
|
|
78
|
+
|
|
79
|
+
- 文档:`.docx` `.xlsx` `.pptx` `.pdf` `.txt` `.md` `.csv`
|
|
80
|
+
- 图片:`.png` `.jpg` `.jpeg` `.gif` `.webp` `.svg`
|
|
81
|
+
- 压缩包:`.zip` `.tar` `.gz`
|
|
82
|
+
- 音视频:`.mp4` `.mp3`
|
|
83
|
+
- 其他任意二进制格式
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# base64-file-mcp
|
|
2
|
+
|
|
3
|
+
MCP 插件:将 Base64 编码字符串解码为任意格式文件(docx、xlsx、pdf、png、jpg、txt 等)。
|
|
4
|
+
|
|
5
|
+
## 3 个工具函数
|
|
6
|
+
|
|
7
|
+
| 工具名 | 功能 |
|
|
8
|
+
|--------|------|
|
|
9
|
+
| `base64_to_file` | Base64 → 文件,写入磁盘,返回文件路径和元数据 |
|
|
10
|
+
| `base64_to_file_and_return_b64` | Base64 → 文件 + 写入磁盘 + 同时返回 Base64 数据 |
|
|
11
|
+
| `file_to_base64` | 读取本地文件 → 编码为 Base64 字符串 |
|
|
12
|
+
|
|
13
|
+
## 在商道平台导入(推荐方式)
|
|
14
|
+
|
|
15
|
+
在商道平台的 MCP 插件配置中,使用 uvx 命令导入:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
uvx --from base64-file-mcp base64-to-file
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
商道平台会自动发现并注册 3 个工具函数。
|
|
22
|
+
|
|
23
|
+
## 本地测试
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1. 安装 uv(如果未安装)
|
|
27
|
+
pip install uv
|
|
28
|
+
|
|
29
|
+
# 2. 直接通过 uvx 运行(不需要提前安装)
|
|
30
|
+
uvx --from base64-file-mcp base64-to-file
|
|
31
|
+
|
|
32
|
+
# 3. 或先安装再运行
|
|
33
|
+
uv pip install -e .
|
|
34
|
+
base64-to-file
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## MCP STDIO 协议交互示例
|
|
38
|
+
|
|
39
|
+
运行后,MCP 服务器通过 stdin/stdout 与平台通信。以下是手动测试步骤:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 列出工具
|
|
43
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | uvx --from base64-file-mcp base64-to-file
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 输出说明
|
|
47
|
+
|
|
48
|
+
`base64_to_file` 工具返回的 JSON:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"success": true,
|
|
53
|
+
"file_path": "C:\\Users\\...\\base64_to_file_xxx\\报告.docx",
|
|
54
|
+
"file_name": "报告.docx",
|
|
55
|
+
"file_size": 24576,
|
|
56
|
+
"mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
57
|
+
"output_dir": "C:\\Users\\...\\base64_to_file_xxx"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 支持的文件格式
|
|
62
|
+
|
|
63
|
+
通过 `file_name` 参数的扩展名自动识别,支持但不限于:
|
|
64
|
+
|
|
65
|
+
- 文档:`.docx` `.xlsx` `.pptx` `.pdf` `.txt` `.md` `.csv`
|
|
66
|
+
- 图片:`.png` `.jpg` `.jpeg` `.gif` `.webp` `.svg`
|
|
67
|
+
- 压缩包:`.zip` `.tar` `.gz`
|
|
68
|
+
- 音视频:`.mp4` `.mp3`
|
|
69
|
+
- 其他任意二进制格式
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""base64 → 文件 转换 MCP 服务器。
|
|
2
|
+
|
|
3
|
+
将 Base64 编码字符串解码为 docx、pdf、png 等任意格式文件。
|
|
4
|
+
以 STDIO 传输方式运行,供商道平台通过 uvx 拉取调用。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import json
|
|
11
|
+
import tempfile
|
|
12
|
+
import uuid
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from mcp.server import Server, NotificationOptions
|
|
17
|
+
from mcp.server.models import InitializationOptions
|
|
18
|
+
from mcp.types import EmbeddedResource, ImageContent, TextContent, Tool
|
|
19
|
+
|
|
20
|
+
import mcp.server.stdio
|
|
21
|
+
|
|
22
|
+
# ── 常量 ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
SERVER_NAME = "base64-to-file"
|
|
25
|
+
SERVER_VERSION = "1.0.0"
|
|
26
|
+
|
|
27
|
+
# ── MCP Server 实例 ─────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
server = Server(SERVER_NAME)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── 辅助函数 ─────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
def _safe_filename(file_name: str) -> str:
|
|
35
|
+
"""清理文件名,防止路径穿越。"""
|
|
36
|
+
name = Path(file_name).name
|
|
37
|
+
if not name:
|
|
38
|
+
name = f"output_{uuid.uuid4().hex[:8]}"
|
|
39
|
+
return name
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _guess_mime(ext: str) -> str:
|
|
43
|
+
"""根据扩展名猜测 MIME 类型。"""
|
|
44
|
+
mime_map = {
|
|
45
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
46
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
47
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
48
|
+
".pdf": "application/pdf",
|
|
49
|
+
".png": "image/png",
|
|
50
|
+
".jpg": "image/jpeg",
|
|
51
|
+
".jpeg": "image/jpeg",
|
|
52
|
+
".gif": "image/gif",
|
|
53
|
+
".webp": "image/webp",
|
|
54
|
+
".svg": "image/svg+xml",
|
|
55
|
+
".txt": "text/plain",
|
|
56
|
+
".md": "text/markdown",
|
|
57
|
+
".json": "application/json",
|
|
58
|
+
".xml": "application/xml",
|
|
59
|
+
".csv": "text/csv",
|
|
60
|
+
".zip": "application/zip",
|
|
61
|
+
".tar": "application/x-tar",
|
|
62
|
+
".gz": "application/gzip",
|
|
63
|
+
".mp4": "video/mp4",
|
|
64
|
+
".mp3": "audio/mpeg",
|
|
65
|
+
}
|
|
66
|
+
return mime_map.get(ext.lower(), "application/octet-stream")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── 工具定义 ─────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
@server.list_tools()
|
|
72
|
+
async def list_tools() -> list[Tool]:
|
|
73
|
+
return [
|
|
74
|
+
Tool(
|
|
75
|
+
name="base64_to_file",
|
|
76
|
+
description="将Base64编码的字符串解码为二进制文件并写入磁盘。"
|
|
77
|
+
"支持任意文件格式(docx、xlsx、pdf、png、jpg、txt 等),"
|
|
78
|
+
"通过 file_name 参数的扩展名自动识别格式。",
|
|
79
|
+
inputSchema={
|
|
80
|
+
"type": "object",
|
|
81
|
+
"properties": {
|
|
82
|
+
"b64_str": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Base64编码的文件数据字符串",
|
|
85
|
+
},
|
|
86
|
+
"file_name": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "输出文件名(含扩展名),如 报告.docx、photo.png",
|
|
89
|
+
"default": "output.docx",
|
|
90
|
+
},
|
|
91
|
+
"output_dir": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "输出目录(可选,默认使用平台临时目录)",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
"required": ["b64_str"],
|
|
97
|
+
},
|
|
98
|
+
),
|
|
99
|
+
Tool(
|
|
100
|
+
name="base64_to_file_and_return_b64",
|
|
101
|
+
description="将Base64编码的字符串解码后,同时写入磁盘并返回文件内容(Base64编码)和元数据。"
|
|
102
|
+
"适用于既需要文件落盘又需要将数据回传给调用方的场景。",
|
|
103
|
+
inputSchema={
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {
|
|
106
|
+
"b64_str": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "Base64编码的文件数据字符串",
|
|
109
|
+
},
|
|
110
|
+
"file_name": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "输出文件名(含扩展名),如 报告.docx",
|
|
113
|
+
"default": "output.docx",
|
|
114
|
+
},
|
|
115
|
+
"output_dir": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "输出目录(可选,默认使用平台临时目录)",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
"required": ["b64_str"],
|
|
121
|
+
},
|
|
122
|
+
),
|
|
123
|
+
Tool(
|
|
124
|
+
name="file_to_base64",
|
|
125
|
+
description="读取本地文件并将其内容编码为Base64字符串。"
|
|
126
|
+
"用于将已有文件编码为Base64以便在其他工具中传输。",
|
|
127
|
+
inputSchema={
|
|
128
|
+
"type": "object",
|
|
129
|
+
"properties": {
|
|
130
|
+
"file_path": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"description": "要读取的本地文件路径",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
"required": ["file_path"],
|
|
136
|
+
},
|
|
137
|
+
),
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@server.call_tool()
|
|
142
|
+
async def call_tool(
|
|
143
|
+
name: str, arguments: dict[str, Any]
|
|
144
|
+
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
145
|
+
if name == "base64_to_file":
|
|
146
|
+
return await _handle_base64_to_file(arguments, return_b64=False)
|
|
147
|
+
elif name == "base64_to_file_and_return_b64":
|
|
148
|
+
return await _handle_base64_to_file(arguments, return_b64=True)
|
|
149
|
+
elif name == "file_to_base64":
|
|
150
|
+
return await _handle_file_to_base64(arguments)
|
|
151
|
+
else:
|
|
152
|
+
raise ValueError(f"未知工具: {name}")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def _handle_base64_to_file(
|
|
156
|
+
arguments: dict[str, Any], return_b64: bool = False
|
|
157
|
+
) -> list[TextContent]:
|
|
158
|
+
b64_str: str = arguments.get("b64_str", "")
|
|
159
|
+
file_name: str = arguments.get("file_name", "output.docx")
|
|
160
|
+
output_dir: str | None = arguments.get("output_dir")
|
|
161
|
+
|
|
162
|
+
if not b64_str:
|
|
163
|
+
return [TextContent(type="text", text=json.dumps({
|
|
164
|
+
"success": False,
|
|
165
|
+
"error": "b64_str 参数不能为空",
|
|
166
|
+
}, ensure_ascii=False))]
|
|
167
|
+
|
|
168
|
+
safe_name = _safe_filename(file_name)
|
|
169
|
+
ext = Path(safe_name).suffix
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# 容忍 base64 中可能出现的换行/空格
|
|
173
|
+
compact = "".join(b64_str.split())
|
|
174
|
+
file_bytes = base64.b64decode(compact, validate=False)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
return [TextContent(type="text", text=json.dumps({
|
|
177
|
+
"success": False,
|
|
178
|
+
"error": f"Base64解码失败: {str(e)}",
|
|
179
|
+
}, ensure_ascii=False))]
|
|
180
|
+
|
|
181
|
+
if output_dir:
|
|
182
|
+
out_dir = Path(output_dir)
|
|
183
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
else:
|
|
185
|
+
out_dir = Path(tempfile.mkdtemp(prefix="base64_to_file_"))
|
|
186
|
+
|
|
187
|
+
output_path = out_dir / safe_name
|
|
188
|
+
output_path.write_bytes(file_bytes)
|
|
189
|
+
|
|
190
|
+
file_size = len(file_bytes)
|
|
191
|
+
mime_type = _guess_mime(ext)
|
|
192
|
+
|
|
193
|
+
result: dict[str, Any] = {
|
|
194
|
+
"success": True,
|
|
195
|
+
"file_path": str(output_path.resolve()),
|
|
196
|
+
"file_name": safe_name,
|
|
197
|
+
"file_size": file_size,
|
|
198
|
+
"mime_type": mime_type,
|
|
199
|
+
"output_dir": str(out_dir.resolve()),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if return_b64:
|
|
203
|
+
result["b64_str"] = base64.b64encode(file_bytes).decode("ascii")
|
|
204
|
+
|
|
205
|
+
return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False))]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def _handle_file_to_base64(
|
|
209
|
+
arguments: dict[str, Any]
|
|
210
|
+
) -> list[TextContent]:
|
|
211
|
+
file_path: str = arguments.get("file_path", "")
|
|
212
|
+
|
|
213
|
+
if not file_path:
|
|
214
|
+
return [TextContent(type="text", text=json.dumps({
|
|
215
|
+
"success": False,
|
|
216
|
+
"error": "file_path 参数不能为空",
|
|
217
|
+
}, ensure_ascii=False))]
|
|
218
|
+
|
|
219
|
+
path = Path(file_path)
|
|
220
|
+
if not path.exists():
|
|
221
|
+
return [TextContent(type="text", text=json.dumps({
|
|
222
|
+
"success": False,
|
|
223
|
+
"error": f"文件不存在: {file_path}",
|
|
224
|
+
}, ensure_ascii=False))]
|
|
225
|
+
|
|
226
|
+
if not path.is_file():
|
|
227
|
+
return [TextContent(type="text", text=json.dumps({
|
|
228
|
+
"success": False,
|
|
229
|
+
"error": f"路径不是文件: {file_path}",
|
|
230
|
+
}, ensure_ascii=False))]
|
|
231
|
+
|
|
232
|
+
file_bytes = path.read_bytes()
|
|
233
|
+
b64_str = base64.b64encode(file_bytes).decode("ascii")
|
|
234
|
+
mime_type = _guess_mime(path.suffix)
|
|
235
|
+
|
|
236
|
+
return [TextContent(type="text", text=json.dumps({
|
|
237
|
+
"success": True,
|
|
238
|
+
"file_path": str(path.resolve()),
|
|
239
|
+
"file_name": path.name,
|
|
240
|
+
"file_size": len(file_bytes),
|
|
241
|
+
"mime_type": mime_type,
|
|
242
|
+
"b64_str": b64_str,
|
|
243
|
+
}, ensure_ascii=False))]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ── 入口 ─────────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
def main():
|
|
249
|
+
"""MCP 服务器入口函数(供 uvx / CLI 调用)。"""
|
|
250
|
+
import anyio
|
|
251
|
+
|
|
252
|
+
async def _run():
|
|
253
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
254
|
+
await server.run(
|
|
255
|
+
read_stream,
|
|
256
|
+
write_stream,
|
|
257
|
+
InitializationOptions(
|
|
258
|
+
server_name=SERVER_NAME,
|
|
259
|
+
server_version=SERVER_VERSION,
|
|
260
|
+
capabilities=server.get_capabilities(
|
|
261
|
+
notification_options=NotificationOptions(),
|
|
262
|
+
experimental_capabilities={},
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
anyio.run(_run)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if __name__ == "__main__":
|
|
271
|
+
main()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "base64-file-mcp"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP 插件:将 Base64 编码字符串解码为 docx、pdf、png 等任意格式文件"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CM-Pub" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "base64", "docx", "file", "converter"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp>=1.0.0",
|
|
23
|
+
"anyio>=4.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
base64-to-file = "base64_file_mcp:main"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["base64_file_mcp"]
|