convertfilefast-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.
- convertfilefast_mcp-0.1.0/PKG-INFO +131 -0
- convertfilefast_mcp-0.1.0/README.md +119 -0
- convertfilefast_mcp-0.1.0/convertfilefast_mcp.egg-info/PKG-INFO +131 -0
- convertfilefast_mcp-0.1.0/convertfilefast_mcp.egg-info/SOURCES.txt +9 -0
- convertfilefast_mcp-0.1.0/convertfilefast_mcp.egg-info/dependency_links.txt +1 -0
- convertfilefast_mcp-0.1.0/convertfilefast_mcp.egg-info/entry_points.txt +2 -0
- convertfilefast_mcp-0.1.0/convertfilefast_mcp.egg-info/requires.txt +2 -0
- convertfilefast_mcp-0.1.0/convertfilefast_mcp.egg-info/top_level.txt +1 -0
- convertfilefast_mcp-0.1.0/pyproject.toml +23 -0
- convertfilefast_mcp-0.1.0/server.py +355 -0
- convertfilefast_mcp-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: convertfilefast-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for ConvertFileFast — convert 50+ file formats and run PDF/image operations as AI-agent tools.
|
|
5
|
+
Author: ConvertFileFast
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: mcp,model-context-protocol,pdf,file-conversion,convert,agents
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: fastmcp>=3.0
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
|
|
13
|
+
# ConvertFileFast MCP server
|
|
14
|
+
|
|
15
|
+
An [MCP](https://modelcontextprotocol.io) server that lets AI agents (Claude
|
|
16
|
+
Desktop, Claude Code, Cursor, etc.) convert files and run PDF/image operations
|
|
17
|
+
through the ConvertFileFast API.
|
|
18
|
+
|
|
19
|
+
It is a **separate service** that talks to the ConvertFileFast REST API over
|
|
20
|
+
HTTP — it does not import the backend, so it never affects the API's
|
|
21
|
+
dependencies, and it mirrors how the hosted MCP runs in production.
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|------|--------------|
|
|
27
|
+
| `convert_file` | Convert between 40+ format pairs (DOCX→PDF, PDF→CSV, PDF→JPG, HTML→PDF, URL→PDF, PNG→JPG, CSV→JSON, …) |
|
|
28
|
+
| `merge_pdfs` | Merge multiple PDFs into one |
|
|
29
|
+
| `split_pdf` | Extract pages/ranges from a PDF |
|
|
30
|
+
| `compress_pdf` | Reduce PDF size (low/medium/high/maximum) |
|
|
31
|
+
| `rotate_pdf` | Rotate pages (90/180/270°) |
|
|
32
|
+
| `protect_pdf` | Add password protection |
|
|
33
|
+
| `unlock_pdf` | Remove password protection |
|
|
34
|
+
| `resize_image` | Resize an image |
|
|
35
|
+
| `compress_image` | Compress an image (and optionally cap dimensions) |
|
|
36
|
+
|
|
37
|
+
Every tool accepts a public `source_url` (preferred — the agent passes a URL and
|
|
38
|
+
never handles binary) **or** `file_base64`. The result file is written to the
|
|
39
|
+
output directory and the tool returns its path.
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
A ConvertFileFast API key (starts with `cff_`). Create one at
|
|
44
|
+
<https://www.convertfilefast.com/signup>.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
The server reads these environment variables:
|
|
49
|
+
|
|
50
|
+
| Variable | Default | Purpose |
|
|
51
|
+
|----------|---------|---------|
|
|
52
|
+
| `CONVERTFILEFAST_API_KEY` | — | Your `cff_` key (sent as `X-API-Key`) |
|
|
53
|
+
| `CONVERTFILEFAST_API_BASE` | `https://api.convertfilefast.com` | API base URL (set to `http://127.0.0.1:8000` to test against a local backend) |
|
|
54
|
+
| `CONVERTFILEFAST_OUTPUT_DIR` | `~/ConvertFileFast` | Where converted files are saved |
|
|
55
|
+
| `CONVERTFILEFAST_TIMEOUT` | `180` | Per-request timeout (seconds) |
|
|
56
|
+
|
|
57
|
+
## Install (local, works today)
|
|
58
|
+
|
|
59
|
+
Until the package is published to PyPI, point your MCP client at the local
|
|
60
|
+
virtual environment created for this repo.
|
|
61
|
+
|
|
62
|
+
### Claude Desktop
|
|
63
|
+
|
|
64
|
+
Edit `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"convertfilefast": {
|
|
70
|
+
"command": "C:\\Projetos\\GitHub-Clones\\conversor-pdf\\mcp\\.venv\\Scripts\\python.exe",
|
|
71
|
+
"args": ["C:\\Projetos\\GitHub-Clones\\conversor-pdf\\mcp\\server.py"],
|
|
72
|
+
"env": {
|
|
73
|
+
"CONVERTFILEFAST_API_KEY": "cff_REPLACE_WITH_YOUR_KEY"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Restart Claude Desktop, then ask: *"Convert https://example.com/report.docx to PDF."*
|
|
81
|
+
|
|
82
|
+
### Cursor
|
|
83
|
+
|
|
84
|
+
Add the same block to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per
|
|
85
|
+
project).
|
|
86
|
+
|
|
87
|
+
### Claude Code
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
claude mcp add convertfilefast \
|
|
91
|
+
--env CONVERTFILEFAST_API_KEY=cff_REPLACE_WITH_YOUR_KEY \
|
|
92
|
+
-- "C:\Projetos\GitHub-Clones\conversor-pdf\mcp\.venv\Scripts\python.exe" \
|
|
93
|
+
"C:\Projetos\GitHub-Clones\conversor-pdf\mcp\server.py"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> **Testing against the local backend:** add
|
|
97
|
+
> `"CONVERTFILEFAST_API_BASE": "http://127.0.0.1:8000"` to the `env` block and
|
|
98
|
+
> start the backend first (`uvicorn app.main:app` from `api/`).
|
|
99
|
+
|
|
100
|
+
## Future: published install
|
|
101
|
+
|
|
102
|
+
Once published to PyPI, the portable, machine-independent config will be:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"mcpServers": {
|
|
107
|
+
"convertfilefast": {
|
|
108
|
+
"command": "uvx",
|
|
109
|
+
"args": ["convertfilefast-mcp"],
|
|
110
|
+
"env": { "CONVERTFILEFAST_API_KEY": "cff_..." }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# from this directory
|
|
120
|
+
uv venv .venv --python 3.12
|
|
121
|
+
uv pip install --python .venv/Scripts/python.exe fastmcp httpx
|
|
122
|
+
|
|
123
|
+
# run (stdio)
|
|
124
|
+
CONVERTFILEFAST_API_KEY=cff_... .venv/Scripts/python.exe server.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Inspect the tools with the MCP Inspector:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx @modelcontextprotocol/inspector .venv/Scripts/python.exe server.py
|
|
131
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ConvertFileFast MCP server
|
|
2
|
+
|
|
3
|
+
An [MCP](https://modelcontextprotocol.io) server that lets AI agents (Claude
|
|
4
|
+
Desktop, Claude Code, Cursor, etc.) convert files and run PDF/image operations
|
|
5
|
+
through the ConvertFileFast API.
|
|
6
|
+
|
|
7
|
+
It is a **separate service** that talks to the ConvertFileFast REST API over
|
|
8
|
+
HTTP — it does not import the backend, so it never affects the API's
|
|
9
|
+
dependencies, and it mirrors how the hosted MCP runs in production.
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
| Tool | What it does |
|
|
14
|
+
|------|--------------|
|
|
15
|
+
| `convert_file` | Convert between 40+ format pairs (DOCX→PDF, PDF→CSV, PDF→JPG, HTML→PDF, URL→PDF, PNG→JPG, CSV→JSON, …) |
|
|
16
|
+
| `merge_pdfs` | Merge multiple PDFs into one |
|
|
17
|
+
| `split_pdf` | Extract pages/ranges from a PDF |
|
|
18
|
+
| `compress_pdf` | Reduce PDF size (low/medium/high/maximum) |
|
|
19
|
+
| `rotate_pdf` | Rotate pages (90/180/270°) |
|
|
20
|
+
| `protect_pdf` | Add password protection |
|
|
21
|
+
| `unlock_pdf` | Remove password protection |
|
|
22
|
+
| `resize_image` | Resize an image |
|
|
23
|
+
| `compress_image` | Compress an image (and optionally cap dimensions) |
|
|
24
|
+
|
|
25
|
+
Every tool accepts a public `source_url` (preferred — the agent passes a URL and
|
|
26
|
+
never handles binary) **or** `file_base64`. The result file is written to the
|
|
27
|
+
output directory and the tool returns its path.
|
|
28
|
+
|
|
29
|
+
## Prerequisites
|
|
30
|
+
|
|
31
|
+
A ConvertFileFast API key (starts with `cff_`). Create one at
|
|
32
|
+
<https://www.convertfilefast.com/signup>.
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
The server reads these environment variables:
|
|
37
|
+
|
|
38
|
+
| Variable | Default | Purpose |
|
|
39
|
+
|----------|---------|---------|
|
|
40
|
+
| `CONVERTFILEFAST_API_KEY` | — | Your `cff_` key (sent as `X-API-Key`) |
|
|
41
|
+
| `CONVERTFILEFAST_API_BASE` | `https://api.convertfilefast.com` | API base URL (set to `http://127.0.0.1:8000` to test against a local backend) |
|
|
42
|
+
| `CONVERTFILEFAST_OUTPUT_DIR` | `~/ConvertFileFast` | Where converted files are saved |
|
|
43
|
+
| `CONVERTFILEFAST_TIMEOUT` | `180` | Per-request timeout (seconds) |
|
|
44
|
+
|
|
45
|
+
## Install (local, works today)
|
|
46
|
+
|
|
47
|
+
Until the package is published to PyPI, point your MCP client at the local
|
|
48
|
+
virtual environment created for this repo.
|
|
49
|
+
|
|
50
|
+
### Claude Desktop
|
|
51
|
+
|
|
52
|
+
Edit `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"convertfilefast": {
|
|
58
|
+
"command": "C:\\Projetos\\GitHub-Clones\\conversor-pdf\\mcp\\.venv\\Scripts\\python.exe",
|
|
59
|
+
"args": ["C:\\Projetos\\GitHub-Clones\\conversor-pdf\\mcp\\server.py"],
|
|
60
|
+
"env": {
|
|
61
|
+
"CONVERTFILEFAST_API_KEY": "cff_REPLACE_WITH_YOUR_KEY"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Restart Claude Desktop, then ask: *"Convert https://example.com/report.docx to PDF."*
|
|
69
|
+
|
|
70
|
+
### Cursor
|
|
71
|
+
|
|
72
|
+
Add the same block to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per
|
|
73
|
+
project).
|
|
74
|
+
|
|
75
|
+
### Claude Code
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
claude mcp add convertfilefast \
|
|
79
|
+
--env CONVERTFILEFAST_API_KEY=cff_REPLACE_WITH_YOUR_KEY \
|
|
80
|
+
-- "C:\Projetos\GitHub-Clones\conversor-pdf\mcp\.venv\Scripts\python.exe" \
|
|
81
|
+
"C:\Projetos\GitHub-Clones\conversor-pdf\mcp\server.py"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> **Testing against the local backend:** add
|
|
85
|
+
> `"CONVERTFILEFAST_API_BASE": "http://127.0.0.1:8000"` to the `env` block and
|
|
86
|
+
> start the backend first (`uvicorn app.main:app` from `api/`).
|
|
87
|
+
|
|
88
|
+
## Future: published install
|
|
89
|
+
|
|
90
|
+
Once published to PyPI, the portable, machine-independent config will be:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"convertfilefast": {
|
|
96
|
+
"command": "uvx",
|
|
97
|
+
"args": ["convertfilefast-mcp"],
|
|
98
|
+
"env": { "CONVERTFILEFAST_API_KEY": "cff_..." }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# from this directory
|
|
108
|
+
uv venv .venv --python 3.12
|
|
109
|
+
uv pip install --python .venv/Scripts/python.exe fastmcp httpx
|
|
110
|
+
|
|
111
|
+
# run (stdio)
|
|
112
|
+
CONVERTFILEFAST_API_KEY=cff_... .venv/Scripts/python.exe server.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Inspect the tools with the MCP Inspector:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx @modelcontextprotocol/inspector .venv/Scripts/python.exe server.py
|
|
119
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: convertfilefast-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for ConvertFileFast — convert 50+ file formats and run PDF/image operations as AI-agent tools.
|
|
5
|
+
Author: ConvertFileFast
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: mcp,model-context-protocol,pdf,file-conversion,convert,agents
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: fastmcp>=3.0
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
|
|
13
|
+
# ConvertFileFast MCP server
|
|
14
|
+
|
|
15
|
+
An [MCP](https://modelcontextprotocol.io) server that lets AI agents (Claude
|
|
16
|
+
Desktop, Claude Code, Cursor, etc.) convert files and run PDF/image operations
|
|
17
|
+
through the ConvertFileFast API.
|
|
18
|
+
|
|
19
|
+
It is a **separate service** that talks to the ConvertFileFast REST API over
|
|
20
|
+
HTTP — it does not import the backend, so it never affects the API's
|
|
21
|
+
dependencies, and it mirrors how the hosted MCP runs in production.
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|------|--------------|
|
|
27
|
+
| `convert_file` | Convert between 40+ format pairs (DOCX→PDF, PDF→CSV, PDF→JPG, HTML→PDF, URL→PDF, PNG→JPG, CSV→JSON, …) |
|
|
28
|
+
| `merge_pdfs` | Merge multiple PDFs into one |
|
|
29
|
+
| `split_pdf` | Extract pages/ranges from a PDF |
|
|
30
|
+
| `compress_pdf` | Reduce PDF size (low/medium/high/maximum) |
|
|
31
|
+
| `rotate_pdf` | Rotate pages (90/180/270°) |
|
|
32
|
+
| `protect_pdf` | Add password protection |
|
|
33
|
+
| `unlock_pdf` | Remove password protection |
|
|
34
|
+
| `resize_image` | Resize an image |
|
|
35
|
+
| `compress_image` | Compress an image (and optionally cap dimensions) |
|
|
36
|
+
|
|
37
|
+
Every tool accepts a public `source_url` (preferred — the agent passes a URL and
|
|
38
|
+
never handles binary) **or** `file_base64`. The result file is written to the
|
|
39
|
+
output directory and the tool returns its path.
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
A ConvertFileFast API key (starts with `cff_`). Create one at
|
|
44
|
+
<https://www.convertfilefast.com/signup>.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
The server reads these environment variables:
|
|
49
|
+
|
|
50
|
+
| Variable | Default | Purpose |
|
|
51
|
+
|----------|---------|---------|
|
|
52
|
+
| `CONVERTFILEFAST_API_KEY` | — | Your `cff_` key (sent as `X-API-Key`) |
|
|
53
|
+
| `CONVERTFILEFAST_API_BASE` | `https://api.convertfilefast.com` | API base URL (set to `http://127.0.0.1:8000` to test against a local backend) |
|
|
54
|
+
| `CONVERTFILEFAST_OUTPUT_DIR` | `~/ConvertFileFast` | Where converted files are saved |
|
|
55
|
+
| `CONVERTFILEFAST_TIMEOUT` | `180` | Per-request timeout (seconds) |
|
|
56
|
+
|
|
57
|
+
## Install (local, works today)
|
|
58
|
+
|
|
59
|
+
Until the package is published to PyPI, point your MCP client at the local
|
|
60
|
+
virtual environment created for this repo.
|
|
61
|
+
|
|
62
|
+
### Claude Desktop
|
|
63
|
+
|
|
64
|
+
Edit `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"convertfilefast": {
|
|
70
|
+
"command": "C:\\Projetos\\GitHub-Clones\\conversor-pdf\\mcp\\.venv\\Scripts\\python.exe",
|
|
71
|
+
"args": ["C:\\Projetos\\GitHub-Clones\\conversor-pdf\\mcp\\server.py"],
|
|
72
|
+
"env": {
|
|
73
|
+
"CONVERTFILEFAST_API_KEY": "cff_REPLACE_WITH_YOUR_KEY"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Restart Claude Desktop, then ask: *"Convert https://example.com/report.docx to PDF."*
|
|
81
|
+
|
|
82
|
+
### Cursor
|
|
83
|
+
|
|
84
|
+
Add the same block to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per
|
|
85
|
+
project).
|
|
86
|
+
|
|
87
|
+
### Claude Code
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
claude mcp add convertfilefast \
|
|
91
|
+
--env CONVERTFILEFAST_API_KEY=cff_REPLACE_WITH_YOUR_KEY \
|
|
92
|
+
-- "C:\Projetos\GitHub-Clones\conversor-pdf\mcp\.venv\Scripts\python.exe" \
|
|
93
|
+
"C:\Projetos\GitHub-Clones\conversor-pdf\mcp\server.py"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> **Testing against the local backend:** add
|
|
97
|
+
> `"CONVERTFILEFAST_API_BASE": "http://127.0.0.1:8000"` to the `env` block and
|
|
98
|
+
> start the backend first (`uvicorn app.main:app` from `api/`).
|
|
99
|
+
|
|
100
|
+
## Future: published install
|
|
101
|
+
|
|
102
|
+
Once published to PyPI, the portable, machine-independent config will be:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"mcpServers": {
|
|
107
|
+
"convertfilefast": {
|
|
108
|
+
"command": "uvx",
|
|
109
|
+
"args": ["convertfilefast-mcp"],
|
|
110
|
+
"env": { "CONVERTFILEFAST_API_KEY": "cff_..." }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# from this directory
|
|
120
|
+
uv venv .venv --python 3.12
|
|
121
|
+
uv pip install --python .venv/Scripts/python.exe fastmcp httpx
|
|
122
|
+
|
|
123
|
+
# run (stdio)
|
|
124
|
+
CONVERTFILEFAST_API_KEY=cff_... .venv/Scripts/python.exe server.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Inspect the tools with the MCP Inspector:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx @modelcontextprotocol/inspector .venv/Scripts/python.exe server.py
|
|
131
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
server.py
|
|
4
|
+
convertfilefast_mcp.egg-info/PKG-INFO
|
|
5
|
+
convertfilefast_mcp.egg-info/SOURCES.txt
|
|
6
|
+
convertfilefast_mcp.egg-info/dependency_links.txt
|
|
7
|
+
convertfilefast_mcp.egg-info/entry_points.txt
|
|
8
|
+
convertfilefast_mcp.egg-info/requires.txt
|
|
9
|
+
convertfilefast_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
server
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "convertfilefast-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for ConvertFileFast — convert 50+ file formats and run PDF/image operations as AI-agent tools."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "ConvertFileFast" }]
|
|
9
|
+
keywords = ["mcp", "model-context-protocol", "pdf", "file-conversion", "convert", "agents"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"fastmcp>=3.0",
|
|
12
|
+
"httpx>=0.27",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
convertfilefast-mcp = "server:main"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["setuptools>=68"]
|
|
20
|
+
build-backend = "setuptools.build_meta"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools]
|
|
23
|
+
py-modules = ["server"]
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""ConvertFileFast MCP server.
|
|
2
|
+
|
|
3
|
+
Exposes file conversion as tools for AI agents (Claude, Cursor, ChatGPT, ...).
|
|
4
|
+
|
|
5
|
+
This is a *separate service* from the REST backend: it talks to the
|
|
6
|
+
ConvertFileFast HTTP API and never imports the backend (keeps the backend's
|
|
7
|
+
dependencies untouched and matches how the hosted MCP runs in production).
|
|
8
|
+
|
|
9
|
+
Auth: the caller's ConvertFileFast API key (prefix ``cff_``), taken from the
|
|
10
|
+
``CONVERTFILEFAST_API_KEY`` environment variable and sent as ``X-API-Key``.
|
|
11
|
+
|
|
12
|
+
Configuration (env vars):
|
|
13
|
+
CONVERTFILEFAST_API_BASE API base URL (default: production API)
|
|
14
|
+
CONVERTFILEFAST_API_KEY the cff_ key used for requests
|
|
15
|
+
CONVERTFILEFAST_OUTPUT_DIR where converted files are written (default ~/ConvertFileFast)
|
|
16
|
+
CONVERTFILEFAST_TIMEOUT per-request timeout in seconds (default 180)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import base64
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Annotated, Literal, Optional
|
|
26
|
+
from urllib.parse import urlparse
|
|
27
|
+
|
|
28
|
+
import httpx
|
|
29
|
+
from fastmcp import FastMCP
|
|
30
|
+
from pydantic import Field
|
|
31
|
+
|
|
32
|
+
API_BASE = os.environ.get("CONVERTFILEFAST_API_BASE", "https://api.convertfilefast.com").rstrip("/")
|
|
33
|
+
API_KEY = os.environ.get("CONVERTFILEFAST_API_KEY", "")
|
|
34
|
+
OUTPUT_DIR = Path(os.environ.get("CONVERTFILEFAST_OUTPUT_DIR", str(Path.home() / "ConvertFileFast")))
|
|
35
|
+
HTTP_TIMEOUT = float(os.environ.get("CONVERTFILEFAST_TIMEOUT", "180"))
|
|
36
|
+
|
|
37
|
+
mcp = FastMCP(
|
|
38
|
+
"ConvertFileFast",
|
|
39
|
+
instructions=(
|
|
40
|
+
"Convert files between 50+ formats and run PDF/image operations "
|
|
41
|
+
"(merge, split, compress, rotate, protect, unlock, resize). Prefer "
|
|
42
|
+
"passing a public URL via `source_url`; otherwise pass base64. The "
|
|
43
|
+
"result file is saved locally and the tool returns its path."
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# --- Conversion matrix (mirrors the backend's /v2/convert/{from}-to-{to}) ---
|
|
48
|
+
_DOC_TO_PDF = ["docx", "doc", "xlsx", "pptx", "ppt", "rtf", "odt", "txt", "csv"]
|
|
49
|
+
_WEB_TO_PDF = ["html", "markdown", "url"]
|
|
50
|
+
_IMG_TO_PDF = ["jpg", "png", "heic", "webp", "avif", "svg", "bmp", "tiff"]
|
|
51
|
+
_PDF_TARGETS = ["docx", "txt", "csv", "xlsx", "jpg", "png"]
|
|
52
|
+
_IMG_TO_IMG = [
|
|
53
|
+
"jpg-to-png", "png-to-jpg", "jpg-to-webp", "png-to-webp",
|
|
54
|
+
"heic-to-jpg", "heic-to-png", "webp-to-jpg", "webp-to-png",
|
|
55
|
+
"avif-to-jpg", "avif-to-png", "svg-to-png", "svg-to-jpg",
|
|
56
|
+
"bmp-to-jpg", "bmp-to-png", "tiff-to-jpg", "tiff-to-png",
|
|
57
|
+
]
|
|
58
|
+
_DATA = ["csv-to-json", "json-to-csv", "csv-to-xlsx", "xlsx-to-csv"]
|
|
59
|
+
|
|
60
|
+
VALID_SLUGS: set[str] = set(_IMG_TO_IMG) | set(_DATA)
|
|
61
|
+
for _s in _DOC_TO_PDF + _WEB_TO_PDF + _IMG_TO_PDF:
|
|
62
|
+
VALID_SLUGS.add(f"{_s}-to-pdf")
|
|
63
|
+
for _t in _PDF_TARGETS:
|
|
64
|
+
VALID_SLUGS.add(f"pdf-to-{_t}")
|
|
65
|
+
|
|
66
|
+
_EXT_ALIAS = {"jpeg": "jpg", "tif": "tiff", "md": "markdown", "htm": "html", "text": "txt"}
|
|
67
|
+
|
|
68
|
+
TargetFormat = Literal["pdf", "docx", "xlsx", "csv", "txt", "json", "jpg", "png", "webp"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# --------------------------------------------------------------------------- #
|
|
72
|
+
# Helpers
|
|
73
|
+
# --------------------------------------------------------------------------- #
|
|
74
|
+
def _client() -> httpx.Client:
|
|
75
|
+
headers = {"User-Agent": "convertfilefast-mcp"}
|
|
76
|
+
if API_KEY:
|
|
77
|
+
headers["X-API-Key"] = API_KEY
|
|
78
|
+
return httpx.Client(base_url=API_BASE, headers=headers, timeout=HTTP_TIMEOUT)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _formval(v: object) -> str:
|
|
82
|
+
if isinstance(v, bool):
|
|
83
|
+
return "true" if v else "false"
|
|
84
|
+
return str(v)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _filename_from_response(resp: httpx.Response, fallback: str) -> str:
|
|
88
|
+
cd = resp.headers.get("content-disposition", "")
|
|
89
|
+
m = re.search(r"filename\*?=(?:UTF-8'')?\"?([^\";]+)\"?", cd)
|
|
90
|
+
return os.path.basename(m.group(1)) if m else fallback
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _deliver(content: bytes, out_name: str) -> dict:
|
|
94
|
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
out_path = OUTPUT_DIR / out_name
|
|
96
|
+
out_path.write_bytes(content)
|
|
97
|
+
return {"status": "success", "output_path": str(out_path), "filename": out_name, "bytes": len(content)}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _error(resp: httpx.Response) -> dict:
|
|
101
|
+
try:
|
|
102
|
+
detail = resp.json().get("detail", resp.text[:300])
|
|
103
|
+
except Exception:
|
|
104
|
+
detail = resp.text[:300]
|
|
105
|
+
hint = None
|
|
106
|
+
if resp.status_code in (401, 403):
|
|
107
|
+
hint = "Set a valid CONVERTFILEFAST_API_KEY (cff_...)."
|
|
108
|
+
elif resp.status_code == 429:
|
|
109
|
+
hint = "Rate or credit limit reached. Add credits or slow down."
|
|
110
|
+
return {"status": "error", "http_status": resp.status_code, "detail": detail, "hint": hint}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _send(
|
|
114
|
+
endpoint: str,
|
|
115
|
+
*,
|
|
116
|
+
default_out: str,
|
|
117
|
+
source_url: Optional[str] = None,
|
|
118
|
+
file_base64: Optional[str] = None,
|
|
119
|
+
filename: Optional[str] = None,
|
|
120
|
+
extra: Optional[dict] = None,
|
|
121
|
+
) -> dict:
|
|
122
|
+
"""POST to a conversion/operation endpoint with url-or-upload input."""
|
|
123
|
+
data: dict[str, str] = {k: _formval(v) for k, v in (extra or {}).items() if v is not None}
|
|
124
|
+
files = None
|
|
125
|
+
if source_url:
|
|
126
|
+
data["url"] = source_url
|
|
127
|
+
elif file_base64:
|
|
128
|
+
try:
|
|
129
|
+
raw = base64.b64decode(file_base64)
|
|
130
|
+
except Exception:
|
|
131
|
+
return {"status": "error", "detail": "file_base64 is not valid base64."}
|
|
132
|
+
files = {"file": (filename or "input", raw)}
|
|
133
|
+
else:
|
|
134
|
+
return {"status": "error", "detail": "Provide either source_url or file_base64."}
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
with _client() as c:
|
|
138
|
+
resp = c.post(endpoint, data=data, files=files)
|
|
139
|
+
except httpx.RequestError as e:
|
|
140
|
+
return {"status": "error", "detail": f"Request to API failed: {e}"}
|
|
141
|
+
|
|
142
|
+
if resp.status_code != 200:
|
|
143
|
+
return _error(resp)
|
|
144
|
+
return _deliver(resp.content, _filename_from_response(resp, default_out))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _guess_source_format(source_url: Optional[str], filename: Optional[str], target: str) -> Optional[str]:
|
|
148
|
+
name = filename or (urlparse(source_url).path if source_url else "")
|
|
149
|
+
ext = Path(name).suffix.lower().lstrip(".")
|
|
150
|
+
ext = _EXT_ALIAS.get(ext, ext)
|
|
151
|
+
if ext:
|
|
152
|
+
return ext
|
|
153
|
+
if source_url and target == "pdf":
|
|
154
|
+
return "url" # a URL with no file extension, to PDF, is a webpage
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# --------------------------------------------------------------------------- #
|
|
159
|
+
# Tools
|
|
160
|
+
# --------------------------------------------------------------------------- #
|
|
161
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
162
|
+
def convert_file(
|
|
163
|
+
target_format: Annotated[TargetFormat, Field(description="Desired output format, e.g. 'pdf', 'csv', 'png'.")],
|
|
164
|
+
source_url: Annotated[Optional[str], Field(description="Public URL to the source file or webpage (preferred). For a webpage to PDF, point at the page and use target_format='pdf'.")] = None,
|
|
165
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded source file. Use only when there is no URL.")] = None,
|
|
166
|
+
filename: Annotated[Optional[str], Field(description="Original filename (with extension); used to detect the source format and name the output.")] = None,
|
|
167
|
+
source_format: Annotated[Optional[str], Field(description="Source format token (e.g. 'docx', 'pdf', 'png', 'html', 'url'). Inferred from filename/URL if omitted.")] = None,
|
|
168
|
+
) -> dict:
|
|
169
|
+
"""Convert a document, spreadsheet, presentation, image, PDF, or data file to another format.
|
|
170
|
+
|
|
171
|
+
Supports 40+ conversions, e.g. DOCX->PDF, PDF->CSV, PDF->JPG, HTML->PDF,
|
|
172
|
+
URL(webpage)->PDF, PNG->JPG, CSV->JSON. Pass a public `source_url` (preferred)
|
|
173
|
+
or `file_base64`. Returns the local path of the converted file.
|
|
174
|
+
"""
|
|
175
|
+
src = (source_format or _guess_source_format(source_url, filename, target_format) or "").lower()
|
|
176
|
+
src = _EXT_ALIAS.get(src, src)
|
|
177
|
+
if not src:
|
|
178
|
+
return {"status": "error", "detail": "Could not determine the source format. Pass `source_format` (e.g. 'docx') or a `filename` with an extension."}
|
|
179
|
+
|
|
180
|
+
slug = f"{src}-to-{target_format}"
|
|
181
|
+
if slug not in VALID_SLUGS:
|
|
182
|
+
valid_targets = sorted(s.split("-to-")[1] for s in VALID_SLUGS if s.startswith(f"{src}-to-"))
|
|
183
|
+
return {"status": "error", "detail": f"Conversion '{slug}' is not supported.", "supported_targets_for_source": valid_targets or None}
|
|
184
|
+
|
|
185
|
+
stem = Path(filename).stem if filename else "converted"
|
|
186
|
+
return _send(f"/v2/convert/{slug}", default_out=f"{stem}.{target_format}",
|
|
187
|
+
source_url=source_url, file_base64=file_base64, filename=filename)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
191
|
+
def merge_pdfs(
|
|
192
|
+
source_urls: Annotated[Optional[list[str]], Field(description="Public URLs of the PDFs to merge, in order (minimum 2).")] = None,
|
|
193
|
+
files_base64: Annotated[Optional[list[str]], Field(description="Base64-encoded PDFs to merge, in order (minimum 2). Use when there are no URLs.")] = None,
|
|
194
|
+
output_name: Annotated[str, Field(description="Name for the merged PDF.")] = "merged.pdf",
|
|
195
|
+
) -> dict:
|
|
196
|
+
"""Merge multiple PDFs into a single PDF, preserving the given order."""
|
|
197
|
+
blobs: list[tuple[str, bytes]] = []
|
|
198
|
+
if files_base64:
|
|
199
|
+
for i, b in enumerate(files_base64):
|
|
200
|
+
try:
|
|
201
|
+
blobs.append((f"part{i + 1}.pdf", base64.b64decode(b)))
|
|
202
|
+
except Exception:
|
|
203
|
+
return {"status": "error", "detail": f"files_base64[{i}] is not valid base64."}
|
|
204
|
+
elif source_urls:
|
|
205
|
+
try:
|
|
206
|
+
with httpx.Client(timeout=HTTP_TIMEOUT, follow_redirects=True) as fetch:
|
|
207
|
+
for i, u in enumerate(source_urls):
|
|
208
|
+
rr = fetch.get(u)
|
|
209
|
+
if rr.status_code != 200:
|
|
210
|
+
return {"status": "error", "detail": f"Could not fetch {u} (HTTP {rr.status_code})."}
|
|
211
|
+
blobs.append((f"part{i + 1}.pdf", rr.content))
|
|
212
|
+
except httpx.RequestError as e:
|
|
213
|
+
return {"status": "error", "detail": f"Failed to fetch a URL: {e}"}
|
|
214
|
+
else:
|
|
215
|
+
return {"status": "error", "detail": "Provide source_urls or files_base64 (at least 2 PDFs)."}
|
|
216
|
+
|
|
217
|
+
if len(blobs) < 2:
|
|
218
|
+
return {"status": "error", "detail": "Need at least 2 PDFs to merge."}
|
|
219
|
+
|
|
220
|
+
files = [("files", (name, data)) for name, data in blobs]
|
|
221
|
+
try:
|
|
222
|
+
with _client() as c:
|
|
223
|
+
resp = c.post("/v2/pdf/merge", files=files)
|
|
224
|
+
except httpx.RequestError as e:
|
|
225
|
+
return {"status": "error", "detail": f"Request to API failed: {e}"}
|
|
226
|
+
if resp.status_code != 200:
|
|
227
|
+
return _error(resp)
|
|
228
|
+
return _deliver(resp.content, _filename_from_response(resp, output_name))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
232
|
+
def split_pdf(
|
|
233
|
+
pages: Annotated[str, Field(description="Pages/ranges to extract, e.g. '1,3,5-7' or '1-5'.")],
|
|
234
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the PDF.")] = None,
|
|
235
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded PDF.")] = None,
|
|
236
|
+
) -> dict:
|
|
237
|
+
"""Extract specific pages/ranges from a PDF (returns the resulting file(s))."""
|
|
238
|
+
return _send("/v2/pdf/split", default_out="split.zip", source_url=source_url,
|
|
239
|
+
file_base64=file_base64, extra={"pages": pages})
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
243
|
+
def compress_pdf(
|
|
244
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the PDF.")] = None,
|
|
245
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded PDF.")] = None,
|
|
246
|
+
quality: Annotated[Literal["low", "medium", "high", "maximum"], Field(description="Compression preset (more compression = smaller file).")] = "medium",
|
|
247
|
+
remove_metadata: Annotated[bool, Field(description="Strip PDF metadata to reduce size further.")] = False,
|
|
248
|
+
) -> dict:
|
|
249
|
+
"""Reduce the file size of a PDF."""
|
|
250
|
+
return _send("/v2/pdf/compress", default_out="compressed.pdf", source_url=source_url,
|
|
251
|
+
file_base64=file_base64, extra={"quality": quality, "remove_metadata": remove_metadata})
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
255
|
+
def rotate_pdf(
|
|
256
|
+
angle: Annotated[Literal[90, 180, 270], Field(description="Rotation in degrees (clockwise).")],
|
|
257
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the PDF.")] = None,
|
|
258
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded PDF.")] = None,
|
|
259
|
+
pages: Annotated[Optional[str], Field(description="Pages to rotate, e.g. '1,3,5-7'. Omit to rotate all pages.")] = None,
|
|
260
|
+
) -> dict:
|
|
261
|
+
"""Rotate pages in a PDF."""
|
|
262
|
+
return _send("/v2/pdf/rotate", default_out="rotated.pdf", source_url=source_url,
|
|
263
|
+
file_base64=file_base64, extra={"angle": angle, "pages": pages})
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
267
|
+
def protect_pdf(
|
|
268
|
+
password: Annotated[str, Field(description="Password to set on the PDF.")],
|
|
269
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the PDF.")] = None,
|
|
270
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded PDF.")] = None,
|
|
271
|
+
) -> dict:
|
|
272
|
+
"""Add password protection (encryption) to a PDF."""
|
|
273
|
+
return _send("/v2/pdf/protect", default_out="protected.pdf", source_url=source_url,
|
|
274
|
+
file_base64=file_base64, extra={"password": password})
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
278
|
+
def unlock_pdf(
|
|
279
|
+
password: Annotated[str, Field(description="Current password of the PDF.")],
|
|
280
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the PDF.")] = None,
|
|
281
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded PDF.")] = None,
|
|
282
|
+
) -> dict:
|
|
283
|
+
"""Remove password protection from a PDF (requires the current password)."""
|
|
284
|
+
return _send("/v2/pdf/unlock", default_out="unlocked.pdf", source_url=source_url,
|
|
285
|
+
file_base64=file_base64, extra={"password": password})
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
289
|
+
def resize_image(
|
|
290
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the image.")] = None,
|
|
291
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded image.")] = None,
|
|
292
|
+
width: Annotated[Optional[int], Field(description="Target width in pixels.")] = None,
|
|
293
|
+
height: Annotated[Optional[int], Field(description="Target height in pixels.")] = None,
|
|
294
|
+
fit: Annotated[Literal["contain", "cover", "fill"], Field(description="How to fit within width/height.")] = "contain",
|
|
295
|
+
format: Annotated[Optional[str], Field(description="Output format (jpeg, png, webp, ...). Defaults to source format.")] = None,
|
|
296
|
+
) -> dict:
|
|
297
|
+
"""Resize an image to the given width/height."""
|
|
298
|
+
if width is None and height is None:
|
|
299
|
+
return {"status": "error", "detail": "Provide at least one of width or height."}
|
|
300
|
+
return _send("/v2/image/resize", default_out="resized.png", source_url=source_url,
|
|
301
|
+
file_base64=file_base64, extra={"width": width, "height": height, "fit": fit, "format": format})
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@mcp.tool(annotations={"idempotentHint": True, "openWorldHint": True})
|
|
305
|
+
def compress_image(
|
|
306
|
+
source_url: Annotated[Optional[str], Field(description="Public URL of the image.")] = None,
|
|
307
|
+
file_base64: Annotated[Optional[str], Field(description="Base64-encoded image.")] = None,
|
|
308
|
+
quality: Annotated[int, Field(description="Compression quality 1-100 (higher = better quality, larger file).", ge=1, le=100)] = 85,
|
|
309
|
+
format: Annotated[Optional[str], Field(description="Output format (jpeg, png, webp, ...). Defaults to source format.")] = None,
|
|
310
|
+
max_width: Annotated[Optional[int], Field(description="Optionally cap the width in pixels.")] = None,
|
|
311
|
+
max_height: Annotated[Optional[int], Field(description="Optionally cap the height in pixels.")] = None,
|
|
312
|
+
) -> dict:
|
|
313
|
+
"""Compress an image to reduce file size, optionally capping its dimensions."""
|
|
314
|
+
return _send("/v2/image/compress", default_out="compressed.png", source_url=source_url,
|
|
315
|
+
file_base64=file_base64,
|
|
316
|
+
extra={"quality": quality, "format": format, "max_width": max_width, "max_height": max_height})
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@mcp.tool(annotations={"idempotentHint": False, "openWorldHint": True})
|
|
320
|
+
def report_issue(
|
|
321
|
+
summary: Annotated[str, Field(description="What went wrong or what's missing. Be specific.")],
|
|
322
|
+
category: Annotated[
|
|
323
|
+
Literal["bug", "missing_conversion", "quality", "other"],
|
|
324
|
+
Field(description="Type of feedback."),
|
|
325
|
+
] = "other",
|
|
326
|
+
tool: Annotated[Optional[str], Field(description="Which tool/conversion this is about, e.g. 'convert_file docx-to-pdf'.")] = None,
|
|
327
|
+
context: Annotated[Optional[str], Field(description="Extra detail: error message, input format, expected vs actual output.")] = None,
|
|
328
|
+
) -> dict:
|
|
329
|
+
"""Report a bug, missing conversion, or quality issue with ConvertFileFast to the maintainers.
|
|
330
|
+
|
|
331
|
+
Use this when a conversion fails, a format you need isn't supported, or the
|
|
332
|
+
output quality is poor. Your report goes into the team's development backlog.
|
|
333
|
+
"""
|
|
334
|
+
data: dict[str, str] = {"summary": summary, "category": category}
|
|
335
|
+
if tool:
|
|
336
|
+
data["tool"] = tool
|
|
337
|
+
if context:
|
|
338
|
+
data["context"] = context
|
|
339
|
+
try:
|
|
340
|
+
with _client() as c:
|
|
341
|
+
resp = c.post("/v2/feedback", data=data)
|
|
342
|
+
except httpx.RequestError as e:
|
|
343
|
+
return {"status": "error", "detail": f"Could not send feedback: {e}"}
|
|
344
|
+
if resp.status_code != 200:
|
|
345
|
+
return _error(resp)
|
|
346
|
+
return {"status": "received", "message": "Thanks — your report was logged for the ConvertFileFast team."}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def main() -> None:
|
|
350
|
+
"""Console-script / module entry point. Runs the stdio MCP server."""
|
|
351
|
+
mcp.run() # stdio transport by default
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
if __name__ == "__main__":
|
|
355
|
+
main()
|