Any2Markdown MCP Server 提供统一的RESTful API接口,支持将PDF、Word、Excel文档转换为Markdown格式。新的设计采用单一端点模式,通过/api/v1/convert端点处理所有类型的文档转换,支持两种调用方式:
- 文件上传方式 (multipart/form-data) - 直接上传文件
- JSON方式 (application/json) - 使用base64编码的文件内容
- 所有文档类型使用同一个API端点
- 统一的请求和响应格式
- 自动文件类型检测
- 支持单文件和多文件转换
- 支持文件特定选项和全局选项
- 支持多种输出格式
- 标准的HTTP multipart/form-data支持
- 清晰的错误信息
- 完整的API文档
端点: POST /api/v1/convert
功能:
- 支持PDF、Word、Excel文档转换
- 自动文件类型检测
- 单文件或多文件处理
- 两种调用方式:文件上传和JSON
Content-Type: multipart/form-data
表单字段:
文件字段:
file- 单文件上传file1,file2,file3, ... - 多文件上传
选项字段 (所有可选):
output_format- 输出格式 (markdown/html/json,默认markdown)extract_images- 是否提取图片 (true/false,默认true)remove_header_footer- 是否移除页眉页脚 (true/false,默认true)include_content- 是否包含转换内容 (true/false,默认true)
PDF特定选项:
paginate_output- 是否分页输出 (true/false,默认true)start_page- 起始页码 (数字,默认0)end_page- 结束页码 (数字,可选)languages- 语言列表 (逗号分隔,如"zh,en",默认"auto")
Word特定选项:
preserve_formatting- 是否保留格式 (true/false,默认true)
Excel特定选项:
include_formulas- 是否包含公式 (true/false,默认true)sheet_names- 工作表名称 (逗号分隔,可选)
单文件上传示例:
curl -X POST "http://localhost:3000/api/v1/convert" \
-F "file=@document.pdf" \
-F "output_format=markdown" \
-F "extract_images=true" \
-F "include_content=false" \
-F "start_page=0" \
-F "end_page=5" \
-F "languages=zh,en"多文件上传示例:
curl -X POST "http://localhost:3000/api/v1/convert" \
-F "file1=@document.pdf" \
-F "file2=@report.docx" \
-F "file3=@data.xlsx" \
-F "output_format=markdown" \
-F "extract_images=true" \
-F "include_content=false"Content-Type: application/json
请求体结构:
{
"files": [
{
"filename": "document.pdf",
"file_content": "base64-encoded-content",
"options": {
"output_format": "markdown",
"extract_images": true,
"start_page": 0,
"end_page": 5,
"languages": ["zh", "en"]
}
}
],
"global_options": {
"remove_header_footer": true,
"include_content": false
}
}字段说明:
files(必需) - 文件列表filename(必需) - 文件名,用于确定文件类型file_content(必需) - Base64编码的文件内容options(可选) - 文件特定选项
global_options(可选) - 全局选项,会被文件特定选项覆盖
单文件JSON示例:
curl -X POST "http://localhost:3000/api/v1/convert" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"filename": "document.pdf",
"file_content": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwo...",
"options": {
"output_format": "markdown",
"extract_images": true,
"include_content": false
}
}
]
}'多文件JSON示例:
curl -X POST "http://localhost:3000/api/v1/convert" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"filename": "doc1.pdf",
"file_content": "base64-content-1",
"options": {
"start_page": 0,
"end_page": 2
}
},
{
"filename": "doc2.docx",
"file_content": "base64-content-2",
"options": {
"preserve_formatting": true
}
}
],
"global_options": {
"output_format": "markdown",
"extract_images": true,
"include_content": false
}
}'{
"success": true,
"data": {
"markdown_content": "# 文档标题\n\n内容...",
"metadata": {
"source_type": "pdf",
"total_pages": 10,
"images_extracted": 5,
"detected_language": "zh",
"processing_time": 3.45,
"file_size": 1024000,
"options_used": {
"output_format": "markdown",
"extract_images": true,
"start_page": 0,
"end_page": 5
}
},
"images": [
{
"filename": "pdf_123456_page_1_img_1.png",
"url": "http://host:port/static/pdf_123456_page_1_img_1.png",
"size": 15234,
"page": 1,
"index": 1
}
],
"conversion_info": {
"status": "success",
"filename": "document.pdf",
"format": "markdown",
"processor": "PDFProcessor"
}
},
"message": "文件转换成功",
"timestamp": "2024-12-28T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"success": true,
"data": {
"results": [
{
"markdown_content": "...",
"metadata": { /* 转换元数据 */ },
"images": [ /* 图片列表 */ ],
"conversion_info": {
"status": "success",
"filename": "doc1.pdf",
"format": "markdown",
"processor": "PDFProcessor"
}
},
{
"conversion_info": {
"status": "error",
"filename": "doc2.docx",
"error": "不支持的文件格式"
},
"metadata": {}
}
],
"summary": {
"total": 2,
"successful": 1,
"failed": 1,
"processing_time": 8.23
}
},
"message": "批量转换完成: 1成功, 1失败",
"timestamp": "2024-12-28T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": {
"validation_errors": [
{
"field": "file",
"message": "至少需要上传一个文件",
"type": "missing"
}
]
}
},
"timestamp": "2024-12-28T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}端点: GET /api/v1/analyze/pdf
查询参数:
file_content(必需) - Base64编码的PDF内容filename(可选) - 文件名,默认"document.pdf"
端点: POST /api/v1/validate
请求体:
{
"file_content": "base64-encoded-content",
"filename": "document.pdf"
}端点: GET /api/v1/status
端点: GET /api/v1/formats
端点: GET /api/v1/docs - Swagger UI界面
端点: GET /api/v1/openapi.json - OpenAPI规范
| 状态码 | 说明 | 描述 |
|---|---|---|
200 |
成功 | 操作成功完成 |
400 |
请求错误 | 参数验证失败、文件格式错误 |
401 |
未授权 | API密钥无效或缺失 |
413 |
文件过大 | 文件大小超过限制 |
422 |
处理失败 | 文档转换失败 |
429 |
请求过多 | 触发限流 |
500 |
服务器错误 | 内部错误 |
VALIDATION_ERROR- 参数验证失败UNSUPPORTED_FORMAT- 不支持的文件格式FILE_TOO_LARGE- 文件大小超过限制PROCESSING_FAILED- 文档转换失败RATE_LIMIT_EXCEEDED- 触发限流UNAUTHORIZED- 未授权访问INTERNAL_ERROR- 内部服务器错误
| 格式 | 扩展名 | MIME类型 | 最大大小 | 支持功能 |
|---|---|---|---|---|
.pdf |
application/pdf |
100MB | 文本、图片、表格、页眉页脚 | |
| Word | .docx |
application/vnd.openxmlformats-officedocument.wordprocessingml.document |
50MB | 文本、图片、公式、页眉页脚 |
| Word | .doc |
application/msword |
50MB | 文本、图片、基础格式 |
| Excel | .xlsx |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
30MB | 文本、图片、公式、图表 |
| Excel | .xls |
application/vnd.ms-excel |
30MB | 文本、基础公式 |
markdown- Markdown格式 (默认)html- HTML格式json- JSON格式
<!DOCTYPE html>
<html>
<head>
<title>文档转换</title>
</head>
<body>
<h1>Any2Markdown 转换器</h1>
<!-- 单文件上传 -->
<form action="/api/v1/convert" method="post" enctype="multipart/form-data">
<div>
<label>选择文件:</label>
<input type="file" name="file" accept=".pdf,.docx,.doc,.xlsx,.xls" required>
</div>
<div>
<label>输出格式:</label>
<select name="output_format">
<option value="markdown">Markdown</option>
<option value="html">HTML</option>
<option value="json">JSON</option>
</select>
</div>
<div>
<label>
<input type="checkbox" name="extract_images" value="true" checked>
提取图片
</label>
</div>
<div>
<label>
<input type="checkbox" name="include_content" value="false">
仅返回元数据
</label>
</div>
<button type="submit">转换</button>
</form>
<!-- 多文件上传 -->
<form action="/api/v1/convert" method="post" enctype="multipart/form-data">
<h2>批量转换</h2>
<div>
<label>文件1:</label>
<input type="file" name="file1" accept=".pdf,.docx,.doc,.xlsx,.xls">
</div>
<div>
<label>文件2:</label>
<input type="file" name="file2" accept=".pdf,.docx,.doc,.xlsx,.xls">
</div>
<div>
<label>文件3:</label>
<input type="file" name="file3" accept=".pdf,.docx,.doc,.xlsx,.xls">
</div>
<div>
<label>
<input type="checkbox" name="include_content" value="false" checked>
仅返回元数据
</label>
</div>
<button type="submit">批量转换</button>
</form>
</body>
</html>// 单文件上传
async function convertSingleFile(file, options = {}) {
const formData = new FormData();
formData.append('file', file);
// 添加选项
Object.entries(options).forEach(([key, value]) => {
formData.append(key, value);
});
try {
const response = await fetch('/api/v1/convert', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
console.log('转换成功:', result.data);
return result.data;
} else {
console.error('转换失败:', result.error);
throw new Error(result.error.message);
}
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
// 多文件上传
async function convertMultipleFiles(files, options = {}) {
const formData = new FormData();
// 添加文件
files.forEach((file, index) => {
formData.append(`file${index + 1}`, file);
});
// 添加全局选项
Object.entries(options).forEach(([key, value]) => {
formData.append(key, value);
});
try {
const response = await fetch('/api/v1/convert', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
console.log('批量转换完成:', result.data);
return result.data;
} else {
console.error('批量转换失败:', result.error);
throw new Error(result.error.message);
}
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
// JSON方式转换
async function convertWithJSON(fileData) {
try {
const response = await fetch('/api/v1/convert', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(fileData)
});
const result = await response.json();
if (result.success) {
console.log('JSON转换成功:', result.data);
return result.data;
} else {
console.error('JSON转换失败:', result.error);
throw new Error(result.error.message);
}
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
// 使用示例
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
try {
const result = await convertSingleFile(file, {
output_format: 'markdown',
extract_images: 'true',
include_content: 'false'
});
console.log('转换结果:', result);
// 处理转换结果
} catch (error) {
alert('转换失败: ' + error.message);
}
}
});import requests
import base64
import json
class Any2MarkdownClient:
def __init__(self, base_url="http://localhost:3000"):
self.base_url = base_url
self.convert_url = f"{base_url}/api/v1/convert"
def convert_file_upload(self, file_path, **options):
"""文件上传方式转换"""
with open(file_path, 'rb') as f:
files = {'file': f}
data = options
response = requests.post(self.convert_url, files=files, data=data)
return response.json()
def convert_multiple_files(self, file_paths, **options):
"""多文件上传转换"""
files = {}
for i, file_path in enumerate(file_paths, 1):
files[f'file{i}'] = open(file_path, 'rb')
try:
response = requests.post(self.convert_url, files=files, data=options)
return response.json()
finally:
# 关闭文件
for f in files.values():
f.close()
def convert_json(self, files_data, global_options=None):
"""JSON方式转换"""
payload = {
"files": files_data,
"global_options": global_options or {}
}
headers = {'Content-Type': 'application/json'}
response = requests.post(self.convert_url, json=payload, headers=headers)
return response.json()
def convert_file_to_base64(self, file_path):
"""将文件转换为base64编码"""
with open(file_path, 'rb') as f:
return base64.b64encode(f.read()).decode()
# 使用示例
client = Any2MarkdownClient()
# 单文件上传
result = client.convert_file_upload(
'document.pdf',
output_format='markdown',
extract_images='true',
include_content='false'
)
# 多文件上传
result = client.convert_multiple_files(
['doc1.pdf', 'doc2.docx', 'data.xlsx'],
output_format='markdown',
include_content='false'
)
# JSON方式
files_data = [
{
"filename": "document.pdf",
"file_content": client.convert_file_to_base64("document.pdf"),
"options": {
"start_page": 0,
"end_page": 5
}
}
]
result = client.convert_json(
files_data,
global_options={
"output_format": "markdown",
"extract_images": True,
"include_content": False
}
)
print(json.dumps(result, indent=2, ensure_ascii=False))- PDF: 最大100MB
- Word: 最大50MB
- Excel: 最大30MB
- 同时处理请求数: 10个
- 单个请求超时: 300秒
- 每分钟最大请求数: 60次
- 每小时最大请求数: 1000次
- 严格的文件类型检查
- 文件大小限制
- 恶意文件检测
- 文件名清理
- 参数验证
- XSS防护
- 不暴露内部错误信息
- 统一的错误响应格式
- 详细的日志记录
# 服务配置
HOST=0.0.0.0
PORT=3000
DEBUG=false
# 文件处理
TEMP_IMAGE_DIR=./temp_images
MAX_FILE_SIZE=104857600 # 100MB
# 日志配置
LOG_LEVEL=INFO
LOG_FILE=./logs/api.logFROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 3000
CMD ["python", "run_server.py"]server {
listen 80;
server_name your-domain.com;
client_max_body_size 100M;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# 静态文件服务
location /static/ {
proxy_pass http://localhost:3000;
expires 1d;
add_header Cache-Control "public, immutable";
}
}新的统一API设计具有以下优势:
- 简化接口 - 单一端点处理所有转换需求
- 灵活调用 - 支持文件上传和JSON两种方式
- 自动检测 - 根据文件扩展名自动选择处理器
- 批量处理 - 原生支持多文件转换
- 标准兼容 - 符合RESTful设计原则和HTTP标准
- 易于集成 - 支持HTML表单、Ajax、各种编程语言
- 完整文档 - 提供详细的API文档和使用示例
这种设计使API更加直观和易用,同时保持了强大的功能和灵活性。