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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }