@aiquants/html-to-markdown 0.2.6 → 0.3.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.
@@ -1,364 +0,0 @@
1
- import { Window } from "happy-dom";
2
- const window = new Window();
3
- Object.assign(global, {
4
- document: window.document,
5
- window,
6
- self: window
7
- });
8
- import { J as JSONRPCMessageSchema, g as getPackageVersion, S as Server, L as ListToolsRequestSchema, C as CallToolRequestSchema } from "./version-PCzxchJe.js";
9
- import process from "node:process";
10
- import { promises } from "fs";
11
- import { join, resolve, dirname } from "path";
12
- import { h as htmlToMarkdown } from "./core-CouDTni-.js";
13
- class ReadBuffer {
14
- append(chunk) {
15
- this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
16
- }
17
- readMessage() {
18
- if (!this._buffer) {
19
- return null;
20
- }
21
- const index = this._buffer.indexOf("\n");
22
- if (index === -1) {
23
- return null;
24
- }
25
- const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
26
- this._buffer = this._buffer.subarray(index + 1);
27
- return deserializeMessage(line);
28
- }
29
- clear() {
30
- this._buffer = void 0;
31
- }
32
- }
33
- function deserializeMessage(line) {
34
- return JSONRPCMessageSchema.parse(JSON.parse(line));
35
- }
36
- function serializeMessage(message) {
37
- return JSON.stringify(message) + "\n";
38
- }
39
- class StdioServerTransport {
40
- constructor(_stdin = process.stdin, _stdout = process.stdout) {
41
- this._stdin = _stdin;
42
- this._stdout = _stdout;
43
- this._readBuffer = new ReadBuffer();
44
- this._started = false;
45
- this._ondata = (chunk) => {
46
- this._readBuffer.append(chunk);
47
- this.processReadBuffer();
48
- };
49
- this._onerror = (error) => {
50
- var _a;
51
- (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
52
- };
53
- }
54
- /**
55
- * Starts listening for messages on stdin.
56
- */
57
- async start() {
58
- if (this._started) {
59
- throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
60
- }
61
- this._started = true;
62
- this._stdin.on("data", this._ondata);
63
- this._stdin.on("error", this._onerror);
64
- }
65
- processReadBuffer() {
66
- var _a, _b;
67
- while (true) {
68
- try {
69
- const message = this._readBuffer.readMessage();
70
- if (message === null) {
71
- break;
72
- }
73
- (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
74
- } catch (error) {
75
- (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
76
- }
77
- }
78
- }
79
- async close() {
80
- var _a;
81
- this._stdin.off("data", this._ondata);
82
- this._stdin.off("error", this._onerror);
83
- const remainingDataListeners = this._stdin.listenerCount("data");
84
- if (remainingDataListeners === 0) {
85
- this._stdin.pause();
86
- }
87
- this._readBuffer.clear();
88
- (_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
89
- }
90
- send(message) {
91
- return new Promise((resolve2) => {
92
- const json = serializeMessage(message);
93
- if (this._stdout.write(json)) {
94
- resolve2();
95
- } else {
96
- this._stdout.once("drain", resolve2);
97
- }
98
- });
99
- }
100
- }
101
- function parseHtmlToMarkdownArgs(args) {
102
- if (!args || typeof args !== "object") {
103
- throw new Error("Arguments are required");
104
- }
105
- const { url, html_content, locale = "en-US", output_format = "markdown", save_path, save_directory } = args;
106
- if (!url && !html_content) {
107
- throw new Error("Either 'url' or 'html_content' is required");
108
- }
109
- if (url && typeof url !== "string") {
110
- throw new Error("'url' must be a string");
111
- }
112
- if (html_content && typeof html_content !== "string") {
113
- throw new Error("'html_content' must be a string");
114
- }
115
- if (locale && !["en-US", "ja-JP"].includes(locale)) {
116
- throw new Error("'locale' must be 'en-US' or 'ja-JP'");
117
- }
118
- if (output_format && !["markdown", "html", "both"].includes(output_format)) {
119
- throw new Error("'output_format' must be 'markdown', 'html', or 'both'");
120
- }
121
- if (save_path && typeof save_path !== "string") {
122
- throw new Error("'save_path' must be a string");
123
- }
124
- if (save_directory && typeof save_directory !== "string") {
125
- throw new Error("'save_directory' must be a string");
126
- }
127
- if (save_path && save_directory) {
128
- throw new Error("Cannot specify both 'save_path' and 'save_directory'");
129
- }
130
- return {
131
- url,
132
- html_content,
133
- locale,
134
- output_format,
135
- save_path,
136
- save_directory
137
- };
138
- }
139
- function generateFilename(url, outputFormat) {
140
- if (url && url !== "direct-html-input") {
141
- try {
142
- const urlObj = new URL(url);
143
- let filename = urlObj.pathname.split("/").pop() || "page";
144
- filename = filename.replace(/\.[^.]*$/, "");
145
- filename = filename.replace(/[<>:"/\\|?*]/g, "_");
146
- if (!filename || filename.match(/^\.+$/)) {
147
- filename = "page";
148
- }
149
- return filename;
150
- } catch {
151
- return "page";
152
- }
153
- }
154
- return "page";
155
- }
156
- async function saveContentToFile(content, savePath, saveDirectory, url, outputFormat) {
157
- if (!savePath && !saveDirectory) {
158
- return null;
159
- }
160
- let finalPath;
161
- if (savePath) {
162
- finalPath = resolve(savePath);
163
- } else if (saveDirectory) {
164
- const filename = generateFilename(url);
165
- const extension = outputFormat === "html" ? ".html" : ".md";
166
- finalPath = resolve(saveDirectory, `${filename}${extension}`);
167
- } else {
168
- return null;
169
- }
170
- await promises.mkdir(dirname(finalPath), { recursive: true });
171
- await promises.writeFile(finalPath, content, "utf-8");
172
- return finalPath;
173
- }
174
- class HtmlToMarkdownMcpServer {
175
- constructor() {
176
- this.version = getPackageVersion();
177
- this.server = new Server(
178
- {
179
- name: "@aiquants/html-to-markdown",
180
- version: this.version,
181
- description: "Convert web pages and HTML to Markdown with JavaScript dynamic content support, table structure preservation, and file saving capabilities."
182
- },
183
- {
184
- capabilities: {
185
- tools: {}
186
- }
187
- }
188
- );
189
- this.setupToolHandlers();
190
- }
191
- /**
192
- * Setup tool handlers for the MCP server.
193
- * MCP サーバーのツールハンドラーを設定
194
- */
195
- setupToolHandlers() {
196
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
197
- return {
198
- tools: [
199
- {
200
- name: "html_to_markdown",
201
- description: "Convert web pages or HTML strings to Markdown format. Handles JavaScript-rendered dynamic content and preserves table structures with file saving capabilities.",
202
- inputSchema: {
203
- type: "object",
204
- properties: {
205
- url: {
206
- type: "string",
207
- description: "URL of the web page to convert (e.g., https://example.com). Handles JavaScript-rendered dynamic content effectively."
208
- },
209
- html_content: {
210
- type: "string",
211
- description: "HTML string to convert directly. Use instead of URL for local files or API response HTML content."
212
- },
213
- locale: {
214
- type: "string",
215
- description: "Browser locale setting. Use 'ja-JP' for Japanese sites, 'en-US' for English sites. Affects content rendering and language detection.",
216
- enum: ["en-US", "ja-JP"],
217
- default: "en-US"
218
- },
219
- output_format: {
220
- type: "string",
221
- description: "Output format: 'markdown' (Markdown only), 'html' (original HTML only), 'both' (returns both formats).",
222
- enum: ["markdown", "html", "both"],
223
- default: "markdown"
224
- },
225
- save_path: {
226
- type: "string",
227
- description: "Complete file path including filename to save converted content (e.g., '/home/user/output.md'). Cannot be used with save_directory."
228
- },
229
- save_directory: {
230
- type: "string",
231
- description: "Directory path to save converted content with auto-generated filename (e.g., '/home/user/downloads'). Cannot be used with save_path."
232
- }
233
- },
234
- anyOf: [{ required: ["url"] }, { required: ["html_content"] }]
235
- }
236
- }
237
- ]
238
- };
239
- });
240
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
241
- switch (request.params.name) {
242
- case "html_to_markdown": {
243
- return await this.handleHtmlToMarkdown(request.params.arguments);
244
- }
245
- default:
246
- throw new Error(`Unknown tool: ${request.params.name}`);
247
- }
248
- });
249
- }
250
- /**
251
- * Handle HTML to Markdown conversion tool call.
252
- * HTML から Markdown への変換ツール呼び出しを処理
253
- */
254
- async handleHtmlToMarkdown(args) {
255
- try {
256
- const validatedArgs = parseHtmlToMarkdownArgs(args);
257
- const { url, html_content, locale = "en-US", output_format = "markdown", save_path, save_directory } = validatedArgs;
258
- if (url) {
259
- try {
260
- new URL(url);
261
- } catch {
262
- throw new Error(`Invalid URL: ${url}`);
263
- }
264
- }
265
- const result = await htmlToMarkdown(url || "direct-html-input", {
266
- locale,
267
- htmlContent: html_content
268
- });
269
- const savedPaths = [];
270
- if (save_path || save_directory) {
271
- switch (output_format) {
272
- case "markdown": {
273
- const savedPath = await saveContentToFile(result.markdown, save_path, save_directory, url, "markdown");
274
- if (savedPath) savedPaths.push(savedPath);
275
- break;
276
- }
277
- case "html": {
278
- const savedPath = await saveContentToFile(result.html, save_path, save_directory, url, "html");
279
- if (savedPath) savedPaths.push(savedPath);
280
- break;
281
- }
282
- case "both": {
283
- const markdownPath = save_path ? save_path.endsWith(".md") ? save_path : `${save_path}.md` : save_directory ? join(save_directory, `${generateFilename(url)}.md`) : void 0;
284
- if (markdownPath) {
285
- const savedMarkdownPath = await saveContentToFile(result.markdown, markdownPath, void 0, url, "markdown");
286
- if (savedMarkdownPath) savedPaths.push(savedMarkdownPath);
287
- }
288
- const htmlPath = save_path ? save_path.endsWith(".html") ? save_path : `${save_path}.html` : save_directory ? join(save_directory, `${generateFilename(url)}.html`) : void 0;
289
- if (htmlPath) {
290
- const savedHtmlPath = await saveContentToFile(result.html, htmlPath, void 0, url, "html");
291
- if (savedHtmlPath) savedPaths.push(savedHtmlPath);
292
- }
293
- break;
294
- }
295
- }
296
- }
297
- let responseText = "";
298
- switch (output_format) {
299
- case "markdown":
300
- responseText = result.markdown;
301
- break;
302
- case "html":
303
- responseText = result.html;
304
- break;
305
- case "both":
306
- responseText = `=== Markdown ===
307
- ${result.markdown}
308
-
309
- === HTML ===
310
- ${result.html}`;
311
- break;
312
- default:
313
- throw new Error(`Invalid output format: ${output_format}`);
314
- }
315
- if (savedPaths.length > 0) {
316
- responseText += `
317
-
318
- === Files Saved ===
319
- ${savedPaths.map((path) => `- ${path}`).join("\n")}`;
320
- }
321
- const content = [
322
- {
323
- type: "text",
324
- text: responseText
325
- }
326
- ];
327
- return {
328
- content,
329
- isError: false
330
- };
331
- } catch (error) {
332
- const errorMessage = error instanceof Error ? error.message : String(error);
333
- return {
334
- content: [
335
- {
336
- type: "text",
337
- text: `Error: ${errorMessage}`
338
- }
339
- ],
340
- isError: true
341
- };
342
- }
343
- }
344
- /**
345
- * Start the MCP server.
346
- * MCP サーバーを開始
347
- */
348
- async start() {
349
- const transport = new StdioServerTransport();
350
- await this.server.connect(transport);
351
- console.info("HTML to Markdown MCP server started");
352
- }
353
- }
354
- const createMcpServer = () => {
355
- return new HtmlToMarkdownMcpServer();
356
- };
357
- const runMcpServer = async () => {
358
- const server = createMcpServer();
359
- await server.start();
360
- };
361
- export {
362
- createMcpServer as c,
363
- runMcpServer as r
364
- };
@@ -1 +0,0 @@
1
- const t=new(require("happy-dom").Window);Object.assign(global,{document:t.document,window:t,self:t});const e=require("./version-DTaUKNBS.cjs"),r=require("node:process"),n=require("fs"),o=require("path"),s=require("./core-D2dlEt1l.cjs");class a{append(t){this._buffer=this._buffer?Buffer.concat([this._buffer,t]):t}readMessage(){if(!this._buffer)return null;const t=this._buffer.indexOf("\n");if(-1===t)return null;const r=this._buffer.toString("utf8",0,t).replace(/\r$/,"");return this._buffer=this._buffer.subarray(t+1),function(t){return e.JSONRPCMessageSchema.parse(JSON.parse(t))}(r)}clear(){this._buffer=void 0}}class i{constructor(t=r.stdin,e=r.stdout){this._stdin=t,this._stdout=e,this._readBuffer=new a,this._started=!1,this._ondata=t=>{this._readBuffer.append(t),this.processReadBuffer()},this._onerror=t=>{var e;null===(e=this.onerror)||void 0===e||e.call(this,t)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(var t,e;;)try{const e=this._readBuffer.readMessage();if(null===e)break;null===(t=this.onmessage)||void 0===t||t.call(this,e)}catch(r){null===(e=this.onerror)||void 0===e||e.call(this,r)}}async close(){var t;this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror);0===this._stdin.listenerCount("data")&&this._stdin.pause(),this._readBuffer.clear(),null===(t=this.onclose)||void 0===t||t.call(this)}send(t){return new Promise(e=>{const r=function(t){return JSON.stringify(t)+"\n"}(t);this._stdout.write(r)?e():this._stdout.once("drain",e)})}}function c(t,e){if(t&&"direct-html-input"!==t)try{let e=new URL(t).pathname.split("/").pop()||"page";return e=e.replace(/\.[^.]*$/,""),e=e.replace(/[<>:"/\\|?*]/g,"_"),e&&!e.match(/^\.+$/)||(e="page"),e}catch{return"page"}return"page"}async function l(t,e,r,s,a){if(!e&&!r)return null;let i;if(e)i=o.resolve(e);else{if(!r)return null;{const t=c(s),e="html"===a?".html":".md";i=o.resolve(r,`${t}${e}`)}}return await n.promises.mkdir(o.dirname(i),{recursive:!0}),await n.promises.writeFile(i,t,"utf-8"),i}class d{constructor(){this.version=e.getPackageVersion(),this.server=new e.Server({name:"@aiquants/html-to-markdown",version:this.version,description:"Convert web pages and HTML to Markdown with JavaScript dynamic content support, table structure preservation, and file saving capabilities."},{capabilities:{tools:{}}}),this.setupToolHandlers()}setupToolHandlers(){this.server.setRequestHandler(e.ListToolsRequestSchema,async()=>({tools:[{name:"html_to_markdown",description:"Convert web pages or HTML strings to Markdown format. Handles JavaScript-rendered dynamic content and preserves table structures with file saving capabilities.",inputSchema:{type:"object",properties:{url:{type:"string",description:"URL of the web page to convert (e.g., https://example.com). Handles JavaScript-rendered dynamic content effectively."},html_content:{type:"string",description:"HTML string to convert directly. Use instead of URL for local files or API response HTML content."},locale:{type:"string",description:"Browser locale setting. Use 'ja-JP' for Japanese sites, 'en-US' for English sites. Affects content rendering and language detection.",enum:["en-US","ja-JP"],default:"en-US"},output_format:{type:"string",description:"Output format: 'markdown' (Markdown only), 'html' (original HTML only), 'both' (returns both formats).",enum:["markdown","html","both"],default:"markdown"},save_path:{type:"string",description:"Complete file path including filename to save converted content (e.g., '/home/user/output.md'). Cannot be used with save_directory."},save_directory:{type:"string",description:"Directory path to save converted content with auto-generated filename (e.g., '/home/user/downloads'). Cannot be used with save_path."}},anyOf:[{required:["url"]},{required:["html_content"]}]}}]})),this.server.setRequestHandler(e.CallToolRequestSchema,async t=>{if("html_to_markdown"===t.params.name)return await this.handleHtmlToMarkdown(t.params.arguments);throw new Error(`Unknown tool: ${t.params.name}`)})}async handleHtmlToMarkdown(t){try{const e=function(t){if(!t||"object"!=typeof t)throw new Error("Arguments are required");const{url:e,html_content:r,locale:n="en-US",output_format:o="markdown",save_path:s,save_directory:a}=t;if(!e&&!r)throw new Error("Either 'url' or 'html_content' is required");if(e&&"string"!=typeof e)throw new Error("'url' must be a string");if(r&&"string"!=typeof r)throw new Error("'html_content' must be a string");if(n&&!["en-US","ja-JP"].includes(n))throw new Error("'locale' must be 'en-US' or 'ja-JP'");if(o&&!["markdown","html","both"].includes(o))throw new Error("'output_format' must be 'markdown', 'html', or 'both'");if(s&&"string"!=typeof s)throw new Error("'save_path' must be a string");if(a&&"string"!=typeof a)throw new Error("'save_directory' must be a string");if(s&&a)throw new Error("Cannot specify both 'save_path' and 'save_directory'");return{url:e,html_content:r,locale:n,output_format:o,save_path:s,save_directory:a}}(t),{url:r,html_content:n,locale:a="en-US",output_format:i="markdown",save_path:d,save_directory:h}=e;if(r)try{new URL(r)}catch{throw new Error(`Invalid URL: ${r}`)}const u=await s.htmlToMarkdown(r||"direct-html-input",{locale:a,htmlContent:n}),m=[];if(d||h)switch(i){case"markdown":{const t=await l(u.markdown,d,h,r,"markdown");t&&m.push(t);break}case"html":{const t=await l(u.html,d,h,r,"html");t&&m.push(t);break}case"both":{const t=d?d.endsWith(".md")?d:`${d}.md`:h?o.join(h,`${c(r)}.md`):void 0;if(t){const e=await l(u.markdown,t,void 0,r,"markdown");e&&m.push(e)}const e=d?d.endsWith(".html")?d:`${d}.html`:h?o.join(h,`${c(r)}.html`):void 0;if(e){const t=await l(u.html,e,void 0,r,"html");t&&m.push(t)}break}}let f="";switch(i){case"markdown":f=u.markdown;break;case"html":f=u.html;break;case"both":f=`=== Markdown ===\n${u.markdown}\n\n=== HTML ===\n${u.html}`;break;default:throw new Error(`Invalid output format: ${i}`)}m.length>0&&(f+=`\n\n=== Files Saved ===\n${m.map(t=>`- ${t}`).join("\n")}`);return{content:[{type:"text",text:f}],isError:!1}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}async start(){const t=new i;await this.server.connect(t)}}const h=()=>new d;exports.createMcpServer=h,exports.runMcpServer=async()=>{const t=h();await t.start()};