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,12 @@
1
+ .venv/
2
+ .pytest_cache/
3
+ .pytest_tmp/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ out/
8
+ ContractAgentRuns/
9
+ ContractAgentProfiles/
10
+
11
+ # 配置/参考目录(含 COS 密钥等敏感文件,勿提交)
12
+ config/
@@ -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,6 @@
1
+ """支持 `python -m base64_file_mcp` 直接运行。"""
2
+
3
+ from base64_file_mcp import main
4
+
5
+ if __name__ == "__main__":
6
+ 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"]