file-convert-mcp 0.1.2__py3-none-any.whl
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.
- file_convert_mcp/__init__.py +3 -0
- file_convert_mcp/server.py +254 -0
- file_convert_mcp-0.1.2.dist-info/METADATA +223 -0
- file_convert_mcp-0.1.2.dist-info/RECORD +7 -0
- file_convert_mcp-0.1.2.dist-info/WHEEL +4 -0
- file_convert_mcp-0.1.2.dist-info/entry_points.txt +2 -0
- file_convert_mcp-0.1.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import re
|
|
4
|
+
import magic
|
|
5
|
+
import json
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
import aiohttp
|
|
10
|
+
from starlette.responses import JSONResponse
|
|
11
|
+
import asyncio
|
|
12
|
+
|
|
13
|
+
# Load environment variables
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
# Initialize FastMCP
|
|
17
|
+
mcp = FastMCP("file-convert-mcp")
|
|
18
|
+
base_url = 'https://convert.guru'
|
|
19
|
+
MAX_FILE_SIZE = 40 * 1024 * 1024
|
|
20
|
+
api_key = os.getenv("CONVERT_GURU_API_KEY")
|
|
21
|
+
if api_key is None:
|
|
22
|
+
api_key = ''
|
|
23
|
+
|
|
24
|
+
@mcp.custom_route("/health", methods=["GET"])
|
|
25
|
+
async def health_check(request):
|
|
26
|
+
return JSONResponse({"status": "healthy", "service": "mcp-server"})
|
|
27
|
+
|
|
28
|
+
@mcp.custom_route("/tools", methods=["GET"])
|
|
29
|
+
async def list_tools(request):
|
|
30
|
+
tools_info = []
|
|
31
|
+
for tool_name, tool in (await mcp.get_tools()).items():
|
|
32
|
+
tools_info.append({
|
|
33
|
+
"name": tool_name,
|
|
34
|
+
"description": tool.description or "No description available",
|
|
35
|
+
"async": asyncio.iscoroutinefunction(tool.fn)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return JSONResponse({
|
|
39
|
+
"tools": tools_info,
|
|
40
|
+
"total_tools": len(tools_info)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
async def detect_file_type(file_path: str) -> Dict[str, Any]:
|
|
45
|
+
"""Detect file type by sending the first 200 bytes of the file to Convert.Guru API."""
|
|
46
|
+
if not file_path:
|
|
47
|
+
return {"error": "file_path parameter is required"}
|
|
48
|
+
|
|
49
|
+
if not os.path.exists(file_path):
|
|
50
|
+
return {"error": f"File not found (use the absolute file path): {file_path}"}
|
|
51
|
+
|
|
52
|
+
if not os.path.isfile(file_path):
|
|
53
|
+
return {"error": f"Not a file: {file_path}"}
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
with open(file_path, "rb") as f:
|
|
57
|
+
first_200_bytes = f.read(200) # Read only the first 200 bytes
|
|
58
|
+
|
|
59
|
+
_, file_extension = os.path.splitext(file_path)
|
|
60
|
+
file_extension = file_extension.lstrip('.') # Remove dot
|
|
61
|
+
filename = os.path.basename(file_path)
|
|
62
|
+
file_size_bytes = os.path.getsize(file_path)
|
|
63
|
+
size_hex = hex(file_size_bytes)
|
|
64
|
+
mime_type = magic.Magic(mime=True).from_file(file_path)
|
|
65
|
+
|
|
66
|
+
data = [file_extension,size_hex,mime_type,filename]
|
|
67
|
+
separator = '\xFE'
|
|
68
|
+
joined_data = separator.join(data)
|
|
69
|
+
headers = {'cache-control': 'no-cache', 'api-key': api_key}
|
|
70
|
+
|
|
71
|
+
data_array = bytearray()
|
|
72
|
+
for char in joined_data:
|
|
73
|
+
data_array.append(ord(char) & 0xFF)
|
|
74
|
+
|
|
75
|
+
data_array.append(0xFE)
|
|
76
|
+
data_array.extend(first_200_bytes)
|
|
77
|
+
|
|
78
|
+
async with aiohttp.ClientSession() as session:
|
|
79
|
+
async with session.post(base_url + '/api/v1/detect_file_type', data=data_array, headers=headers) as response:
|
|
80
|
+
if response.status == 200:
|
|
81
|
+
content = await response.text()
|
|
82
|
+
comment_pattern = re.compile(r'<!--.+-->')
|
|
83
|
+
# Remove all occurrences of the pattern from the string
|
|
84
|
+
content = comment_pattern.sub('', content)
|
|
85
|
+
return {"result": {"file_type_description": content.strip()}}
|
|
86
|
+
else:
|
|
87
|
+
error_text = await response.text()
|
|
88
|
+
return {"error": f"API request failed with status {response.status}: {error_text}"}
|
|
89
|
+
|
|
90
|
+
except aiohttp.ClientError as e:
|
|
91
|
+
return {"error": f"Error making API request: {e}"}
|
|
92
|
+
except Exception as e:
|
|
93
|
+
return {"error": f"Error processing file: {e}"}
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
async def convert_file(file_path: str, ext_out: str) -> Dict[str, Any]:
|
|
97
|
+
"""Convert a file to other format via Convert.Guru API. Pass the file path and output extension."""
|
|
98
|
+
if not file_path:
|
|
99
|
+
return {"error": "file_path parameter is required"}
|
|
100
|
+
|
|
101
|
+
if not ext_out:
|
|
102
|
+
return {"error": "ext_out parameter is required"}
|
|
103
|
+
|
|
104
|
+
if not os.path.exists(file_path):
|
|
105
|
+
return {"error": f"File not found (use the absolute file path): {file_path}"}
|
|
106
|
+
|
|
107
|
+
if not os.path.isfile(file_path):
|
|
108
|
+
return {"error": f"Not a file: {file_path}"}
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
with open(file_path, "rb") as f:
|
|
112
|
+
chunk = f.read(MAX_FILE_SIZE + 1)
|
|
113
|
+
if len(chunk) > MAX_FILE_SIZE:
|
|
114
|
+
return {"error": "File exceeds the maximum upload size of 40 MiB."}
|
|
115
|
+
|
|
116
|
+
filename = os.path.basename(file_path)
|
|
117
|
+
|
|
118
|
+
data = aiohttp.FormData()
|
|
119
|
+
data.add_field('file', chunk, filename=filename)
|
|
120
|
+
headers = {'cache-control': 'no-cache', 'api-key': api_key}
|
|
121
|
+
|
|
122
|
+
async with aiohttp.ClientSession() as session:
|
|
123
|
+
async with session.post(base_url + '/api/v1/upload', data=data, headers=headers) as response:
|
|
124
|
+
if response.status == 200:
|
|
125
|
+
response_text = await response.text()
|
|
126
|
+
try:
|
|
127
|
+
# Explicitly parse using json.loads
|
|
128
|
+
json_result = json.loads(response_text)
|
|
129
|
+
json_payload = {
|
|
130
|
+
"filename": json_result['Filename'], "ext_out": ext_out.lower()}
|
|
131
|
+
conv_response_text = await post_json(base_url + '/api/v1/convert', json_payload, headers)
|
|
132
|
+
try:
|
|
133
|
+
# Explicitly parse using json.loads
|
|
134
|
+
conv_json_result = json.loads(
|
|
135
|
+
conv_response_text)
|
|
136
|
+
|
|
137
|
+
## check for error in conv_json_result
|
|
138
|
+
if conv_json_result.get('error'):
|
|
139
|
+
conv_json_result['error'] = f"File conversion failed: {conv_json_result.get('error')}"
|
|
140
|
+
return conv_json_result
|
|
141
|
+
|
|
142
|
+
file_info = conv_json_result.get('file')
|
|
143
|
+
if file_info and 'ext_out' in file_info and 'urls' in file_info and file_info['urls']:
|
|
144
|
+
file_extension = file_info.get('ext_out')
|
|
145
|
+
save_path = file_path + '.converted' + \
|
|
146
|
+
f".{file_extension}"
|
|
147
|
+
saved_paths = []
|
|
148
|
+
for file_url_path in file_info['urls']:
|
|
149
|
+
full_file_url = (base_url + file_url_path
|
|
150
|
+
if file_url_path.startswith('/') else file_url_path)
|
|
151
|
+
download_result = await download_file(session, full_file_url, save_path)
|
|
152
|
+
if 'error' in download_result:
|
|
153
|
+
download_result['result'] = {"converted_file_paths": saved_paths}
|
|
154
|
+
return download_result
|
|
155
|
+
saved_paths.append(download_result['result']['converted_file_path'])
|
|
156
|
+
return {"result": {
|
|
157
|
+
"converted_file_path": saved_paths[0],
|
|
158
|
+
"converted_file_paths": saved_paths,
|
|
159
|
+
}}
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
return {"error": f"File URL information not found in the API response.: {conv_response_text}"}
|
|
163
|
+
except json.JSONDecodeError:
|
|
164
|
+
# If the response is not JSON (convert)
|
|
165
|
+
return {"error": f"API convert request failed - response text is not JSON: {conv_response_text}"}
|
|
166
|
+
|
|
167
|
+
except json.JSONDecodeError:
|
|
168
|
+
# If the response is not JSON (upload)
|
|
169
|
+
return {"error": f"API upload request failed - response text is not JSON: {response_text}"}
|
|
170
|
+
else:
|
|
171
|
+
error_text = await response.text()
|
|
172
|
+
return {"error": f"API request failed with status {response.status}: {error_text}"}
|
|
173
|
+
|
|
174
|
+
except aiohttp.ClientError as e:
|
|
175
|
+
return {"error": f"Error making API request: {e}"}
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return {"error": f"Error processing file: {e}"}
|
|
178
|
+
|
|
179
|
+
async def post_json(url: str, data: dict, headers):
|
|
180
|
+
"""
|
|
181
|
+
Posts JSON data to a specified URL using aiohttp.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
url: The URL to post the JSON data to.
|
|
185
|
+
data: A Python dictionary that will be converted to JSON.
|
|
186
|
+
"""
|
|
187
|
+
cloned_headers = headers.copy()
|
|
188
|
+
cloned_headers['Content-Type'] = 'application/json'
|
|
189
|
+
async with aiohttp.ClientSession() as session:
|
|
190
|
+
try:
|
|
191
|
+
async with session.post(url, json=data, headers=cloned_headers) as response:
|
|
192
|
+
response_text = await response.text()
|
|
193
|
+
status = response.status
|
|
194
|
+
err = {}
|
|
195
|
+
|
|
196
|
+
if status == 200 or status == 400:
|
|
197
|
+
return response_text
|
|
198
|
+
elif status == 524:
|
|
199
|
+
err = {"error": f"Error HTTP 524 - operation takes too long to complete, timeout occured."}
|
|
200
|
+
elif status == 500:
|
|
201
|
+
err = {"error": f"Error HTTP 500 - operation takes too long to complete, internal server error."}
|
|
202
|
+
else:
|
|
203
|
+
err = {"error": f"Error HTTP {status})."}
|
|
204
|
+
|
|
205
|
+
return json.dumps(err)
|
|
206
|
+
except aiohttp.ClientError as e:
|
|
207
|
+
err = {"error": f"An error occurred during the request (exception): {e}"}
|
|
208
|
+
return json.dumps(err)
|
|
209
|
+
|
|
210
|
+
async def download_file(session: aiohttp.ClientSession, url: str, save_path: str) -> Dict[str, Any]:
|
|
211
|
+
"""Downloads a file from a URL and saves it to the specified path."""
|
|
212
|
+
try:
|
|
213
|
+
async with session.get(url) as response:
|
|
214
|
+
if response.status == 200:
|
|
215
|
+
root, extension = os.path.splitext(save_path)
|
|
216
|
+
counter = 1
|
|
217
|
+
while True:
|
|
218
|
+
try:
|
|
219
|
+
f = open(save_path, 'xb')
|
|
220
|
+
break
|
|
221
|
+
except FileExistsError:
|
|
222
|
+
counter += 1
|
|
223
|
+
save_path = f"{root}_{counter}{extension}"
|
|
224
|
+
try:
|
|
225
|
+
with f:
|
|
226
|
+
async for chunk in response.content.iter_chunked(8192):
|
|
227
|
+
f.write(chunk)
|
|
228
|
+
except BaseException:
|
|
229
|
+
os.remove(save_path)
|
|
230
|
+
raise
|
|
231
|
+
return {"result": {"converted_file_path": save_path}}
|
|
232
|
+
else:
|
|
233
|
+
response_text = await response.text()
|
|
234
|
+
return {"error": f"File download from {url} failed with status {response.status}: {response_text}"}
|
|
235
|
+
except aiohttp.ClientError as e:
|
|
236
|
+
return {"error": f"An error occurred during file download from {url}: {e}"}
|
|
237
|
+
|
|
238
|
+
def main():
|
|
239
|
+
"""Start the installed command and direct script with the same settings."""
|
|
240
|
+
transport = os.getenv("TRANSPORT", "").upper()
|
|
241
|
+
if transport == "HTTP":
|
|
242
|
+
port = os.getenv("PORT", "8000")
|
|
243
|
+
try:
|
|
244
|
+
port_int = int(port)
|
|
245
|
+
mcp.run(transport="http", host="0.0.0.0", port=port_int)
|
|
246
|
+
except ValueError:
|
|
247
|
+
print(f"Invalid PORT value: {port}. Using default stdio transport.", file=sys.stderr)
|
|
248
|
+
mcp.run()
|
|
249
|
+
else:
|
|
250
|
+
mcp.run()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
main()
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: file-convert-mcp
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: MCP tool for file conversion and file format detection by Convert.Guru
|
|
5
|
+
Project-URL: Homepage, https://convert.guru
|
|
6
|
+
Project-URL: Repository, https://github.com/convertguru/file-convert-mcp
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: aiohttp
|
|
11
|
+
Requires-Dist: fastmcp<3,>=2.14.7
|
|
12
|
+
Requires-Dist: python-dotenv
|
|
13
|
+
Requires-Dist: python-magic-bin==0.4.14; sys_platform == 'win32'
|
|
14
|
+
Requires-Dist: python-magic; sys_platform != 'win32'
|
|
15
|
+
Requires-Dist: starlette
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# File Convert MCP Server
|
|
19
|
+
[](https://smithery.ai/server/@convertguru/file-convert-mcp)
|
|
20
|
+
|
|
21
|
+
A Model Context Protocol (MCP) server for converting files between various formats.
|
|
22
|
+
|
|
23
|
+
File Convert is an MCP server designed to handle the conversion of a wide array of file formats, including images, office documents, audio, video, text, and data files. It aims to provide seamless transformation into popular formats such as PDF, JPG, MP4, and HTML, among others. This server offers a set of powerful tools to convert diverse file types into easily accessible and widely compatible formats like PDF, JPG, PNG, TXT.
|
|
24
|
+
|
|
25
|
+
## ✨ Features
|
|
26
|
+
|
|
27
|
+
- **Intelligent File Type Detection:** Employs a combination of AI, TrID, and magic bytes for accurate file type identification.
|
|
28
|
+
- **Versatile File Conversion:** Supports conversion between numerous file types and the following popular formats:
|
|
29
|
+
- **Images:** Handles a vast range of formats, including many legacy ones. Converts to PDF, JPG, PNG.
|
|
30
|
+
- **Office Documents:** Converts to PDF, TXT, HTML.
|
|
31
|
+
- **Audio/Video:** Converts to MP3, MP4.
|
|
32
|
+
- **Databases:** Converts to CSV.
|
|
33
|
+
- **Various Files:** Offers conversion capabilities for other file types as well.
|
|
34
|
+
|
|
35
|
+
## 🛠️ Requirements
|
|
36
|
+
|
|
37
|
+
1. **Python:** Version 3.12 or higher is required.
|
|
38
|
+
2. **uv Tool:** Install the [Python uv tool](https://docs.astral.sh/uv/getting-started/installation/) for efficient dependency management and easy execution of the MCP server.
|
|
39
|
+
3. **API Key (Development):** As of May 2025, the MCP is in its testing phase, and **no API key is currently required** for development. For future production use, please [contact the Convert.Guru team](https://convert.guru/contact) to obtain an API key.
|
|
40
|
+
|
|
41
|
+
## 🚀 Usage with Desktop App
|
|
42
|
+
|
|
43
|
+
To integrate this server with your desktop application, add the following configuration to your app's server settings. If necessary, adjust the absolute path to the `uv`/`uvx` executables (or `uv.exe`/`uvx.exe` on Windows).
|
|
44
|
+
|
|
45
|
+
**Using `uvx` (recommended):**
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"file-convert": {
|
|
51
|
+
"command": "uvx",
|
|
52
|
+
"args": ["--from", "git+https://github.com/convertguru/file-convert-mcp.git", "file-convert-mcp"],
|
|
53
|
+
"env": {
|
|
54
|
+
"CONVERT_GURU_API_KEY": "your_api_key_here"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**If you have cloned the repository locally (adjust paths as needed):**
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"file-convert": {
|
|
67
|
+
"command": "/home/User/.local/bin/uv",
|
|
68
|
+
"args": ["--directory", "/home/User/file-convert-mcp/src/file_convert_mcp", "run", "server.py"],
|
|
69
|
+
"env": {
|
|
70
|
+
"CONVERT_GURU_API_KEY": "your_api_key_here"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Using local `uvx`:**
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"file-convert": {
|
|
83
|
+
"command": "uvx",
|
|
84
|
+
"args": ["--from", "/home/User/file-convert-mcp", "file-convert-mcp"],
|
|
85
|
+
"env": {
|
|
86
|
+
"CONVERT_GURU_API_KEY": "your_api_key_here"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Environment Variables
|
|
94
|
+
|
|
95
|
+
The MCP server supports the following environment variables:
|
|
96
|
+
|
|
97
|
+
- **`CONVERT_GURU_API_KEY`**: Your Convert.Guru API key (optional for development)
|
|
98
|
+
- **`TRANSPORT`**: Transport method for the MCP server
|
|
99
|
+
- `"stdio"` (default): Standard input/output transport
|
|
100
|
+
- `"http"`: HTTP transport (streamable) with REST API endpoints
|
|
101
|
+
- **`PORT`**: Port number for HTTP transport (default: 8000, only used when `TRANSPORT=http`)
|
|
102
|
+
|
|
103
|
+
When using HTTP transport, the server provides additional endpoints:
|
|
104
|
+
- `GET /health` - Health check endpoint
|
|
105
|
+
- `GET /tools` - List of available MCP tools
|
|
106
|
+
|
|
107
|
+
Also, see this guide on how to [set up MCP tools in Claude Desktop](https://modelcontextprotocol.io/quickstart/user#for-claude-desktop-users).
|
|
108
|
+
|
|
109
|
+
### Installing via Smithery
|
|
110
|
+
|
|
111
|
+
To install file-convert-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@convertguru/file-convert-mcp):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx -y @smithery/cli install @convertguru/file-convert-mcp --client claude
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 🛠️ Development
|
|
118
|
+
|
|
119
|
+
Get started with local development by following these steps:
|
|
120
|
+
|
|
121
|
+
**1. Clone the repository:**
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git clone https://github.com/convertguru/file-convert-mcp.git
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**2. Fetch and cache Python dependencies (optional, `uvx` handles this automatically):**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cd file-convert-mcp
|
|
131
|
+
uv sync
|
|
132
|
+
cd ..
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**3. Create .env file with your (optional for now) API key:**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
echo "CONVERT_GURU_API_KEY=your_api_key_here" > file-convert-mcp/.env
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**4. Run the MCP server locally using various `uv` commands:**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Using uvx with .env file from the local directory
|
|
145
|
+
cd file-convert-mcp
|
|
146
|
+
UV_ENV_FILE=.env uvx --from ./file-convert-mcp file-convert-mcp
|
|
147
|
+
|
|
148
|
+
# OR using uv directly to run the server script
|
|
149
|
+
uv run file-convert-mcp/src/file_convert_mcp/server.py
|
|
150
|
+
|
|
151
|
+
# OR specifying the directory for uv
|
|
152
|
+
uv --directory file-convert-mcp/src/file_convert_mcp run server.py
|
|
153
|
+
|
|
154
|
+
# OR navigating into the server directory
|
|
155
|
+
cd file-convert-mcp/src/file_convert_mcp
|
|
156
|
+
uv run server.py
|
|
157
|
+
|
|
158
|
+
# OR using uvx to fetch the core from the GitHub repository + local .env file
|
|
159
|
+
UV_ENV_FILE=.env uvx --from git+https://github.com/convertguru/file-convert-mcp.git file-convert-mcp
|
|
160
|
+
|
|
161
|
+
# OR run with HTTP transport on custom port
|
|
162
|
+
TRANSPORT=http PORT=9000 uv run file-convert-mcp/src/file_convert_mcp/server.py
|
|
163
|
+
|
|
164
|
+
# OR run with stdio transport (default)
|
|
165
|
+
TRANSPORT=stdio uv run file-convert-mcp/src/file_convert_mcp/server.py
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**5. Create .env file with your configuration (optional for now):**
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Basic configuration
|
|
172
|
+
echo "CONVERT_GURU_API_KEY=your_api_key_here" > file-convert-mcp/.env
|
|
173
|
+
|
|
174
|
+
# OR with transport configuration
|
|
175
|
+
echo "CONVERT_GURU_API_KEY=your_api_key_here" > file-convert-mcp/.env
|
|
176
|
+
echo "TRANSPORT=http" >> file-convert-mcp/.env
|
|
177
|
+
echo "PORT=8000" >> file-convert-mcp/.env
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**6. Modify the server logic if needed:**
|
|
181
|
+
Edit the main server file located at `src/file_convert_mcp/server.py.`
|
|
182
|
+
|
|
183
|
+
**7. Clearing the `uv` Cache (if needed):**
|
|
184
|
+
If `uv` has cached an older version of the code in `~/.cache/uv`, you might need to clear the cache. Alternatively, use `uv` with the `-n` or `--no-cache` option to bypass it.
|
|
185
|
+
|
|
186
|
+
## ⚙️ Available Tools
|
|
187
|
+
|
|
188
|
+
The MCP server provides the following tools:
|
|
189
|
+
|
|
190
|
+
* `detect_file_type`: Analyzes the first 200 bytes of an uploaded file and uses the Convert.Guru API to determine its type.
|
|
191
|
+
* `convert_file`: Converts a given file to a specified output format. The desired file extension should be passed as the `ext_out` parameter.
|
|
192
|
+
|
|
193
|
+
Files larger than 40 MiB are rejected before upload. All result files are saved
|
|
194
|
+
beside the input file. Existing files are preserved by adding numbered suffixes.
|
|
195
|
+
The result contains `converted_file_paths` for all downloaded files and retains
|
|
196
|
+
`converted_file_path` for the first file for compatibility. If a later download
|
|
197
|
+
fails, the error response includes the paths already downloaded.
|
|
198
|
+
|
|
199
|
+
### Local development and checks (shared user installation)
|
|
200
|
+
|
|
201
|
+
```powershell
|
|
202
|
+
python -m pip install --user pytest build hatchling editables
|
|
203
|
+
python -m pip install --user --no-build-isolation -e .
|
|
204
|
+
python -m pytest -q
|
|
205
|
+
python -m build --no-isolation
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
These tests use a local API substitute and do not upload files to Convert.Guru.
|
|
209
|
+
They cover the 40 MiB boundary, multiple results, existing files, partial failure,
|
|
210
|
+
native file detection, and the installed STDIO/HTTP command. Large temporary test
|
|
211
|
+
files are removed automatically. Building creates local artifacts in `dist/`;
|
|
212
|
+
it does not publish them.
|
|
213
|
+
|
|
214
|
+
On Windows, the package uses `python-magic-bin`, which bundles libmagic.
|
|
215
|
+
FastMCP is constrained to the tested 2.x API. If the user Scripts directory is not
|
|
216
|
+
on PATH, launch `file-convert-mcp.exe` using its full path, or run
|
|
217
|
+
`python -m file_convert_mcp.server`.
|
|
218
|
+
|
|
219
|
+
## 📜 License
|
|
220
|
+
|
|
221
|
+
Visit us [https://convert.guru](https://convert.guru/)
|
|
222
|
+
|
|
223
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for complete details.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
file_convert_mcp/__init__.py,sha256=lByJAD0oq9PtxtDVVGK_RCi13uaZ5VDFuQ4xSwoyB4I,122
|
|
2
|
+
file_convert_mcp/server.py,sha256=51O-caBzdPQa67FRELIVGPK21dctWImVcpYYen3dDxI,11498
|
|
3
|
+
file_convert_mcp-0.1.2.dist-info/METADATA,sha256=V5IMSeDYIRvwIOdBzrCX2m8bCdkcNIJR_cfTft8Q2jU,8433
|
|
4
|
+
file_convert_mcp-0.1.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
5
|
+
file_convert_mcp-0.1.2.dist-info/entry_points.txt,sha256=OCX21YgRXluFTDsRq01P5AHAyvU2sH7D9KcfMr7J98U,66
|
|
6
|
+
file_convert_mcp-0.1.2.dist-info/licenses/LICENSE,sha256=pl9FVNBTGggLFoBSc-xb8sIn4i3h_Y6iTTqId29qVhM,1090
|
|
7
|
+
file_convert_mcp-0.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 convert.guru
|
|
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.
|