aspose-html-cloud-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.
- aspose_html_cloud_mcp-1.0.0/.gitignore +8 -0
- aspose_html_cloud_mcp-1.0.0/LICENSE +21 -0
- aspose_html_cloud_mcp-1.0.0/PKG-INFO +199 -0
- aspose_html_cloud_mcp-1.0.0/README.md +173 -0
- aspose_html_cloud_mcp-1.0.0/pyproject.toml +53 -0
- aspose_html_cloud_mcp-1.0.0/src/aspose_html_cloud_mcp/__init__.py +5 -0
- aspose_html_cloud_mcp-1.0.0/src/aspose_html_cloud_mcp/__main__.py +5 -0
- aspose_html_cloud_mcp-1.0.0/src/aspose_html_cloud_mcp/auth.py +65 -0
- aspose_html_cloud_mcp-1.0.0/src/aspose_html_cloud_mcp/server.py +482 -0
- aspose_html_cloud_mcp-1.0.0/stderr.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aspose Pty Ltd
|
|
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,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aspose-html-cloud-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server that exposes Aspose.HTML Cloud document conversion (HTML, Markdown, SVG, EPUB ? PDF, DOCX, PNG, etc.) as tools for AI assistants.
|
|
5
|
+
Project-URL: Homepage, https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP
|
|
6
|
+
Project-URL: Repository, https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP
|
|
7
|
+
Project-URL: Issues, https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP/issues
|
|
8
|
+
Author-email: Aspose <support@aspose.cloud>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,aspose,claude,conversion,copilot,cursor,html,mcp,model-context-protocol,pdf
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: mcp>=1.0.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Aspose.HTML Cloud MCP Server (Python)
|
|
28
|
+
|
|
29
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes **Aspose.HTML Cloud** document conversion as tools for AI assistants like Claude Desktop, Cursor, VS Code Copilot, and other MCP-compatible clients.
|
|
30
|
+
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
Converts documents between formats via the [Aspose.HTML Cloud API](https://products.aspose.cloud/html/).
|
|
34
|
+
|
|
35
|
+
| Supported input formats | Supported output formats |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `html`, `mhtml`, `xhtml`, `epub`, `svg`, `md` | `pdf`, `xps`, `docx`, `doc`, `jpeg`, `png`, `bmp`, `gif`, `tiff`, `webp`, `md`, `mhtml`, `svg` |
|
|
38
|
+
|
|
39
|
+
**Available tools:**
|
|
40
|
+
|
|
41
|
+
| Tool | Input source |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `convert_url_to_format` | Public URL |
|
|
44
|
+
| `convert_content_to_format` | Raw HTML / SVG / Markdown string |
|
|
45
|
+
| `convert_base64_to_format` | Base64-encoded document |
|
|
46
|
+
| `convert_file_to_format` | Local file path |
|
|
47
|
+
| `vectorize_image` | Raster image ? SVG vectorization |
|
|
48
|
+
|
|
49
|
+
**Example prompts:**
|
|
50
|
+
|
|
51
|
+
> "Convert https://example.com to PDF"
|
|
52
|
+
|
|
53
|
+
> "Convert this HTML to DOCX: \<html\>\<body\>\<h1\>Hello\</h1\>\</body\>\</html\>"
|
|
54
|
+
|
|
55
|
+
> "Convert C:\docs\report.html to PNG"
|
|
56
|
+
|
|
57
|
+
> "Vectorize this PNG image into SVG"
|
|
58
|
+
|
|
59
|
+
## Prerequisites
|
|
60
|
+
|
|
61
|
+
- **Python 3.10+**
|
|
62
|
+
- A free **Aspose Cloud** account -- sign up at [dashboard.aspose.cloud](https://dashboard.aspose.cloud/) and create an application to get your **Client ID** and **Client Secret**
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install aspose-html-cloud-mcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or run directly with [`uvx`](https://docs.astral.sh/uv/) (no install needed):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uvx aspose-html-cloud-mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configuration with MCP Clients
|
|
77
|
+
|
|
78
|
+
Credentials are passed via environment variables `ASPOSE_CLIENT_ID` and `ASPOSE_CLIENT_SECRET`.
|
|
79
|
+
|
|
80
|
+
### Claude Desktop
|
|
81
|
+
|
|
82
|
+
Edit your Claude Desktop config file:
|
|
83
|
+
|
|
84
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
85
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"aspose-html-cloud": {
|
|
91
|
+
"command": "uvx",
|
|
92
|
+
"args": ["aspose-html-cloud-mcp"],
|
|
93
|
+
"env": {
|
|
94
|
+
"ASPOSE_CLIENT_ID": "your-client-id",
|
|
95
|
+
"ASPOSE_CLIENT_SECRET": "your-client-secret"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### VS Code (Copilot)
|
|
103
|
+
|
|
104
|
+
Add to your `.vscode/settings.json` or user settings:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcp": {
|
|
109
|
+
"servers": {
|
|
110
|
+
"aspose-html-cloud": {
|
|
111
|
+
"command": "uvx",
|
|
112
|
+
"args": ["aspose-html-cloud-mcp"],
|
|
113
|
+
"env": {
|
|
114
|
+
"ASPOSE_CLIENT_ID": "your-client-id",
|
|
115
|
+
"ASPOSE_CLIENT_SECRET": "your-client-secret"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Cursor
|
|
124
|
+
|
|
125
|
+
Add to your Cursor MCP configuration (`~/.cursor/mcp.json`):
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"mcpServers": {
|
|
130
|
+
"aspose-html-cloud": {
|
|
131
|
+
"command": "uvx",
|
|
132
|
+
"args": ["aspose-html-cloud-mcp"],
|
|
133
|
+
"env": {
|
|
134
|
+
"ASPOSE_CLIENT_ID": "your-client-id",
|
|
135
|
+
"ASPOSE_CLIENT_SECRET": "your-client-secret"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Alternative: Using pip + python
|
|
143
|
+
|
|
144
|
+
If you installed via `pip install` instead of `uvx`, replace the command:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"command": "aspose-html-cloud-mcp"
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Or:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"command": "python",
|
|
157
|
+
"args": ["-m", "aspose_html_cloud_mcp"]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Environment Variables
|
|
162
|
+
|
|
163
|
+
| Variable | Required | Description |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `ASPOSE_CLIENT_ID` | Yes | Your Aspose Cloud application Client ID |
|
|
166
|
+
| `ASPOSE_CLIENT_SECRET` | Yes | Your Aspose Cloud application Client Secret |
|
|
167
|
+
| `ASPOSE_HTML_API_URL` | No | Override the API base URL (default: `https://api.aspose.cloud`) |
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Clone the repository
|
|
173
|
+
git clone https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP.git
|
|
174
|
+
cd Aspose.HTML-Cloud-MCP/python
|
|
175
|
+
|
|
176
|
+
# Create a virtual environment and install in dev mode
|
|
177
|
+
python -m venv .venv
|
|
178
|
+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
179
|
+
pip install -e .
|
|
180
|
+
|
|
181
|
+
# Run the server
|
|
182
|
+
export ASPOSE_CLIENT_ID="your-client-id"
|
|
183
|
+
export ASPOSE_CLIENT_SECRET="your-client-secret"
|
|
184
|
+
aspose-html-cloud-mcp
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Publishing to PyPI
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install build twine
|
|
191
|
+
python -m build
|
|
192
|
+
twine upload dist/*
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
198
|
+
|
|
199
|
+
The Aspose.HTML Cloud API itself requires a separate subscription -- a free tier is available at [aspose.cloud](https://purchase.aspose.cloud/pricing).
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Aspose.HTML Cloud MCP Server (Python)
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes **Aspose.HTML Cloud** document conversion as tools for AI assistants like Claude Desktop, Cursor, VS Code Copilot, and other MCP-compatible clients.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Converts documents between formats via the [Aspose.HTML Cloud API](https://products.aspose.cloud/html/).
|
|
8
|
+
|
|
9
|
+
| Supported input formats | Supported output formats |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `html`, `mhtml`, `xhtml`, `epub`, `svg`, `md` | `pdf`, `xps`, `docx`, `doc`, `jpeg`, `png`, `bmp`, `gif`, `tiff`, `webp`, `md`, `mhtml`, `svg` |
|
|
12
|
+
|
|
13
|
+
**Available tools:**
|
|
14
|
+
|
|
15
|
+
| Tool | Input source |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `convert_url_to_format` | Public URL |
|
|
18
|
+
| `convert_content_to_format` | Raw HTML / SVG / Markdown string |
|
|
19
|
+
| `convert_base64_to_format` | Base64-encoded document |
|
|
20
|
+
| `convert_file_to_format` | Local file path |
|
|
21
|
+
| `vectorize_image` | Raster image ? SVG vectorization |
|
|
22
|
+
|
|
23
|
+
**Example prompts:**
|
|
24
|
+
|
|
25
|
+
> "Convert https://example.com to PDF"
|
|
26
|
+
|
|
27
|
+
> "Convert this HTML to DOCX: \<html\>\<body\>\<h1\>Hello\</h1\>\</body\>\</html\>"
|
|
28
|
+
|
|
29
|
+
> "Convert C:\docs\report.html to PNG"
|
|
30
|
+
|
|
31
|
+
> "Vectorize this PNG image into SVG"
|
|
32
|
+
|
|
33
|
+
## Prerequisites
|
|
34
|
+
|
|
35
|
+
- **Python 3.10+**
|
|
36
|
+
- A free **Aspose Cloud** account -- sign up at [dashboard.aspose.cloud](https://dashboard.aspose.cloud/) and create an application to get your **Client ID** and **Client Secret**
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install aspose-html-cloud-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or run directly with [`uvx`](https://docs.astral.sh/uv/) (no install needed):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uvx aspose-html-cloud-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration with MCP Clients
|
|
51
|
+
|
|
52
|
+
Credentials are passed via environment variables `ASPOSE_CLIENT_ID` and `ASPOSE_CLIENT_SECRET`.
|
|
53
|
+
|
|
54
|
+
### Claude Desktop
|
|
55
|
+
|
|
56
|
+
Edit your Claude Desktop config file:
|
|
57
|
+
|
|
58
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
59
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"aspose-html-cloud": {
|
|
65
|
+
"command": "uvx",
|
|
66
|
+
"args": ["aspose-html-cloud-mcp"],
|
|
67
|
+
"env": {
|
|
68
|
+
"ASPOSE_CLIENT_ID": "your-client-id",
|
|
69
|
+
"ASPOSE_CLIENT_SECRET": "your-client-secret"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### VS Code (Copilot)
|
|
77
|
+
|
|
78
|
+
Add to your `.vscode/settings.json` or user settings:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcp": {
|
|
83
|
+
"servers": {
|
|
84
|
+
"aspose-html-cloud": {
|
|
85
|
+
"command": "uvx",
|
|
86
|
+
"args": ["aspose-html-cloud-mcp"],
|
|
87
|
+
"env": {
|
|
88
|
+
"ASPOSE_CLIENT_ID": "your-client-id",
|
|
89
|
+
"ASPOSE_CLIENT_SECRET": "your-client-secret"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Cursor
|
|
98
|
+
|
|
99
|
+
Add to your Cursor MCP configuration (`~/.cursor/mcp.json`):
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"aspose-html-cloud": {
|
|
105
|
+
"command": "uvx",
|
|
106
|
+
"args": ["aspose-html-cloud-mcp"],
|
|
107
|
+
"env": {
|
|
108
|
+
"ASPOSE_CLIENT_ID": "your-client-id",
|
|
109
|
+
"ASPOSE_CLIENT_SECRET": "your-client-secret"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Alternative: Using pip + python
|
|
117
|
+
|
|
118
|
+
If you installed via `pip install` instead of `uvx`, replace the command:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"command": "aspose-html-cloud-mcp"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"command": "python",
|
|
131
|
+
"args": ["-m", "aspose_html_cloud_mcp"]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Environment Variables
|
|
136
|
+
|
|
137
|
+
| Variable | Required | Description |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `ASPOSE_CLIENT_ID` | Yes | Your Aspose Cloud application Client ID |
|
|
140
|
+
| `ASPOSE_CLIENT_SECRET` | Yes | Your Aspose Cloud application Client Secret |
|
|
141
|
+
| `ASPOSE_HTML_API_URL` | No | Override the API base URL (default: `https://api.aspose.cloud`) |
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Clone the repository
|
|
147
|
+
git clone https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP.git
|
|
148
|
+
cd Aspose.HTML-Cloud-MCP/python
|
|
149
|
+
|
|
150
|
+
# Create a virtual environment and install in dev mode
|
|
151
|
+
python -m venv .venv
|
|
152
|
+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
153
|
+
pip install -e .
|
|
154
|
+
|
|
155
|
+
# Run the server
|
|
156
|
+
export ASPOSE_CLIENT_ID="your-client-id"
|
|
157
|
+
export ASPOSE_CLIENT_SECRET="your-client-secret"
|
|
158
|
+
aspose-html-cloud-mcp
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Publishing to PyPI
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
pip install build twine
|
|
165
|
+
python -m build
|
|
166
|
+
twine upload dist/*
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
172
|
+
|
|
173
|
+
The Aspose.HTML Cloud API itself requires a separate subscription -- a free tier is available at [aspose.cloud](https://purchase.aspose.cloud/pricing).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aspose-html-cloud-mcp"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP server that exposes Aspose.HTML Cloud document conversion (HTML, Markdown, SVG, EPUB ? PDF, DOCX, PNG, etc.) as tools for AI assistants."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Aspose", email = "support@aspose.cloud" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"aspose",
|
|
19
|
+
"html",
|
|
20
|
+
"conversion",
|
|
21
|
+
"pdf",
|
|
22
|
+
"ai",
|
|
23
|
+
"claude",
|
|
24
|
+
"copilot",
|
|
25
|
+
"cursor",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 4 - Beta",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Topic :: Software Development :: Libraries",
|
|
37
|
+
"Topic :: Text Processing :: Markup :: HTML",
|
|
38
|
+
]
|
|
39
|
+
dependencies = [
|
|
40
|
+
"mcp>=1.0.0",
|
|
41
|
+
"httpx>=0.27.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP"
|
|
46
|
+
Repository = "https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP"
|
|
47
|
+
Issues = "https://github.com/aspose-html-cloud/Aspose.HTML-Cloud-MCP/issues"
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
aspose-html-cloud-mcp = "aspose_html_cloud_mcp:main"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/aspose_html_cloud_mcp"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Aspose Cloud OAuth2 client-credentials authentication.
|
|
2
|
+
|
|
3
|
+
Credentials are resolved from environment variables ``ASPOSE_CLIENT_ID`` and
|
|
4
|
+
``ASPOSE_CLIENT_SECRET``. Tokens are cached and automatically refreshed when
|
|
5
|
+
they expire (with a 30-second safety buffer).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from urllib.parse import quote
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
TOKEN_ENDPOINT = "https://api.aspose.cloud/connect/token"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AsposeHtmlAuth:
|
|
20
|
+
"""Manages bearer-token acquisition for the Aspose.HTML Cloud API."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, http_client: httpx.AsyncClient) -> None:
|
|
23
|
+
self._http = http_client
|
|
24
|
+
self._client_id = os.environ.get("ASPOSE_CLIENT_ID", "")
|
|
25
|
+
self._client_secret = os.environ.get("ASPOSE_CLIENT_SECRET", "")
|
|
26
|
+
if not self._client_id:
|
|
27
|
+
raise RuntimeError(
|
|
28
|
+
"Environment variable ASPOSE_CLIENT_ID is not set. "
|
|
29
|
+
"Get your free credentials at https://dashboard.aspose.cloud/"
|
|
30
|
+
)
|
|
31
|
+
if not self._client_secret:
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
"Environment variable ASPOSE_CLIENT_SECRET is not set. "
|
|
34
|
+
"Get your free credentials at https://dashboard.aspose.cloud/"
|
|
35
|
+
)
|
|
36
|
+
self._cached_token: str | None = None
|
|
37
|
+
self._token_expiry: float = 0.0
|
|
38
|
+
|
|
39
|
+
async def get_token(self) -> str:
|
|
40
|
+
"""Return a valid bearer token, fetching a new one when needed."""
|
|
41
|
+
now = time.monotonic()
|
|
42
|
+
if self._cached_token is not None and now < self._token_expiry:
|
|
43
|
+
return self._cached_token
|
|
44
|
+
|
|
45
|
+
body = (
|
|
46
|
+
f"grant_type=client_credentials"
|
|
47
|
+
f"&client_id={quote(self._client_id, safe='')}"
|
|
48
|
+
f"&client_secret={quote(self._client_secret, safe='')}"
|
|
49
|
+
)
|
|
50
|
+
response = await self._http.post(
|
|
51
|
+
TOKEN_ENDPOINT,
|
|
52
|
+
content=body,
|
|
53
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
54
|
+
)
|
|
55
|
+
if response.status_code >= 400:
|
|
56
|
+
raise RuntimeError(
|
|
57
|
+
f"Failed to obtain Aspose bearer token: "
|
|
58
|
+
f"{response.status_code} - {response.text}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
data = response.json()
|
|
62
|
+
self._cached_token = data["access_token"]
|
|
63
|
+
# Subtract a 30-second buffer to avoid using a just-expired token
|
|
64
|
+
self._token_expiry = now + data.get("expires_in", 3600) - 30
|
|
65
|
+
return self._cached_token
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
"""Aspose.HTML Cloud MCP Server -- stdio-based Model Context Protocol server.
|
|
2
|
+
|
|
3
|
+
Exposes Aspose.HTML Cloud document conversion and image vectorization as MCP
|
|
4
|
+
tools for AI assistants (Claude Desktop, Cursor, VS Code Copilot, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
from mcp.server.fastmcp import FastMCP
|
|
21
|
+
|
|
22
|
+
from .auth import AsposeHtmlAuth
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Constants
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
SUPPORTED_INPUT_FORMATS = {"html", "mhtml", "xhtml", "epub", "svg", "md"}
|
|
29
|
+
SUPPORTED_OUTPUT_FORMATS = {
|
|
30
|
+
"pdf", "xps", "docx", "doc",
|
|
31
|
+
"jpeg", "png", "bmp", "gif", "tiff", "webp",
|
|
32
|
+
"md", "mhtml", "svg",
|
|
33
|
+
}
|
|
34
|
+
SUPPORTED_VECTORIZATION_FORMATS = {"png", "jpeg", "gif", "tiff", "bmp", "webp"}
|
|
35
|
+
|
|
36
|
+
API_BASE = os.environ.get("ASPOSE_HTML_API_URL", "https://api.aspose.cloud")
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Logging -- write to stderr so stdout stays clean for JSON-RPC
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
logging.basicConfig(
|
|
43
|
+
stream=sys.stderr,
|
|
44
|
+
level=logging.INFO,
|
|
45
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
46
|
+
)
|
|
47
|
+
logger = logging.getLogger("aspose_html_cloud_mcp")
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Shared HTTP client & auth (created lazily on first tool call)
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
_http_client: httpx.AsyncClient | None = None
|
|
54
|
+
_auth: AsposeHtmlAuth | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_http_client() -> httpx.AsyncClient:
|
|
58
|
+
global _http_client
|
|
59
|
+
if _http_client is None:
|
|
60
|
+
_http_client = httpx.AsyncClient(timeout=httpx.Timeout(300.0))
|
|
61
|
+
return _http_client
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_auth() -> AsposeHtmlAuth:
|
|
65
|
+
global _auth
|
|
66
|
+
if _auth is None:
|
|
67
|
+
_auth = AsposeHtmlAuth(_get_http_client())
|
|
68
|
+
return _auth
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Result helpers
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
def _success(base64_data: str, output_format: str, size_bytes: int, source: str) -> dict[str, Any]:
|
|
76
|
+
return {
|
|
77
|
+
"success": True,
|
|
78
|
+
"message": f"Successfully converted to {output_format.upper()} ({size_bytes:,} bytes).",
|
|
79
|
+
"fileBase64": base64_data,
|
|
80
|
+
"outputFormat": output_format,
|
|
81
|
+
"fileSizeBytes": size_bytes,
|
|
82
|
+
"source": source,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _failure(message: str) -> dict[str, Any]:
|
|
87
|
+
return {
|
|
88
|
+
"success": False,
|
|
89
|
+
"message": message,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Validation
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
def _validate_formats(input_format: str, output_format: str) -> dict[str, Any] | None:
|
|
98
|
+
if input_format not in SUPPORTED_INPUT_FORMATS:
|
|
99
|
+
return _failure(
|
|
100
|
+
f"Unsupported input format '{input_format}'. "
|
|
101
|
+
f"Supported: {', '.join(sorted(SUPPORTED_INPUT_FORMATS))}."
|
|
102
|
+
)
|
|
103
|
+
if output_format not in SUPPORTED_OUTPUT_FORMATS:
|
|
104
|
+
return _failure(
|
|
105
|
+
f"Unsupported output format '{output_format}'. "
|
|
106
|
+
f"Supported: {', '.join(sorted(SUPPORTED_OUTPUT_FORMATS))}."
|
|
107
|
+
)
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _is_http_url(value: str) -> bool:
|
|
112
|
+
try:
|
|
113
|
+
parsed = urlparse(value)
|
|
114
|
+
return parsed.scheme in ("http", "https") and bool(parsed.netloc)
|
|
115
|
+
except Exception:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Core API calls
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
async def _execute_conversion(
|
|
124
|
+
input_format: str,
|
|
125
|
+
output_format: str,
|
|
126
|
+
request_body: dict[str, Any],
|
|
127
|
+
source: str,
|
|
128
|
+
) -> dict[str, Any]:
|
|
129
|
+
auth = _get_auth()
|
|
130
|
+
http = _get_http_client()
|
|
131
|
+
token = await auth.get_token()
|
|
132
|
+
endpoint = f"{API_BASE}/v4.0/html/conversion/{input_format}-{output_format}/sync"
|
|
133
|
+
|
|
134
|
+
logger.info("POST %s", endpoint)
|
|
135
|
+
logger.info("Request body: %s", json.dumps(request_body))
|
|
136
|
+
|
|
137
|
+
start = time.monotonic()
|
|
138
|
+
response = await http.post(
|
|
139
|
+
endpoint,
|
|
140
|
+
json=request_body,
|
|
141
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
142
|
+
)
|
|
143
|
+
elapsed_ms = (time.monotonic() - start) * 1000
|
|
144
|
+
logger.info("Response: %d in %.0fms", response.status_code, elapsed_ms)
|
|
145
|
+
|
|
146
|
+
if response.status_code >= 400:
|
|
147
|
+
logger.error(
|
|
148
|
+
"Conversion failed. Status=%d Body=%s",
|
|
149
|
+
response.status_code, response.text,
|
|
150
|
+
)
|
|
151
|
+
return _failure(
|
|
152
|
+
f"Conversion API returned {response.status_code}: {response.text}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
file_bytes = response.content
|
|
156
|
+
logger.info("Conversion succeeded. Response size=%d bytes", len(file_bytes))
|
|
157
|
+
|
|
158
|
+
b64 = base64.b64encode(file_bytes).decode("ascii")
|
|
159
|
+
return _success(b64, output_format, len(file_bytes), source)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def _execute_vectorization(
|
|
163
|
+
image_format: str,
|
|
164
|
+
request_body: dict[str, Any],
|
|
165
|
+
source: str,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
auth = _get_auth()
|
|
168
|
+
http = _get_http_client()
|
|
169
|
+
token = await auth.get_token()
|
|
170
|
+
endpoint = f"{API_BASE}/v4.0/html/vectorization/{image_format}/sync"
|
|
171
|
+
|
|
172
|
+
logger.info("POST %s", endpoint)
|
|
173
|
+
logger.info("Request body: %s", json.dumps(request_body))
|
|
174
|
+
|
|
175
|
+
start = time.monotonic()
|
|
176
|
+
response = await http.post(
|
|
177
|
+
endpoint,
|
|
178
|
+
json=request_body,
|
|
179
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
180
|
+
)
|
|
181
|
+
elapsed_ms = (time.monotonic() - start) * 1000
|
|
182
|
+
logger.info("Response: %d in %.0fms", response.status_code, elapsed_ms)
|
|
183
|
+
|
|
184
|
+
if response.status_code >= 400:
|
|
185
|
+
logger.error(
|
|
186
|
+
"Vectorization failed. Status=%d Body=%s",
|
|
187
|
+
response.status_code, response.text,
|
|
188
|
+
)
|
|
189
|
+
return _failure(
|
|
190
|
+
f"Vectorization API returned {response.status_code}: {response.text}"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
file_bytes = response.content
|
|
194
|
+
logger.info("Vectorization succeeded. Response size=%d bytes", len(file_bytes))
|
|
195
|
+
|
|
196
|
+
b64 = base64.b64encode(file_bytes).decode("ascii")
|
|
197
|
+
return _success(b64, "svg", len(file_bytes), source)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def _upload_file(file_path: str) -> str:
|
|
201
|
+
"""Upload a local file to Aspose Cloud storage and return the storage path."""
|
|
202
|
+
auth = _get_auth()
|
|
203
|
+
http = _get_http_client()
|
|
204
|
+
token = await auth.get_token()
|
|
205
|
+
endpoint = f"{API_BASE}/v4.0/html/file"
|
|
206
|
+
|
|
207
|
+
file_name = Path(file_path).name
|
|
208
|
+
with open(file_path, "rb") as f:
|
|
209
|
+
file_data = f.read()
|
|
210
|
+
|
|
211
|
+
response = await http.post(
|
|
212
|
+
endpoint,
|
|
213
|
+
files={"file": (file_name, file_data, "application/octet-stream")},
|
|
214
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if response.status_code >= 400:
|
|
218
|
+
raise RuntimeError(
|
|
219
|
+
f"Upload failed: {response.status_code} - {response.text}"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
data = response.json()
|
|
223
|
+
uploaded = data.get("uploaded", [])
|
|
224
|
+
if not uploaded:
|
|
225
|
+
raise RuntimeError("Upload succeeded but no file paths were returned.")
|
|
226
|
+
return uploaded[0]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
# MCP Server & Tools
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
mcp = FastMCP(
|
|
234
|
+
"aspose-html-cloud",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@mcp.tool(
|
|
239
|
+
name="convert_url_to_format",
|
|
240
|
+
description=(
|
|
241
|
+
"Converts a publicly accessible URL (HTML, MHTML, XHTML, EPUB, SVG, or Markdown page) "
|
|
242
|
+
"to a target document format using Aspose.HTML Cloud. "
|
|
243
|
+
"Returns the converted file encoded as a Base64 string together with metadata. "
|
|
244
|
+
"Supported input formats: html, mhtml, xhtml, epub, svg, md. "
|
|
245
|
+
"Supported output formats: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg."
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
async def convert_url_to_format(
|
|
249
|
+
source_url: str,
|
|
250
|
+
output_format: str,
|
|
251
|
+
input_format: str = "html",
|
|
252
|
+
) -> str:
|
|
253
|
+
"""Convert a public URL to the requested format.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
source_url: Publicly accessible URL of the source document (e.g. https://example.com/page.html).
|
|
257
|
+
output_format: Output format. Supported values: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg.
|
|
258
|
+
input_format: Input format of the source URL. Defaults to 'html'. Supported values: html, mhtml, xhtml, epub, svg, md.
|
|
259
|
+
"""
|
|
260
|
+
input_format = input_format.lower().strip()
|
|
261
|
+
output_format = output_format.lower().strip()
|
|
262
|
+
|
|
263
|
+
logger.info(
|
|
264
|
+
"convert_url_to_format: sourceUrl=%s %s->%s",
|
|
265
|
+
source_url, input_format, output_format,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
validation = _validate_formats(input_format, output_format)
|
|
269
|
+
if validation is not None:
|
|
270
|
+
return json.dumps(validation)
|
|
271
|
+
|
|
272
|
+
if not _is_http_url(source_url):
|
|
273
|
+
return json.dumps(
|
|
274
|
+
_failure(f"'{source_url}' is not a valid absolute HTTP/HTTPS URL.")
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
result = await _execute_conversion(
|
|
278
|
+
input_format, output_format, {"InputPath": source_url}, source_url,
|
|
279
|
+
)
|
|
280
|
+
return json.dumps(result)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@mcp.tool(
|
|
284
|
+
name="convert_content_to_format",
|
|
285
|
+
description=(
|
|
286
|
+
"Converts raw HTML (or SVG, Markdown, etc.) content provided as a string "
|
|
287
|
+
"to a target document format using Aspose.HTML Cloud. "
|
|
288
|
+
"Use this when you have the markup content directly rather than a URL or file. "
|
|
289
|
+
"Returns the converted file encoded as a Base64 string together with metadata. "
|
|
290
|
+
"Supported input formats: html, mhtml, xhtml, epub, svg, md. "
|
|
291
|
+
"Supported output formats: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg."
|
|
292
|
+
),
|
|
293
|
+
)
|
|
294
|
+
async def convert_content_to_format(
|
|
295
|
+
content: str,
|
|
296
|
+
output_format: str,
|
|
297
|
+
input_format: str = "html",
|
|
298
|
+
) -> str:
|
|
299
|
+
"""Convert raw markup content to the requested format.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
content: The markup content to convert (e.g. '<html><body><h1>Hello</h1></body></html>').
|
|
303
|
+
output_format: Output format. Supported values: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg.
|
|
304
|
+
input_format: Input format of the content. Defaults to 'html'. Supported values: html, mhtml, xhtml, epub, svg, md.
|
|
305
|
+
"""
|
|
306
|
+
input_format = input_format.lower().strip()
|
|
307
|
+
output_format = output_format.lower().strip()
|
|
308
|
+
|
|
309
|
+
logger.info(
|
|
310
|
+
"convert_content_to_format: contentLength=%d %s->%s",
|
|
311
|
+
len(content), input_format, output_format,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
validation = _validate_formats(input_format, output_format)
|
|
315
|
+
if validation is not None:
|
|
316
|
+
return json.dumps(validation)
|
|
317
|
+
|
|
318
|
+
if not content or not content.strip():
|
|
319
|
+
return json.dumps(_failure("Content must not be empty."))
|
|
320
|
+
|
|
321
|
+
result = await _execute_conversion(
|
|
322
|
+
input_format, output_format, {"InputContent": content}, "(content)",
|
|
323
|
+
)
|
|
324
|
+
return json.dumps(result)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@mcp.tool(
|
|
328
|
+
name="convert_base64_to_format",
|
|
329
|
+
description=(
|
|
330
|
+
"Converts a Base64-encoded document (HTML, SVG, Markdown, etc.) "
|
|
331
|
+
"to a target document format using Aspose.HTML Cloud. "
|
|
332
|
+
"Use this when the source document is available as a Base64 string. "
|
|
333
|
+
"Returns the converted file encoded as a Base64 string together with metadata. "
|
|
334
|
+
"Supported input formats: html, mhtml, xhtml, epub, svg, md. "
|
|
335
|
+
"Supported output formats: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg."
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
async def convert_base64_to_format(
|
|
339
|
+
input_base64: str,
|
|
340
|
+
output_format: str,
|
|
341
|
+
input_format: str = "html",
|
|
342
|
+
) -> str:
|
|
343
|
+
"""Convert a Base64-encoded document to the requested format.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
input_base64: Base64-encoded source document content.
|
|
347
|
+
output_format: Output format. Supported values: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg.
|
|
348
|
+
input_format: Input format of the content. Defaults to 'html'. Supported values: html, mhtml, xhtml, epub, svg, md.
|
|
349
|
+
"""
|
|
350
|
+
input_format = input_format.lower().strip()
|
|
351
|
+
output_format = output_format.lower().strip()
|
|
352
|
+
|
|
353
|
+
logger.info(
|
|
354
|
+
"convert_base64_to_format: base64Length=%d %s->%s",
|
|
355
|
+
len(input_base64), input_format, output_format,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
validation = _validate_formats(input_format, output_format)
|
|
359
|
+
if validation is not None:
|
|
360
|
+
return json.dumps(validation)
|
|
361
|
+
|
|
362
|
+
if not input_base64 or not input_base64.strip():
|
|
363
|
+
return json.dumps(_failure("Base64 input must not be empty."))
|
|
364
|
+
|
|
365
|
+
result = await _execute_conversion(
|
|
366
|
+
input_format, output_format, {"InputBase64": input_base64}, "(base64)",
|
|
367
|
+
)
|
|
368
|
+
return json.dumps(result)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@mcp.tool(
|
|
372
|
+
name="convert_file_to_format",
|
|
373
|
+
description=(
|
|
374
|
+
"Converts a local file (HTML, MHTML, XHTML, EPUB, SVG, or Markdown) "
|
|
375
|
+
"to a target document format using Aspose.HTML Cloud. "
|
|
376
|
+
"The file is uploaded to the cloud first, then converted. "
|
|
377
|
+
"Returns the converted file encoded as a Base64 string together with metadata. "
|
|
378
|
+
"Supported input formats: html, mhtml, xhtml, epub, svg, md. "
|
|
379
|
+
"Supported output formats: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg."
|
|
380
|
+
),
|
|
381
|
+
)
|
|
382
|
+
async def convert_file_to_format(
|
|
383
|
+
file_path: str,
|
|
384
|
+
output_format: str,
|
|
385
|
+
input_format: str = "html",
|
|
386
|
+
) -> str:
|
|
387
|
+
"""Convert a local file to the requested format.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
file_path: Absolute path to the local file to convert (e.g. C:\\docs\\page.html or /home/user/page.html).
|
|
391
|
+
output_format: Output format. Supported values: pdf, xps, docx, doc, jpeg, png, bmp, gif, tiff, webp, md, mhtml, svg.
|
|
392
|
+
input_format: Input format of the file. Defaults to 'html'. Supported values: html, mhtml, xhtml, epub, svg, md.
|
|
393
|
+
"""
|
|
394
|
+
input_format = input_format.lower().strip()
|
|
395
|
+
output_format = output_format.lower().strip()
|
|
396
|
+
|
|
397
|
+
logger.info(
|
|
398
|
+
"convert_file_to_format: filePath=%s %s->%s",
|
|
399
|
+
file_path, input_format, output_format,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
validation = _validate_formats(input_format, output_format)
|
|
403
|
+
if validation is not None:
|
|
404
|
+
return json.dumps(validation)
|
|
405
|
+
|
|
406
|
+
if not Path(file_path).is_file():
|
|
407
|
+
return json.dumps(_failure(f"File not found: '{file_path}'."))
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
storage_path = await _upload_file(file_path)
|
|
411
|
+
logger.info("File uploaded to storage: %s", storage_path)
|
|
412
|
+
except Exception as exc:
|
|
413
|
+
logger.error("File upload failed for %s: %s", file_path, exc)
|
|
414
|
+
return json.dumps(_failure(f"File upload failed: {exc}"))
|
|
415
|
+
|
|
416
|
+
result = await _execute_conversion(
|
|
417
|
+
input_format, output_format, {"InputPath": storage_path}, file_path,
|
|
418
|
+
)
|
|
419
|
+
return json.dumps(result)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@mcp.tool(
|
|
423
|
+
name="vectorize_image",
|
|
424
|
+
description=(
|
|
425
|
+
"Vectorizes a raster image (PNG, JPEG, GIF, TIFF, BMP, or WebP) into SVG format "
|
|
426
|
+
"using Aspose.HTML Cloud. This performs true image vectorization, converting pixels "
|
|
427
|
+
"into scalable vector paths, not simply wrapping the image in an SVG element. "
|
|
428
|
+
"The image can be provided as a public URL, a local file path, or a Base64-encoded string. "
|
|
429
|
+
"Returns the resulting SVG file encoded as a Base64 string together with metadata."
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
async def vectorize_image(
|
|
433
|
+
image_source: str,
|
|
434
|
+
image_format: str,
|
|
435
|
+
) -> str:
|
|
436
|
+
"""Vectorize a raster image into SVG.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
image_source: Image source -- a public URL (e.g. https://example.com/photo.png), an absolute local file path (e.g. C:\\images\\photo.png), or a Base64-encoded image string.
|
|
440
|
+
image_format: Image format. Supported values: png, jpeg, gif, tiff, bmp, webp.
|
|
441
|
+
"""
|
|
442
|
+
image_format = image_format.lower().strip()
|
|
443
|
+
|
|
444
|
+
logger.info("vectorize_image: source=%s format=%s", image_source, image_format)
|
|
445
|
+
|
|
446
|
+
if image_format not in SUPPORTED_VECTORIZATION_FORMATS:
|
|
447
|
+
return json.dumps(
|
|
448
|
+
_failure(
|
|
449
|
+
f"Unsupported image format '{image_format}'. "
|
|
450
|
+
f"Supported: {', '.join(sorted(SUPPORTED_VECTORIZATION_FORMATS))}."
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if _is_http_url(image_source):
|
|
455
|
+
request_body: dict[str, Any] = {"InputPath": image_source}
|
|
456
|
+
source = image_source
|
|
457
|
+
elif Path(image_source).is_file():
|
|
458
|
+
try:
|
|
459
|
+
storage_path = await _upload_file(image_source)
|
|
460
|
+
logger.info("File uploaded to storage: %s", storage_path)
|
|
461
|
+
except Exception as exc:
|
|
462
|
+
logger.error("File upload failed for %s: %s", image_source, exc)
|
|
463
|
+
return json.dumps(_failure(f"File upload failed: {exc}"))
|
|
464
|
+
request_body = {"InputPath": storage_path}
|
|
465
|
+
source = image_source
|
|
466
|
+
else:
|
|
467
|
+
if not image_source or not image_source.strip():
|
|
468
|
+
return json.dumps(_failure("Image source must not be empty."))
|
|
469
|
+
request_body = {"InputBase64": image_source}
|
|
470
|
+
source = "(base64)"
|
|
471
|
+
|
|
472
|
+
result = await _execute_vectorization(image_format, request_body, source)
|
|
473
|
+
return json.dumps(result)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ---------------------------------------------------------------------------
|
|
477
|
+
# Entry point
|
|
478
|
+
# ---------------------------------------------------------------------------
|
|
479
|
+
|
|
480
|
+
def main() -> None:
|
|
481
|
+
"""Run the MCP server over stdio."""
|
|
482
|
+
mcp.run(transport="stdio")
|
|
File without changes
|