markdowntoimage-cn-mcp 1.0.2
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.
- package/README.md +85 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +160 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# markdowntoimage-cn-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for the [MarkdownToImage](https://markdowntoimage.cn) API. Convert Markdown to beautiful images directly from Claude, Cursor, or any MCP-compatible client.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **generate_image** — Convert Markdown to PNG/JPEG/WebP images with themes, code highlighting, KaTeX math, and Mermaid diagrams
|
|
8
|
+
- **get_usage** — Check your monthly API quota and credit balance
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
### 1. Get an API Token
|
|
13
|
+
|
|
14
|
+
Sign up at [markdowntoimage.cn](https://markdowntoimage.cn) and create a token at [Profile → API Tokens](https://markdowntoimage.cn/profile/api-tokens).
|
|
15
|
+
|
|
16
|
+
### 2. Configure in Claude Desktop
|
|
17
|
+
|
|
18
|
+
Add to your `claude_desktop_config.json`:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"markdowntoimage": {
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": ["-y", "markdowntoimage-cn-mcp"],
|
|
26
|
+
"env": {
|
|
27
|
+
"MTI_API_TOKEN": "mti_your_token_here"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Use It
|
|
35
|
+
|
|
36
|
+
Ask Claude: *"Convert this markdown to an image: # Hello World"*
|
|
37
|
+
|
|
38
|
+
## Environment Variables
|
|
39
|
+
|
|
40
|
+
| Variable | Required | Default | Description |
|
|
41
|
+
|----------|----------|---------|-------------|
|
|
42
|
+
| `MTI_API_TOKEN` | Yes | — | Your API token (starts with `mti_`) |
|
|
43
|
+
| `MTI_BASE_URL` | No | `https://markdowntoimage.cn` | API base URL |
|
|
44
|
+
|
|
45
|
+
## Tools
|
|
46
|
+
|
|
47
|
+
### generate_image
|
|
48
|
+
|
|
49
|
+
Convert Markdown text to an image.
|
|
50
|
+
|
|
51
|
+
**Parameters:**
|
|
52
|
+
|
|
53
|
+
| Name | Type | Required | Description |
|
|
54
|
+
|------|------|----------|-------------|
|
|
55
|
+
| `markdown` | string | Yes | Markdown content to convert |
|
|
56
|
+
| `width` | number | No | Image width, 200-2560 (default: 800) |
|
|
57
|
+
| `format` | string | No | `png`, `jpeg`, or `webp` (default: `png`) |
|
|
58
|
+
| `quality` | number | No | Scale factor 1.0-3.0 (default: 2.0) |
|
|
59
|
+
| `theme` | string | No | Theme ID (e.g. `minimal-dark`) |
|
|
60
|
+
| `codeStyle` | string | No | Code highlight style (e.g. `nord`) |
|
|
61
|
+
| `fontFamily` | string | No | Font ID (e.g. `inter`, `fira-code`) |
|
|
62
|
+
| `mode` | string | No | `url` or `binary` (default: `url`) |
|
|
63
|
+
|
|
64
|
+
### get_usage
|
|
65
|
+
|
|
66
|
+
Check your current API usage statistics. No parameters required.
|
|
67
|
+
|
|
68
|
+
## Quota & Pricing
|
|
69
|
+
|
|
70
|
+
- **50 free requests/month** per account
|
|
71
|
+
- After the free quota, each request costs **1 credit**
|
|
72
|
+
- Users with credits always get **watermark-free** images
|
|
73
|
+
- Purchase credits at [markdowntoimage.cn/profile/credits](https://markdowntoimage.cn/profile/credits)
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install
|
|
79
|
+
npm run build
|
|
80
|
+
npm start
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
const API_TOKEN = process.env.MTI_API_TOKEN;
|
|
6
|
+
const BASE_URL = process.env.MTI_BASE_URL || 'https://markdowntoimage.cn';
|
|
7
|
+
if (!API_TOKEN) {
|
|
8
|
+
process.stderr.write('Error: MTI_API_TOKEN environment variable is required.\n' +
|
|
9
|
+
'Get your API token at: https://markdowntoimage.cn/profile/api-tokens\n');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: 'markdowntoimage',
|
|
14
|
+
version: '1.0.2',
|
|
15
|
+
});
|
|
16
|
+
// ── generate_image tool ──────────────────────────────────────────────
|
|
17
|
+
server.registerTool('generate_image', {
|
|
18
|
+
description: 'Convert Markdown text to a beautiful image. Supports code highlighting, math formulas (KaTeX), Mermaid diagrams, and multiple themes/fonts.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
markdown: z.string().describe('The Markdown content to convert to an image'),
|
|
21
|
+
width: z
|
|
22
|
+
.number()
|
|
23
|
+
.min(200)
|
|
24
|
+
.max(2560)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Image width in pixels (200-2560, default: 800)'),
|
|
27
|
+
format: z
|
|
28
|
+
.enum(['png', 'jpeg', 'webp'])
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Output image format (default: png)'),
|
|
31
|
+
quality: z
|
|
32
|
+
.number()
|
|
33
|
+
.min(1.0)
|
|
34
|
+
.max(3.0)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Device scale factor for image quality (1.0-3.0, default: 2.0)'),
|
|
37
|
+
theme: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Theme ID. Available: light, dark, github, github-dark, nord, monokai, solarized, ' +
|
|
41
|
+
'dracula, one-dark, ocean, pastel, mint, lavender, purple-dream, midnight, forest, ' +
|
|
42
|
+
'sunset, transparent, transparent-dark'),
|
|
43
|
+
codeStyle: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Code highlight style. Available: nord, monokai, github, github-dark, atom-one-dark, ' +
|
|
47
|
+
'atom-one-light, vs, vs2015, xcode, solarized-dark, dracula, tomorrow-night, ' +
|
|
48
|
+
'tomorrow-night-blue, night-owl, shades-of-purple, a11y-dark, a11y-light, default'),
|
|
49
|
+
fontFamily: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Font family ID. Available: sans (Noto Sans), inter, serif (Noto Serif), fira (Fira Code), ' +
|
|
53
|
+
'jetbrains (JetBrains Mono), roboto, ubuntu, modern (Work Sans), elegant (Lora), ' +
|
|
54
|
+
'article (Source Sans), docs (Open Sans), japanese (Noto Sans JP), chinese (Noto Sans SC), ' +
|
|
55
|
+
'arabic (Noto Naskh Arabic), cyrillic (Noto Sans Cyrillic)'),
|
|
56
|
+
mode: z
|
|
57
|
+
.enum(['url', 'binary'])
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('Response mode: "url" returns an image URL, "binary" returns raw image data (default: url)'),
|
|
60
|
+
},
|
|
61
|
+
}, async (params) => {
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(`${BASE_URL}/api/v1/images/generate`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
Authorization: `Bearer ${API_TOKEN}`,
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
markdown: params.markdown,
|
|
71
|
+
width: params.width,
|
|
72
|
+
format: params.format,
|
|
73
|
+
quality: params.quality,
|
|
74
|
+
theme: params.theme,
|
|
75
|
+
codeStyle: params.codeStyle,
|
|
76
|
+
fontFamily: params.fontFamily,
|
|
77
|
+
mode: params.mode ?? 'url',
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
const errorBody = await res.json().catch(() => null);
|
|
82
|
+
const msg = errorBody?.error?.message || `HTTP ${res.status}`;
|
|
83
|
+
return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
|
|
84
|
+
}
|
|
85
|
+
// binary mode → return image directly
|
|
86
|
+
if (params.mode === 'binary') {
|
|
87
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
88
|
+
const mimeType = res.headers.get('content-type') || 'image/png';
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: 'image',
|
|
93
|
+
data: buffer.toString('base64'),
|
|
94
|
+
mimeType,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// url mode → return structured text
|
|
100
|
+
const json = await res.json();
|
|
101
|
+
const data = json.data;
|
|
102
|
+
const quota = json.meta?.quota;
|
|
103
|
+
let text = `Image generated successfully!\n\nURL: ${data.imageUrl}\nFormat: ${data.format}\nWidth: ${data.width}px`;
|
|
104
|
+
if (quota) {
|
|
105
|
+
text += `\n\nQuota: ${quota.freeRemaining} free requests remaining this month`;
|
|
106
|
+
if (quota.creditBalance !== undefined) {
|
|
107
|
+
text += `, ${quota.creditBalance} credits available`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { content: [{ type: 'text', text }] };
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: 'text', text: `Request failed: ${err.message}` }],
|
|
115
|
+
isError: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// ── get_usage tool ───────────────────────────────────────────────────
|
|
120
|
+
server.registerTool('get_usage', {
|
|
121
|
+
description: 'Check your current API usage stats: monthly quota, credits balance, and reset date.',
|
|
122
|
+
}, async () => {
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`${BASE_URL}/api/v1/account/usage`, {
|
|
125
|
+
headers: { Authorization: `Bearer ${API_TOKEN}` },
|
|
126
|
+
});
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
const errorBody = await res.json().catch(() => null);
|
|
129
|
+
const msg = errorBody?.error?.message || `HTTP ${res.status}`;
|
|
130
|
+
return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
|
|
131
|
+
}
|
|
132
|
+
const json = await res.json();
|
|
133
|
+
const d = json.data;
|
|
134
|
+
const text = [
|
|
135
|
+
'API Usage Statistics',
|
|
136
|
+
'',
|
|
137
|
+
`Monthly Quota: ${d.monthlyUsed} / ${d.monthlyQuota} used`,
|
|
138
|
+
`Free Remaining: ${d.monthlyRemaining}`,
|
|
139
|
+
`Credit Balance: ${d.creditBalance}`,
|
|
140
|
+
`Current Period: ${d.yearMonth}`,
|
|
141
|
+
`Resets On: ${d.resetDate}`,
|
|
142
|
+
].join('\n');
|
|
143
|
+
return { content: [{ type: 'text', text }] };
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: 'text', text: `Request failed: ${err.message}` }],
|
|
148
|
+
isError: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// ── Start server ─────────────────────────────────────────────────────
|
|
153
|
+
async function main() {
|
|
154
|
+
const transport = new StdioServerTransport();
|
|
155
|
+
await server.connect(transport);
|
|
156
|
+
}
|
|
157
|
+
main().catch((err) => {
|
|
158
|
+
process.stderr.write(`Failed to start MCP server: ${err}\n`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "markdowntoimage-cn-mcp",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "MCP server for MarkdownToImage API - Convert Markdown to beautiful images",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"markdowntoimage-cn-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"start": "node dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"markdown",
|
|
20
|
+
"image",
|
|
21
|
+
"markdown-to-image",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"claude",
|
|
24
|
+
"ai"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
29
|
+
"zod": "^3.24.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"typescript": "^5.8.3",
|
|
33
|
+
"@types/node": "^22.15.21"
|
|
34
|
+
}
|
|
35
|
+
}
|