@kakedashi/md-to-article-mcp 1.0.0
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 +96 -0
- package/dist/clipboard.js +28 -0
- package/dist/converter.js +27 -0
- package/dist/index.js +135 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @kakedashi/md-to-article-mcp
|
|
2
|
+
|
|
3
|
+
Markdown ファイルを読み込み、X Article エディタにペースト可能なリッチテキスト(`text/html`)としてクリップボードにコピーする MCP ツールです。
|
|
4
|
+
|
|
5
|
+
## インストール
|
|
6
|
+
|
|
7
|
+
### npx で使う(インストール不要)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @kakedashi/md-to-article-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### グローバルインストール
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @kakedashi/md-to-article-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Claude Desktop への設定
|
|
20
|
+
|
|
21
|
+
`claude_desktop_config.json` に以下を追加してください。
|
|
22
|
+
|
|
23
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
24
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"md-to-article": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@kakedashi/md-to-article-mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
グローバルインストール済みの場合:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"md-to-article": {
|
|
43
|
+
"command": "md-to-article-mcp"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 使い方
|
|
50
|
+
|
|
51
|
+
Claude に以下のように依頼するだけです。
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
~/Desktop/my-article.md を X Article 用にクリップボードにコピーして
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Claude が `md_to_article` ツールを呼び出し、変換後の HTML がクリップボードにコピーされます。
|
|
58
|
+
その後、X Article エディタで **Cmd+V**(macOS)または **Ctrl+V**(Windows/Linux)でペーストしてください。
|
|
59
|
+
|
|
60
|
+
### ツール仕様
|
|
61
|
+
|
|
62
|
+
| 項目 | 内容 |
|
|
63
|
+
|------|------|
|
|
64
|
+
| ツール名 | `md_to_article` |
|
|
65
|
+
| 引数 | `filepath` (string, 必須) — Markdown ファイルのパス |
|
|
66
|
+
|
|
67
|
+
## 対応プラットフォーム
|
|
68
|
+
|
|
69
|
+
| OS | クリップボード方法 | 必要なコマンド |
|
|
70
|
+
|----|------------------|---------------|
|
|
71
|
+
| macOS | `osascript` 経由で `«data HTML»` 形式でコピー | 標準搭載(追加インストール不要) |
|
|
72
|
+
| Windows | PowerShell の `Set-Clipboard -AsHtml` | 標準搭載(追加インストール不要) |
|
|
73
|
+
| Linux | `xclip -selection clipboard -t text/html` | `xclip` のインストールが必要 |
|
|
74
|
+
|
|
75
|
+
### Linux での xclip インストール
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Ubuntu/Debian
|
|
79
|
+
sudo apt install xclip
|
|
80
|
+
|
|
81
|
+
# Fedora
|
|
82
|
+
sudo dnf install xclip
|
|
83
|
+
|
|
84
|
+
# Arch Linux
|
|
85
|
+
sudo pacman -S xclip
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 変換仕様
|
|
89
|
+
|
|
90
|
+
- **画像** (``) → alt テキストのみに置換(画像は埋め込まれません)
|
|
91
|
+
- **コードブロック** → プレーンテキストとして `<pre>` タグに変換
|
|
92
|
+
- その他の Markdown 記法(見出し、太字、リンク、リストなど)は HTML に変換されます
|
|
93
|
+
|
|
94
|
+
## ライセンス
|
|
95
|
+
|
|
96
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.copyHtmlToClipboard = copyHtmlToClipboard;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function copyHtmlToClipboard(html) {
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
if (platform === "darwin") {
|
|
8
|
+
copyHtmlMacOS(html);
|
|
9
|
+
}
|
|
10
|
+
else if (platform === "win32") {
|
|
11
|
+
copyHtmlWindows(html);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
copyHtmlLinux(html);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function copyHtmlMacOS(html) {
|
|
18
|
+
const hex = Buffer.from(html, "utf-8").toString("hex").toUpperCase();
|
|
19
|
+
const script = `set the clipboard to «data HTML${hex}»`;
|
|
20
|
+
(0, child_process_1.execSync)(`osascript -e '${script.replace(/'/g, "'\"'\"'")}'`);
|
|
21
|
+
}
|
|
22
|
+
function copyHtmlWindows(html) {
|
|
23
|
+
const escaped = html.replace(/"/g, '\\"');
|
|
24
|
+
(0, child_process_1.execSync)(`powershell -Command "Set-Clipboard -Value \\"${escaped}\\" -AsHtml"`, { shell: "powershell.exe" });
|
|
25
|
+
}
|
|
26
|
+
function copyHtmlLinux(html) {
|
|
27
|
+
(0, child_process_1.execSync)(`echo ${JSON.stringify(html)} | xclip -selection clipboard -t text/html`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertMarkdownToHtml = convertMarkdownToHtml;
|
|
4
|
+
const marked_1 = require("marked");
|
|
5
|
+
function convertMarkdownToHtml(markdown) {
|
|
6
|
+
const renderer = new marked_1.Renderer();
|
|
7
|
+
// img タグは alt テキストのみに置換
|
|
8
|
+
renderer.image = (href, title, text) => {
|
|
9
|
+
return text ? text : "";
|
|
10
|
+
};
|
|
11
|
+
// pre/code ブロックはプレーンテキストにフォールバック
|
|
12
|
+
renderer.code = (code, _language) => {
|
|
13
|
+
return `<pre>${escapeHtml(code)}</pre>`;
|
|
14
|
+
};
|
|
15
|
+
renderer.codespan = (code) => {
|
|
16
|
+
return `<code>${escapeHtml(code)}</code>`;
|
|
17
|
+
};
|
|
18
|
+
return (0, marked_1.marked)(markdown, { renderer });
|
|
19
|
+
}
|
|
20
|
+
function escapeHtml(text) {
|
|
21
|
+
return text
|
|
22
|
+
.replace(/&/g, "&")
|
|
23
|
+
.replace(/</g, "<")
|
|
24
|
+
.replace(/>/g, ">")
|
|
25
|
+
.replace(/"/g, """)
|
|
26
|
+
.replace(/'/g, "'");
|
|
27
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
38
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
39
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const converter_js_1 = require("./converter.js");
|
|
43
|
+
const clipboard_js_1 = require("./clipboard.js");
|
|
44
|
+
const server = new index_js_1.Server({ name: "md-to-article-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
45
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
46
|
+
tools: [
|
|
47
|
+
{
|
|
48
|
+
name: "md_to_article",
|
|
49
|
+
description: "Markdown ファイルを読み込み、X Article エディタにペースト可能なリッチテキスト(text/html)としてクリップボードにコピーします",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
filepath: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "変換する Markdown ファイルのパス",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: ["filepath"],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
}));
|
|
63
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
64
|
+
if (request.params.name !== "md_to_article") {
|
|
65
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
66
|
+
}
|
|
67
|
+
const { filepath } = request.params.arguments;
|
|
68
|
+
const resolvedPath = path.resolve(filepath);
|
|
69
|
+
// ファイル読み込み
|
|
70
|
+
let markdown;
|
|
71
|
+
try {
|
|
72
|
+
markdown = fs.readFileSync(resolvedPath, "utf-8");
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: `ファイルが見つかりません: ${resolvedPath}\n${message}`,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// HTML 変換
|
|
87
|
+
let html;
|
|
88
|
+
try {
|
|
89
|
+
html = (0, converter_js_1.convertMarkdownToHtml)(markdown);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: `Markdown の変換に失敗しました: ${message}`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// クリップボードへコピー
|
|
104
|
+
try {
|
|
105
|
+
(0, clipboard_js_1.copyHtmlToClipboard)(html);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `クリップボードへのコピーに失敗しました: ${message}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: "クリップボードにコピーしました。X Article エディタで Cmd+V してください。",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
async function main() {
|
|
129
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
130
|
+
await server.connect(transport);
|
|
131
|
+
}
|
|
132
|
+
main().catch((err) => {
|
|
133
|
+
console.error(err);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kakedashi/md-to-article-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP tool to convert Markdown files to rich text and copy to clipboard for X Article editor",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"md-to-article-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "tsx src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"markdown",
|
|
23
|
+
"clipboard",
|
|
24
|
+
"x-article"
|
|
25
|
+
],
|
|
26
|
+
"author": "kakedashi",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
30
|
+
"marked": "^4.3.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/marked": "^4.3.2",
|
|
34
|
+
"@types/node": "^25.5.0",
|
|
35
|
+
"tsx": "^4.21.0",
|
|
36
|
+
"typescript": "^6.0.2"
|
|
37
|
+
}
|
|
38
|
+
}
|